From a93358eafa77be9ba4d92a97a871e3c596f2cfad Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 14 Nov 2018 19:22:45 +0100 Subject: [PATCH 01/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f21cb2a..3d5140e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Post, Put and Delete functions will be gradually added in future releases. ```python # Import and call primary Client class from almapipy import AlmaCnxn -alma = AlmaCnxn('your_api_key', format='json') +alma = AlmaCnxn('your_api_key', data_format='json') ``` ### Access Bibliographic Data Alma provides a set of Web services for handling bibliographic records related information, enabling you to quickly and easily manipulate bibliographic records related details. These Web services can be used by external systems to retrieve or update bibliographic records related data. From d0ae7722b74fc58c950f58f3fd8254991ba03f9c Mon Sep 17 00:00:00 2001 From: pac0san Date: Fri, 16 Nov 2018 20:16:25 +0100 Subject: [PATCH 02/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d5140e..3a30b0d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Post, Put and Delete functions will be gradually added in future releases. ```python # Import and call primary Client class from almapipy import AlmaCnxn -alma = AlmaCnxn('your_api_key', data_format='json') +alma = AlmaCnxn('your_api_key', location='Europe', data_format='json') ``` ### Access Bibliographic Data Alma provides a set of Web services for handling bibliographic records related information, enabling you to quickly and easily manipulate bibliographic records related details. These Web services can be used by external systems to retrieve or update bibliographic records related data. From a879801e1fee9468c8c1db398c8e680400bcd06a Mon Sep 17 00:00:00 2001 From: pac0san Date: Fri, 16 Nov 2018 20:29:28 +0100 Subject: [PATCH 03/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a30b0d..2f21e62 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Post, Put and Delete functions will be gradually added in future releases. | [courses](#access-courses) | X | | | | | [resource sharing partners](#access-resource-sharing-partners) | X | | | | | [task-lists](#access-task-lists) | X | | | | -| [users](#access-users) | X | | | | +| [users](#access-users) | X | In Progress | In Progress | In Progress | | [electronic](#access-electronic) | X | | | | ## Use From 2597548a2cc8a872a4fda544640a951dd0210aea Mon Sep 17 00:00:00 2001 From: pac0san Date: Sat, 17 Nov 2018 11:07:31 +0100 Subject: [PATCH 04/47] Starting out with 'push' function --- almapipy/users.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/almapipy/users.py b/almapipy/users.py index bced946..85f6356 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -7,7 +7,7 @@ class SubClientUsers(Client): Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems - such as student information systems (SIS)—to retrieve or update user data. + such as student information systems (SIS) to retrieve or update user data. For more info: https://developers.exlibrisgroup.com/alma/apis/users """ @@ -85,6 +85,55 @@ def get(self, user_id=None, query={}, limit=10, offset=0, return response + def post(self, identifier, id_type, user_data={}, raw=False): + """Create a single user if it does not exists yet in Alma + + Args: + id_type (str): The identifier type for the user + Values: from the User Identifier Type code table. + identifier (str): The identifier itself for the user. + user_data (dict): Data for user enrollment. + Setting words for fields: [first_name, last_name, + middle_name, email, job_category, general_info]. + Format {'field': 'value', 'field2', 'value2'}. + e.g. data = {'first_name': 'Sterling', 'last_name': 'Archer'} + raw (bool): If true, returns raw requests object. + + Returns: + The user (at Alma) if a new user is created. + Empty list if the 'identifier' was already set. + + """ + + args = {} + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + # Search query for the 'identifier' in Alma + query = {} + query['identifier'] = identifier + args['id_type'] = id_type + args['q'] = self.__format_query__(query) + + # Search for a user with this 'user_identifier' + response = self.read(url, args, raw=raw) + + if not response: + # No user exists with this 'identifier': Let's create it. + user_data['identifier'] = identifier + args['q'] = self.__format_query__(user_data) + +# TODO: status, segment_type? + args['status'] = 'ACTIVE' + args['segment_type'] = 'External' + +# TODO: define 'self.write' in Client Class at client.py using 'request.post' + response = self.write(url, args, raw=raw) + + return response + + class SubClientUsersLoans(Client): """Handles the Loans endpoints of Users API""" From 601018aba8a7570310b709be5997409616309a37 Mon Sep 17 00:00:00 2001 From: pac0san Date: Sat, 17 Nov 2018 11:25:32 +0100 Subject: [PATCH 05/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f21e62..5836079 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Post, Put and Delete functions will be gradually added in future releases. | [courses](#access-courses) | X | | | | | [resource sharing partners](#access-resource-sharing-partners) | X | | | | | [task-lists](#access-task-lists) | X | | | | -| [users](#access-users) | X | In Progress | In Progress | In Progress | +| [users](#access-users) | X | In Progress | | | | [electronic](#access-electronic) | X | | | | ## Use From 7eafca05cfcd065c1bec1b52e961c5d41abc448d Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 12:01:40 +0100 Subject: [PATCH 06/47] Updating setup.py for libary packaging --- setup.py | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/setup.py b/setup.py index 280b474..3e94529 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,31 @@ from setuptools import setup -#from distutils.core import setup -try: - from pypandoc import convert +from setuptools import find_packages - def read_md(f): return convert(f, 'rst') -except ImportError: - print("warning: pypandoc module not found, could not convert Markdown to RST") +with open("README.md", "r") as fh: + long_description = fh.read() - def read_md(f): return open(f, 'r').read() - - -VERSION = "1.0.0" +VERSION = "1.0.1" setup( name="almapipy", - packages=['almapipy'], version=VERSION, - description="Python requests wrapper for the Ex Libris Alma API", - license='MIT' - long_description=read_md('README.md'), author="Steve Pelkey", author_email="spelkey@ucdavis.edu", - url='https://github.com/UCDavisLibrary/almapipy', + description="Python requests wrapper for the Ex Libris Alma API", + long_description=long_description, +# long_description_content_type="text/markdown", + url="https://github.com/UCDavisLibrary/almapipy", install_requires=['requests'], - keywords='alma exlibris exlibrisgroup api bibliographic' + keywords='alma exlibris exlibrisgroup api bibliographic', + packages=find_packages(), classifiers=[ + "Programming Language :: Python :: 3" + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", - - 'Programming Language :: Python :: 3.6' - ] + ], ) From 123b1e6b32943d148da4cfa74952c1d082ae4bdb Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 13:00:21 +0100 Subject: [PATCH 07/47] Importing 'client.py' from UCDavisLibrary/almapipy/tree/dev branch --- almapipy/client.py | 177 ++++++++++++++++++++++++++++++++------------- 1 file changed, 125 insertions(+), 52 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index de6213c..5872954 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -19,10 +19,64 @@ def __init__(self, cnxn_params={}): # instantiate dictionary for storing alma api connection parameters self.cnxn_params = cnxn_params + def create(self, url, data, args, object_type, raw=False): + """ + Uses requests library to make Exlibris API Post call. + + Args: + url (str): Exlibris API endpoint url. + data (dict): Data to be posted. + args (dict): Query string parameters for API call. + object_type (str): Type of object to be posted (see alma docs) + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ +# print(url) + + # Determine format of data to be posted according to order of importance: + # 1) Local declaration, 2) dtype of data parameter, 3) global setting. + headers = {} + if 'format' not in args.keys(): + if type(data) == ET or type(data) == ET.Element: + content_type = 'xml' + elif type(data) == dict: + content_type = 'json' + else: + content_type = self.cnxn_params['format'] + args['format'] = self.cnxn_params['format'] + else: + content_type = args['format'] + + # Declare data type in header, convert to string if necessary. + if content_type == 'json': + headers['content-type'] = 'application/json' + if type(data) != str: + data = json.dumps(data) + elif content_type == 'xml': + headers['content-type'] = 'application/xml' + if type(data) == ET or type(data) == ET.Element: + data = ET.tostring(data, encoding='unicode') + elif type(data) != str: + message = "XML payload must be either string or ElementTree." + raise utils.ArgError(message) + else: + message = "Post content type must be either 'json' or 'xml'" + raise utils.ArgError(message) + + # Send request and parse response + response = requests.post(url, data=data, params=args, headers=headers) + if raw: + return response + content = self.__parse_response__(response) + + return content + def read(self, url, args, raw=False): """ - Uses requests library to makes Exlibris API call. - Parses XML into python. + Uses requests library to make Exlibris API Get call. + Returns data of type specified during init of base class. Args: url (str): Exlibris API endpoint url. @@ -32,7 +86,7 @@ def read(self, url, args, raw=False): Returns: JSON-esque, xml, or raw response. """ - print(url) +# print(url) # handle data format. Allow for overriding of global setting. data_format = self.cnxn_params['format'] @@ -45,55 +99,8 @@ def read(self, url, args, raw=False): if raw: return response - # Get meta data from headers. - status = response.status_code - try: - response_type = response.headers['Content-Type'] - response_type, charset = response_type.split(";") - except: - message = str(status) + " - Unknown Error" - raise utils.AlmaError(message, status, url) - - # decode response if xml. - if response_type == 'application/xml': - xml_ns = self.cnxn_params['xml_ns'] # xml namespace - content = ET.fromstring(response.text) - - # Received response from ex libris, but error retrieving data. - if str(status)[0] in ['4', '5']: - try: - first_error = content.find("header:errorList", xml_ns)[0] - message = first_error.find("header:errorCode", xml_ns).text - message += " - " - message += first_error.find("header:errorMessage", xml_ns).text - message += ". See Alma documentation for more information." - except: - message = str(status) + " - Unknown Error" - raise utils.AlmaError(message, status, url) - - # decode response if json. - elif response_type == 'application/json': - content = response.json() - - # Received response from ex libris, but error retrieving data. - if str(status)[0] in ['4', '5']: - try: - first_error = content['errorList']['error'][0] - message = first_error['errorCode'] - message += " - " - message += first_error['errorMessage'] - message += ". See Alma documentation for more information." - except: - message = str(status) + " - Unknown Error" - raise utils.AlmaError(message, status, url) - - else: - content = response - - if str(status)[0] in ['4', '5']: - message = str(status) + " - " - message += str(content.text) - raise utils.AlmaError(message, status, url) + # Parse content + content = self.__parse_response__(response) return content @@ -181,3 +188,69 @@ def __read_all__(self, url, args, raw, response, data_key, max_limit=100): response = responses return response + + def __parse_response__(self, response): + """Parses alma response depending on content type. + + Args: + response: requests object from Alma. + + Returns: + Content of response in format specified in header. + """ + status = response.status_code + url = response.url + try: + response_type = response.headers['Content-Type'] + if ";" in response_type: + response_type, charset = response_type.split(";") + except: + message = 'Error ' + str(status) + response.text + raise utils.AlmaError(message, status, url) + + # decode response if xml. + if response_type == 'application/xml': + xml_ns = self.cnxn_params['xml_ns'] # xml namespace + content = ET.fromstring(response.text) + + # Received response from ex libris, but error retrieving data. + if str(status)[0] in ['4', '5']: + try: + first_error = content.find("header:errorList", xml_ns)[0] + message = first_error.find("header:errorCode", xml_ns).text + message += " - " + message += first_error.find("header:errorMessage", xml_ns).text + message += " See Alma documentation for more information." + except: + message = 'Error ' + str(status) + " - " + str(content) + raise utils.AlmaError(message, status, url) + + # decode response if json. + elif response_type == 'application/json': + content = response.json() + + # Received response from ex libris, but error retrieving data. + if str(status)[0] in ['4', '5']: + try: + if 'web_service_result' in content.keys(): + first_error = content['web_service_result']['errorList']['error'][0] + else: + first_error = content['errorList']['error'][0] + message = first_error['errorCode'] + message += " - " + message += first_error['errorMessage'] + if 'trackingID' in first_error.keys(): + message += "TrackingID: " + message['trackingID'] + message += " See Alma documentation for more information." + except: + message = 'Error ' + str(status) + " - " + str(content) + raise utils.AlmaError(message, status, url) + + else: + content = response + + if str(status)[0] in ['4', '5']: + message = str(status) + " - " + message += str(content.text) + raise utils.AlmaError(message, status, url) + return content From 3a4db00a887cb7232049382d3f5efd2baffb3409 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 13:43:15 +0100 Subject: [PATCH 08/47] Updates to 'push' function --- almapipy/client.py | 1 + almapipy/users.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index 5872954..21a1d28 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -73,6 +73,7 @@ def create(self, url, data, args, object_type, raw=False): return content + def read(self, url, args, raw=False): """ Uses requests library to make Exlibris API Get call. diff --git a/almapipy/users.py b/almapipy/users.py index 85f6356..6830315 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -99,7 +99,7 @@ def post(self, identifier, id_type, user_data={}, raw=False): e.g. data = {'first_name': 'Sterling', 'last_name': 'Archer'} raw (bool): If true, returns raw requests object. - Returns: + Returns: ¿? The user (at Alma) if a new user is created. Empty list if the 'identifier' was already set. @@ -111,9 +111,9 @@ def post(self, identifier, id_type, user_data={}, raw=False): url = self.cnxn_params['api_uri_full'] # Search query for the 'identifier' in Alma + args['id_type'] = id_type query = {} query['identifier'] = identifier - args['id_type'] = id_type args['q'] = self.__format_query__(query) # Search for a user with this 'user_identifier' @@ -121,15 +121,15 @@ def post(self, identifier, id_type, user_data={}, raw=False): if not response: # No user exists with this 'identifier': Let's create it. - user_data['identifier'] = identifier - args['q'] = self.__format_query__(user_data) + args.clear() + args['apikey'] = self.cnxn_params['api_key'] + user_data['identifier'] = identifier # TODO: status, segment_type? - args['status'] = 'ACTIVE' - args['segment_type'] = 'External' + user_data['status'] = 'ACTIVE' + user_data['segment_type'] = 'External' -# TODO: define 'self.write' in Client Class at client.py using 'request.post' - response = self.write(url, args, raw=raw) + response = self.create(url, user_data, args, raw=raw) return response From 25cad0d43bbd83bbabeb25fdbc6795f055471ff4 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 16:07:27 +0100 Subject: [PATCH 09/47] General 'char coding' change to UTF8 --- almapipy.egg-info/PKG-INFO | 220 ++++ almapipy.egg-info/SOURCES.txt | 20 + almapipy.egg-info/dependency_links.txt | 1 + almapipy.egg-info/requires.txt | 1 + almapipy.egg-info/top_level.txt | 1 + almapipy/__init__.py | 168 +-- almapipy/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 2837 bytes .../__pycache__/acquisitions.cpython-37.pyc | Bin 0 -> 15333 bytes almapipy/__pycache__/analytics.cpython-37.pyc | Bin 0 -> 5845 bytes almapipy/__pycache__/bibs.cpython-37.pyc | Bin 0 -> 17436 bytes almapipy/__pycache__/client.cpython-37.pyc | Bin 0 -> 6217 bytes almapipy/__pycache__/conf.cpython-37.pyc | Bin 0 -> 17458 bytes almapipy/__pycache__/courses.cpython-37.pyc | Bin 0 -> 8335 bytes .../__pycache__/electronic.cpython-37.pyc | Bin 0 -> 6000 bytes almapipy/__pycache__/partners.cpython-37.pyc | Bin 0 -> 3601 bytes .../__pycache__/task_lists.cpython-37.pyc | Bin 0 -> 4311 bytes almapipy/__pycache__/users.cpython-37.pyc | Bin 0 -> 10320 bytes almapipy/__pycache__/utils.cpython-37.pyc | Bin 0 -> 1196 bytes almapipy/acquisitions.py | 948 +++++++------- almapipy/analytics.py | 350 ++--- almapipy/bibs.py | 998 +++++++-------- almapipy/client.py | 516 ++++---- almapipy/conf.py | 1136 +++++++++-------- almapipy/courses.py | 464 +++---- almapipy/electronic.py | 316 ++--- almapipy/partners.py | 202 +-- almapipy/task_lists.py | 226 ++-- almapipy/users.py | 675 +++++----- almapipy/utils.py | 54 +- 29 files changed, 3283 insertions(+), 3013 deletions(-) create mode 100644 almapipy.egg-info/PKG-INFO create mode 100644 almapipy.egg-info/SOURCES.txt create mode 100644 almapipy.egg-info/dependency_links.txt create mode 100644 almapipy.egg-info/requires.txt create mode 100644 almapipy.egg-info/top_level.txt create mode 100644 almapipy/__pycache__/__init__.cpython-37.pyc create mode 100644 almapipy/__pycache__/acquisitions.cpython-37.pyc create mode 100644 almapipy/__pycache__/analytics.cpython-37.pyc create mode 100644 almapipy/__pycache__/bibs.cpython-37.pyc create mode 100644 almapipy/__pycache__/client.cpython-37.pyc create mode 100644 almapipy/__pycache__/conf.cpython-37.pyc create mode 100644 almapipy/__pycache__/courses.cpython-37.pyc create mode 100644 almapipy/__pycache__/electronic.cpython-37.pyc create mode 100644 almapipy/__pycache__/partners.cpython-37.pyc create mode 100644 almapipy/__pycache__/task_lists.cpython-37.pyc create mode 100644 almapipy/__pycache__/users.cpython-37.pyc create mode 100644 almapipy/__pycache__/utils.cpython-37.pyc diff --git a/almapipy.egg-info/PKG-INFO b/almapipy.egg-info/PKG-INFO new file mode 100644 index 0000000..0a7c528 --- /dev/null +++ b/almapipy.egg-info/PKG-INFO @@ -0,0 +1,220 @@ +Metadata-Version: 1.1 +Name: almapipy +Version: 1.0.1 +Summary: Python requests wrapper for the Ex Libris Alma API +Home-page: https://github.com/UCDavisLibrary/almapipy +Author: Steve Pelkey +Author-email: spelkey@ucdavis.edu +License: UNKNOWN +Description: # almapipy: Python Wrapper for Alma API + + almapipy is python requests wrapper for easily accessing the Ex Libris Alma API. It is designed to be lightweight, and imposes a structure that mimics the [Alma API architecture](https://developers.exlibrisgroup.com/alma/apis). + + ## Installation + ```pip install almapipy``` + + ## Progress and Roadmap + Get functionality has been developed around all the Alma APIs (listed below). + Post, Put and Delete functions will be gradually added in future releases. + + | API | Get | Post | Put | Delete | + | --- | :---: | :---: | :---: | :---: | + | [bibs](#access-bibliographic-data) | X | | | | + | [analytics](#access-reports) | X | NA | NA | NA | + | [acquisitions](#access-acquisitions) | X | | | | + | [configuration](#access-configuration-settings) | X | | | | + | [courses](#access-courses) | X | | | | + | [resource sharing partners](#access-resource-sharing-partners) | X | | | | + | [task-lists](#access-task-lists) | X | | | | + | [users](#access-users) | X | In Progress | | | + | [electronic](#access-electronic) | X | | | | + + ## Use + + ### Import + ```python + # Import and call primary Client class + from almapipy import AlmaCnxn + alma = AlmaCnxn('your_api_key', location='Europe', data_format='json') + ``` + ### Access Bibliographic Data + Alma provides a set of Web services for handling bibliographic records related information, enabling you to quickly and easily manipulate bibliographic records related details. These Web services can be used by external systems to retrieve or update bibliographic records related data. + ```python + # Use Alma mms_id for retrieving bib records + harry_potter = "9980963346303126" + bib_record = alma.bibs.catalog.get(harry_potter) + + # get holding items for a bib record + holdings = alma.bibs.catalog.get_holdings(harry_potter) + + # get loans by title + loans = alma.bibs.loans.get_by_title(harry_potter) + # or by a specific holding item + loans = alma.bibs.loans.get_by_item(harry_potter, holding_id, item_id) + + # get requests or availability of bib + alma.bibs.requests.get_by_title(harry_potter) + alma.bibs.requests.get_by_item(harry_potter, holding_id, item_id) + alma.bibs.requests.get_availability(harry_potter, period=20) + + # get digital representations + alma.bibs.representations.get(harry_potter) + + # get linked data + alma.bibs.linked_data.get(harry_potter) + ``` + + ### Access Reports + The Analytics API returns an Alma report. + ```python + # Find the system path to the report if don't know path + alma.analytics.paths.get('/shared') + + # retrieve the report as an XML ET element (native response) + report = alma.analytics.reports.get('path_to_report') + + # or convert the xml to json after API call + report = alma.analytics.reports.get('path_to_report', return_json = True) + ``` + + ### Access Courses + Alma provides a set of Web services for handling courses and reading lists related information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems such as Courses Management Systems to retrieve or update courses and reading lists related data. + ```python + # Get a complete list of courses. Makes multiple calls if necessary. + course_list = alma.courses.get(all_records = True) + + # or filter on search parameters + econ_courses = alma.courses.get(query = {'code': 'ECN'}) + + # get reading lists for a course + course_id = econ_courses['course'][0]['id'] + reading_lists = alma.courses.reading_lists.get(course_id) + + # get more detailed information about a specific reading list + reading_list_id = reading_lists['reading_list'][0]['id'] + alma.courses.reading_lists(course_id, reading_list_id, view = 'full') + + # get citations for a reading list + alma.courses.citations(course_id, reading_list_id) + ``` + + ### Access Users + Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems—such as student information systems (SIS)—to retrieve or update user data. + ```python + # Get a list of users or filter on search parameters + users = alma.users.get(query = {'first_name': 'Sterling', 'last_name': 'Archer'}) + + # get more information on that user + user_id = users['user'][0]['primary_id'] + alma.users.get(user_id) + + # get all loans or requests for a user. Makes multiple calls if necessary. + loans = alma.user.loans.get(user_id, all_records = True) + requests = alma.user.requests.get(user_id, all_records = True) + + # get deposits or fees for a user + deposits = alma.users.deposits.get(user_id) + fees = alma.users.fees.get(user_id) + ``` + ### Access Acquisitions + Alma provides a set of Web services for handling acquisitions information, enabling you to quickly and easily manipulate acquisitions details. These Web services can be used by external systems - such as subscription agent systems - to retrieve or update acquisitions data. + ```python + # get all funds + alma.acq.funds.get(all_records=True) + + # get po_lines by search + amazon_lines = alma.acq.po_lines.get(query={'vendor_account': 'AMAZON'}) + single_line_id = amazon_lines['po_line'][0]['number'] + # or by a specific line number + alma.acq.po_lines.get(single_line_id) + + # search for a vendor + alma.acq.vendors.get(status='active', query={'name':'AMAZON'}) + # or get a specific vendor + alma.acq.vendors.get('AMAZON.COM') + + # get invoices or polines for a specific vendor + alma.acq.vendors.get_invoices('AMAZON.COM') + alma.acq.vendors.get_po_lines('AMAZON.COM') + + # or get specific invoices + alma.acq.invoices.get('invoice_id') + + # get all licenses + alma.acq.licenses.get(all_records=True) + ``` + ### Access Configuration Settings + Alma provides a set of Web services for handling Configuration related information, enabling you to quickly and easily receive configuration details. These Web services can be used by external systems in order to get list of possible data. + ```python + # Get libraries, locations, departments, and hours + libraries = alma.conf.units.get_libaries() + library_id = libraries['library'][0]['code'] + locations = alma.conf.units.get_locations(library_id) + hours = alma.conf.general.get_hours(library_id) + departments = alma.conf.units.get_departments() + + # Get system code tables + table = 'UserGroups' + alma.conf.general.get_code_table(table) + + # Get scheduled jobs and run history + jobs = alma.conf.jobs.get() + job_id = jobs['job'][0]['id'] + run_history = alma.conf.jobs.get_instances(job_id) + + # Get sets and set members + sets = alma.conf.sets.get() + set_id = sets['set'][0]['id'] + set_members = alma.conf.sets.get_members(set_id) + + # get profiles and reminders + depost_profiles = alma.conf.deposit_profiles.get() + import_profiles = alma.conf.import_profiles.get() + reminders = alma.conf.reminders.get() + ``` + ### Access Resource Sharing Partners + Alma provides a set of Web services for handling Resource Sharing Partner information, enabling you to quickly and easily manipulate partner details. These Web services can be used by external systems to retrieve or update partner data. + ```python + # get partners + partners = alma.partners.get() + ``` + ### Access Electronic + Alma provides a set of Web services for handling electronic information, enabling you to quickly and easily manipulate electronic details. These Web services can be used by external systems in order to retrieve or update electronic data. + ```python + # get e-collections + collections = alma.electronic.collections.get() + collection_id = collections['electronic_collection'][0]['id'] + + # get services for a collection + services = alma.electronic.services.get(collection_id) + service_id = services['electronic_service'][0]['id'] + + # get portfolios for a service + alma.electronic.portfolios.get(collection_id, service_id) + + ``` + ### Access Task Lists + Alma provides a set of Web services for handling task lists information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems. + ```python + # get requested resources for a specific circulation desk + alma.task_lists.resources.get(library_id, circ_desk) + + # get lending requests for a specific library + alma.task_lists.lending.get(library_id) + + ``` + + ## Attribution and Contact + + + * **Author**: [Steve Pelkey](mailto:spelkey@ucdavis.edu) + +Keywords: alma exlibris exlibrisgroup api bibliographic +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Science/Research +Classifier: Natural Language :: English +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/almapipy.egg-info/SOURCES.txt b/almapipy.egg-info/SOURCES.txt new file mode 100644 index 0000000..ff6b7d0 --- /dev/null +++ b/almapipy.egg-info/SOURCES.txt @@ -0,0 +1,20 @@ +MANIFEST.in +README.md +setup.py +almapipy/__init__.py +almapipy/acquisitions.py +almapipy/analytics.py +almapipy/bibs.py +almapipy/client.py +almapipy/conf.py +almapipy/courses.py +almapipy/electronic.py +almapipy/partners.py +almapipy/task_lists.py +almapipy/users.py +almapipy/utils.py +almapipy.egg-info/PKG-INFO +almapipy.egg-info/SOURCES.txt +almapipy.egg-info/dependency_links.txt +almapipy.egg-info/requires.txt +almapipy.egg-info/top_level.txt \ No newline at end of file diff --git a/almapipy.egg-info/dependency_links.txt b/almapipy.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/almapipy.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/almapipy.egg-info/requires.txt b/almapipy.egg-info/requires.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/almapipy.egg-info/requires.txt @@ -0,0 +1 @@ +requests diff --git a/almapipy.egg-info/top_level.txt b/almapipy.egg-info/top_level.txt new file mode 100644 index 0000000..468e7ed --- /dev/null +++ b/almapipy.egg-info/top_level.txt @@ -0,0 +1 @@ +almapipy diff --git a/almapipy/__init__.py b/almapipy/__init__.py index 77a690e..f85186a 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -1,83 +1,85 @@ -""" -Python client for Ex Libris Alma -""" -__author__ = "Steve Pelkey (spelkey@ucdavis.edu)" -__version__ = "0.0.9" - -import os - -from .client import Client -from .bibs import SubClientBibs -from .analytics import SubClientAnalytics -from .courses import SubClientCourses -from .users import SubClientUsers -from .acquisitions import SubClientAcquistions -from .conf import SubClientConfiguration -from .partners import SubClientPartners -from .electronic import SubClientElectronic -from .task_lists import SubClientTaskList -from . import utils - - -class AlmaCnxn(Client): - """"Interface with Alma APIs. - - Various Apis are namespaced beneath this object according to documentation - at https://developers.exlibrisgroup.com/alma/apis. - - E.g. - > alma = AlmaCnxn(your_api_key) - > alma.bibs.catalog.get_record(bib_id) # returns bibliographic records - - Args: - api_key (str): Your Api Key - Location (str): Geographic location of library. - data_format (str): Format of returned data. json or xml. - If xml is selected, data will be returned as python xml ElementTree. - """ - - def __init__(self, apikey, location='America', data_format='json'): - - super(AlmaCnxn, self).__init__() - - # determine base uri based on location - locations = {'America': 'https://api-na.hosted.exlibrisgroup.com', - 'Europe': 'https://api-eu.hosted.exlibrisgroup.com', - 'Asia Pacific': 'https://api-ap.hosted.exlibrisgroup.com', - 'Canada': 'https://api-ca.hosted.exlibrisgroup.com', - 'China': 'https://api-cn.hosted.exlibrisgroup.com'} - if location != 'America': - if location not in locations.keys(): - message = "Valid location arguments are " - message += ", ".join(locations.keys()) - raise utils.ArgError(message=message) - self.cnxn_params['location'] = location - self.cnxn_params['base_uri'] = locations[location] - - # handle preferred format - if data_format not in ['json', 'xml']: - message = "Format argument must be either 'json' or 'xml'" - raise utils.ArgError(message) - self.cnxn_params['format'] = data_format - ns = {'header': 'http://com/exlibris/urm/general/xmlbeans'} - self.cnxn_params['xml_ns'] = ns - - # TODO: validate api key. return list of accessible endpoints - # call __validate_key__ - self.cnxn_params['api_key'] = apikey - - # Hook in the various Alma APIs based on what API key can access - self.bibs = SubClientBibs(self.cnxn_params) - self.analytics = SubClientAnalytics(self.cnxn_params) - self.courses = SubClientCourses(self.cnxn_params) - self.users = SubClientUsers(self.cnxn_params) - self.acq = SubClientAcquistions(self.cnxn_params) - self.conf = SubClientConfiguration(self.cnxn_params) - self.partners = SubClientPartners(self.cnxn_params) - self.electronic = SubClientElectronic(self.cnxn_params) - self.task_lists = SubClientTaskList(self.cnxn_params) - - def __validate_key__(self, apikey): - # loop through each api and access the /test endpoint. - # return list of accessible apis. - pass +#-*- coding: utf-8-unix -*- + +""" +Python client for Ex Libris Alma +""" +__author__ = "Steve Pelkey (spelkey@ucdavis.edu)" +__version__ = "0.0.9" + +import os + +from .client import Client +from .bibs import SubClientBibs +from .analytics import SubClientAnalytics +from .courses import SubClientCourses +from .users import SubClientUsers +from .acquisitions import SubClientAcquistions +from .conf import SubClientConfiguration +from .partners import SubClientPartners +from .electronic import SubClientElectronic +from .task_lists import SubClientTaskList +from . import utils + + +class AlmaCnxn(Client): + """"Interface with Alma APIs. + + Various Apis are namespaced beneath this object according to documentation + at https://developers.exlibrisgroup.com/alma/apis. + + E.g. + > alma = AlmaCnxn(your_api_key) + > alma.bibs.catalog.get_record(bib_id) # returns bibliographic records + + Args: + api_key (str): Your Api Key + Location (str): Geographic location of library. + data_format (str): Format of returned data. json or xml. + If xml is selected, data will be returned as python xml ElementTree. + """ + + def __init__(self, apikey, location='America', data_format='json'): + + super(AlmaCnxn, self).__init__() + + # determine base uri based on location + locations = {'America': 'https://api-na.hosted.exlibrisgroup.com', + 'Europe': 'https://api-eu.hosted.exlibrisgroup.com', + 'Asia Pacific': 'https://api-ap.hosted.exlibrisgroup.com', + 'Canada': 'https://api-ca.hosted.exlibrisgroup.com', + 'China': 'https://api-cn.hosted.exlibrisgroup.com'} + if location != 'America': + if location not in locations.keys(): + message = "Valid location arguments are " + message += ", ".join(locations.keys()) + raise utils.ArgError(message=message) + self.cnxn_params['location'] = location + self.cnxn_params['base_uri'] = locations[location] + + # handle preferred format + if data_format not in ['json', 'xml']: + message = "Format argument must be either 'json' or 'xml'" + raise utils.ArgError(message) + self.cnxn_params['format'] = data_format + ns = {'header': 'http://com/exlibris/urm/general/xmlbeans'} + self.cnxn_params['xml_ns'] = ns + + # TODO: validate api key. return list of accessible endpoints + # call __validate_key__ + self.cnxn_params['api_key'] = apikey + + # Hook in the various Alma APIs based on what API key can access + self.bibs = SubClientBibs(self.cnxn_params) + self.analytics = SubClientAnalytics(self.cnxn_params) + self.courses = SubClientCourses(self.cnxn_params) + self.users = SubClientUsers(self.cnxn_params) + self.acq = SubClientAcquistions(self.cnxn_params) + self.conf = SubClientConfiguration(self.cnxn_params) + self.partners = SubClientPartners(self.cnxn_params) + self.electronic = SubClientElectronic(self.cnxn_params) + self.task_lists = SubClientTaskList(self.cnxn_params) + + def __validate_key__(self, apikey): + # loop through each api and access the /test endpoint. + # return list of accessible apis. + pass diff --git a/almapipy/__pycache__/__init__.cpython-37.pyc b/almapipy/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e58046b27acf9ce0779f784040d176d2815b9a7f GIT binary patch literal 2837 zcma)8OOM+|5^hSQL_O!}$2f78>mWO$g_p`Lh~XHS%+4A{3}XO$11xhOde|+Ac9WE= zn_f#8Ut$A3+ykS=?3qY!D4B?x|t65p7u9SDV>Zv}N%?ZD%{tj>SW@n_Y@7S-hbxXIG*t z7H_Jn*bm|2kriArT|f=L0v@$=w*dJ3ry z4pqjx%Nq}kd?tevsh-G1aG>WFd|f7jKTCBeMLAk}pNF4^U;cs)U=MzAa2z?S){(V| zXz;K+tscHfPc;g*Yr$d8)xx9+NjGchQBi`HBpcSUziSC3*{vmq$N)C*;8f<4<2z=CbH(XnJVwBiqyUlT z)4&vgD3UV6uv$l0bKC?oW9ItScq}les+glULit?TfKQ<)=V4N0V~%?qb6oR$8^_@^ zvXe3Vc3G6O@vguFii5Q+>-?C2yQBgCsOCx)uBbmnP2y-^xX;bHBhfG4N}pyRx>d z)>FkKpc8XgRHq`_0(TlC|1wLjpc*c9<UF$=LZM1+2gk8-DYQKVUO9TK0YR%!N~_jP zav@=PK(WBGL)B$nZ^k8L<0)1#;A)I-ol2hTsD&uT|Mt45Tw8g%#6kr_+KnH_X`Y%m zUb$FtH0DQ<=73F^MLt%GV}JspUV^15o^#-t2Fjh4OXUbWf*tf#)pUSTY1K}ub_0}L zSnR3MfR8w8^5iLWNa`loRDeDLRz8}wT?Bgc*tLmn`v>%`z+mTy6}ApNYOh=@1d~+@ z&w{&!ZB&SQ7fMakS^HhX7QNlD&8!p0iQ-zv@z3l((b)FB@gyli>d?m*a zyuKG2yECBwxsrN(2+1scR*b9iVoUivoiE;jwFATX0I|#@AyvN zah+`(yUwubBktnQce-x3*+=Qc-*Ri+)xx#)1iPEP=$}3bj_}qL3!9$K1NakShg@C0FM&?Ks9>8OPQga6pR>&LfaWrEsF&+Q@;=$U5!N67n(vO+4~9+{SQZWuuSa zCQ4{UUgGx^;EFvw4F(B26FxKw*} zQv20oNeZ9PB#{*UKm&0U0vd>(@JAXfFPTjeyT5nGx!`X9cIBNrwzrT~7h~=={Kmyd H*SPs#2%j82 literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/acquisitions.cpython-37.pyc b/almapipy/__pycache__/acquisitions.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a73ae700068337e6d895d6e10a2d20257d4e11ca GIT binary patch literal 15333 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 literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/analytics.cpython-37.pyc b/almapipy/__pycache__/analytics.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dda3f3e83fdf410ef595c7a4e4a21c6840f7017d GIT binary patch literal 5845 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 literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/bibs.cpython-37.pyc b/almapipy/__pycache__/bibs.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06bcca7966e964ad0557851ff1b700418f5c26fb GIT binary patch literal 17436 zcmeHP-;di?b|ytiqBLV$cH+d|WNmJmWMg~YWZ-Q4JNCk!1a{JHwFv}Dq-JPS zqI^k>J*tcX%|5pO!9F-e3v7$_rLS$#f1xi$QKCQ}3KUqNXkUv(ANrlcUzZw}C<-O;8=bY~z`oe(&bp_l1{?~v1^!8On`FAR$n~sBX_yvEAovqlar}Wj9 zs?u@EEA_ROrYavPwr1-;Q*7N)ztmbf%4NHPa)rxfl&iLZvccsF$~C)=a-GXnlo#wp zloz>dpnSkSi1I-$*HAuWH&AYHxo#i6rK}wJrHU@Cs8Qvj=Q@KBN99rIdO;VbvrWU% zIsAfGv2&D`YAdaht+q5*>8WYrC?4j=t!gapuiZ9H*FC5by^!yETI}C@xMzd)8;4Z#Au<8#K{gz|C|^=p}9Rw~XIK{DPljH&GudUnvu%t=j5Dxs9%$ z#h~+%j-xUkRnFoLT&o_B{~AfzC@aaC(sAWoMN`fyVTtbi5^c4$P|uFbVI@1RhDLT= z3+vhOLb#Y6AFve+U2SuGh+pSk(A;^8Uni>K&Dta3j^F+ogVCzqb2@F??~cEitF~|g zeNhI#%FNhoi-$=k4Ce^HvT?6l|A|s9W7kCnaEybq*55YI(5SkLjWbP_%W?R!YQ1H zUM}2z-5uO<>`UlvYr*5=wvEC{DbfSS>xmi!?k^PaBL4j9cblIDkl$vn>#qmapu6pi zo40lc-R32~JL=<+eqerr8~lLyszx7~po zw%ZFN;>WO4w7MGqYpBPxMK%35eyPy;^|az9R2HoatiIE3NA-5Q@7p7fju+eQyCcg> zu2kD3N$s|H3D4k{l6FVw9Dy8ZTR;bfu9x`CY#jLsZb@;3F$Agz4AE>LimmrFMn1xn zv`omSw#r$VkWXz@vN9o`+N#3%JC` z(=SGEd=kHaaYY?}zc7-C+~xy%rAwngn}J`3KYxb;G#=o8TzOCq)rt0{8kX*8;&r?9 zpb}~i^-1L`^^a6s0iuLuT={WwT%luIlkb{n5364(531av4qU3*W#Ca#uFHi ziTW~->Opy6;GL^>6-ZVRe>ExD#+|D8GrI4YOO2uz?nV7==LJ1~2~@0*tCxj|_9J-&49QeXz7PTvhf#|E(DGONE6 zI%2(^wRT=?1shq3+J}g@U9`Czw?*4kUAPQ}KA z+DJFp71VYIThuFm;3OS3UAuU%bAJd!WiH2$8VY~MwPCd^+ja>OERQXd=rceRyGxH6 zwyhn@^~^Q%a@^2xdpB^qTy2MxTnd#HG=XNF>gr;0?x=wgc5rr)`ZF_Auxu27`5!J`Am`;De7Yf11m+s6y?>C$homkDwYI2UbC$8D3JlBy)1+; zBAt30Y3Q6F%!_p4SoVs+klLyyFDFiLrF19+L29It*HG;{L11m6aSJdo1;3HD<%F-{ z-r#lYlo#tTYC25ZG1_Z}dJyG-@HA>?t&ms0F*3nFc%rq+*GJlRjj?BQ# zQjDW9cA~5ih5|XyB-a=-`JDE!1QaH34`vG}Tp<)jNdt0LfSeUZ&Z_kS1}qiHbY{1G z&n8Gp!84!a;*|m__HdVFZn?k$cA(?RfIvwh2uYwr&>rDBJM@T53yLk9_uO!sr#?9k z>4`OTweV0kG+pxD3Ge~pR$$&ZfAgaGlN;9|GlYh#X5gFgT?0SlH|oQyv2k%?97@vV=Fp=I08`2eZ37Y$=ve%4nQq%!VoW5ctmFx zVFj!Uz4%hxzom_(`+aYTdhUi!A6RInVwfY6G=lqCqtr5d3T+`2xpv81PL!_LZ)rRe zV?tG~CNrMdtzxCDOV0&@UQdj8QDg!Mi;7nYb3h{a2vMcWv@tr0??eB_wPw;>AYMUP zP^1apj6h;VkKHoTf5sQ_2A!=kyhN24UZQ$ZA2KZ7q&umqSfbqu?Z^+0cjT=`i-3c6 z`pBC&6Ev^`yr>NTiLN$EfR@tu2Qz&=xo)>Y^{^GZy=|PxFr@-qkTHeI8Zd@&#MT*S zDicjyNbZAYn4HrYUt%w>%wAp`ooYarD$uCP_+nT$;F>-fUs&*Y_A)RaRVd7wJY&Vl zB@@!T{ID^X@k`C#avC_e4Lub4SZwv|8FJzOyi@>ryKergzx40$;4bBH7h9Q zZ>;$@YR$6=n!=9Dd35$kHizVj#X1!4pD*A@jl_IHn7)UE1`Ew>H0FkZ*ithLv=+1K za~kS{1Or(mX3%2{I5K6be`{Y1$ZI1ed6~e7m@;vkc1&meHXW_eu1UL7w9EYkK4OZC z*dvN!0y;prp*2o5PBvbN`6isoHlo1J;TN36E+x9`J%qWdfbJ^y5GUAG&{t(Hft|5y zC{>tHuZu-AF@7Os&?V}#z;MwMFVYiFW0&*X@Ikp5(PS#!D5F{>Fq#8Y|p# zERe>@Niy&~*GqyR3OLuzkB2dZRYWIhD6UF=8`Zm)GkHDN@$6v3{AoM@yQ|W`n8XXO zn$CSZgov?VH-Kj#ow#=Ci@lHfm?lg=0?h50FD~HbxlnI*5IcaUk)#qhCnlwTymw4~v{1IUwFZ)R96Hm6>Wdbe2TFII>Trm(LXudLA#E zGER7Xc$r(m8g7G74&9KJWO+DZLMb1VnL;)$^P*%fYpI~kj?XLciptxV%31O>B}I`z zcAh5aA5kG7qo*vgg`XLSXVVvu5OF;a0#mmAd-Uksz@MSwEVE7z`%C-+vU3RjON-?r zC)n}WwNshp7-&`v7r zAUT-Ku**znU@=MDOzP)Y7M zQ?{!c5V3hgku2VaB#JZ4>1zdZIxU7g^GJlTc7rZOjI>eyWfmv>0)@=KGZ4?n?1h0C z=+dN&pTY4`8VAvR6fc#NxX2isnCQWE+}W|q4{DQ|T|rb;3rpZOYm_KRaT3lTLVBnp z358ONksFTqsczRe6%fnII7TX89EIU{fs<3ph^p#{!WKEMI$-@C0^2mMN~r?SbFPo< zJFo-yKo)G9!O-cty_7bfECBN0vsi?<&jDw@Ssmi;a2?rPcfhPj5gBsJpvedUvc{;5 zRWo0&H7Wg$P7+e*xnkg0DQEBQ$Q6!EfNlDrvq7Y*p!;nfku>F>4E^EAvqI#N7qVaw zF5c$!E7!*DQoo)$QjAB7Rh35udGs45Wqb@q{SLemte%s!H19diouJwG2jMoSz>7FV z2kBsxH)wTS&kc8J@N#F92+z3rHsxQXa8RSx`2^Zd9*-lezdMwYCegj-Cw>rs_;F08 zL_z0LS3puzUsjRmN1Z7?Pnr*jioQCXz4B}V-{*^>O(WV=A6WOynijE)l4eQPl|wE_}i!6pC` zLm(x`hGdjtV@9W@N0S%rkeQ2g?JipnScq7ObpmF}-&9rFwHE42psvfe&pQ&>2@fQ615H*T#J62+g|?|r^}wR|~! zo)Mv?pE&VRvW^le5keH^I!A~U7#_Lq$%_wDZqN)5NYLu}FG$|@EP0xblsq_2vr~^h zM}wJHGc)xV-U!Z5kPw8w8a``4su3U0n{NC+1v$(-klDk&6qq*hYw%5RZcuL(Qa)}v zeFSnG`)&GzwWK(u>e4JdwcKK6DPWZBc=2I-a&3@{g4ik`KWagQdDt)cC#!cnX5mgK2VIrmQp)-l#SSI`$(e_qcS z35Svq;VeFO5R=JUH-2w z1j%xw@VAWosXC|ea@r5CZ?bdC8q76kX?L5{{SrpMx+1$ literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/client.cpython-37.pyc b/almapipy/__pycache__/client.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fd11b72342200dcee422b266118c2f858a58276 GIT binary patch literal 6217 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 F{{r0hZ;Su{ literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/conf.cpython-37.pyc b/almapipy/__pycache__/conf.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1ce292535ea8b08974acd3ffa64b1bdd71b603e GIT binary patch literal 17458 zcmeHPO^h7JdF`H_|CwEmDB1e4WwmWt+_86e$ne(;V{=2A5(#D{YbjY6I~eqI_3XAd z(><#0;c5m$Ab^Yn0W2Q_1kgbW0z=6q_Z)%*$R!s8z8FR$x14l|;frAyKKOlA{X07| zyGzrsD6om{sjBX(uCD&-eeZkK%fDP%sVdlh_y52A{;ywEl>ehlwB?bwirxDF2c|H! zuME_-s!}@F&keM;rYbKhOk?>UDlBiQ?`iEk@zz9{kqdLmRxlYgC z9dX0AUB|eM8&!2|Tm6Bd54n5KW|pTLx@Y;i+ta^ib&=%vY!jtDm+O0m!}_+ft7qEL zxz#s(i;1Re2Nw=R`|G;p7+rDw*d6Jp7 z=7z54+13#;j!wZLdXL7cHAPybIdsw}AZ`cJE^xrs{{vN6J*`s7#$IZ{r1BfvJg99;t#z z6|dk5oGU#mf6HOMTu{Q2+_TC>MN_UQevYnu4{dccKOd(Helbp${BoSG_|-UF^XqYX z#b1rnCzt|bsccV9h)3J^TKCR~M+-`L%Fc+}ld}&nFhObG>UNlGPX78xwYlZFBMyUW z?HjD$I(zZ-rAy5G=7xFU()o>Z7tWp8=$aRM8y8M@&zr_Y)96}kldcTPbVN6i>KdL! z9S-VY&pW+Qzu#;GdD9(^gPQ5Q>vV<&HwIqth52*i4afF9%scV({QPTPwj7Ha{dQ?r zCW7bYUw6yxdO`keIxe_k$MU^)9<$C17ToK)1+%sNc7w@82eY+j_bo5@;{2Q591LCV zw@=vOH@Ww>2F5d#)oaYiJM0NrEQNCSFRp)3Gtw zoz5!Fndfj&w5lrqHPmOcx|;l(EO>~`sEuua9vXCvfz{~*)lO&NvQeMX^-kxVk#oI>ub5VMBNNB9ShTRirr%qm+Z&6|fC2!=0Nh3&G z3408VuOTzd5jfr#Fve6sU7p~PfQf0(D)$RhjMhc^RrwX93jjYr-_w9Xlqqj()50`A z#mK&|-7ijyc$Si}4bS$v<#QVd4u2NHKYqL1Kp_T;qu3i-rrop6uzXw zZzj)!v7oE6k91o)#q6kfaPRPq;WH#HNH1{L8V5yyDt>tduWnLp$ty9Yzg1hHDjIs( z%qNx@ zHkOZ!krjkN7N3d)Np&xJ-xK3SU<$xK-ZMp~MdV5DW^?-v-UYl`_TDHjNw_CJjeO-c;Wm@bP$GJOANb3Rvx_+{sJy( zs={2;cFw;{RlhAj9^RBkbU>+vFh>ygjlfdWTKd30C3q0s zn~d8diU5qw)wRacjb{W!F;Nt6WN25hdk>ur5owv6O|zkpfd7d&kV{V)a-f&yzlpGe zhq53s5s%7(Xd+6+LlaRlo}%&Hv@;0Nd!(``VOk#pZJccItmzSFr>dj}>Z=tnAS+!)- zl3CBPYN%c_?3##+w6(JFCagZjedr^OzK56{!jTtS9Izi64CzV<1KxIh3ryMY({y=5 zR%Nd4xW4W{4e~rNJE08q`e3p8{vK`sf*rf+&@y%Ax^z>>HiqdVs6!p<%(~tk`QXE- z!gbGw|B-qVG$-F4h(OBlzR>_`kJ zfuUIzmI^Nf@tV|HR81O9GznADMv8?!$V{w|+Z6`+cQhYQ;-EYs9BwX;$fA)e)2uvo z=&Y1&%%7Xrk1{tA@kW8g%sfnEegX3^w$UgM#5~ZsyYGQ}^=t0QCR~B?MqTg% zA_hRFaMdbATtY!dM~xJ$9Rw(xPYEs%O(Egw0LYM)is}&^rC-5A^ff5HPyuffDUr&s zGa7UOwx|Z(Y}EH>v_q=;W*T{kHl5x$bNX~bcjy|o*Q3S1uzm6mKpH)49B3{1)V}TS zW$VG~bg-0%MK>?jD5dDA#|}w)C=3-=?1a*pZGvw9Dv`E*8~z}2XW^|wsa94NW00-< z=~NJexn!kg6fMMUL2*7|m@6F7oDvkog9b(UQbHJSYiOcbkm`}pk#wqXiYk<8NwWiS zwpmOG5Tr~68Z1Ug5wrwCYHWvIS*KZ*nrsn`l0Oo3hrltjQ)7Z?+o_Iw26XHoqCc=2=8=vU})p3c{#n0f)}b}hPf2EF)2y7)yp z=yW)O-X68%{RS00l0=m!L%khnwG_b?EH&LCjzknWZW=3K3q@E#Z!b}2=P_F%mj8ua z@N1#J7SOrPqADn7hTGEe#Z|iWHV#X;Erfkqb&eTsC1$uuSs`4Jj`-~pXmaw)G{2=! z7Y^gMBBa_9Ulk#`q*%01$_@lb;wz9<2Hv{vjm$k#kSJyp>$3!gA(x@c3}nPN5H6NT zJt!?^NI^so2F5u4Ac%s*;Rqidd3*G&(g5EQkzXW1sypi6U59vck4_l6_?ky@CP7^U zsDkAHbP0z;4wYF$e|YqqKKD2_JwFX%a0GfzW0KHwzKPeyTR7Y>iI&s0f<5-8|BKAD zEWD}sP)K;wYQ~$^1U;`M9u>Mo&EfEZg%mw62+v6+5j`&m&xsORGJ*$`^t=eqSrzm= z$NyF?l_F1`ROA1EJUlJ(Rpj&h zZBsa{i-spd49opuQ6l3w&Z?`OEl3X(kvFd?ILcIV?I~0u zK&LIEy?ls*L$#Nb?h=Mdp{gQ+q8mbIiIf)#irzv{G^@PGpopNm6bD68d7+@F0x16= zqqxXmNGdKX#&=V}kO!wtat17j96z21Wyk;4!%xWyNVTzPi__r|s9uP2s8ifyGZ(RZo9gT&$ ziuzP;MpcOvl?7iTL@*IGcxW`TMLnMvjZhfyb?OcY>*YMY1}PLF?5tiwJVNJ8?C}i4ERT%>xRRdyk*SvSa1r*JelhU9YI(>`uP25ynW`TSyKDhK|DeZV&+W>Iy!4U zDI#%HLK?|gCuYoRiL2N?RML{ZqJ46vO(LJMB!7Ia`4PbjsbG=Ipl_pRoJK6gFZ$Z( zx&%2Qn0Sok`qeyVLFoOD)DaT$Bk4+D`a^aTPon+FpQqh~{3DnIKaTUq5)Vu0d9pQN z8}hGk9^6m-Y{pM~ZT^~xeBRHXn+R8BM|0hT9NgqBAJa>Oj^t?GA$zaK&H;K?2&s?Q z(rorcK5XBxDw~g(#}e0YRL@XN%=8cO2tOwvNiEKbKP6b?5vuA$UkWr2>JkX4e|o?r zQ0YT<2~UwrIG1(_(H}bgNg{@7EB@okV}}YQU|%{^Sg`06J>LTP|B((BLNBm8*9*iW zn^_o-N>e=VV4M(LbTk(bUHusRA6f8^USSsYqtcYE95*H?M{PU=#IHqd{eeM1oq*#Q zkiX!Izt^Kb3xD81fWSCUoyi0ORJz@)2Sro-ZxmO98E*F{0xOI#~Si+92f!(uh%GcuB!09=* z^{OQt;eKQZSK1#sJs0J=XrSCMGBbj8#)kj$r>k!q`%R}xmZv8wGY4vPj^=iWIIg!=3MNK##**#adk6!T097A5^u4R{u%NvD#jC3{D1GcDC#1W7F93EP%zr1Aas z0h1qOQYAcQ`aoxLpqUH9&IT=Q$ zu!aSpi=K7do4EByNGA3t)~D9Q3T$CdtoN}>mta38=OR}(xyB`Yfp^}S;^~)VziyQ^ zwKLX>mSbJAvKoE)A!G$k=1%kVtTD}dnLo`pg@sjbZjDbH&G&V8?_8yM?qM9kNJZlp z?rrF~w=cH?k%r^7bkK!11?S|>_AU_c%A0)w$D?ZTq6N{#PwXoSY z$YgsbOA8K+1?OiLKi-ToW5>GJ46AJZ^~JB=+)rRAy+&%D`6G+(-{w0ycQd5zTJCBY z_tgoQ#UCtn8jsI^+Wj&7LU;RN+R;1-cjdTy?H~!eFQws#Og`1@Eqsu|K`?JuRJa@M zrQP8{wwoqqNr%z!;3!iXWd~E?2SJoXSrD8gTYLnG<+SYLw`@P=F4^=O9~*b4YPWTh zkn$!qd?obhfs2$U7%~R_XJjS~GVnuVa>9nW`efo{fWkRr9X2PndSTKOE({lD`zXx# zu$f%VoQdTV$_eMVrl=lIBGY0 zy($5w29t!$cog3XQmgN8jHEhXXGIid= z7a&+kqh}$5nvj6~R0+-cDjh(UjD_A{?}n+6tAO&5M69xe4^T|fOzPFSxgcp#f@v6q zS*kQOA@%rnEHObSAE3O7(87frL}4H#d^RJ4KQ0Gh=sjqi#0Tuk)=P|#nL{XxC{zrG zsiMF`3J*qc77ejj#nAQf0Rr(6oY*f`%z#!l*vcM{M{;GA6*cE6XR6Mx93F2jJ3F0n zhQ9|PG#Mo0<;|^?LkdPCl^|T2X{ObdVVbZo<{D9UZg%2m5M}IaggC#!-k<`)D}rw_ z8f+Ja0;t4bZZU$N@E8VWR76*ZUF&P-*4O7%P5XTdZ{E7CbE)lzYhbR z@#SKW3+_x&f>yC2Juon!K{14|tcA=Xq|FE`!s>N-kT(#b@KjB}5tRuBwQF#?PPG@u(J9+z*tfT#$@NTE)xQgWn~$&qRbcyAb* zrikV@+}hH^zHMqlR7@}W>@sfML2`SlE$SfIjlOFre?_vJY6*?T=UxL(M$|U0U6PFg z8)PDeDV7SR*q_o|Fzz`TNg8aD2Ir9+(MS_nZ6b!ER#3ts*3Q-#(&ug|VxsB{C1ir_ z+L5^+Ps?viXJ(qr4AK1caO(yV3(Ioa9zVUvYp%{vsUyaa*TI%$|CP`*MQiGJ%7^z* zn7D>67`AHYLSv3DcoVN63ZMzd3)ehrs*ijfZ)zol@FtFET=&$^gm(y{03zUjYX8~> zRcIJ`V7*_Tc$3DYE}9=ZwC~}UK+WOjX1@UB1fG>c0009NR?BE#`8N@ORTEWsxk588 zeowKV0DjYKpypApv|^Vb zyR<-U+hA+Eapb4!DeBN6p+2ncWu;Q3zKdeJSvc@ndi7ALN@YXpIcoJRC4|C?Q9@vy zdjo9cv727>d4kITx26GF4;6sD1dw*^Ws|mSzcyxfL;(ZUY7Olbfu`zGvQEjJ5t+d9 z9ozZU8hy-SnaU1zoJpk zK=F4L7h$?lJ;LE50H!E61>hkH_XNO;;OmKo0cJ=2yaZnwU!b$3M2*@gSUI67{1t4?$U zitZvP8Wi-8P*7cfb>jf2JgdPn+zJ721$@6D4>JcOr;dORuvY{4)yChvy}#Wn;t?SI zk~$@~E)kFFzBI%;1!3c7XA!SBpLzh`?)TNal+5$V#py3j-0oMbYaPS7V&)ft&eT4D zC};FufViQG1{_CG^JiT|S8>L#*xB+uTD2bp%rirrVT-2xKLk6kYkK&CNZ0eLrMO#| z`y+YQZDH>5+xXeNVjMxxXO%CP7)N!t#yC1BxE-qlr*r!?^Cuhausv+&eDx3-jL**^ zTm}4$4%;69p!;hzIVI2?i!=1s!4&4cDn?f@J;UaNcZ|{bhS4?o-*AdGvzcbq+^^uU zgBSk^{-3QKu&?^45(oVy`zaPe;|#t$!zlK(Um7bL*SqFFt#ZeDcjQdH2$R2CwE8=_ z2OZm4C+A3+{YY*6*dkULR{>IOVekCKH=u+if1&PX=or;)wVQb(H2=pdgmo(~cH-u+ t(Ckpn&QcMo89bWdmecdb+`m+uSAHKZV`)~S;^Nt1CJWUbb_vK=RZ7e?x>Q`ZJk$H+Eqp(J1_&dAG{ zBIOx!*Q;`Y0#1Rx_&+!=1^N&A&|kvWKKY^Td(elTJETO(*{&U`=9^%j^DbZBHo7a1ObpRAOXq4Fi6n#6&a)_1 zA&O^@1?dX@>J=oy=$qW=x47B2B4cRs7Pr1I`!=_suU$UBoewHqKaWM2$ux<=;1)*d zFg#oFC}3lm?nYcF7BD3;mJZo(!~mk)jY5XSD6?o%!H+q3rT}Yk;zEx3Y2Jd`ItXGT95_RP}i`sQ7VOA$|l>% zva#Ci^*Fl6=~zg$A@0S|Kt}4eO!M(Zn2ve@cB~hSBh_oRLJt(Sa2WK^yN&-<{M9`q zh569<(kKkyz|Ungx%AA7F5Nvb@Oc_eK6@%1Qm8bS zFsR;Mz~i2HHyploNnBbFcqrB{4#fHOKn#$5>-_NI+ZQgqbB+fa)Uxy_#bhB30wrkL zdw6~IY42mGQoUiAZm1v$cf_Q(y`O}= z_tP*R!QE5EZlOU60?n<-N?s@@2#iqs$5T(So0;? z(d?U&Ry#S~TNqz1p|IPM#4OqnAg@m4$G433@-+U+L1N(CPMDLmLr!bXhMR+XL_<8H zj@LquUU^P28%10G6|-@wx}w9E`O1Sf>QUD{7W~;VjE|=19}y({>ZCV#6THuUzozH3w#n9ZgE$v!XIWKpfh1jYVeMdX ziE%WFGIj=QzU{yKEviq4LkeMP z{RkzPO!ru=9MOTw0-3SBDBD@=7sRnIMW}cC%pgr;%nr7e%Y>ndlqiZUkgq}zeeR1Y zXk$Kv*~q;J$6Pg5)1lQ^gb{X2$1i#h7CYaqS!-rWTM6PO&ZEjqYLn8;CUE+X?T!rg z*mTx6V7oHMu}^0}l~Q2BiePZ?IrwkRIjTiI<{uv%=~{@aSiPQb&XwkrR^bKgvUI@H zcxxd1%IrPjfFVgCZrRqmP&(C#mF8W9x)$czKiN36-?$x^$)XUz4&cp_aI*AZDPu6X zC_RLDfsb*zZiBU@1-~vWSX#O2`!H;nEJzIg3O=)?2sp7Cy?r`;zq_olxjfbk3YRI{ zx{G|3CjJ@?yF6zrr8hk|Jtg@gYV|rLZ%{&{UnN9}rFC0mZ$eN%Mq<3+na5il(>70l znH_Mm)ACHuT(NAFucF+BMC?6z{Ru1pwOvPkA3EhpN?xGkMM^Y3_eg4{q9skz#GbS+ zMU3H@c59{WnVM5W6wDsLZbj5*AskKALyJ4yoe{JrDHNGpSS09aH1&UH=jRsLx%Z9f z*~e<;>G_RA4rUrPHr#wIq}YHSV&mDD!Ecc%EX~N(7k~*yZZ9yhQ#chDgNY$?TfbZK zI^;HLS~W{k+;0_5(JpN6Jg~r)ZtxFyS?$~)pF_xUb_|610lOXTie&zytZ{=m&)#}E zE`1g%t>-hx$VX|ZTYt+?^y&9}AzmSB(T6mHU9tYP4UjLvpybPxP}n(HhP6+}lXOwg zH8n_e?FJ(y!IA7zrCUX&KY?6!>V7y2xj`)mZ|A_R0TYcQ1TX~bieg*iScPjVz%tuh zZB1S~%1NFTx(HY3GDdr21zj3z{%64T9F%`|fa?#_t*XB(-aONz~~>*Q&A*m9B*KBwSrP}a{6eueO53ftUI8A$=Pc# zrLvp2SaU*s4=v$(MrlZj1@%>q4ih!xN@{vod|7&ztNWw(5Q-RzZYXdCWv!lCeQ)*j Fe*jJ?AnO1C literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/partners.cpython-37.pyc b/almapipy/__pycache__/partners.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0810f5aef9c1a5fda89d96c248752a16a50c5021 GIT binary patch literal 3601 zcmd5<&2QYs73Yv#F122#N|PYA8x+%`AKD_)+Kv&)f>79vq1J%w7*RihEedv)Gi%Z2 zlIs~#@-C>8bLgS@A6lS?_RxQb*Y@H|dk=Ex?+v*t$*zL7r_6$nZ-z5(-n@_BoBhe< z%RL9}%YXjsH~)CUasE!7W%I$@!DIG7q$64COxcJr;@#ApdLxfHKXs%h{m&ffEB3-0 z`RI3KfPP^69oao_2H{Hv5d&5SAEk-P3UHmWNKzAHc+~=Mckq~-Aj%mr>5N>-Mqc7f zm~^H085{Z1C;76QII=qlWGL4#+L1nX>D6x@l;dWHM?x2w(nkCoJNG!+-E=DWOy@_5 zRE7&~RKfEJ|Fs%})kjH;(MhiPp~z&KWRLkL%H*Yv6+bu>ng&{#R$7u_?o<>>p1sdi zCdPKjJTG~X^JireKTYQxlDHBk0Xr30GAmP2C_Zac<5Cp@CfKs_esQRbI(tDR57RAi$ z4u=wJ(|iUyZK>yJGS-QCtn+fV73b5TfWd}hmY88H&sgEH3-3^)cL&c$c+BrWD)vX` zPfq1TOtQ*(0+;O}I4tLb>sT(>!wh`uUTxlC+YdWVJK|n-wjIyea|)MczJRRAlMWob z_Hgl*RqNOck8Z5gs=LrKDs{5h{yz^^caPOLl6k!N?SCafx1nK%$3mvVot>Qz)a2HU z&G>`y&gQM{+c!6FPbNE?@%Y2>_RZV!Lv?+7i)Pj#QK$-TEQ}(h>wc?!G%3?`;Mabf z&*t@|ID4K&u)LU>`pRo@{Xk_BF*#aK8z$1~S*c8625#*el}_{}c;v5+Mp8RpjE28N zjGN&k&bN%n;zP9<9?Y|NcrTC3DH1X_{1GPPh%aS^cXfQ29Oc8=yg1A=Yw=k!o1Zev zfY^efVH71dz^Q!wXYAZ*zEx|oCog^8 z!FPNc^u_Oy6rZ$7Ng$IZ#-hwcSYjx1%}m9~B#D>Eu+_7PWq z5!rFBrQsXnJWrt=>|5%L;}Z!n1cK-{Ac-8%^YbD|$&YaqeVhPco5^Omb!+-&v9-bZ z#n0LdfT{r*$%oW@QAi%-?O4uCLt>op|P7i{=a;;pG7*ByK2I%xcZvg|>v zQMcV-(5XGbDqkQB^;KFBA`E7EW>g)b+=vKb2Z5%PwSZH5h{@VR7}dU3Le~8V{u44H zM@EC5zDz=T%Ro$|ewUVBq0vicoLz@Y%iC@Bw`kTiBJU9)lxfH}z}4PkRnQFc0}$t% zA$!B^F`uoweHJi0ZpcE`ch_B?`JQjFY4OhMs01m4fbcAXH>0TTMbR{uWlB8G@vIbS z`=uLEvB1&Rv7i1vkyDi{M|x?xKv9J>AxPNu-Tvi}+0hsStLA~bgU9>`H{ Xas^FwxYt12kEsk;k$u+p*6;lrSy{-J literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/task_lists.cpython-37.pyc b/almapipy/__pycache__/task_lists.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7efb9a0e84c450ac2ef377b0cba90fca0c339d7 GIT binary patch literal 4311 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 t`pR2hY)GkKQ^k1r||dmzX9(334j0q literal 0 HcmV?d00001 diff --git a/almapipy/__pycache__/utils.cpython-37.pyc b/almapipy/__pycache__/utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dceea66d045e56550f9dd1c6d000a15886078d3 GIT binary patch literal 1196 zcmZ8gL5tHs6rM>YZCYFH?qwB3f(Ic88@!2#Y!??5UsZ;Jbo>&!hvs3%(~h;CHOgME8<}{_=VMOzT1isY;BICP;D-6xB@X zU?x>LtyM6s^R&u}-1JjOwKWQ>6L`!BK$4gO2}jV_6%GV;8_&jFJ~~Z|Tt^S4kVk$= zWog4rLe}`4=gC}h-t;-27ot|U9&rA-PSj?n%eg30&VLZyfm9a$e2hLAsZBIZi(`}I z=}a!8%SE0>Z;P~^%e*Q~Z~+I3JWmn)_)vU;)M9q^jD;K*(U1iD~mkW($ zVH$%1`;<}p&tB!YgJzxr>{agFk}0*a_hNQS7>Pa5qSN%okZ`TXi+%uRMLBDw_)i`@ z7>qB@gEUdf*t3(gO^9!I^O$F_s7OUuBU2hY@{ z)Y^y7Q1&d(Dv-K6m&PPlvhlSvB{W$!Zmkuxh8d}*`W_tD1B4+!NShw#c2qcDccY7Y zIHouT2BQ-?q8<1x54TkxxA{V60$el78<^{IzIvDuFit4=n+A! zt-j7=d3c^*Cn^&G1hez^DhQeG!MFMV0jslOhK|0!U2m$gW4#sfA#B^yP{`#$%WkKk zcUM0f9QEH_kUhbW!4XEq!hC8OO+wG=-t@L(XeZrjx#nzX?Encj{_FKEeS`WK++^;^ H8x8&egU$S{ literal 0 HcmV?d00001 diff --git a/almapipy/acquisitions.py b/almapipy/acquisitions.py index 1fcace9..eb75eac 100644 --- a/almapipy/acquisitions.py +++ b/almapipy/acquisitions.py @@ -1,473 +1,475 @@ -from .client import Client -from . import utils - - -class SubClientAcquistions(Client): - """ - Alma provides a set of Web services for handling acquisitions information, - enabling you to quickly and easily manipulate acquisitions details. - These Web services can be used by external systems - - such as subscription agent systems - to retrieve or update acquisitions data. - For more info: https://developers.exlibrisgroup.com/alma/apis/acq - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/acq" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/acq" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d5b14609-b590-470e-baba-9944682f8c7e.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.funds = SubClientAcquistionsFunds(self.cnxn_params) - self.po_lines = SubClientAcquistionsPO(self.cnxn_params) - self.vendors = SubClientAcquistionsVendors(self.cnxn_params) - self.invoices = SubClientAcquistionsInvoices(self.cnxn_params) - self.licenses = SubClientAcquistionsLicenses(self.cnxn_params) - - -class SubClientAcquistionsFunds(Client): - """Handles the Funds endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/funds' - self.cnxn_params['api_uri_full'] += '/funds' - - def get(self, limit=10, offset=0, library=None, all_records=False, - q_params={}, raw=False): - """Retrieve a list of funds. - - Args: - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - library (str): The code of the library that owns the PO line - for which the relevant funds should be retrieved. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of funds. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - url = self.cnxn_params['api_uri_full'] - args['limit'] = limit - args['offset'] = int(offset) - - if library: - args['library'] = str(library) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='fund') - return response - - -class SubClientAcquistionsPO(Client): - """Handles the PO Lines endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/po-lines' - self.cnxn_params['api_uri_full'] += '/po-lines' - - def get(self, po_line_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list or a single PO-Line. - - Args: - po_line_id (str): The PO-Line number ('number' field in record). - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [title, author, mms_id, - publisher, publication_year, publication_place, issn_isbn, - shelving_location, vendor_code, vendor_name, vendor_account, - fund_code, fund_name, number, po_number, invoice_reference & all]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'vendor_account': 'AMAZON'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of po-lines or a specific po-line. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if po_line_id: - url += ("/" + str(po_line_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if po_line_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='po_line') - return response - - def get_items(self, po_line_id, q_params={}, raw=False): - """Retrieve a list items related to a specific PO-line - - Args: - po_line_id (str): The PO-Line number ('number' field in record). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of items in PO-Line - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(po_line_id) + "/items") - - response = self.read(url, args, raw=raw) - return response - - -class SubClientAcquistionsVendors(Client): - """Handles the Vendor endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/vendors' - self.cnxn_params['api_uri_full'] += '/vendors' - - def get(self, vendor_id=None, status='ALL', type_='ALL', - query={}, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a vendor list or a single vendor. - - Args: - vendor_id (str): A unique identifier for the user (vendorCode). - status (str): Vendor Status. Valid values: [active, inactive, ALL]. - type_ (str): Vendor Type.Valid values: [material_supplier, - access_provider, licensor, governmental]. - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [nterface_name, name, code, library & all.]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'name': 'AMAZON'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of vendor or a specific vendor's details. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if vendor_id: - url += ("/" + str(vendor_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - args['status'] = str(status) - args['type'] = str(type_) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if vendor_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='vendor') - return response - - def get_invoices(self, vendor_id, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve invoices for a specific vendor. - - Args: - vendor_id (str): A unique identifier for the user (vendorCode). - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - library (str): The code of the library that owns the PO line - for which the relevant funds should be retrieved. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of invoices for vendor. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += "/" + (str(vendor_id)) - url += "/invoices" - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='invoice') - return response - - def get_po_lines(self, vendor_id, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve po-lines for a specific vendor. - - Args: - vendor_id (str): A unique identifier for the user (vendorCode). - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - library (str): The code of the library that owns the PO line - for which the relevant funds should be retrieved. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of po-lines for vendor. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += "/" + (str(vendor_id)) - url += "/po-lines" - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='po_line') - return response - - -class SubClientAcquistionsInvoices(Client): - """Handles the Invoices endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/invoices' - self.cnxn_params['api_uri_full'] += '/invoices' - - def get(self, invoice_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list or a single invoice. - - Args: - invoice_id (str): The invoice id. - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [invoice_number, vendor_code]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'vendor_code': 'AMAZON'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of invoices or specific invoice. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if invoice_id: - url += ("/" + str(invoice_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if invoice_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='invoice') - return response - - -class SubClientAcquistionsLicenses(Client): - """Handles the Licenses endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/licenses' - self.cnxn_params['api_uri_full'] += '/licenses' - - def get(self, license_id=None, status='ALL', review_status='ALL', - query={}, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a list or a single license. - - Args: - license_id (str): The license id (license_code). - status (str): Valid values are ACTIVE, DELETED, DRAFT, EXPIRED, RETIRED, ALL - review_status (str): alid values are ALL, and the listed values in LicenseReviewStatuses code table - query (dict): Search query for filtering licenses. Optional. - Searching for words from fields: [name, code, licensor]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'name': 'license_name'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of licenses or a specific license. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if license_id: - url += ("/" + str(license_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - args['status'] = str(status) - args['review_status'] = str(review_status) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if license_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='license') - return response - - def get_amendments(self, license_id, amendment_id=None, q_params={}, raw=False): - """Retrieve a specific license's amendments. - - Args: - license_id (str): The license id (license_code). - amendment_id (str): The amendment id (amendment_code). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of amendments or specific amendment for a license - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(license_id) + "/amendments") - if amendment_id: - url += ("/" + str(amendment_id)) - - response = self.read(url, args, raw=raw) - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientAcquistions(Client): + """ + Alma provides a set of Web services for handling acquisitions information, + enabling you to quickly and easily manipulate acquisitions details. + These Web services can be used by external systems - + such as subscription agent systems - to retrieve or update acquisitions data. + For more info: https://developers.exlibrisgroup.com/alma/apis/acq + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/acq" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/acq" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d5b14609-b590-470e-baba-9944682f8c7e.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.funds = SubClientAcquistionsFunds(self.cnxn_params) + self.po_lines = SubClientAcquistionsPO(self.cnxn_params) + self.vendors = SubClientAcquistionsVendors(self.cnxn_params) + self.invoices = SubClientAcquistionsInvoices(self.cnxn_params) + self.licenses = SubClientAcquistionsLicenses(self.cnxn_params) + + +class SubClientAcquistionsFunds(Client): + """Handles the Funds endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/funds' + self.cnxn_params['api_uri_full'] += '/funds' + + def get(self, limit=10, offset=0, library=None, all_records=False, + q_params={}, raw=False): + """Retrieve a list of funds. + + Args: + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + library (str): The code of the library that owns the PO line + for which the relevant funds should be retrieved. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of funds. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + url = self.cnxn_params['api_uri_full'] + args['limit'] = limit + args['offset'] = int(offset) + + if library: + args['library'] = str(library) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='fund') + return response + + +class SubClientAcquistionsPO(Client): + """Handles the PO Lines endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/po-lines' + self.cnxn_params['api_uri_full'] += '/po-lines' + + def get(self, po_line_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list or a single PO-Line. + + Args: + po_line_id (str): The PO-Line number ('number' field in record). + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [title, author, mms_id, + publisher, publication_year, publication_place, issn_isbn, + shelving_location, vendor_code, vendor_name, vendor_account, + fund_code, fund_name, number, po_number, invoice_reference & all]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'vendor_account': 'AMAZON'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of po-lines or a specific po-line. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if po_line_id: + url += ("/" + str(po_line_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if po_line_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='po_line') + return response + + def get_items(self, po_line_id, q_params={}, raw=False): + """Retrieve a list items related to a specific PO-line + + Args: + po_line_id (str): The PO-Line number ('number' field in record). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of items in PO-Line + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(po_line_id) + "/items") + + response = self.read(url, args, raw=raw) + return response + + +class SubClientAcquistionsVendors(Client): + """Handles the Vendor endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/vendors' + self.cnxn_params['api_uri_full'] += '/vendors' + + def get(self, vendor_id=None, status='ALL', type_='ALL', + query={}, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a vendor list or a single vendor. + + Args: + vendor_id (str): A unique identifier for the user (vendorCode). + status (str): Vendor Status. Valid values: [active, inactive, ALL]. + type_ (str): Vendor Type.Valid values: [material_supplier, + access_provider, licensor, governmental]. + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [nterface_name, name, code, library & all.]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'name': 'AMAZON'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of vendor or a specific vendor's details. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if vendor_id: + url += ("/" + str(vendor_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + args['status'] = str(status) + args['type'] = str(type_) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if vendor_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='vendor') + return response + + def get_invoices(self, vendor_id, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve invoices for a specific vendor. + + Args: + vendor_id (str): A unique identifier for the user (vendorCode). + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + library (str): The code of the library that owns the PO line + for which the relevant funds should be retrieved. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of invoices for vendor. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += "/" + (str(vendor_id)) + url += "/invoices" + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='invoice') + return response + + def get_po_lines(self, vendor_id, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve po-lines for a specific vendor. + + Args: + vendor_id (str): A unique identifier for the user (vendorCode). + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + library (str): The code of the library that owns the PO line + for which the relevant funds should be retrieved. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of po-lines for vendor. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += "/" + (str(vendor_id)) + url += "/po-lines" + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='po_line') + return response + + +class SubClientAcquistionsInvoices(Client): + """Handles the Invoices endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/invoices' + self.cnxn_params['api_uri_full'] += '/invoices' + + def get(self, invoice_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list or a single invoice. + + Args: + invoice_id (str): The invoice id. + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [invoice_number, vendor_code]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'vendor_code': 'AMAZON'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of invoices or specific invoice. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if invoice_id: + url += ("/" + str(invoice_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if invoice_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='invoice') + return response + + +class SubClientAcquistionsLicenses(Client): + """Handles the Licenses endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/licenses' + self.cnxn_params['api_uri_full'] += '/licenses' + + def get(self, license_id=None, status='ALL', review_status='ALL', + query={}, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a list or a single license. + + Args: + license_id (str): The license id (license_code). + status (str): Valid values are ACTIVE, DELETED, DRAFT, EXPIRED, RETIRED, ALL + review_status (str): alid values are ALL, and the listed values in LicenseReviewStatuses code table + query (dict): Search query for filtering licenses. Optional. + Searching for words from fields: [name, code, licensor]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'name': 'license_name'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of licenses or a specific license. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if license_id: + url += ("/" + str(license_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + args['status'] = str(status) + args['review_status'] = str(review_status) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if license_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='license') + return response + + def get_amendments(self, license_id, amendment_id=None, q_params={}, raw=False): + """Retrieve a specific license's amendments. + + Args: + license_id (str): The license id (license_code). + amendment_id (str): The amendment id (amendment_code). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of amendments or specific amendment for a license + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(license_id) + "/amendments") + if amendment_id: + url += ("/" + str(amendment_id)) + + response = self.read(url, args, raw=raw) + return response diff --git a/almapipy/analytics.py b/almapipy/analytics.py index 50403df..ecfa912 100644 --- a/almapipy/analytics.py +++ b/almapipy/analytics.py @@ -1,174 +1,176 @@ -from .client import Client -from . import utils -import xml.etree.ElementTree as ET - - -class SubClientAnalytics(Client): - """ - Handles requests to analytics API. - For more info: https://developers.exlibrisgroup.com/alma/apis/analytics - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/analytics" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/analytics" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/10788916-19f6-4f19-aaf1-c18fa0c31ccd.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' - - # Hook in subclients of api - self.paths = SubClientAnalyticsPaths(self.cnxn_params) - self.reports = SubClientAnalyticsReports(self.cnxn_params) - - -class SubClientAnalyticsPaths(Client): - """Handles the path endpoints of analytics API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/paths' - self.cnxn_params['api_uri_full'] += '/paths' - - def get(self, path=None, q_params={}, raw=False): - """This API lists the contents of the Alma Analytics report directory. - If path is not specified, will just return info of root folder. - - Args: - path (str): folder directory relative to root. - Does not need to be url encoded. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - ls of directory specificed by path. - - """ - url = self.cnxn_params['api_uri_full'] - if path: - url += ("/" + str(path)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientAnalyticsReports(Client): - """Handles the reports endpoints of analytics API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/reports' - self.cnxn_params['api_uri_full'] += '/reports' - - def get(self, path, _filter=None, limit=25, col_names=True, return_json=False, - all_records=False, q_params={}, raw=False): - """This API returns an Alma Analytics report as XML. - JSON currently unavailable. Use return_json param to convert after the call. - - Args: - path (str): path of report relative to report root. - non-URL-encoded. Leave slashes and spaces. - _filter (str): An XML representation of a filter in OBI format. - See documentation for more info. - limit (int): Maximum number of results to return - Between 25 and 1000 (multiples of 25). - col_names (bool): Include column heading information. - To ensure consistent sort order it might be required to turn it off. - return_json (false): If True, converts xml into json-like structure. - all_records (bool): Return all rows for a report. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - If all_records == True, returns a list. - - Returns: - XML ET or json-like structure of report, - - """ - url = self.cnxn_params['api_uri_full'] - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['path'] = path - args['format'] = 'xml' - args['limit'] = str(int(limit)) - args['col_names'] = col_names - if _filter: - args['filter'] = _filter - row_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}Row" - set_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}rowset" - columns_tag = "{http://www.w3.org/2001/XMLSchema}element" - report = self.read(url, args, raw=raw) - - if raw: - # extract xml from raw response - # start list with raw responses - if not all_records: - return report - responses = [report] - report = ET.fromstring(report.text) - - if all_records: - # check if there are more records to get - if report[0].find('IsFinished').text == 'false': - get_more = True - - # just need token and apikey for future calls - margs = {'apikey': self.cnxn_params['api_key']} - margs['token'] = report[0].find('ResumptionToken').text - margs['format'] = 'xml' - - # find report content in XML report - xml_rows = list(report.iter(set_tag))[0] - else: - get_more = False - - # make additional api calls and append rows to original xml - while get_more: - - report_more = self.read(url, margs, raw=raw) - - if raw: - responses += [report_more] - report_more = ET.fromstring(report_more.text) - - else: - for new_row in report_more.iter(row_tag): - xml_rows.append(new_row) - - # break loop if no more records - if report_more[0].find('IsFinished').text != 'false': - get_more = False - - if raw: - return responses - - if return_json: - # extract column names - columns_tag = "{http://www.w3.org/2001/XMLSchema}element" - columns = list(report.iter(columns_tag)) - headers = {} - for col in columns: - key = col.attrib['name'] - try: - value = col.attrib['{urn:saw-sql}columnHeading'] - except: - value = col.attrib['name'] - value = value.lower().replace(" ", "_") - headers[key] = value - - # covert to list of dicts - dicts = [] - rows = report.iter(row_tag) - for row in rows: - values = [col.text for col in row] - keys = [headers[col.tag.split('}')[-1]] for col in row] - dicts.append({key: value for key, value in zip(keys, values)}) - return dicts - - return report +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils +import xml.etree.ElementTree as ET + + +class SubClientAnalytics(Client): + """ + Handles requests to analytics API. + For more info: https://developers.exlibrisgroup.com/alma/apis/analytics + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/analytics" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/analytics" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/10788916-19f6-4f19-aaf1-c18fa0c31ccd.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' + + # Hook in subclients of api + self.paths = SubClientAnalyticsPaths(self.cnxn_params) + self.reports = SubClientAnalyticsReports(self.cnxn_params) + + +class SubClientAnalyticsPaths(Client): + """Handles the path endpoints of analytics API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/paths' + self.cnxn_params['api_uri_full'] += '/paths' + + def get(self, path=None, q_params={}, raw=False): + """This API lists the contents of the Alma Analytics report directory. + If path is not specified, will just return info of root folder. + + Args: + path (str): folder directory relative to root. + Does not need to be url encoded. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + ls of directory specificed by path. + + """ + url = self.cnxn_params['api_uri_full'] + if path: + url += ("/" + str(path)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientAnalyticsReports(Client): + """Handles the reports endpoints of analytics API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/reports' + self.cnxn_params['api_uri_full'] += '/reports' + + def get(self, path, _filter=None, limit=25, col_names=True, return_json=False, + all_records=False, q_params={}, raw=False): + """This API returns an Alma Analytics report as XML. + JSON currently unavailable. Use return_json param to convert after the call. + + Args: + path (str): path of report relative to report root. + non-URL-encoded. Leave slashes and spaces. + _filter (str): An XML representation of a filter in OBI format. + See documentation for more info. + limit (int): Maximum number of results to return + Between 25 and 1000 (multiples of 25). + col_names (bool): Include column heading information. + To ensure consistent sort order it might be required to turn it off. + return_json (false): If True, converts xml into json-like structure. + all_records (bool): Return all rows for a report. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + If all_records == True, returns a list. + + Returns: + XML ET or json-like structure of report, + + """ + url = self.cnxn_params['api_uri_full'] + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['path'] = path + args['format'] = 'xml' + args['limit'] = str(int(limit)) + args['col_names'] = col_names + if _filter: + args['filter'] = _filter + row_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}Row" + set_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}rowset" + columns_tag = "{http://www.w3.org/2001/XMLSchema}element" + report = self.read(url, args, raw=raw) + + if raw: + # extract xml from raw response + # start list with raw responses + if not all_records: + return report + responses = [report] + report = ET.fromstring(report.text) + + if all_records: + # check if there are more records to get + if report[0].find('IsFinished').text == 'false': + get_more = True + + # just need token and apikey for future calls + margs = {'apikey': self.cnxn_params['api_key']} + margs['token'] = report[0].find('ResumptionToken').text + margs['format'] = 'xml' + + # find report content in XML report + xml_rows = list(report.iter(set_tag))[0] + else: + get_more = False + + # make additional api calls and append rows to original xml + while get_more: + + report_more = self.read(url, margs, raw=raw) + + if raw: + responses += [report_more] + report_more = ET.fromstring(report_more.text) + + else: + for new_row in report_more.iter(row_tag): + xml_rows.append(new_row) + + # break loop if no more records + if report_more[0].find('IsFinished').text != 'false': + get_more = False + + if raw: + return responses + + if return_json: + # extract column names + columns_tag = "{http://www.w3.org/2001/XMLSchema}element" + columns = list(report.iter(columns_tag)) + headers = {} + for col in columns: + key = col.attrib['name'] + try: + value = col.attrib['{urn:saw-sql}columnHeading'] + except: + value = col.attrib['name'] + value = value.lower().replace(" ", "_") + headers[key] = value + + # covert to list of dicts + dicts = [] + rows = report.iter(row_tag) + for row in rows: + values = [col.text for col in row] + keys = [headers[col.tag.split('}')[-1]] for col in row] + dicts.append({key: value for key, value in zip(keys, values)}) + return dicts + + return report diff --git a/almapipy/bibs.py b/almapipy/bibs.py index 3d9d32e..d8fbbc0 100644 --- a/almapipy/bibs.py +++ b/almapipy/bibs.py @@ -1,498 +1,500 @@ -from .client import Client -from . import utils - - -class SubClientBibs(Client): - """ - Handles requests to bib endpoint. - For more info: https://developers.exlibrisgroup.com/alma/apis/bibs - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to Bibs. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/bibs" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/bibs" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/af2fb69d-64f4-42bc-bb05-d8a0ae56936e.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of bib - self.catalog = SubClientBibsCatalog(self.cnxn_params) - self.collections = SubClientBibsCollections(self.cnxn_params) - self.loans = SubClientBibsLoans(self.cnxn_params) - self.requests = SubClientBibsRequests(self.cnxn_params) - self.representations = SubClientBibsRepresentations(self.cnxn_params) - self.linked_data = SubClientBibsLinkedData(self.cnxn_params) - - -class SubClientBibsCatalog(Client): - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params - - def get(self, bib_ids, expand=None, q_params={}, raw=False): - """ - Returns Bib records from a list of Bib IDs submitted in a parameter. - - Args: - bib_ids (list or str): list of bib Record IDs. len = 1-100. - or string of one record id. - expand (str): provides additional information: - p_avail - Expand physical inventory information. - e_avail - Expand electronic inventory information. - d_avail - Expand digital inventory information. - To use more than one, use a comma separator. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Returns a single or list of bib records. - https://developers.exlibrisgroup.com/alma/apis/xsd/rest_bibs.xsd?tags=GET - - """ - url = self.cnxn_params['api_uri_full'] - - # validate arguments - if type(q_params) != dict: - message = "q_params must be a dictionary." - raise utils.ArgError(message) - if type(bib_ids) != list and type(bib_ids) != str: - message = "bib_ids must be a list of ids, or single string." - raise utils.ArgError(message) - - # format arguments - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - # determine which endpoint to call. - if type(bib_ids) == str: - url += ('/' + bib_ids) - else: - args['mms_id'] = bib_ids - - if expand: - if expand not in ['p_avail', 'e_avail', 'd_avail']: - message = 'expand must be one of the follow: ' + str(expand) - raise utils.ArgError(message) - args['expand'] = expand - - return self.read(url, args, raw=raw) - - def get_holdings(self, bib_id, holding_id=None, q_params={}, raw=False): - """Returns list of holding records or single holding record - for a given bib record ID. - - If retrieving a single holding record with the holding_id param, - it is returned as MARC XML format, so it is not recommended to - use this service with JSON format. - You can overide the json global setting - by entering 'format':'xml' as item in q_params parameter. - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of holding records or single holding record - for a given bib record ID. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += '/holdings' - if holding_id: - url += ('/' + str(holding_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_holding_items(self, bib_id, holding_id, item_id=None, q_params={}, raw=False): - """Returns list of holding record items or a single item - for a given holding record of a bib. - Includes label printing information - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item id (item_pid). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of holding record items or a single item - for a given bib record ID. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += ('/holdings/' + str(holding_id)) + '/items' - if item_id: - url += ('/' + str(item_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_portfolios(self, bib_id, portfolio_id=None, q_params={}, raw=False): - """Returns a list or single portfolio for a Bib. - - If retrieving a single holding record with the holding_id param, - it is returned as MARC XML format, so it is not recommended to - use this service with JSON format. - You can overide the json global setting - by entering 'format':'xml' as item in q_params parameter. - - Args: - bib_id (str): The bib ID (mms_id). - portfolio_id (str): The Electronic Portfolio ID. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Returns a list or single portfolio for a Bib. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += '/portfolios' - if portfolio_id: - url += ('/' + str(portfolio_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsCollections(Client): - """Handles collections""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/collections' - self.cnxn_params['api_uri_full'] += '/collections' - - def get(self, pid=None, query={}, q_params={}, raw=False): - """Returns meta data about collections in libraries. - If pid argument is used, will only return one collection. - - Args: - pid (str): The collection ID. - query (dict): Search query for filtering list. Optional. - Searching for words from fields: [library, collection name, external system, external ID]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A list of collections or a collection for a given pid. - - """ - url = self.cnxn_params['api_uri_full'] - if pid: - url += ("/" + str(pid)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - return response - - def get_bibs(self, pid, q_params={}, raw=False): - """Get bibs in a collection using pid. - - Args: - pid (str): The collection ID. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A a list of bibliographic titles in a given collection. - - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(pid)) - url += '/bibs' - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsLoans(Client): - """Accesses loans endpoints""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get_by_item(self, bib_id, holding_id, item_id, - loan_id=None, q_params={}, raw=False): - """Returns Loan by Item information. - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - loan_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given item. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += ('/holdings/' + str(holding_id)) - url += ('/items/' + str(item_id) + "/loans") - if loan_id: - url += ('/' + str(loan_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_by_title(self, bib_id, loan_id=None, q_params={}, raw=False): - """Returns Loan by title information. - - Args: - bib_id (str): The bib ID (mms_id). - loan_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given title. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id) + '/loans') - if loan_id: - url += ('/' + str(loan_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsRequests(Client): - """Accesses user request endpoints""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get_by_item(self, bib_id, holding_id, item_id, - request_id=None, q_params={}, raw=False): - """Returns Loan by Item information. - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - request_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given item. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += ('/holdings/' + str(holding_id)) - url += ('/items/' + str(item_id) + "/requests") - if request_id: - url += ('/' + str(request_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_by_title(self, bib_id, request_id=None, q_params={}, raw=False): - """Returns Loan by title information. - - Args: - bib_id (str): The bib ID (mms_id). - request_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given title. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id) + '/requests') - if request_id: - url += ('/' + str(request_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_availability(self, bib_id, period, period_type='days', - holding_id=None, item_id=None, q_params={}, raw=False): - """Returns list of periods in which specific title or item - is unavailable for booking. - - To get a specific item, holding_id and item_id parameters are required. - - Note: user_id does not populate if retrieving by just bid_id. - - Args: - bib_id (str): The bib ID (mms_id). - period (str or int): The number of days/weeks/months to retrieve availability for. - period_type (str): The type of period of interest. Optional. Possible values: days, weeks, months. - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of periods title/item is unavailable for booking. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - if holding_id and item_id: - url += ("/holdings/" + str(holding_id)) - url += ('/items/' + str(item_id)) - elif holding_id or item_id: - message = "If getting availability for an item, " - message += "Both holding_id and item_id are required arguments." - raise utils.ArgError(message) - url += "/booking-availability" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['period'] = str(period) - args['period_type'] = str(period_type) - - return self.read(url, args, raw=raw) - - def get_options(self, bib_id, user_id='GUEST', - holding_id=None, item_id=None, - q_params={}, raw=False): - """Returns request options for a specific title or item based on user. - - To get a specific item, holding_id and item_id parameters are required. - - Note: user_id does not populate if retrieving by just bid_id. - - Args: - bib_id (str): The bib ID (mms_id). - user_id (str): The id of the user for which the request options will be calculated. - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Request options for a specific title or item based on user. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - if holding_id and item_id: - url += ("/holdings/" + str(holding_id)) - url += ('/items/' + str(item_id)) - elif holding_id or item_id: - message = "If getting request options for an item, " - message += "Both holding_id and item_id are required arguments." - raise utils.ArgError(message) - url += "/request-options" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['user_id'] = str(user_id) - - return self.read(url, args, raw=raw) - - -class SubClientBibsRepresentations(Client): - """Handles Digital Representations""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, bib_id, q_params={}, raw=False): - """Returns a list of Digital Representations for a given Bib MMS-ID. - - Args: - bib_id (str): The bib ID (mms_id). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A list of Digital Representations for a given bib record. - - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += "/representations" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_details(self, bib_id, rep_id, files=False, q_params={}, raw=False): - """Returns a specific Digital Representation's details. - Supported for Remote and Non-Remote Representations. - - Args: - bib_id (str): The bib ID (mms_id). - rep_id (str): The Representation ID. - files (bool): Denote whether to return files? - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A list of Digital Representations for a given bib record. - - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += "/representations/" - url += rep_id - if files: - url += "/files" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsLinkedData(Client): - """Handles Linked Data for a Bib Record""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, bib_id, q_params={}, raw=False): - """Returns Linked data for a given Bib MMS-ID. - - Args: - bib_id (str): The bib ID (mms_id). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Linked data URIs for a given bib record. - - """ - url = self.cnxn_params['api_uri_full'] - url += "/linked-open-data" - url += ("/" + str(bib_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientBibs(Client): + """ + Handles requests to bib endpoint. + For more info: https://developers.exlibrisgroup.com/alma/apis/bibs + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to Bibs. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/bibs" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/bibs" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/af2fb69d-64f4-42bc-bb05-d8a0ae56936e.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of bib + self.catalog = SubClientBibsCatalog(self.cnxn_params) + self.collections = SubClientBibsCollections(self.cnxn_params) + self.loans = SubClientBibsLoans(self.cnxn_params) + self.requests = SubClientBibsRequests(self.cnxn_params) + self.representations = SubClientBibsRepresentations(self.cnxn_params) + self.linked_data = SubClientBibsLinkedData(self.cnxn_params) + + +class SubClientBibsCatalog(Client): + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params + + def get(self, bib_ids, expand=None, q_params={}, raw=False): + """ + Returns Bib records from a list of Bib IDs submitted in a parameter. + + Args: + bib_ids (list or str): list of bib Record IDs. len = 1-100. + or string of one record id. + expand (str): provides additional information: + p_avail - Expand physical inventory information. + e_avail - Expand electronic inventory information. + d_avail - Expand digital inventory information. + To use more than one, use a comma separator. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Returns a single or list of bib records. + https://developers.exlibrisgroup.com/alma/apis/xsd/rest_bibs.xsd?tags=GET + + """ + url = self.cnxn_params['api_uri_full'] + + # validate arguments + if type(q_params) != dict: + message = "q_params must be a dictionary." + raise utils.ArgError(message) + if type(bib_ids) != list and type(bib_ids) != str: + message = "bib_ids must be a list of ids, or single string." + raise utils.ArgError(message) + + # format arguments + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + # determine which endpoint to call. + if type(bib_ids) == str: + url += ('/' + bib_ids) + else: + args['mms_id'] = bib_ids + + if expand: + if expand not in ['p_avail', 'e_avail', 'd_avail']: + message = 'expand must be one of the follow: ' + str(expand) + raise utils.ArgError(message) + args['expand'] = expand + + return self.read(url, args, raw=raw) + + def get_holdings(self, bib_id, holding_id=None, q_params={}, raw=False): + """Returns list of holding records or single holding record + for a given bib record ID. + + If retrieving a single holding record with the holding_id param, + it is returned as MARC XML format, so it is not recommended to + use this service with JSON format. + You can overide the json global setting + by entering 'format':'xml' as item in q_params parameter. + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of holding records or single holding record + for a given bib record ID. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += '/holdings' + if holding_id: + url += ('/' + str(holding_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_holding_items(self, bib_id, holding_id, item_id=None, q_params={}, raw=False): + """Returns list of holding record items or a single item + for a given holding record of a bib. + Includes label printing information + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item id (item_pid). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of holding record items or a single item + for a given bib record ID. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += ('/holdings/' + str(holding_id)) + '/items' + if item_id: + url += ('/' + str(item_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_portfolios(self, bib_id, portfolio_id=None, q_params={}, raw=False): + """Returns a list or single portfolio for a Bib. + + If retrieving a single holding record with the holding_id param, + it is returned as MARC XML format, so it is not recommended to + use this service with JSON format. + You can overide the json global setting + by entering 'format':'xml' as item in q_params parameter. + + Args: + bib_id (str): The bib ID (mms_id). + portfolio_id (str): The Electronic Portfolio ID. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Returns a list or single portfolio for a Bib. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += '/portfolios' + if portfolio_id: + url += ('/' + str(portfolio_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsCollections(Client): + """Handles collections""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/collections' + self.cnxn_params['api_uri_full'] += '/collections' + + def get(self, pid=None, query={}, q_params={}, raw=False): + """Returns meta data about collections in libraries. + If pid argument is used, will only return one collection. + + Args: + pid (str): The collection ID. + query (dict): Search query for filtering list. Optional. + Searching for words from fields: [library, collection name, external system, external ID]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A list of collections or a collection for a given pid. + + """ + url = self.cnxn_params['api_uri_full'] + if pid: + url += ("/" + str(pid)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + return response + + def get_bibs(self, pid, q_params={}, raw=False): + """Get bibs in a collection using pid. + + Args: + pid (str): The collection ID. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A a list of bibliographic titles in a given collection. + + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(pid)) + url += '/bibs' + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsLoans(Client): + """Accesses loans endpoints""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get_by_item(self, bib_id, holding_id, item_id, + loan_id=None, q_params={}, raw=False): + """Returns Loan by Item information. + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + loan_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given item. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += ('/holdings/' + str(holding_id)) + url += ('/items/' + str(item_id) + "/loans") + if loan_id: + url += ('/' + str(loan_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_by_title(self, bib_id, loan_id=None, q_params={}, raw=False): + """Returns Loan by title information. + + Args: + bib_id (str): The bib ID (mms_id). + loan_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given title. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id) + '/loans') + if loan_id: + url += ('/' + str(loan_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsRequests(Client): + """Accesses user request endpoints""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get_by_item(self, bib_id, holding_id, item_id, + request_id=None, q_params={}, raw=False): + """Returns Loan by Item information. + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + request_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given item. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += ('/holdings/' + str(holding_id)) + url += ('/items/' + str(item_id) + "/requests") + if request_id: + url += ('/' + str(request_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_by_title(self, bib_id, request_id=None, q_params={}, raw=False): + """Returns Loan by title information. + + Args: + bib_id (str): The bib ID (mms_id). + request_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given title. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id) + '/requests') + if request_id: + url += ('/' + str(request_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_availability(self, bib_id, period, period_type='days', + holding_id=None, item_id=None, q_params={}, raw=False): + """Returns list of periods in which specific title or item + is unavailable for booking. + + To get a specific item, holding_id and item_id parameters are required. + + Note: user_id does not populate if retrieving by just bid_id. + + Args: + bib_id (str): The bib ID (mms_id). + period (str or int): The number of days/weeks/months to retrieve availability for. + period_type (str): The type of period of interest. Optional. Possible values: days, weeks, months. + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of periods title/item is unavailable for booking. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + if holding_id and item_id: + url += ("/holdings/" + str(holding_id)) + url += ('/items/' + str(item_id)) + elif holding_id or item_id: + message = "If getting availability for an item, " + message += "Both holding_id and item_id are required arguments." + raise utils.ArgError(message) + url += "/booking-availability" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['period'] = str(period) + args['period_type'] = str(period_type) + + return self.read(url, args, raw=raw) + + def get_options(self, bib_id, user_id='GUEST', + holding_id=None, item_id=None, + q_params={}, raw=False): + """Returns request options for a specific title or item based on user. + + To get a specific item, holding_id and item_id parameters are required. + + Note: user_id does not populate if retrieving by just bid_id. + + Args: + bib_id (str): The bib ID (mms_id). + user_id (str): The id of the user for which the request options will be calculated. + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Request options for a specific title or item based on user. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + if holding_id and item_id: + url += ("/holdings/" + str(holding_id)) + url += ('/items/' + str(item_id)) + elif holding_id or item_id: + message = "If getting request options for an item, " + message += "Both holding_id and item_id are required arguments." + raise utils.ArgError(message) + url += "/request-options" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['user_id'] = str(user_id) + + return self.read(url, args, raw=raw) + + +class SubClientBibsRepresentations(Client): + """Handles Digital Representations""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, bib_id, q_params={}, raw=False): + """Returns a list of Digital Representations for a given Bib MMS-ID. + + Args: + bib_id (str): The bib ID (mms_id). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A list of Digital Representations for a given bib record. + + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += "/representations" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_details(self, bib_id, rep_id, files=False, q_params={}, raw=False): + """Returns a specific Digital Representation's details. + Supported for Remote and Non-Remote Representations. + + Args: + bib_id (str): The bib ID (mms_id). + rep_id (str): The Representation ID. + files (bool): Denote whether to return files? + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A list of Digital Representations for a given bib record. + + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += "/representations/" + url += rep_id + if files: + url += "/files" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsLinkedData(Client): + """Handles Linked Data for a Bib Record""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, bib_id, q_params={}, raw=False): + """Returns Linked data for a given Bib MMS-ID. + + Args: + bib_id (str): The bib ID (mms_id). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Linked data URIs for a given bib record. + + """ + url = self.cnxn_params['api_uri_full'] + url += "/linked-open-data" + url += ("/" + str(bib_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) diff --git a/almapipy/client.py b/almapipy/client.py index 21a1d28..81666de 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -1,257 +1,259 @@ -""" -Common Client for interacting with Alma API -""" - -import json -import xml.etree.ElementTree as ET - -import requests - -from . import utils - - -class Client(object): - """ - Reads responses from Alma API and handles response. - """ - - def __init__(self, cnxn_params={}): - # instantiate dictionary for storing alma api connection parameters - self.cnxn_params = cnxn_params - - def create(self, url, data, args, object_type, raw=False): - """ - Uses requests library to make Exlibris API Post call. - - Args: - url (str): Exlibris API endpoint url. - data (dict): Data to be posted. - args (dict): Query string parameters for API call. - object_type (str): Type of object to be posted (see alma docs) - raw (bool): If true, returns raw response. - - Returns: - JSON-esque, xml, or raw response. - """ -# print(url) - - # Determine format of data to be posted according to order of importance: - # 1) Local declaration, 2) dtype of data parameter, 3) global setting. - headers = {} - if 'format' not in args.keys(): - if type(data) == ET or type(data) == ET.Element: - content_type = 'xml' - elif type(data) == dict: - content_type = 'json' - else: - content_type = self.cnxn_params['format'] - args['format'] = self.cnxn_params['format'] - else: - content_type = args['format'] - - # Declare data type in header, convert to string if necessary. - if content_type == 'json': - headers['content-type'] = 'application/json' - if type(data) != str: - data = json.dumps(data) - elif content_type == 'xml': - headers['content-type'] = 'application/xml' - if type(data) == ET or type(data) == ET.Element: - data = ET.tostring(data, encoding='unicode') - elif type(data) != str: - message = "XML payload must be either string or ElementTree." - raise utils.ArgError(message) - else: - message = "Post content type must be either 'json' or 'xml'" - raise utils.ArgError(message) - - # Send request and parse response - response = requests.post(url, data=data, params=args, headers=headers) - if raw: - return response - content = self.__parse_response__(response) - - return content - - - def read(self, url, args, raw=False): - """ - Uses requests library to make Exlibris API Get call. - Returns data of type specified during init of base class. - - Args: - url (str): Exlibris API endpoint url. - args (dict): Query string parameters for API call. - raw (bool): If true, returns raw response. - - Returns: - JSON-esque, xml, or raw response. - """ -# print(url) - - # handle data format. Allow for overriding of global setting. - data_format = self.cnxn_params['format'] - if 'format' not in args.keys(): - args['format'] = data_format - data_format = args['format'] - - # Send request. - response = requests.get(url, params=args) - if raw: - return response - - # Parse content - content = self.__parse_response__(response) - - return content - - def __format_query__(self, query): - """Converts dictionary of brief search query to a formated string. - https://developers.exlibrisgroup.com/blog/How-we-re-building-APIs-at-Ex-Libris#BriefSearch - - Args: - query: dictionary of brief search query. - Format - {'field': 'value', 'field2', 'value2'}. - Returns: - String of query. - """ - q_str = "" - i = 0 - if type(query) != 'dict': - message = "Brief search query must be a dictionary." - for field, filter_value in query.items(): - field = str(field) - filter_value = str(filter_value) - if i > 0: - q_str += " AND " - q_str += (field + "~") - q_str += filter_value.replace(" ", "_") - i += 1 - - return q_str - - def __read_all__(self, url, args, raw, response, data_key, max_limit=100): - """Makes multiple API calls until all records for a query are retrieved. - Called by the 'all_records' parameter. - - Args: - url (str): Exlibris API endpoint url. - args (dict): Query string parameters for API call. - raw (bool): If true, returns raw response. - response (xml, raw, or json): First API call. - data_key (str): Dictionary key for accessing data. - max_limit (int): Max number of records allowed to be retrieved in a single call. - Overrides limit parameter. Reduces the number of API calls needed to retrieve data. - - Returns: - response with remainder of data appended. - """ - # raw will return a list of responses - if raw: - responses = [response] - response = response.json() - - args['offset'] = args['limit'] - limit = args['limit'] - - # get total record count of query - if type(response) == dict: - total_records = int(response['total_record_count']) - elif type(response) == ET.Element: - total_records = int(response.attrib['total_record_count']) - else: - total_records = limit - - # set new retrieval limit - records_retrieved = limit - args['limit'] = max_limit - limit = max_limit - - while True: - if total_records <= records_retrieved: - break - - # make call and increment counter variables - new_response = self.read(url, args, raw=raw) - records_retrieved += limit - args['offset'] += limit - - # append new records to initial response - if type(new_response) == dict: - response[data_key] += new_response[data_key] - elif type(new_response) == ET.Element: - for row in list(new_response): - response.append(row) - elif raw: - responses.append(new_response) - - if raw: - response = responses - - return response - - def __parse_response__(self, response): - """Parses alma response depending on content type. - - Args: - response: requests object from Alma. - - Returns: - Content of response in format specified in header. - """ - status = response.status_code - url = response.url - try: - response_type = response.headers['Content-Type'] - if ";" in response_type: - response_type, charset = response_type.split(";") - except: - message = 'Error ' + str(status) + response.text - raise utils.AlmaError(message, status, url) - - # decode response if xml. - if response_type == 'application/xml': - xml_ns = self.cnxn_params['xml_ns'] # xml namespace - content = ET.fromstring(response.text) - - # Received response from ex libris, but error retrieving data. - if str(status)[0] in ['4', '5']: - try: - first_error = content.find("header:errorList", xml_ns)[0] - message = first_error.find("header:errorCode", xml_ns).text - message += " - " - message += first_error.find("header:errorMessage", xml_ns).text - message += " See Alma documentation for more information." - except: - message = 'Error ' + str(status) + " - " + str(content) - raise utils.AlmaError(message, status, url) - - # decode response if json. - elif response_type == 'application/json': - content = response.json() - - # Received response from ex libris, but error retrieving data. - if str(status)[0] in ['4', '5']: - try: - if 'web_service_result' in content.keys(): - first_error = content['web_service_result']['errorList']['error'][0] - else: - first_error = content['errorList']['error'][0] - message = first_error['errorCode'] - message += " - " - message += first_error['errorMessage'] - if 'trackingID' in first_error.keys(): - message += "TrackingID: " + message['trackingID'] - message += " See Alma documentation for more information." - except: - message = 'Error ' + str(status) + " - " + str(content) - raise utils.AlmaError(message, status, url) - - else: - content = response - - if str(status)[0] in ['4', '5']: - message = str(status) + " - " - message += str(content.text) - raise utils.AlmaError(message, status, url) - return content +#-*- coding: utf-8-unix -*- + +""" +Common Client for interacting with Alma API +""" + +import json +import xml.etree.ElementTree as ET + +import requests + +from . import utils + + +class Client(object): + """ + Reads responses from Alma API and handles response. + """ + + def __init__(self, cnxn_params={}): + # instantiate dictionary for storing alma api connection parameters + self.cnxn_params = cnxn_params + + def create(self, url, data, args, object_type, raw=False): + """ + Uses requests library to make Exlibris API Post call. + + Args: + url (str): Exlibris API endpoint url. + data (dict): Data to be posted. + args (dict): Query string parameters for API call. + object_type (str): Type of object to be posted (see alma docs) + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ +# print(url) + + # Determine format of data to be posted according to order of importance: + # 1) Local declaration, 2) dtype of data parameter, 3) global setting. + headers = {} + if 'format' not in args.keys(): + if type(data) == ET or type(data) == ET.Element: + content_type = 'xml' + elif type(data) == dict: + content_type = 'json' + else: + content_type = self.cnxn_params['format'] + args['format'] = self.cnxn_params['format'] + else: + content_type = args['format'] + + # Declare data type in header, convert to string if necessary. + if content_type == 'json': + headers['content-type'] = 'application/json' + if type(data) != str: + data = json.dumps(data) + elif content_type == 'xml': + headers['content-type'] = 'application/xml' + if type(data) == ET or type(data) == ET.Element: + data = ET.tostring(data, encoding='unicode') + elif type(data) != str: + message = "XML payload must be either string or ElementTree." + raise utils.ArgError(message) + else: + message = "Post content type must be either 'json' or 'xml'" + raise utils.ArgError(message) + + # Send request and parse response + response = requests.post(url, data=data, params=args, headers=headers) + if raw: + return response + content = self.__parse_response__(response) + + return content + + + def read(self, url, args, raw=False): + """ + Uses requests library to make Exlibris API Get call. + Returns data of type specified during init of base class. + + Args: + url (str): Exlibris API endpoint url. + args (dict): Query string parameters for API call. + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ +# print(url) + + # handle data format. Allow for overriding of global setting. + data_format = self.cnxn_params['format'] + if 'format' not in args.keys(): + args['format'] = data_format + data_format = args['format'] + + # Send request. + response = requests.get(url, params=args) + if raw: + return response + + # Parse content + content = self.__parse_response__(response) + + return content + + def __format_query__(self, query): + """Converts dictionary of brief search query to a formated string. + https://developers.exlibrisgroup.com/blog/How-we-re-building-APIs-at-Ex-Libris#BriefSearch + + Args: + query: dictionary of brief search query. + Format - {'field': 'value', 'field2', 'value2'}. + Returns: + String of query. + """ + q_str = "" + i = 0 + if type(query) != 'dict': + message = "Brief search query must be a dictionary." + for field, filter_value in query.items(): + field = str(field) + filter_value = str(filter_value) + if i > 0: + q_str += " AND " + q_str += (field + "~") + q_str += filter_value.replace(" ", "_") + i += 1 + + return q_str + + def __read_all__(self, url, args, raw, response, data_key, max_limit=100): + """Makes multiple API calls until all records for a query are retrieved. + Called by the 'all_records' parameter. + + Args: + url (str): Exlibris API endpoint url. + args (dict): Query string parameters for API call. + raw (bool): If true, returns raw response. + response (xml, raw, or json): First API call. + data_key (str): Dictionary key for accessing data. + max_limit (int): Max number of records allowed to be retrieved in a single call. + Overrides limit parameter. Reduces the number of API calls needed to retrieve data. + + Returns: + response with remainder of data appended. + """ + # raw will return a list of responses + if raw: + responses = [response] + response = response.json() + + args['offset'] = args['limit'] + limit = args['limit'] + + # get total record count of query + if type(response) == dict: + total_records = int(response['total_record_count']) + elif type(response) == ET.Element: + total_records = int(response.attrib['total_record_count']) + else: + total_records = limit + + # set new retrieval limit + records_retrieved = limit + args['limit'] = max_limit + limit = max_limit + + while True: + if total_records <= records_retrieved: + break + + # make call and increment counter variables + new_response = self.read(url, args, raw=raw) + records_retrieved += limit + args['offset'] += limit + + # append new records to initial response + if type(new_response) == dict: + response[data_key] += new_response[data_key] + elif type(new_response) == ET.Element: + for row in list(new_response): + response.append(row) + elif raw: + responses.append(new_response) + + if raw: + response = responses + + return response + + def __parse_response__(self, response): + """Parses alma response depending on content type. + + Args: + response: requests object from Alma. + + Returns: + Content of response in format specified in header. + """ + status = response.status_code + url = response.url + try: + response_type = response.headers['Content-Type'] + if ";" in response_type: + response_type, charset = response_type.split(";") + except: + message = 'Error ' + str(status) + response.text + raise utils.AlmaError(message, status, url) + + # decode response if xml. + if response_type == 'application/xml': + xml_ns = self.cnxn_params['xml_ns'] # xml namespace + content = ET.fromstring(response.text) + + # Received response from ex libris, but error retrieving data. + if str(status)[0] in ['4', '5']: + try: + first_error = content.find("header:errorList", xml_ns)[0] + message = first_error.find("header:errorCode", xml_ns).text + message += " - " + message += first_error.find("header:errorMessage", xml_ns).text + message += " See Alma documentation for more information." + except: + message = 'Error ' + str(status) + " - " + str(content) + raise utils.AlmaError(message, status, url) + + # decode response if json. + elif response_type == 'application/json': + content = response.json() + + # Received response from ex libris, but error retrieving data. + if str(status)[0] in ['4', '5']: + try: + if 'web_service_result' in content.keys(): + first_error = content['web_service_result']['errorList']['error'][0] + else: + first_error = content['errorList']['error'][0] + message = first_error['errorCode'] + message += " - " + message += first_error['errorMessage'] + if 'trackingID' in first_error.keys(): + message += "TrackingID: " + message['trackingID'] + message += " See Alma documentation for more information." + except: + message = 'Error ' + str(status) + " - " + str(content) + raise utils.AlmaError(message, status, url) + + else: + content = response + + if str(status)[0] in ['4', '5']: + message = str(status) + " - " + message += str(content.text) + raise utils.AlmaError(message, status, url) + return content diff --git a/almapipy/conf.py b/almapipy/conf.py index 001d375..7aa6452 100644 --- a/almapipy/conf.py +++ b/almapipy/conf.py @@ -1,567 +1,569 @@ -from .client import Client -from . import utils - - -class SubClientConfiguration(Client): - """ - Alma provides a set of Web services for handling Configuration related - information, enabling you to quickly and easily receive configuration details. - These Web services can be used by external systems in order to get list of - possible data. - For more info: https://developers.exlibrisgroup.com/alma/apis/conf - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/conf" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/conf" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/37088dc9-c685-4641-bc7f-60b5ca7cabed.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.units = SubClientConfigurationUnits(self.cnxn_params) - self.general = SubClientConfigurationGeneral(self.cnxn_params) - self.jobs = SubClientConfigurationJobs(self.cnxn_params) - self.sets = SubClientConfigurationSets(self.cnxn_params) - self.deposit_profiles = SubClientConfigurationDeposit(self.cnxn_params) - self.import_profiles = SubClientConfigurationImport(self.cnxn_params) - self.reminders = SubClientConfigurationReminders(self.cnxn_params) - - -class SubClientConfigurationUnits(Client): - """Handles the Organization Unit endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get_libaries(self, library_id=None, q_params={}, raw=False): - """Retrieve a list of libraries or a specific library - - Args: - library_id (str): The code of the library (libraryCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of libraries or single library - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += '/libraries' - if library_id: - url += ("/" + str(library_id)) - - response = self.read(url, args, raw=raw) - return response - - def get_locations(self, library_id, location_id=None, q_params={}, raw=False): - """Retrieve a list of locations for a library - - Args: - library_id (str): The code of the library (libraryCode). - location_id (str): Code for a specific location (locationCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of library locations. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ('/libraries/' + str(library_id) + "/locations") - if location_id: - url += ("/" + str(location_id)) - - response = self.read(url, args, raw=raw) - return response - - def get_departments(self, q_params={}, raw=False): - """Retrieve a list of configured departments - - Args: - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of departments. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += '/departments' - - response = self.read(url, args, raw=raw) - return response - - -class SubClientConfigurationGeneral(Client): - """Handles the General endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, library_id=None, q_params={}, raw=False): - """Retrieve general configuration of the institution - - Args: - library_id (str): The code of the library (libraryCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - General configuration of the institution - """ - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += "/general" - - response = self.read(url, args, raw=raw) - return response - - def get_hours(self, library_id=None, q_params={}, raw=False): - """Retrieve open hours as configured in Alma. - Note that the library-hours do not necessarily reflect when the - library doors are actually open, but rather start and end times that - effect loan period. - This API is limited to one month of days from 1 year ago to - 3 years ahead for a single request. - - Args: - library_id (str): The code of the library (libraryCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of open hours - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - if library_id: - url += '/libraries' - url += ("/" + str(library_id)) - - url += '/open-hours' - - response = self.read(url, args, raw=raw) - return response - - def get_code_table(self, table_name, q_params={}, raw=False): - """This API returns all rows defined for a code-table. - - The main usage of this API is for applications that use Alma APIs, - and need to give the user a drop-down of valid values to choose from. - - See Alma documentation for code-table names. - - Args: - table_name (str): Code table name. (codeTableName) - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Code-table rows - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ('/code-tables/' + str(table_name)) - - response = self.read(url, args, raw=raw) - return response - - -class SubClientConfigurationJobs(Client): - """Handles the Jobs endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/jobs' - self.cnxn_params['api_uri_full'] += '/jobs' - - def get(self, job_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a list of jobs that can be submitted or details for a given job. - - Args: - job_id (str): Unique id of the job. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of jobs or a specific job - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - if job_id: - url += ("/" + str(job_id)) - else: - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if job_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='job') - return response - - def get_instances(self, job_id, instance_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve all the job instances (runs) for a given job id, or specific instance. - - Args: - job_id (str): Unique id of the job. - instance_id (str): Unique id of the specific job instance. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of jobs or a specific job - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(job_id) + "/instances") - - if instance_id: - url += ("/" + str(instance_id)) - else: - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if instance_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='job_instance') - return response - - -class SubClientConfigurationSets(Client): - """Handles the Sets endpoints of Configurations API - A set is a collection of items, such as users or the results of a repository search. - Sets may be used for publishing metadata in bulk, moving a group of records, or to run jobs. - """ - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/sets' - self.cnxn_params['api_uri_full'] += '/sets' - - def get(self, set_id=None, content_type=None, set_type=None, - query={}, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a list of sets or a single set. - - Args: - set_id (str): A unique identifier of the set. - content_type (str): Content type for filtering. - Valid values are from the SetContentType code table. - set_type (str): Set type for filtering. - Valid values are 'ITEMIZED' or 'LOGICAL'. - query (dict): Search query. Searching for words in created_by or name - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of sets or a specific set. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if set_id: - url += ("/" + str(set_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - if content_type: - args['content_type'] = str(content_type) - if set_type: - args['set_type'] = str(set_type) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - response = self.read(url, args, raw=raw) - if set_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='set') - return response - - def get_members(self, set_id, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves members of a Set given a Set ID. - - Args: - set_id (str): A unique identifier of the set. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of members for a specific set - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(set_id) + "/members") - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='member') - return response - - -class SubClientConfigurationDeposit(Client): - """Handles the Deposit profiles endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/deposit-profiles' - self.cnxn_params['api_uri_full'] += '/deposit-profiles' - - def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves list of deposit profiles or specific profile - - Args: - deposit_profile_id (str): A unique identifier of the deposit profile. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of deposit profiles or specific profile. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if deposit_profile_id: - url += ("/" + str(deposit_profile_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if deposit_profile_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='deposit_profile') - - -class SubClientConfigurationImport(Client): - """Handles the Import profiles endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/md-import-profiles' - self.cnxn_params['api_uri_full'] += '/md-import-profiles' - - def get(self, profile_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves list of import profiles or specific profile - - Args: - profile_id (str): A unique identifier of the import profile. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of import profiles or specific profile. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if profile_id: - url += ("/" + str(profile_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if profile_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='import_profile') - return response - - -class SubClientConfigurationReminders(Client): - """Handles the Reminder endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/reminders' - self.cnxn_params['api_uri_full'] += '/reminders' - - def get(self, reminder_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves list of reminders or specific reminder. - - Args: - reminder_id (str): A unique identifier of the reminder. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of reminders or specific reminder. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if reminder_id: - url += ("/" + str(reminder_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if reminder_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='reminder') - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientConfiguration(Client): + """ + Alma provides a set of Web services for handling Configuration related + information, enabling you to quickly and easily receive configuration details. + These Web services can be used by external systems in order to get list of + possible data. + For more info: https://developers.exlibrisgroup.com/alma/apis/conf + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/conf" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/conf" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/37088dc9-c685-4641-bc7f-60b5ca7cabed.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.units = SubClientConfigurationUnits(self.cnxn_params) + self.general = SubClientConfigurationGeneral(self.cnxn_params) + self.jobs = SubClientConfigurationJobs(self.cnxn_params) + self.sets = SubClientConfigurationSets(self.cnxn_params) + self.deposit_profiles = SubClientConfigurationDeposit(self.cnxn_params) + self.import_profiles = SubClientConfigurationImport(self.cnxn_params) + self.reminders = SubClientConfigurationReminders(self.cnxn_params) + + +class SubClientConfigurationUnits(Client): + """Handles the Organization Unit endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get_libaries(self, library_id=None, q_params={}, raw=False): + """Retrieve a list of libraries or a specific library + + Args: + library_id (str): The code of the library (libraryCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of libraries or single library + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += '/libraries' + if library_id: + url += ("/" + str(library_id)) + + response = self.read(url, args, raw=raw) + return response + + def get_locations(self, library_id, location_id=None, q_params={}, raw=False): + """Retrieve a list of locations for a library + + Args: + library_id (str): The code of the library (libraryCode). + location_id (str): Code for a specific location (locationCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of library locations. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ('/libraries/' + str(library_id) + "/locations") + if location_id: + url += ("/" + str(location_id)) + + response = self.read(url, args, raw=raw) + return response + + def get_departments(self, q_params={}, raw=False): + """Retrieve a list of configured departments + + Args: + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of departments. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += '/departments' + + response = self.read(url, args, raw=raw) + return response + + +class SubClientConfigurationGeneral(Client): + """Handles the General endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, library_id=None, q_params={}, raw=False): + """Retrieve general configuration of the institution + + Args: + library_id (str): The code of the library (libraryCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + General configuration of the institution + """ + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += "/general" + + response = self.read(url, args, raw=raw) + return response + + def get_hours(self, library_id=None, q_params={}, raw=False): + """Retrieve open hours as configured in Alma. + Note that the library-hours do not necessarily reflect when the + library doors are actually open, but rather start and end times that + effect loan period. + This API is limited to one month of days from 1 year ago to + 3 years ahead for a single request. + + Args: + library_id (str): The code of the library (libraryCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of open hours + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + if library_id: + url += '/libraries' + url += ("/" + str(library_id)) + + url += '/open-hours' + + response = self.read(url, args, raw=raw) + return response + + def get_code_table(self, table_name, q_params={}, raw=False): + """This API returns all rows defined for a code-table. + + The main usage of this API is for applications that use Alma APIs, + and need to give the user a drop-down of valid values to choose from. + + See Alma documentation for code-table names. + + Args: + table_name (str): Code table name. (codeTableName) + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Code-table rows + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ('/code-tables/' + str(table_name)) + + response = self.read(url, args, raw=raw) + return response + + +class SubClientConfigurationJobs(Client): + """Handles the Jobs endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/jobs' + self.cnxn_params['api_uri_full'] += '/jobs' + + def get(self, job_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a list of jobs that can be submitted or details for a given job. + + Args: + job_id (str): Unique id of the job. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of jobs or a specific job + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + if job_id: + url += ("/" + str(job_id)) + else: + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if job_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='job') + return response + + def get_instances(self, job_id, instance_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve all the job instances (runs) for a given job id, or specific instance. + + Args: + job_id (str): Unique id of the job. + instance_id (str): Unique id of the specific job instance. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of jobs or a specific job + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(job_id) + "/instances") + + if instance_id: + url += ("/" + str(instance_id)) + else: + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if instance_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='job_instance') + return response + + +class SubClientConfigurationSets(Client): + """Handles the Sets endpoints of Configurations API + A set is a collection of items, such as users or the results of a repository search. + Sets may be used for publishing metadata in bulk, moving a group of records, or to run jobs. + """ + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/sets' + self.cnxn_params['api_uri_full'] += '/sets' + + def get(self, set_id=None, content_type=None, set_type=None, + query={}, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a list of sets or a single set. + + Args: + set_id (str): A unique identifier of the set. + content_type (str): Content type for filtering. + Valid values are from the SetContentType code table. + set_type (str): Set type for filtering. + Valid values are 'ITEMIZED' or 'LOGICAL'. + query (dict): Search query. Searching for words in created_by or name + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of sets or a specific set. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if set_id: + url += ("/" + str(set_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + if content_type: + args['content_type'] = str(content_type) + if set_type: + args['set_type'] = str(set_type) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + response = self.read(url, args, raw=raw) + if set_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='set') + return response + + def get_members(self, set_id, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves members of a Set given a Set ID. + + Args: + set_id (str): A unique identifier of the set. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of members for a specific set + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(set_id) + "/members") + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='member') + return response + + +class SubClientConfigurationDeposit(Client): + """Handles the Deposit profiles endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/deposit-profiles' + self.cnxn_params['api_uri_full'] += '/deposit-profiles' + + def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves list of deposit profiles or specific profile + + Args: + deposit_profile_id (str): A unique identifier of the deposit profile. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of deposit profiles or specific profile. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if deposit_profile_id: + url += ("/" + str(deposit_profile_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if deposit_profile_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='deposit_profile') + + +class SubClientConfigurationImport(Client): + """Handles the Import profiles endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/md-import-profiles' + self.cnxn_params['api_uri_full'] += '/md-import-profiles' + + def get(self, profile_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves list of import profiles or specific profile + + Args: + profile_id (str): A unique identifier of the import profile. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of import profiles or specific profile. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if profile_id: + url += ("/" + str(profile_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if profile_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='import_profile') + return response + + +class SubClientConfigurationReminders(Client): + """Handles the Reminder endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/reminders' + self.cnxn_params['api_uri_full'] += '/reminders' + + def get(self, reminder_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves list of reminders or specific reminder. + + Args: + reminder_id (str): A unique identifier of the reminder. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of reminders or specific reminder. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if reminder_id: + url += ("/" + str(reminder_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if reminder_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='reminder') + return response diff --git a/almapipy/courses.py b/almapipy/courses.py index 43420ec..945d0e5 100644 --- a/almapipy/courses.py +++ b/almapipy/courses.py @@ -1,231 +1,233 @@ -from .client import Client -from . import utils - - -class SubClientCourses(Client): - """ - The Courses API allows access to courses and reading lists related information. - These Web services can be used by external systems such as Courses Management - Systems to retrieve or update courses and reading lists related data. - For more info: https://developers.exlibrisgroup.com/alma/apis/courses - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/courses" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/courses" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/25ede018-da5d-4780-8fda-a8e5d103faba.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - #self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' - - # Hook in subclients of api - self.reading_lists = SubClientCoursesReadingLists(self.cnxn_params) - self.citations = SubClientCoursesCitations(self.cnxn_params) - self.owners = SubClientCoursesOwners(self.cnxn_params) - self.tags = SubClientCoursesTags(self.cnxn_params) - - def get(self, course_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a courses list or a single course. - - Args: - course_id (str): The identifier of a single course. - Gets more detailed information. - query (dict): Search query for filtering a course list. Optional. - Searching for words from fields: [code, section, name, notes, - instructors, searchable_ids, year, academic_department, - all]. Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'code': 'ECN'} returns a list of econ classes - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of courses or a specific course resource. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if course_id: - url += ("/" + str(course_id)) - else: - # include paramets specific to course list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if course_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='course') - return response - - -class SubClientCoursesReadingLists(Client): - """Handles the reading list endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id=None, view='brief', q_params={}, raw=False): - """Retrieves all Reading Lists, or a specific list, for a Course. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - view (str): 'brief' or 'full' view of reading list. - Only applies when retrieving a single record. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of reading lists or single list - for a given course ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - if reading_list_id: - url += ('/' + str(reading_list_id)) - if view not in ['brief', 'full']: - message = "Valid view arguments are 'brief' or 'full'" - raise utils.ArgError(message) - args['view'] = view - - return self.read(url, args, raw=raw) - - -class SubClientCoursesCitations(Client): - """Handles the citations endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id, citation_id=None, q_params={}, raw=False): - """Retrieves all citations, or a specific citation, for a reading list. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - citation_id (str): The identifier of the citation. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of citations or single citation - for a given reading list ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - url += ('/' + str(reading_list_id)) - url += '/citations' - - if citation_id: - url += ('/' + str(citation_id)) - - return self.read(url, args, raw=raw) - - -class SubClientCoursesOwners(Client): - """Handles the owners endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id, owner_id=None, q_params={}, raw=False): - """Retrieves all owners, or a specific owner, for a reading list. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - owner_id (str): The primary identifier of the user (primary_id). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of owner or single owner - for a given reading list ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - url += ('/' + str(reading_list_id)) - url += '/owners' - - if owner_id: - url += ('/' + str(owner_id)) - - return self.read(url, args, raw=raw) - - -class SubClientCoursesTags(Client): - """Handles the citation tags endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id, citation_id, q_params={}, raw=False): - """Retrieves a citation's tag list. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - citation_id (str): The identifier of the citation. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of a citation's tags in for a given reading list ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - url += ('/' + str(reading_list_id)) - url += '/citations' - url += ('/' + str(citation_id) + "/tags") - - return self.read(url, args, raw=raw) +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientCourses(Client): + """ + The Courses API allows access to courses and reading lists related information. + These Web services can be used by external systems such as Courses Management + Systems to retrieve or update courses and reading lists related data. + For more info: https://developers.exlibrisgroup.com/alma/apis/courses + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/courses" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/courses" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/25ede018-da5d-4780-8fda-a8e5d103faba.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + #self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' + + # Hook in subclients of api + self.reading_lists = SubClientCoursesReadingLists(self.cnxn_params) + self.citations = SubClientCoursesCitations(self.cnxn_params) + self.owners = SubClientCoursesOwners(self.cnxn_params) + self.tags = SubClientCoursesTags(self.cnxn_params) + + def get(self, course_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a courses list or a single course. + + Args: + course_id (str): The identifier of a single course. + Gets more detailed information. + query (dict): Search query for filtering a course list. Optional. + Searching for words from fields: [code, section, name, notes, + instructors, searchable_ids, year, academic_department, + all]. Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'code': 'ECN'} returns a list of econ classes + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of courses or a specific course resource. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if course_id: + url += ("/" + str(course_id)) + else: + # include paramets specific to course list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if course_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='course') + return response + + +class SubClientCoursesReadingLists(Client): + """Handles the reading list endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id=None, view='brief', q_params={}, raw=False): + """Retrieves all Reading Lists, or a specific list, for a Course. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + view (str): 'brief' or 'full' view of reading list. + Only applies when retrieving a single record. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of reading lists or single list + for a given course ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + if reading_list_id: + url += ('/' + str(reading_list_id)) + if view not in ['brief', 'full']: + message = "Valid view arguments are 'brief' or 'full'" + raise utils.ArgError(message) + args['view'] = view + + return self.read(url, args, raw=raw) + + +class SubClientCoursesCitations(Client): + """Handles the citations endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id, citation_id=None, q_params={}, raw=False): + """Retrieves all citations, or a specific citation, for a reading list. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + citation_id (str): The identifier of the citation. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of citations or single citation + for a given reading list ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + url += ('/' + str(reading_list_id)) + url += '/citations' + + if citation_id: + url += ('/' + str(citation_id)) + + return self.read(url, args, raw=raw) + + +class SubClientCoursesOwners(Client): + """Handles the owners endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id, owner_id=None, q_params={}, raw=False): + """Retrieves all owners, or a specific owner, for a reading list. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + owner_id (str): The primary identifier of the user (primary_id). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of owner or single owner + for a given reading list ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + url += ('/' + str(reading_list_id)) + url += '/owners' + + if owner_id: + url += ('/' + str(owner_id)) + + return self.read(url, args, raw=raw) + + +class SubClientCoursesTags(Client): + """Handles the citation tags endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id, citation_id, q_params={}, raw=False): + """Retrieves a citation's tag list. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + citation_id (str): The identifier of the citation. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of a citation's tags in for a given reading list ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + url += ('/' + str(reading_list_id)) + url += '/citations' + url += ('/' + str(citation_id) + "/tags") + + return self.read(url, args, raw=raw) diff --git a/almapipy/electronic.py b/almapipy/electronic.py index ea68d53..b045233 100644 --- a/almapipy/electronic.py +++ b/almapipy/electronic.py @@ -1,157 +1,159 @@ -from .client import Client -from . import utils - - -class SubClientElectronic(Client): - """ - Alma provides a set of Web services for handling electronic information, - enabling you to quickly and easily manipulate electronic details. - These Web services can be used by external systems in order to retrieve or - update electronic data. - For more info: https://developers.exlibrisgroup.com/alma/apis/electronic - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/electronic" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/electronic" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/e7cf39e9-adce-4be1-aeb9-a31f452960da.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.collections = SubClientElectronicCollections(self.cnxn_params) - self.services = SubClientElectronicServices(self.cnxn_params) - self.portfolios = SubClientElectronicPortfolios(self.cnxn_params) - - -class SubClientElectronicCollections(Client): - """Handles the e-collections endpoints of Electronic API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/e-collections' - self.cnxn_params['api_uri_full'] += '/e-collections' - - def get(self, collection_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of electronic collections. - - Args: - collection_id (str): Unique ID of the electronic collection. - query (dict): Search query for filtering a course list. Optional. - Searching for words from fields: [interface_name, keywords, - name, po_line_id]. Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of electronic collections or specific collection. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if collection_id: - url += ("/" + str(collection_id)) - else: - # include paramets specific to course list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if collection_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='electronic_collection') - return response - - -class SubClientElectronicServices(Client): - """Handles the e-services endpoints of Electronic API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/e-collections/' - self.cnxn_params['api_uri_full'] += '/e-collections/' - - def get(self, collection_id, service_id=None, q_params={}, raw=False): - """Returns a list of electronic services for a given electronic collection. - - Args: - collection_id (str): Unique ID of the electronic collection. - service_id (str): Unique ID of the electronic service. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of electronic services for a given electronic collection - or a specific e-service. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(collection_id) - url += '/e-services' - if service_id: - url += ('/' + str(service_id)) - - return self.read(url, args, raw=raw) - - -class SubClientElectronicPortfolios(Client): - """Handles the e-services endpoints of Electronic API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/e-collections/' - self.cnxn_params['api_uri_full'] += '/e-collections/' - - def get(self, collection_id, service_id, portfolio_id=None, q_params={}, raw=False): - """Returns a list of portfolios for an electronic services for a given electronic collection. - - Args: - collection_id (str): Unique ID of the electronic collection. - service_id (str): Unique ID of the electronic service. - portfolio_id (str): Unique ID of the portfolio. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of portfolios for an electronic services for a given electronic collection - or a specific portfolio. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(collection_id) - url += '/e-services' - url += ('/' + str(service_id)) - url += "/portfolios" - if portfolio_id: - url += ('/' + str(portfolio_id)) - return self.read(url, args, raw=raw) +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientElectronic(Client): + """ + Alma provides a set of Web services for handling electronic information, + enabling you to quickly and easily manipulate electronic details. + These Web services can be used by external systems in order to retrieve or + update electronic data. + For more info: https://developers.exlibrisgroup.com/alma/apis/electronic + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/electronic" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/electronic" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/e7cf39e9-adce-4be1-aeb9-a31f452960da.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.collections = SubClientElectronicCollections(self.cnxn_params) + self.services = SubClientElectronicServices(self.cnxn_params) + self.portfolios = SubClientElectronicPortfolios(self.cnxn_params) + + +class SubClientElectronicCollections(Client): + """Handles the e-collections endpoints of Electronic API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/e-collections' + self.cnxn_params['api_uri_full'] += '/e-collections' + + def get(self, collection_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of electronic collections. + + Args: + collection_id (str): Unique ID of the electronic collection. + query (dict): Search query for filtering a course list. Optional. + Searching for words from fields: [interface_name, keywords, + name, po_line_id]. Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of electronic collections or specific collection. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if collection_id: + url += ("/" + str(collection_id)) + else: + # include paramets specific to course list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if collection_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='electronic_collection') + return response + + +class SubClientElectronicServices(Client): + """Handles the e-services endpoints of Electronic API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/e-collections/' + self.cnxn_params['api_uri_full'] += '/e-collections/' + + def get(self, collection_id, service_id=None, q_params={}, raw=False): + """Returns a list of electronic services for a given electronic collection. + + Args: + collection_id (str): Unique ID of the electronic collection. + service_id (str): Unique ID of the electronic service. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of electronic services for a given electronic collection + or a specific e-service. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(collection_id) + url += '/e-services' + if service_id: + url += ('/' + str(service_id)) + + return self.read(url, args, raw=raw) + + +class SubClientElectronicPortfolios(Client): + """Handles the e-services endpoints of Electronic API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/e-collections/' + self.cnxn_params['api_uri_full'] += '/e-collections/' + + def get(self, collection_id, service_id, portfolio_id=None, q_params={}, raw=False): + """Returns a list of portfolios for an electronic services for a given electronic collection. + + Args: + collection_id (str): Unique ID of the electronic collection. + service_id (str): Unique ID of the electronic service. + portfolio_id (str): Unique ID of the portfolio. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of portfolios for an electronic services for a given electronic collection + or a specific portfolio. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(collection_id) + url += '/e-services' + url += ('/' + str(service_id)) + url += "/portfolios" + if portfolio_id: + url += ('/' + str(portfolio_id)) + return self.read(url, args, raw=raw) diff --git a/almapipy/partners.py b/almapipy/partners.py index b0491e2..16c64f6 100644 --- a/almapipy/partners.py +++ b/almapipy/partners.py @@ -1,100 +1,102 @@ -from .client import Client -from . import utils - - -class SubClientPartners(Client): - """ - Alma provides a set of Web services for handling Resource Sharing Partner - information, enabling you to quickly and easily manipulate partner details. - These Web services can be used by external systems to retrieve or update - partner data. - For more info: https://developers.exlibrisgroup.com/alma/apis/partners - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/partners" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/partners" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/8883ef41-c3b8-4792-9ff8-cb6b729d6e07.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.lending_requests = SubClientPartnersLending(self.cnxn_params) - - def get(self, partner_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves a list of Resource Sharing Partners or specific partner. - - Args: - partner_id (str): The code of the Resource Sharing Partner (partner_code). - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of partners or specific partner - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if partner_id: - url += ("/" + str(partner_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if partner_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='partner') - return response - - -class SubClientPartnersLending(Client): - """Handles the Lending Request endpoints of Resource Sharing Partners API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, partner_id, request_id, q_params={}, raw=False): - """Retrieve a lending request from a specific partner. - - Args: - partner_id (str): The code of the Resource Sharing Partner (partner_code). - request_id (str): The ID of the requested lending request. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Lending request from a specific partner. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(partner_id) + "/lending-requests") - url += ("/" + str(request_id)) - - response = self.read(url, args, raw=raw) - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientPartners(Client): + """ + Alma provides a set of Web services for handling Resource Sharing Partner + information, enabling you to quickly and easily manipulate partner details. + These Web services can be used by external systems to retrieve or update + partner data. + For more info: https://developers.exlibrisgroup.com/alma/apis/partners + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/partners" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/partners" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/8883ef41-c3b8-4792-9ff8-cb6b729d6e07.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.lending_requests = SubClientPartnersLending(self.cnxn_params) + + def get(self, partner_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves a list of Resource Sharing Partners or specific partner. + + Args: + partner_id (str): The code of the Resource Sharing Partner (partner_code). + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of partners or specific partner + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if partner_id: + url += ("/" + str(partner_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if partner_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='partner') + return response + + +class SubClientPartnersLending(Client): + """Handles the Lending Request endpoints of Resource Sharing Partners API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, partner_id, request_id, q_params={}, raw=False): + """Retrieve a lending request from a specific partner. + + Args: + partner_id (str): The code of the Resource Sharing Partner (partner_code). + request_id (str): The ID of the requested lending request. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Lending request from a specific partner. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(partner_id) + "/lending-requests") + url += ("/" + str(request_id)) + + response = self.read(url, args, raw=raw) + return response diff --git a/almapipy/task_lists.py b/almapipy/task_lists.py index 1e31b69..d748256 100644 --- a/almapipy/task_lists.py +++ b/almapipy/task_lists.py @@ -1,112 +1,114 @@ -from .client import Client -from . import utils - - -class SubClientTaskList(Client): - """ - Alma provides a set of Web services for handling task lists information, - enabling you to quickly and easily manipulate their details. - These Web services can be used by external systems. - For more info: https://developers.exlibrisgroup.com/alma/apis/taskslists - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/task-lists" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/taskslists" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d48a1a58-d90c-4eb2-b69f-c17f7a016fd3.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.resources = SubClientTaskListResources(self.cnxn_params) - self.lending = SubClientTaskListLending(self.cnxn_params) - - -class SubClientTaskListResources(Client): - """Handles the requested resources endpoints of Task List API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/requested-resources' - self.cnxn_params['api_uri_full'] += '/requested-resources' - - def get(self, library_id, circ_desk, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of requested resources to be picked from the shelf in Alma - for a specific library/circ_desk. - - Args: - library_id (str): The library of the given circulation desk or department where the resources are located. - Use conf.units.get_libraries() to retrieve valid arguments. - circ_desk (str): The circulation desk where the action is being performed. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of requested resources. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - args['library'] = str(library_id) - args['circ_desk'] = str(circ_desk) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, - raw=raw, response=response, - data_key='requested_resource') - return response - - -class SubClientTaskListLending(Client): - """Handles the requested resources endpoints of Task List API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/rs/lending-requests' - self.cnxn_params['api_uri_full'] += '/rs/lending-requests' - - def get(self, library_id, q_params={}, raw=False): - """Retrieve list of lending requests in Alma. - - Args: - library_id (str): The library of the given circulation desk or department where the resources are located. - Use conf.units.get_libraries() to retrieve valid arguments. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of lending requests. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['library'] = str(library_id) - - url = self.cnxn_params['api_uri_full'] - - response = self.read(url, args, raw=raw) - - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientTaskList(Client): + """ + Alma provides a set of Web services for handling task lists information, + enabling you to quickly and easily manipulate their details. + These Web services can be used by external systems. + For more info: https://developers.exlibrisgroup.com/alma/apis/taskslists + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/task-lists" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/taskslists" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d48a1a58-d90c-4eb2-b69f-c17f7a016fd3.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.resources = SubClientTaskListResources(self.cnxn_params) + self.lending = SubClientTaskListLending(self.cnxn_params) + + +class SubClientTaskListResources(Client): + """Handles the requested resources endpoints of Task List API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/requested-resources' + self.cnxn_params['api_uri_full'] += '/requested-resources' + + def get(self, library_id, circ_desk, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of requested resources to be picked from the shelf in Alma + for a specific library/circ_desk. + + Args: + library_id (str): The library of the given circulation desk or department where the resources are located. + Use conf.units.get_libraries() to retrieve valid arguments. + circ_desk (str): The circulation desk where the action is being performed. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of requested resources. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + args['library'] = str(library_id) + args['circ_desk'] = str(circ_desk) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, + raw=raw, response=response, + data_key='requested_resource') + return response + + +class SubClientTaskListLending(Client): + """Handles the requested resources endpoints of Task List API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/rs/lending-requests' + self.cnxn_params['api_uri_full'] += '/rs/lending-requests' + + def get(self, library_id, q_params={}, raw=False): + """Retrieve list of lending requests in Alma. + + Args: + library_id (str): The library of the given circulation desk or department where the resources are located. + Use conf.units.get_libraries() to retrieve valid arguments. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of lending requests. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['library'] = str(library_id) + + url = self.cnxn_params['api_uri_full'] + + response = self.read(url, args, raw=raw) + + return response diff --git a/almapipy/users.py b/almapipy/users.py index 6830315..5a1a53f 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -1,335 +1,340 @@ -from .client import Client -from . import utils - - -class SubClientUsers(Client): - """ - Alma provides a set of Web services for handling user information, - enabling you to quickly and easily manipulate user details. - These Web services can be used by external systems - such as student information systems (SIS) to retrieve or update user data. - For more info: https://developers.exlibrisgroup.com/alma/apis/users - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/users" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/users" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/0aa8d36f-53d6-48ff-8996-485b90b103e4.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.loans = SubClientUsersLoans(self.cnxn_params) - self.requests = SubClientUsersRequests(self.cnxn_params) - self.fees = SubClientUsersFees(self.cnxn_params) - self.deposits = SubClientUsersDeposits(self.cnxn_params) - - def get(self, user_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a user list or a single user. - - Args: - user_id (str): A unique identifier for the user. - Gets more detailed information. - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [primary_id, first_name, - last_name, middle_name, email, job_category, identifiers, - general_info and ALL.]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'first_name': 'Sterling', 'last_name': 'Archer'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of user or a specific user's details. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if user_id: - url += ("/" + str(user_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if user_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user') - return response - - - def post(self, identifier, id_type, user_data={}, raw=False): - """Create a single user if it does not exists yet in Alma - - Args: - id_type (str): The identifier type for the user - Values: from the User Identifier Type code table. - identifier (str): The identifier itself for the user. - user_data (dict): Data for user enrollment. - Setting words for fields: [first_name, last_name, - middle_name, email, job_category, general_info]. - Format {'field': 'value', 'field2', 'value2'}. - e.g. data = {'first_name': 'Sterling', 'last_name': 'Archer'} - raw (bool): If true, returns raw requests object. - - Returns: ¿? - The user (at Alma) if a new user is created. - Empty list if the 'identifier' was already set. - - """ - - args = {} - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - # Search query for the 'identifier' in Alma - args['id_type'] = id_type - query = {} - query['identifier'] = identifier - args['q'] = self.__format_query__(query) - - # Search for a user with this 'user_identifier' - response = self.read(url, args, raw=raw) - - if not response: - # No user exists with this 'identifier': Let's create it. - args.clear() - args['apikey'] = self.cnxn_params['api_key'] - - user_data['identifier'] = identifier -# TODO: status, segment_type? - user_data['status'] = 'ACTIVE' - user_data['segment_type'] = 'External' - - response = self.create(url, user_data, args, raw=raw) - - return response - - -class SubClientUsersLoans(Client): - """Handles the Loans endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, loan_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of loans for a user. - - Args: - user_id (str): A unique identifier for the user. - loan_id (str): A unique identifier for the loan. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a specific loan for a given user. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += (str(user_id) + "/loans") - - if loan_id: - url += ('/' + str(loan_id)) - else: - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - if loan_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='item_loan') - return response - - -class SubClientUsersRequests(Client): - """Handles the Requests endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, request_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of requests for a user. - - Args: - user_id (str): A unique identifier for the user. - request_id (str): A unique identifier for the request. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of requests or a specific request for a given user. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += (str(user_id) + "/requests") - - if request_id: - url += ('/' + str(request_id)) - else: - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - if request_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user_request') - return response - - -class SubClientUsersFees(Client): - """Handles the Fines and Fees endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, fee_id=None, q_params={}, raw=False): - """Retrieve a list of fines and fees for a user. - - Args: - user_id (str): A unique identifier for the user. - fee_id (str): A unique identifier for the fee. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of fines/fees or a specific fine/fee for a given user. - - """ - url = self.cnxn_params['api_uri_full'] - url += (str(user_id)) - url += '/fees' - if fee_id: - url += ('/' + str(fee_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientUsersDeposits(Client): - """Handles the Deposits endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, deposit_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of deposits for a user. - - Args: - user_id (str): A unique identifier for the user. - deposit_id (str): A unique identifier for the deposit. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of deposits or a specific deposit for a given user. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += (str(user_id) + "/deposits") - - if deposit_id: - url += ('/' + str(deposit_id)) - else: - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - if deposit_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user_deposit') - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientUsers(Client): + """ + Alma provides a set of Web services for handling user information, + enabling you to quickly and easily manipulate user details. + These Web services can be used by external systems + such as student information systems (SIS) to retrieve or update user data. + For more info: https://developers.exlibrisgroup.com/alma/apis/users + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/users" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/users" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/0aa8d36f-53d6-48ff-8996-485b90b103e4.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.loans = SubClientUsersLoans(self.cnxn_params) + self.requests = SubClientUsersRequests(self.cnxn_params) + self.fees = SubClientUsersFees(self.cnxn_params) + self.deposits = SubClientUsersDeposits(self.cnxn_params) + + def get(self, user_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a user list or a single user. + + Args: + user_id (str): A unique identifier for the user. + Gets more detailed information. + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [primary_id, first_name, + last_name, middle_name, email, job_category, identifiers, + general_info and ALL.]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'first_name': 'Sterling', 'last_name': 'Archer'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of user or a specific user's details. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if user_id: + url += ("/" + str(user_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if user_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='user') + return response + + + def post(self, identifier, id_type, user_data={}, raw=False): + """Create a single user if it does not exists yet in Alma + + Args: + id_type (str): The identifier type for the user + Values: from the code-table: UserIdentifierTypes + See: + identifier (str): The identifier itself for the user. + See: + user_data (dict): Data for user enrollment. + Setting words for fields: [first_name, last_name, + middle_name, email, user_group, ...]. + See + Values(user_group): code-table: UserGroups. + Format {'field': 'value', 'field2', 'value2'}. + raw (bool): If true, returns raw requests object. + + Returns: (?) + The user (at Alma) if a new user is created. + Empty list if the 'identifier' was already set. + + """ + + args = {} + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + # Search query for the 'identifier' in Alma + args['id_type'] = id_type + query = {} + query['identifier'] = identifier + args['q'] = self.__format_query__(query) + + # Search for a user with this 'user_identifier' + response = self.read(url, args, raw=raw) + + if not response: + # No user exists with this 'identifier': Let's create it. + args.clear() + args['apikey'] = self.cnxn_params['api_key'] + + user_data['identifier'] = identifier +# TODO: status, segment_type? + user_data['status'] = 'ACTIVE' + user_data['segment_type'] = 'External' + + response = self.create(url, user_data, args, raw=raw) + + return response + + +class SubClientUsersLoans(Client): + """Handles the Loans endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, loan_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of loans for a user. + + Args: + user_id (str): A unique identifier for the user. + loan_id (str): A unique identifier for the loan. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a specific loan for a given user. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += (str(user_id) + "/loans") + + if loan_id: + url += ('/' + str(loan_id)) + else: + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + if loan_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='item_loan') + return response + + +class SubClientUsersRequests(Client): + """Handles the Requests endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, request_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of requests for a user. + + Args: + user_id (str): A unique identifier for the user. + request_id (str): A unique identifier for the request. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of requests or a specific request for a given user. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += (str(user_id) + "/requests") + + if request_id: + url += ('/' + str(request_id)) + else: + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + if request_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='user_request') + return response + + +class SubClientUsersFees(Client): + """Handles the Fines and Fees endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, fee_id=None, q_params={}, raw=False): + """Retrieve a list of fines and fees for a user. + + Args: + user_id (str): A unique identifier for the user. + fee_id (str): A unique identifier for the fee. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of fines/fees or a specific fine/fee for a given user. + + """ + url = self.cnxn_params['api_uri_full'] + url += (str(user_id)) + url += '/fees' + if fee_id: + url += ('/' + str(fee_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientUsersDeposits(Client): + """Handles the Deposits endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, deposit_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of deposits for a user. + + Args: + user_id (str): A unique identifier for the user. + deposit_id (str): A unique identifier for the deposit. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of deposits or a specific deposit for a given user. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += (str(user_id) + "/deposits") + + if deposit_id: + url += ('/' + str(deposit_id)) + else: + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + if deposit_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='user_deposit') + return response diff --git a/almapipy/utils.py b/almapipy/utils.py index d0ba87c..15625c3 100644 --- a/almapipy/utils.py +++ b/almapipy/utils.py @@ -1,26 +1,28 @@ -""" -Error classes and other helpful functions -""" - - -class Error(Exception): - """Base class for exceptions""" - pass - - -class AlmaError(Error): - """ - Base Exception class for Alma API calls - """ - - def __init__(self, message, response=None, url=None): - super(AlmaError, self).__init__(message) - self.message = message - self.response = response - self.url = url - - -class ArgError(Error): - def __init__(self, message): - super(ArgError, self).__init__(message) - self.message = "Invalid Argument: " + message +#-*- coding: utf-8-unix -*- + +""" +Error classes and other helpful functions +""" + + +class Error(Exception): + """Base class for exceptions""" + pass + + +class AlmaError(Error): + """ + Base Exception class for Alma API calls + """ + + def __init__(self, message, response=None, url=None): + super(AlmaError, self).__init__(message) + self.message = message + self.response = response + self.url = url + + +class ArgError(Error): + def __init__(self, message): + super(ArgError, self).__init__(message) + self.message = "Invalid Argument: " + message From 99df9e24de8bb279e3c02d96417c97b104eff552 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 16:07:27 +0100 Subject: [PATCH 10/47] General 'char coding' change to UTF8 --- almapipy/__init__.py | 168 +++--- almapipy/acquisitions.py | 948 +++++++++++++++---------------- almapipy/analytics.py | 350 ++++++------ almapipy/bibs.py | 998 ++++++++++++++++----------------- almapipy/client.py | 516 ++++++++--------- almapipy/conf.py | 1136 +++++++++++++++++++------------------- almapipy/courses.py | 464 ++++++++-------- almapipy/electronic.py | 316 +++++------ almapipy/partners.py | 202 +++---- almapipy/task_lists.py | 226 ++++---- almapipy/users.py | 675 +++++++++++----------- almapipy/utils.py | 54 +- 12 files changed, 3040 insertions(+), 3013 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index 77a690e..f85186a 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -1,83 +1,85 @@ -""" -Python client for Ex Libris Alma -""" -__author__ = "Steve Pelkey (spelkey@ucdavis.edu)" -__version__ = "0.0.9" - -import os - -from .client import Client -from .bibs import SubClientBibs -from .analytics import SubClientAnalytics -from .courses import SubClientCourses -from .users import SubClientUsers -from .acquisitions import SubClientAcquistions -from .conf import SubClientConfiguration -from .partners import SubClientPartners -from .electronic import SubClientElectronic -from .task_lists import SubClientTaskList -from . import utils - - -class AlmaCnxn(Client): - """"Interface with Alma APIs. - - Various Apis are namespaced beneath this object according to documentation - at https://developers.exlibrisgroup.com/alma/apis. - - E.g. - > alma = AlmaCnxn(your_api_key) - > alma.bibs.catalog.get_record(bib_id) # returns bibliographic records - - Args: - api_key (str): Your Api Key - Location (str): Geographic location of library. - data_format (str): Format of returned data. json or xml. - If xml is selected, data will be returned as python xml ElementTree. - """ - - def __init__(self, apikey, location='America', data_format='json'): - - super(AlmaCnxn, self).__init__() - - # determine base uri based on location - locations = {'America': 'https://api-na.hosted.exlibrisgroup.com', - 'Europe': 'https://api-eu.hosted.exlibrisgroup.com', - 'Asia Pacific': 'https://api-ap.hosted.exlibrisgroup.com', - 'Canada': 'https://api-ca.hosted.exlibrisgroup.com', - 'China': 'https://api-cn.hosted.exlibrisgroup.com'} - if location != 'America': - if location not in locations.keys(): - message = "Valid location arguments are " - message += ", ".join(locations.keys()) - raise utils.ArgError(message=message) - self.cnxn_params['location'] = location - self.cnxn_params['base_uri'] = locations[location] - - # handle preferred format - if data_format not in ['json', 'xml']: - message = "Format argument must be either 'json' or 'xml'" - raise utils.ArgError(message) - self.cnxn_params['format'] = data_format - ns = {'header': 'http://com/exlibris/urm/general/xmlbeans'} - self.cnxn_params['xml_ns'] = ns - - # TODO: validate api key. return list of accessible endpoints - # call __validate_key__ - self.cnxn_params['api_key'] = apikey - - # Hook in the various Alma APIs based on what API key can access - self.bibs = SubClientBibs(self.cnxn_params) - self.analytics = SubClientAnalytics(self.cnxn_params) - self.courses = SubClientCourses(self.cnxn_params) - self.users = SubClientUsers(self.cnxn_params) - self.acq = SubClientAcquistions(self.cnxn_params) - self.conf = SubClientConfiguration(self.cnxn_params) - self.partners = SubClientPartners(self.cnxn_params) - self.electronic = SubClientElectronic(self.cnxn_params) - self.task_lists = SubClientTaskList(self.cnxn_params) - - def __validate_key__(self, apikey): - # loop through each api and access the /test endpoint. - # return list of accessible apis. - pass +#-*- coding: utf-8-unix -*- + +""" +Python client for Ex Libris Alma +""" +__author__ = "Steve Pelkey (spelkey@ucdavis.edu)" +__version__ = "0.0.9" + +import os + +from .client import Client +from .bibs import SubClientBibs +from .analytics import SubClientAnalytics +from .courses import SubClientCourses +from .users import SubClientUsers +from .acquisitions import SubClientAcquistions +from .conf import SubClientConfiguration +from .partners import SubClientPartners +from .electronic import SubClientElectronic +from .task_lists import SubClientTaskList +from . import utils + + +class AlmaCnxn(Client): + """"Interface with Alma APIs. + + Various Apis are namespaced beneath this object according to documentation + at https://developers.exlibrisgroup.com/alma/apis. + + E.g. + > alma = AlmaCnxn(your_api_key) + > alma.bibs.catalog.get_record(bib_id) # returns bibliographic records + + Args: + api_key (str): Your Api Key + Location (str): Geographic location of library. + data_format (str): Format of returned data. json or xml. + If xml is selected, data will be returned as python xml ElementTree. + """ + + def __init__(self, apikey, location='America', data_format='json'): + + super(AlmaCnxn, self).__init__() + + # determine base uri based on location + locations = {'America': 'https://api-na.hosted.exlibrisgroup.com', + 'Europe': 'https://api-eu.hosted.exlibrisgroup.com', + 'Asia Pacific': 'https://api-ap.hosted.exlibrisgroup.com', + 'Canada': 'https://api-ca.hosted.exlibrisgroup.com', + 'China': 'https://api-cn.hosted.exlibrisgroup.com'} + if location != 'America': + if location not in locations.keys(): + message = "Valid location arguments are " + message += ", ".join(locations.keys()) + raise utils.ArgError(message=message) + self.cnxn_params['location'] = location + self.cnxn_params['base_uri'] = locations[location] + + # handle preferred format + if data_format not in ['json', 'xml']: + message = "Format argument must be either 'json' or 'xml'" + raise utils.ArgError(message) + self.cnxn_params['format'] = data_format + ns = {'header': 'http://com/exlibris/urm/general/xmlbeans'} + self.cnxn_params['xml_ns'] = ns + + # TODO: validate api key. return list of accessible endpoints + # call __validate_key__ + self.cnxn_params['api_key'] = apikey + + # Hook in the various Alma APIs based on what API key can access + self.bibs = SubClientBibs(self.cnxn_params) + self.analytics = SubClientAnalytics(self.cnxn_params) + self.courses = SubClientCourses(self.cnxn_params) + self.users = SubClientUsers(self.cnxn_params) + self.acq = SubClientAcquistions(self.cnxn_params) + self.conf = SubClientConfiguration(self.cnxn_params) + self.partners = SubClientPartners(self.cnxn_params) + self.electronic = SubClientElectronic(self.cnxn_params) + self.task_lists = SubClientTaskList(self.cnxn_params) + + def __validate_key__(self, apikey): + # loop through each api and access the /test endpoint. + # return list of accessible apis. + pass diff --git a/almapipy/acquisitions.py b/almapipy/acquisitions.py index 1fcace9..eb75eac 100644 --- a/almapipy/acquisitions.py +++ b/almapipy/acquisitions.py @@ -1,473 +1,475 @@ -from .client import Client -from . import utils - - -class SubClientAcquistions(Client): - """ - Alma provides a set of Web services for handling acquisitions information, - enabling you to quickly and easily manipulate acquisitions details. - These Web services can be used by external systems - - such as subscription agent systems - to retrieve or update acquisitions data. - For more info: https://developers.exlibrisgroup.com/alma/apis/acq - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/acq" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/acq" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d5b14609-b590-470e-baba-9944682f8c7e.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.funds = SubClientAcquistionsFunds(self.cnxn_params) - self.po_lines = SubClientAcquistionsPO(self.cnxn_params) - self.vendors = SubClientAcquistionsVendors(self.cnxn_params) - self.invoices = SubClientAcquistionsInvoices(self.cnxn_params) - self.licenses = SubClientAcquistionsLicenses(self.cnxn_params) - - -class SubClientAcquistionsFunds(Client): - """Handles the Funds endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/funds' - self.cnxn_params['api_uri_full'] += '/funds' - - def get(self, limit=10, offset=0, library=None, all_records=False, - q_params={}, raw=False): - """Retrieve a list of funds. - - Args: - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - library (str): The code of the library that owns the PO line - for which the relevant funds should be retrieved. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of funds. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - url = self.cnxn_params['api_uri_full'] - args['limit'] = limit - args['offset'] = int(offset) - - if library: - args['library'] = str(library) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='fund') - return response - - -class SubClientAcquistionsPO(Client): - """Handles the PO Lines endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/po-lines' - self.cnxn_params['api_uri_full'] += '/po-lines' - - def get(self, po_line_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list or a single PO-Line. - - Args: - po_line_id (str): The PO-Line number ('number' field in record). - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [title, author, mms_id, - publisher, publication_year, publication_place, issn_isbn, - shelving_location, vendor_code, vendor_name, vendor_account, - fund_code, fund_name, number, po_number, invoice_reference & all]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'vendor_account': 'AMAZON'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of po-lines or a specific po-line. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if po_line_id: - url += ("/" + str(po_line_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if po_line_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='po_line') - return response - - def get_items(self, po_line_id, q_params={}, raw=False): - """Retrieve a list items related to a specific PO-line - - Args: - po_line_id (str): The PO-Line number ('number' field in record). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of items in PO-Line - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(po_line_id) + "/items") - - response = self.read(url, args, raw=raw) - return response - - -class SubClientAcquistionsVendors(Client): - """Handles the Vendor endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/vendors' - self.cnxn_params['api_uri_full'] += '/vendors' - - def get(self, vendor_id=None, status='ALL', type_='ALL', - query={}, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a vendor list or a single vendor. - - Args: - vendor_id (str): A unique identifier for the user (vendorCode). - status (str): Vendor Status. Valid values: [active, inactive, ALL]. - type_ (str): Vendor Type.Valid values: [material_supplier, - access_provider, licensor, governmental]. - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [nterface_name, name, code, library & all.]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'name': 'AMAZON'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of vendor or a specific vendor's details. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if vendor_id: - url += ("/" + str(vendor_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - args['status'] = str(status) - args['type'] = str(type_) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if vendor_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='vendor') - return response - - def get_invoices(self, vendor_id, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve invoices for a specific vendor. - - Args: - vendor_id (str): A unique identifier for the user (vendorCode). - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - library (str): The code of the library that owns the PO line - for which the relevant funds should be retrieved. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of invoices for vendor. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += "/" + (str(vendor_id)) - url += "/invoices" - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='invoice') - return response - - def get_po_lines(self, vendor_id, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve po-lines for a specific vendor. - - Args: - vendor_id (str): A unique identifier for the user (vendorCode). - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - library (str): The code of the library that owns the PO line - for which the relevant funds should be retrieved. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of po-lines for vendor. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += "/" + (str(vendor_id)) - url += "/po-lines" - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='po_line') - return response - - -class SubClientAcquistionsInvoices(Client): - """Handles the Invoices endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/invoices' - self.cnxn_params['api_uri_full'] += '/invoices' - - def get(self, invoice_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list or a single invoice. - - Args: - invoice_id (str): The invoice id. - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [invoice_number, vendor_code]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'vendor_code': 'AMAZON'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of invoices or specific invoice. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if invoice_id: - url += ("/" + str(invoice_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if invoice_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='invoice') - return response - - -class SubClientAcquistionsLicenses(Client): - """Handles the Licenses endpoints of Acquisitions API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/licenses' - self.cnxn_params['api_uri_full'] += '/licenses' - - def get(self, license_id=None, status='ALL', review_status='ALL', - query={}, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a list or a single license. - - Args: - license_id (str): The license id (license_code). - status (str): Valid values are ACTIVE, DELETED, DRAFT, EXPIRED, RETIRED, ALL - review_status (str): alid values are ALL, and the listed values in LicenseReviewStatuses code table - query (dict): Search query for filtering licenses. Optional. - Searching for words from fields: [name, code, licensor]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'name': 'license_name'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of licenses or a specific license. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if license_id: - url += ("/" + str(license_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - args['status'] = str(status) - args['review_status'] = str(review_status) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if license_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='license') - return response - - def get_amendments(self, license_id, amendment_id=None, q_params={}, raw=False): - """Retrieve a specific license's amendments. - - Args: - license_id (str): The license id (license_code). - amendment_id (str): The amendment id (amendment_code). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of amendments or specific amendment for a license - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(license_id) + "/amendments") - if amendment_id: - url += ("/" + str(amendment_id)) - - response = self.read(url, args, raw=raw) - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientAcquistions(Client): + """ + Alma provides a set of Web services for handling acquisitions information, + enabling you to quickly and easily manipulate acquisitions details. + These Web services can be used by external systems - + such as subscription agent systems - to retrieve or update acquisitions data. + For more info: https://developers.exlibrisgroup.com/alma/apis/acq + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/acq" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/acq" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d5b14609-b590-470e-baba-9944682f8c7e.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.funds = SubClientAcquistionsFunds(self.cnxn_params) + self.po_lines = SubClientAcquistionsPO(self.cnxn_params) + self.vendors = SubClientAcquistionsVendors(self.cnxn_params) + self.invoices = SubClientAcquistionsInvoices(self.cnxn_params) + self.licenses = SubClientAcquistionsLicenses(self.cnxn_params) + + +class SubClientAcquistionsFunds(Client): + """Handles the Funds endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/funds' + self.cnxn_params['api_uri_full'] += '/funds' + + def get(self, limit=10, offset=0, library=None, all_records=False, + q_params={}, raw=False): + """Retrieve a list of funds. + + Args: + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + library (str): The code of the library that owns the PO line + for which the relevant funds should be retrieved. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of funds. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + url = self.cnxn_params['api_uri_full'] + args['limit'] = limit + args['offset'] = int(offset) + + if library: + args['library'] = str(library) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='fund') + return response + + +class SubClientAcquistionsPO(Client): + """Handles the PO Lines endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/po-lines' + self.cnxn_params['api_uri_full'] += '/po-lines' + + def get(self, po_line_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list or a single PO-Line. + + Args: + po_line_id (str): The PO-Line number ('number' field in record). + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [title, author, mms_id, + publisher, publication_year, publication_place, issn_isbn, + shelving_location, vendor_code, vendor_name, vendor_account, + fund_code, fund_name, number, po_number, invoice_reference & all]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'vendor_account': 'AMAZON'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of po-lines or a specific po-line. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if po_line_id: + url += ("/" + str(po_line_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if po_line_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='po_line') + return response + + def get_items(self, po_line_id, q_params={}, raw=False): + """Retrieve a list items related to a specific PO-line + + Args: + po_line_id (str): The PO-Line number ('number' field in record). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of items in PO-Line + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(po_line_id) + "/items") + + response = self.read(url, args, raw=raw) + return response + + +class SubClientAcquistionsVendors(Client): + """Handles the Vendor endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/vendors' + self.cnxn_params['api_uri_full'] += '/vendors' + + def get(self, vendor_id=None, status='ALL', type_='ALL', + query={}, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a vendor list or a single vendor. + + Args: + vendor_id (str): A unique identifier for the user (vendorCode). + status (str): Vendor Status. Valid values: [active, inactive, ALL]. + type_ (str): Vendor Type.Valid values: [material_supplier, + access_provider, licensor, governmental]. + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [nterface_name, name, code, library & all.]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'name': 'AMAZON'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of vendor or a specific vendor's details. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if vendor_id: + url += ("/" + str(vendor_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + args['status'] = str(status) + args['type'] = str(type_) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if vendor_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='vendor') + return response + + def get_invoices(self, vendor_id, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve invoices for a specific vendor. + + Args: + vendor_id (str): A unique identifier for the user (vendorCode). + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + library (str): The code of the library that owns the PO line + for which the relevant funds should be retrieved. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of invoices for vendor. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += "/" + (str(vendor_id)) + url += "/invoices" + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='invoice') + return response + + def get_po_lines(self, vendor_id, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve po-lines for a specific vendor. + + Args: + vendor_id (str): A unique identifier for the user (vendorCode). + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + library (str): The code of the library that owns the PO line + for which the relevant funds should be retrieved. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of po-lines for vendor. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += "/" + (str(vendor_id)) + url += "/po-lines" + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='po_line') + return response + + +class SubClientAcquistionsInvoices(Client): + """Handles the Invoices endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/invoices' + self.cnxn_params['api_uri_full'] += '/invoices' + + def get(self, invoice_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list or a single invoice. + + Args: + invoice_id (str): The invoice id. + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [invoice_number, vendor_code]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'vendor_code': 'AMAZON'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of invoices or specific invoice. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if invoice_id: + url += ("/" + str(invoice_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if invoice_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='invoice') + return response + + +class SubClientAcquistionsLicenses(Client): + """Handles the Licenses endpoints of Acquisitions API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/licenses' + self.cnxn_params['api_uri_full'] += '/licenses' + + def get(self, license_id=None, status='ALL', review_status='ALL', + query={}, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a list or a single license. + + Args: + license_id (str): The license id (license_code). + status (str): Valid values are ACTIVE, DELETED, DRAFT, EXPIRED, RETIRED, ALL + review_status (str): alid values are ALL, and the listed values in LicenseReviewStatuses code table + query (dict): Search query for filtering licenses. Optional. + Searching for words from fields: [name, code, licensor]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'name': 'license_name'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of licenses or a specific license. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if license_id: + url += ("/" + str(license_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + args['status'] = str(status) + args['review_status'] = str(review_status) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if license_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='license') + return response + + def get_amendments(self, license_id, amendment_id=None, q_params={}, raw=False): + """Retrieve a specific license's amendments. + + Args: + license_id (str): The license id (license_code). + amendment_id (str): The amendment id (amendment_code). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of amendments or specific amendment for a license + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(license_id) + "/amendments") + if amendment_id: + url += ("/" + str(amendment_id)) + + response = self.read(url, args, raw=raw) + return response diff --git a/almapipy/analytics.py b/almapipy/analytics.py index 50403df..ecfa912 100644 --- a/almapipy/analytics.py +++ b/almapipy/analytics.py @@ -1,174 +1,176 @@ -from .client import Client -from . import utils -import xml.etree.ElementTree as ET - - -class SubClientAnalytics(Client): - """ - Handles requests to analytics API. - For more info: https://developers.exlibrisgroup.com/alma/apis/analytics - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/analytics" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/analytics" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/10788916-19f6-4f19-aaf1-c18fa0c31ccd.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' - - # Hook in subclients of api - self.paths = SubClientAnalyticsPaths(self.cnxn_params) - self.reports = SubClientAnalyticsReports(self.cnxn_params) - - -class SubClientAnalyticsPaths(Client): - """Handles the path endpoints of analytics API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/paths' - self.cnxn_params['api_uri_full'] += '/paths' - - def get(self, path=None, q_params={}, raw=False): - """This API lists the contents of the Alma Analytics report directory. - If path is not specified, will just return info of root folder. - - Args: - path (str): folder directory relative to root. - Does not need to be url encoded. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - ls of directory specificed by path. - - """ - url = self.cnxn_params['api_uri_full'] - if path: - url += ("/" + str(path)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientAnalyticsReports(Client): - """Handles the reports endpoints of analytics API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/reports' - self.cnxn_params['api_uri_full'] += '/reports' - - def get(self, path, _filter=None, limit=25, col_names=True, return_json=False, - all_records=False, q_params={}, raw=False): - """This API returns an Alma Analytics report as XML. - JSON currently unavailable. Use return_json param to convert after the call. - - Args: - path (str): path of report relative to report root. - non-URL-encoded. Leave slashes and spaces. - _filter (str): An XML representation of a filter in OBI format. - See documentation for more info. - limit (int): Maximum number of results to return - Between 25 and 1000 (multiples of 25). - col_names (bool): Include column heading information. - To ensure consistent sort order it might be required to turn it off. - return_json (false): If True, converts xml into json-like structure. - all_records (bool): Return all rows for a report. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - If all_records == True, returns a list. - - Returns: - XML ET or json-like structure of report, - - """ - url = self.cnxn_params['api_uri_full'] - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['path'] = path - args['format'] = 'xml' - args['limit'] = str(int(limit)) - args['col_names'] = col_names - if _filter: - args['filter'] = _filter - row_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}Row" - set_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}rowset" - columns_tag = "{http://www.w3.org/2001/XMLSchema}element" - report = self.read(url, args, raw=raw) - - if raw: - # extract xml from raw response - # start list with raw responses - if not all_records: - return report - responses = [report] - report = ET.fromstring(report.text) - - if all_records: - # check if there are more records to get - if report[0].find('IsFinished').text == 'false': - get_more = True - - # just need token and apikey for future calls - margs = {'apikey': self.cnxn_params['api_key']} - margs['token'] = report[0].find('ResumptionToken').text - margs['format'] = 'xml' - - # find report content in XML report - xml_rows = list(report.iter(set_tag))[0] - else: - get_more = False - - # make additional api calls and append rows to original xml - while get_more: - - report_more = self.read(url, margs, raw=raw) - - if raw: - responses += [report_more] - report_more = ET.fromstring(report_more.text) - - else: - for new_row in report_more.iter(row_tag): - xml_rows.append(new_row) - - # break loop if no more records - if report_more[0].find('IsFinished').text != 'false': - get_more = False - - if raw: - return responses - - if return_json: - # extract column names - columns_tag = "{http://www.w3.org/2001/XMLSchema}element" - columns = list(report.iter(columns_tag)) - headers = {} - for col in columns: - key = col.attrib['name'] - try: - value = col.attrib['{urn:saw-sql}columnHeading'] - except: - value = col.attrib['name'] - value = value.lower().replace(" ", "_") - headers[key] = value - - # covert to list of dicts - dicts = [] - rows = report.iter(row_tag) - for row in rows: - values = [col.text for col in row] - keys = [headers[col.tag.split('}')[-1]] for col in row] - dicts.append({key: value for key, value in zip(keys, values)}) - return dicts - - return report +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils +import xml.etree.ElementTree as ET + + +class SubClientAnalytics(Client): + """ + Handles requests to analytics API. + For more info: https://developers.exlibrisgroup.com/alma/apis/analytics + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/analytics" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/analytics" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/10788916-19f6-4f19-aaf1-c18fa0c31ccd.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' + + # Hook in subclients of api + self.paths = SubClientAnalyticsPaths(self.cnxn_params) + self.reports = SubClientAnalyticsReports(self.cnxn_params) + + +class SubClientAnalyticsPaths(Client): + """Handles the path endpoints of analytics API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/paths' + self.cnxn_params['api_uri_full'] += '/paths' + + def get(self, path=None, q_params={}, raw=False): + """This API lists the contents of the Alma Analytics report directory. + If path is not specified, will just return info of root folder. + + Args: + path (str): folder directory relative to root. + Does not need to be url encoded. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + ls of directory specificed by path. + + """ + url = self.cnxn_params['api_uri_full'] + if path: + url += ("/" + str(path)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientAnalyticsReports(Client): + """Handles the reports endpoints of analytics API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/reports' + self.cnxn_params['api_uri_full'] += '/reports' + + def get(self, path, _filter=None, limit=25, col_names=True, return_json=False, + all_records=False, q_params={}, raw=False): + """This API returns an Alma Analytics report as XML. + JSON currently unavailable. Use return_json param to convert after the call. + + Args: + path (str): path of report relative to report root. + non-URL-encoded. Leave slashes and spaces. + _filter (str): An XML representation of a filter in OBI format. + See documentation for more info. + limit (int): Maximum number of results to return + Between 25 and 1000 (multiples of 25). + col_names (bool): Include column heading information. + To ensure consistent sort order it might be required to turn it off. + return_json (false): If True, converts xml into json-like structure. + all_records (bool): Return all rows for a report. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + If all_records == True, returns a list. + + Returns: + XML ET or json-like structure of report, + + """ + url = self.cnxn_params['api_uri_full'] + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['path'] = path + args['format'] = 'xml' + args['limit'] = str(int(limit)) + args['col_names'] = col_names + if _filter: + args['filter'] = _filter + row_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}Row" + set_tag = "{urn:schemas-microsoft-com:xml-analysis:rowset}rowset" + columns_tag = "{http://www.w3.org/2001/XMLSchema}element" + report = self.read(url, args, raw=raw) + + if raw: + # extract xml from raw response + # start list with raw responses + if not all_records: + return report + responses = [report] + report = ET.fromstring(report.text) + + if all_records: + # check if there are more records to get + if report[0].find('IsFinished').text == 'false': + get_more = True + + # just need token and apikey for future calls + margs = {'apikey': self.cnxn_params['api_key']} + margs['token'] = report[0].find('ResumptionToken').text + margs['format'] = 'xml' + + # find report content in XML report + xml_rows = list(report.iter(set_tag))[0] + else: + get_more = False + + # make additional api calls and append rows to original xml + while get_more: + + report_more = self.read(url, margs, raw=raw) + + if raw: + responses += [report_more] + report_more = ET.fromstring(report_more.text) + + else: + for new_row in report_more.iter(row_tag): + xml_rows.append(new_row) + + # break loop if no more records + if report_more[0].find('IsFinished').text != 'false': + get_more = False + + if raw: + return responses + + if return_json: + # extract column names + columns_tag = "{http://www.w3.org/2001/XMLSchema}element" + columns = list(report.iter(columns_tag)) + headers = {} + for col in columns: + key = col.attrib['name'] + try: + value = col.attrib['{urn:saw-sql}columnHeading'] + except: + value = col.attrib['name'] + value = value.lower().replace(" ", "_") + headers[key] = value + + # covert to list of dicts + dicts = [] + rows = report.iter(row_tag) + for row in rows: + values = [col.text for col in row] + keys = [headers[col.tag.split('}')[-1]] for col in row] + dicts.append({key: value for key, value in zip(keys, values)}) + return dicts + + return report diff --git a/almapipy/bibs.py b/almapipy/bibs.py index 3d9d32e..d8fbbc0 100644 --- a/almapipy/bibs.py +++ b/almapipy/bibs.py @@ -1,498 +1,500 @@ -from .client import Client -from . import utils - - -class SubClientBibs(Client): - """ - Handles requests to bib endpoint. - For more info: https://developers.exlibrisgroup.com/alma/apis/bibs - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to Bibs. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/bibs" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/bibs" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/af2fb69d-64f4-42bc-bb05-d8a0ae56936e.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of bib - self.catalog = SubClientBibsCatalog(self.cnxn_params) - self.collections = SubClientBibsCollections(self.cnxn_params) - self.loans = SubClientBibsLoans(self.cnxn_params) - self.requests = SubClientBibsRequests(self.cnxn_params) - self.representations = SubClientBibsRepresentations(self.cnxn_params) - self.linked_data = SubClientBibsLinkedData(self.cnxn_params) - - -class SubClientBibsCatalog(Client): - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params - - def get(self, bib_ids, expand=None, q_params={}, raw=False): - """ - Returns Bib records from a list of Bib IDs submitted in a parameter. - - Args: - bib_ids (list or str): list of bib Record IDs. len = 1-100. - or string of one record id. - expand (str): provides additional information: - p_avail - Expand physical inventory information. - e_avail - Expand electronic inventory information. - d_avail - Expand digital inventory information. - To use more than one, use a comma separator. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Returns a single or list of bib records. - https://developers.exlibrisgroup.com/alma/apis/xsd/rest_bibs.xsd?tags=GET - - """ - url = self.cnxn_params['api_uri_full'] - - # validate arguments - if type(q_params) != dict: - message = "q_params must be a dictionary." - raise utils.ArgError(message) - if type(bib_ids) != list and type(bib_ids) != str: - message = "bib_ids must be a list of ids, or single string." - raise utils.ArgError(message) - - # format arguments - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - # determine which endpoint to call. - if type(bib_ids) == str: - url += ('/' + bib_ids) - else: - args['mms_id'] = bib_ids - - if expand: - if expand not in ['p_avail', 'e_avail', 'd_avail']: - message = 'expand must be one of the follow: ' + str(expand) - raise utils.ArgError(message) - args['expand'] = expand - - return self.read(url, args, raw=raw) - - def get_holdings(self, bib_id, holding_id=None, q_params={}, raw=False): - """Returns list of holding records or single holding record - for a given bib record ID. - - If retrieving a single holding record with the holding_id param, - it is returned as MARC XML format, so it is not recommended to - use this service with JSON format. - You can overide the json global setting - by entering 'format':'xml' as item in q_params parameter. - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of holding records or single holding record - for a given bib record ID. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += '/holdings' - if holding_id: - url += ('/' + str(holding_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_holding_items(self, bib_id, holding_id, item_id=None, q_params={}, raw=False): - """Returns list of holding record items or a single item - for a given holding record of a bib. - Includes label printing information - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item id (item_pid). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of holding record items or a single item - for a given bib record ID. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += ('/holdings/' + str(holding_id)) + '/items' - if item_id: - url += ('/' + str(item_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_portfolios(self, bib_id, portfolio_id=None, q_params={}, raw=False): - """Returns a list or single portfolio for a Bib. - - If retrieving a single holding record with the holding_id param, - it is returned as MARC XML format, so it is not recommended to - use this service with JSON format. - You can overide the json global setting - by entering 'format':'xml' as item in q_params parameter. - - Args: - bib_id (str): The bib ID (mms_id). - portfolio_id (str): The Electronic Portfolio ID. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Returns a list or single portfolio for a Bib. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += '/portfolios' - if portfolio_id: - url += ('/' + str(portfolio_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsCollections(Client): - """Handles collections""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/collections' - self.cnxn_params['api_uri_full'] += '/collections' - - def get(self, pid=None, query={}, q_params={}, raw=False): - """Returns meta data about collections in libraries. - If pid argument is used, will only return one collection. - - Args: - pid (str): The collection ID. - query (dict): Search query for filtering list. Optional. - Searching for words from fields: [library, collection name, external system, external ID]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A list of collections or a collection for a given pid. - - """ - url = self.cnxn_params['api_uri_full'] - if pid: - url += ("/" + str(pid)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - return response - - def get_bibs(self, pid, q_params={}, raw=False): - """Get bibs in a collection using pid. - - Args: - pid (str): The collection ID. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A a list of bibliographic titles in a given collection. - - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(pid)) - url += '/bibs' - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsLoans(Client): - """Accesses loans endpoints""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get_by_item(self, bib_id, holding_id, item_id, - loan_id=None, q_params={}, raw=False): - """Returns Loan by Item information. - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - loan_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given item. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += ('/holdings/' + str(holding_id)) - url += ('/items/' + str(item_id) + "/loans") - if loan_id: - url += ('/' + str(loan_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_by_title(self, bib_id, loan_id=None, q_params={}, raw=False): - """Returns Loan by title information. - - Args: - bib_id (str): The bib ID (mms_id). - loan_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given title. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id) + '/loans') - if loan_id: - url += ('/' + str(loan_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsRequests(Client): - """Accesses user request endpoints""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get_by_item(self, bib_id, holding_id, item_id, - request_id=None, q_params={}, raw=False): - """Returns Loan by Item information. - - Args: - bib_id (str): The bib ID (mms_id). - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - request_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given item. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += ('/holdings/' + str(holding_id)) - url += ('/items/' + str(item_id) + "/requests") - if request_id: - url += ('/' + str(request_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_by_title(self, bib_id, request_id=None, q_params={}, raw=False): - """Returns Loan by title information. - - Args: - bib_id (str): The bib ID (mms_id). - request_id (str): The loan ID - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a single loan for a given title. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id) + '/requests') - if request_id: - url += ('/' + str(request_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_availability(self, bib_id, period, period_type='days', - holding_id=None, item_id=None, q_params={}, raw=False): - """Returns list of periods in which specific title or item - is unavailable for booking. - - To get a specific item, holding_id and item_id parameters are required. - - Note: user_id does not populate if retrieving by just bid_id. - - Args: - bib_id (str): The bib ID (mms_id). - period (str or int): The number of days/weeks/months to retrieve availability for. - period_type (str): The type of period of interest. Optional. Possible values: days, weeks, months. - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of periods title/item is unavailable for booking. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - if holding_id and item_id: - url += ("/holdings/" + str(holding_id)) - url += ('/items/' + str(item_id)) - elif holding_id or item_id: - message = "If getting availability for an item, " - message += "Both holding_id and item_id are required arguments." - raise utils.ArgError(message) - url += "/booking-availability" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['period'] = str(period) - args['period_type'] = str(period_type) - - return self.read(url, args, raw=raw) - - def get_options(self, bib_id, user_id='GUEST', - holding_id=None, item_id=None, - q_params={}, raw=False): - """Returns request options for a specific title or item based on user. - - To get a specific item, holding_id and item_id parameters are required. - - Note: user_id does not populate if retrieving by just bid_id. - - Args: - bib_id (str): The bib ID (mms_id). - user_id (str): The id of the user for which the request options will be calculated. - holding_id (str): The Holding Record ID (holding_id). - item_id (str): The holding item ID (item_pid). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Request options for a specific title or item based on user. - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - if holding_id and item_id: - url += ("/holdings/" + str(holding_id)) - url += ('/items/' + str(item_id)) - elif holding_id or item_id: - message = "If getting request options for an item, " - message += "Both holding_id and item_id are required arguments." - raise utils.ArgError(message) - url += "/request-options" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['user_id'] = str(user_id) - - return self.read(url, args, raw=raw) - - -class SubClientBibsRepresentations(Client): - """Handles Digital Representations""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, bib_id, q_params={}, raw=False): - """Returns a list of Digital Representations for a given Bib MMS-ID. - - Args: - bib_id (str): The bib ID (mms_id). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A list of Digital Representations for a given bib record. - - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += "/representations" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - def get_details(self, bib_id, rep_id, files=False, q_params={}, raw=False): - """Returns a specific Digital Representation's details. - Supported for Remote and Non-Remote Representations. - - Args: - bib_id (str): The bib ID (mms_id). - rep_id (str): The Representation ID. - files (bool): Denote whether to return files? - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - A list of Digital Representations for a given bib record. - - """ - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(bib_id)) - url += "/representations/" - url += rep_id - if files: - url += "/files" - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientBibsLinkedData(Client): - """Handles Linked Data for a Bib Record""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, bib_id, q_params={}, raw=False): - """Returns Linked data for a given Bib MMS-ID. - - Args: - bib_id (str): The bib ID (mms_id). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Linked data URIs for a given bib record. - - """ - url = self.cnxn_params['api_uri_full'] - url += "/linked-open-data" - url += ("/" + str(bib_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientBibs(Client): + """ + Handles requests to bib endpoint. + For more info: https://developers.exlibrisgroup.com/alma/apis/bibs + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to Bibs. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/bibs" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/bibs" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/af2fb69d-64f4-42bc-bb05-d8a0ae56936e.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of bib + self.catalog = SubClientBibsCatalog(self.cnxn_params) + self.collections = SubClientBibsCollections(self.cnxn_params) + self.loans = SubClientBibsLoans(self.cnxn_params) + self.requests = SubClientBibsRequests(self.cnxn_params) + self.representations = SubClientBibsRepresentations(self.cnxn_params) + self.linked_data = SubClientBibsLinkedData(self.cnxn_params) + + +class SubClientBibsCatalog(Client): + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params + + def get(self, bib_ids, expand=None, q_params={}, raw=False): + """ + Returns Bib records from a list of Bib IDs submitted in a parameter. + + Args: + bib_ids (list or str): list of bib Record IDs. len = 1-100. + or string of one record id. + expand (str): provides additional information: + p_avail - Expand physical inventory information. + e_avail - Expand electronic inventory information. + d_avail - Expand digital inventory information. + To use more than one, use a comma separator. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Returns a single or list of bib records. + https://developers.exlibrisgroup.com/alma/apis/xsd/rest_bibs.xsd?tags=GET + + """ + url = self.cnxn_params['api_uri_full'] + + # validate arguments + if type(q_params) != dict: + message = "q_params must be a dictionary." + raise utils.ArgError(message) + if type(bib_ids) != list and type(bib_ids) != str: + message = "bib_ids must be a list of ids, or single string." + raise utils.ArgError(message) + + # format arguments + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + # determine which endpoint to call. + if type(bib_ids) == str: + url += ('/' + bib_ids) + else: + args['mms_id'] = bib_ids + + if expand: + if expand not in ['p_avail', 'e_avail', 'd_avail']: + message = 'expand must be one of the follow: ' + str(expand) + raise utils.ArgError(message) + args['expand'] = expand + + return self.read(url, args, raw=raw) + + def get_holdings(self, bib_id, holding_id=None, q_params={}, raw=False): + """Returns list of holding records or single holding record + for a given bib record ID. + + If retrieving a single holding record with the holding_id param, + it is returned as MARC XML format, so it is not recommended to + use this service with JSON format. + You can overide the json global setting + by entering 'format':'xml' as item in q_params parameter. + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of holding records or single holding record + for a given bib record ID. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += '/holdings' + if holding_id: + url += ('/' + str(holding_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_holding_items(self, bib_id, holding_id, item_id=None, q_params={}, raw=False): + """Returns list of holding record items or a single item + for a given holding record of a bib. + Includes label printing information + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item id (item_pid). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of holding record items or a single item + for a given bib record ID. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += ('/holdings/' + str(holding_id)) + '/items' + if item_id: + url += ('/' + str(item_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_portfolios(self, bib_id, portfolio_id=None, q_params={}, raw=False): + """Returns a list or single portfolio for a Bib. + + If retrieving a single holding record with the holding_id param, + it is returned as MARC XML format, so it is not recommended to + use this service with JSON format. + You can overide the json global setting + by entering 'format':'xml' as item in q_params parameter. + + Args: + bib_id (str): The bib ID (mms_id). + portfolio_id (str): The Electronic Portfolio ID. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Returns a list or single portfolio for a Bib. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += '/portfolios' + if portfolio_id: + url += ('/' + str(portfolio_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsCollections(Client): + """Handles collections""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/collections' + self.cnxn_params['api_uri_full'] += '/collections' + + def get(self, pid=None, query={}, q_params={}, raw=False): + """Returns meta data about collections in libraries. + If pid argument is used, will only return one collection. + + Args: + pid (str): The collection ID. + query (dict): Search query for filtering list. Optional. + Searching for words from fields: [library, collection name, external system, external ID]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A list of collections or a collection for a given pid. + + """ + url = self.cnxn_params['api_uri_full'] + if pid: + url += ("/" + str(pid)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + return response + + def get_bibs(self, pid, q_params={}, raw=False): + """Get bibs in a collection using pid. + + Args: + pid (str): The collection ID. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A a list of bibliographic titles in a given collection. + + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(pid)) + url += '/bibs' + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsLoans(Client): + """Accesses loans endpoints""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get_by_item(self, bib_id, holding_id, item_id, + loan_id=None, q_params={}, raw=False): + """Returns Loan by Item information. + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + loan_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given item. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += ('/holdings/' + str(holding_id)) + url += ('/items/' + str(item_id) + "/loans") + if loan_id: + url += ('/' + str(loan_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_by_title(self, bib_id, loan_id=None, q_params={}, raw=False): + """Returns Loan by title information. + + Args: + bib_id (str): The bib ID (mms_id). + loan_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given title. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id) + '/loans') + if loan_id: + url += ('/' + str(loan_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsRequests(Client): + """Accesses user request endpoints""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get_by_item(self, bib_id, holding_id, item_id, + request_id=None, q_params={}, raw=False): + """Returns Loan by Item information. + + Args: + bib_id (str): The bib ID (mms_id). + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + request_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given item. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += ('/holdings/' + str(holding_id)) + url += ('/items/' + str(item_id) + "/requests") + if request_id: + url += ('/' + str(request_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_by_title(self, bib_id, request_id=None, q_params={}, raw=False): + """Returns Loan by title information. + + Args: + bib_id (str): The bib ID (mms_id). + request_id (str): The loan ID + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a single loan for a given title. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id) + '/requests') + if request_id: + url += ('/' + str(request_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_availability(self, bib_id, period, period_type='days', + holding_id=None, item_id=None, q_params={}, raw=False): + """Returns list of periods in which specific title or item + is unavailable for booking. + + To get a specific item, holding_id and item_id parameters are required. + + Note: user_id does not populate if retrieving by just bid_id. + + Args: + bib_id (str): The bib ID (mms_id). + period (str or int): The number of days/weeks/months to retrieve availability for. + period_type (str): The type of period of interest. Optional. Possible values: days, weeks, months. + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of periods title/item is unavailable for booking. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + if holding_id and item_id: + url += ("/holdings/" + str(holding_id)) + url += ('/items/' + str(item_id)) + elif holding_id or item_id: + message = "If getting availability for an item, " + message += "Both holding_id and item_id are required arguments." + raise utils.ArgError(message) + url += "/booking-availability" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['period'] = str(period) + args['period_type'] = str(period_type) + + return self.read(url, args, raw=raw) + + def get_options(self, bib_id, user_id='GUEST', + holding_id=None, item_id=None, + q_params={}, raw=False): + """Returns request options for a specific title or item based on user. + + To get a specific item, holding_id and item_id parameters are required. + + Note: user_id does not populate if retrieving by just bid_id. + + Args: + bib_id (str): The bib ID (mms_id). + user_id (str): The id of the user for which the request options will be calculated. + holding_id (str): The Holding Record ID (holding_id). + item_id (str): The holding item ID (item_pid). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Request options for a specific title or item based on user. + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + if holding_id and item_id: + url += ("/holdings/" + str(holding_id)) + url += ('/items/' + str(item_id)) + elif holding_id or item_id: + message = "If getting request options for an item, " + message += "Both holding_id and item_id are required arguments." + raise utils.ArgError(message) + url += "/request-options" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['user_id'] = str(user_id) + + return self.read(url, args, raw=raw) + + +class SubClientBibsRepresentations(Client): + """Handles Digital Representations""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, bib_id, q_params={}, raw=False): + """Returns a list of Digital Representations for a given Bib MMS-ID. + + Args: + bib_id (str): The bib ID (mms_id). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A list of Digital Representations for a given bib record. + + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += "/representations" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + def get_details(self, bib_id, rep_id, files=False, q_params={}, raw=False): + """Returns a specific Digital Representation's details. + Supported for Remote and Non-Remote Representations. + + Args: + bib_id (str): The bib ID (mms_id). + rep_id (str): The Representation ID. + files (bool): Denote whether to return files? + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + A list of Digital Representations for a given bib record. + + """ + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(bib_id)) + url += "/representations/" + url += rep_id + if files: + url += "/files" + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientBibsLinkedData(Client): + """Handles Linked Data for a Bib Record""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, bib_id, q_params={}, raw=False): + """Returns Linked data for a given Bib MMS-ID. + + Args: + bib_id (str): The bib ID (mms_id). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Linked data URIs for a given bib record. + + """ + url = self.cnxn_params['api_uri_full'] + url += "/linked-open-data" + url += ("/" + str(bib_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) diff --git a/almapipy/client.py b/almapipy/client.py index 21a1d28..81666de 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -1,257 +1,259 @@ -""" -Common Client for interacting with Alma API -""" - -import json -import xml.etree.ElementTree as ET - -import requests - -from . import utils - - -class Client(object): - """ - Reads responses from Alma API and handles response. - """ - - def __init__(self, cnxn_params={}): - # instantiate dictionary for storing alma api connection parameters - self.cnxn_params = cnxn_params - - def create(self, url, data, args, object_type, raw=False): - """ - Uses requests library to make Exlibris API Post call. - - Args: - url (str): Exlibris API endpoint url. - data (dict): Data to be posted. - args (dict): Query string parameters for API call. - object_type (str): Type of object to be posted (see alma docs) - raw (bool): If true, returns raw response. - - Returns: - JSON-esque, xml, or raw response. - """ -# print(url) - - # Determine format of data to be posted according to order of importance: - # 1) Local declaration, 2) dtype of data parameter, 3) global setting. - headers = {} - if 'format' not in args.keys(): - if type(data) == ET or type(data) == ET.Element: - content_type = 'xml' - elif type(data) == dict: - content_type = 'json' - else: - content_type = self.cnxn_params['format'] - args['format'] = self.cnxn_params['format'] - else: - content_type = args['format'] - - # Declare data type in header, convert to string if necessary. - if content_type == 'json': - headers['content-type'] = 'application/json' - if type(data) != str: - data = json.dumps(data) - elif content_type == 'xml': - headers['content-type'] = 'application/xml' - if type(data) == ET or type(data) == ET.Element: - data = ET.tostring(data, encoding='unicode') - elif type(data) != str: - message = "XML payload must be either string or ElementTree." - raise utils.ArgError(message) - else: - message = "Post content type must be either 'json' or 'xml'" - raise utils.ArgError(message) - - # Send request and parse response - response = requests.post(url, data=data, params=args, headers=headers) - if raw: - return response - content = self.__parse_response__(response) - - return content - - - def read(self, url, args, raw=False): - """ - Uses requests library to make Exlibris API Get call. - Returns data of type specified during init of base class. - - Args: - url (str): Exlibris API endpoint url. - args (dict): Query string parameters for API call. - raw (bool): If true, returns raw response. - - Returns: - JSON-esque, xml, or raw response. - """ -# print(url) - - # handle data format. Allow for overriding of global setting. - data_format = self.cnxn_params['format'] - if 'format' not in args.keys(): - args['format'] = data_format - data_format = args['format'] - - # Send request. - response = requests.get(url, params=args) - if raw: - return response - - # Parse content - content = self.__parse_response__(response) - - return content - - def __format_query__(self, query): - """Converts dictionary of brief search query to a formated string. - https://developers.exlibrisgroup.com/blog/How-we-re-building-APIs-at-Ex-Libris#BriefSearch - - Args: - query: dictionary of brief search query. - Format - {'field': 'value', 'field2', 'value2'}. - Returns: - String of query. - """ - q_str = "" - i = 0 - if type(query) != 'dict': - message = "Brief search query must be a dictionary." - for field, filter_value in query.items(): - field = str(field) - filter_value = str(filter_value) - if i > 0: - q_str += " AND " - q_str += (field + "~") - q_str += filter_value.replace(" ", "_") - i += 1 - - return q_str - - def __read_all__(self, url, args, raw, response, data_key, max_limit=100): - """Makes multiple API calls until all records for a query are retrieved. - Called by the 'all_records' parameter. - - Args: - url (str): Exlibris API endpoint url. - args (dict): Query string parameters for API call. - raw (bool): If true, returns raw response. - response (xml, raw, or json): First API call. - data_key (str): Dictionary key for accessing data. - max_limit (int): Max number of records allowed to be retrieved in a single call. - Overrides limit parameter. Reduces the number of API calls needed to retrieve data. - - Returns: - response with remainder of data appended. - """ - # raw will return a list of responses - if raw: - responses = [response] - response = response.json() - - args['offset'] = args['limit'] - limit = args['limit'] - - # get total record count of query - if type(response) == dict: - total_records = int(response['total_record_count']) - elif type(response) == ET.Element: - total_records = int(response.attrib['total_record_count']) - else: - total_records = limit - - # set new retrieval limit - records_retrieved = limit - args['limit'] = max_limit - limit = max_limit - - while True: - if total_records <= records_retrieved: - break - - # make call and increment counter variables - new_response = self.read(url, args, raw=raw) - records_retrieved += limit - args['offset'] += limit - - # append new records to initial response - if type(new_response) == dict: - response[data_key] += new_response[data_key] - elif type(new_response) == ET.Element: - for row in list(new_response): - response.append(row) - elif raw: - responses.append(new_response) - - if raw: - response = responses - - return response - - def __parse_response__(self, response): - """Parses alma response depending on content type. - - Args: - response: requests object from Alma. - - Returns: - Content of response in format specified in header. - """ - status = response.status_code - url = response.url - try: - response_type = response.headers['Content-Type'] - if ";" in response_type: - response_type, charset = response_type.split(";") - except: - message = 'Error ' + str(status) + response.text - raise utils.AlmaError(message, status, url) - - # decode response if xml. - if response_type == 'application/xml': - xml_ns = self.cnxn_params['xml_ns'] # xml namespace - content = ET.fromstring(response.text) - - # Received response from ex libris, but error retrieving data. - if str(status)[0] in ['4', '5']: - try: - first_error = content.find("header:errorList", xml_ns)[0] - message = first_error.find("header:errorCode", xml_ns).text - message += " - " - message += first_error.find("header:errorMessage", xml_ns).text - message += " See Alma documentation for more information." - except: - message = 'Error ' + str(status) + " - " + str(content) - raise utils.AlmaError(message, status, url) - - # decode response if json. - elif response_type == 'application/json': - content = response.json() - - # Received response from ex libris, but error retrieving data. - if str(status)[0] in ['4', '5']: - try: - if 'web_service_result' in content.keys(): - first_error = content['web_service_result']['errorList']['error'][0] - else: - first_error = content['errorList']['error'][0] - message = first_error['errorCode'] - message += " - " - message += first_error['errorMessage'] - if 'trackingID' in first_error.keys(): - message += "TrackingID: " + message['trackingID'] - message += " See Alma documentation for more information." - except: - message = 'Error ' + str(status) + " - " + str(content) - raise utils.AlmaError(message, status, url) - - else: - content = response - - if str(status)[0] in ['4', '5']: - message = str(status) + " - " - message += str(content.text) - raise utils.AlmaError(message, status, url) - return content +#-*- coding: utf-8-unix -*- + +""" +Common Client for interacting with Alma API +""" + +import json +import xml.etree.ElementTree as ET + +import requests + +from . import utils + + +class Client(object): + """ + Reads responses from Alma API and handles response. + """ + + def __init__(self, cnxn_params={}): + # instantiate dictionary for storing alma api connection parameters + self.cnxn_params = cnxn_params + + def create(self, url, data, args, object_type, raw=False): + """ + Uses requests library to make Exlibris API Post call. + + Args: + url (str): Exlibris API endpoint url. + data (dict): Data to be posted. + args (dict): Query string parameters for API call. + object_type (str): Type of object to be posted (see alma docs) + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ +# print(url) + + # Determine format of data to be posted according to order of importance: + # 1) Local declaration, 2) dtype of data parameter, 3) global setting. + headers = {} + if 'format' not in args.keys(): + if type(data) == ET or type(data) == ET.Element: + content_type = 'xml' + elif type(data) == dict: + content_type = 'json' + else: + content_type = self.cnxn_params['format'] + args['format'] = self.cnxn_params['format'] + else: + content_type = args['format'] + + # Declare data type in header, convert to string if necessary. + if content_type == 'json': + headers['content-type'] = 'application/json' + if type(data) != str: + data = json.dumps(data) + elif content_type == 'xml': + headers['content-type'] = 'application/xml' + if type(data) == ET or type(data) == ET.Element: + data = ET.tostring(data, encoding='unicode') + elif type(data) != str: + message = "XML payload must be either string or ElementTree." + raise utils.ArgError(message) + else: + message = "Post content type must be either 'json' or 'xml'" + raise utils.ArgError(message) + + # Send request and parse response + response = requests.post(url, data=data, params=args, headers=headers) + if raw: + return response + content = self.__parse_response__(response) + + return content + + + def read(self, url, args, raw=False): + """ + Uses requests library to make Exlibris API Get call. + Returns data of type specified during init of base class. + + Args: + url (str): Exlibris API endpoint url. + args (dict): Query string parameters for API call. + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ +# print(url) + + # handle data format. Allow for overriding of global setting. + data_format = self.cnxn_params['format'] + if 'format' not in args.keys(): + args['format'] = data_format + data_format = args['format'] + + # Send request. + response = requests.get(url, params=args) + if raw: + return response + + # Parse content + content = self.__parse_response__(response) + + return content + + def __format_query__(self, query): + """Converts dictionary of brief search query to a formated string. + https://developers.exlibrisgroup.com/blog/How-we-re-building-APIs-at-Ex-Libris#BriefSearch + + Args: + query: dictionary of brief search query. + Format - {'field': 'value', 'field2', 'value2'}. + Returns: + String of query. + """ + q_str = "" + i = 0 + if type(query) != 'dict': + message = "Brief search query must be a dictionary." + for field, filter_value in query.items(): + field = str(field) + filter_value = str(filter_value) + if i > 0: + q_str += " AND " + q_str += (field + "~") + q_str += filter_value.replace(" ", "_") + i += 1 + + return q_str + + def __read_all__(self, url, args, raw, response, data_key, max_limit=100): + """Makes multiple API calls until all records for a query are retrieved. + Called by the 'all_records' parameter. + + Args: + url (str): Exlibris API endpoint url. + args (dict): Query string parameters for API call. + raw (bool): If true, returns raw response. + response (xml, raw, or json): First API call. + data_key (str): Dictionary key for accessing data. + max_limit (int): Max number of records allowed to be retrieved in a single call. + Overrides limit parameter. Reduces the number of API calls needed to retrieve data. + + Returns: + response with remainder of data appended. + """ + # raw will return a list of responses + if raw: + responses = [response] + response = response.json() + + args['offset'] = args['limit'] + limit = args['limit'] + + # get total record count of query + if type(response) == dict: + total_records = int(response['total_record_count']) + elif type(response) == ET.Element: + total_records = int(response.attrib['total_record_count']) + else: + total_records = limit + + # set new retrieval limit + records_retrieved = limit + args['limit'] = max_limit + limit = max_limit + + while True: + if total_records <= records_retrieved: + break + + # make call and increment counter variables + new_response = self.read(url, args, raw=raw) + records_retrieved += limit + args['offset'] += limit + + # append new records to initial response + if type(new_response) == dict: + response[data_key] += new_response[data_key] + elif type(new_response) == ET.Element: + for row in list(new_response): + response.append(row) + elif raw: + responses.append(new_response) + + if raw: + response = responses + + return response + + def __parse_response__(self, response): + """Parses alma response depending on content type. + + Args: + response: requests object from Alma. + + Returns: + Content of response in format specified in header. + """ + status = response.status_code + url = response.url + try: + response_type = response.headers['Content-Type'] + if ";" in response_type: + response_type, charset = response_type.split(";") + except: + message = 'Error ' + str(status) + response.text + raise utils.AlmaError(message, status, url) + + # decode response if xml. + if response_type == 'application/xml': + xml_ns = self.cnxn_params['xml_ns'] # xml namespace + content = ET.fromstring(response.text) + + # Received response from ex libris, but error retrieving data. + if str(status)[0] in ['4', '5']: + try: + first_error = content.find("header:errorList", xml_ns)[0] + message = first_error.find("header:errorCode", xml_ns).text + message += " - " + message += first_error.find("header:errorMessage", xml_ns).text + message += " See Alma documentation for more information." + except: + message = 'Error ' + str(status) + " - " + str(content) + raise utils.AlmaError(message, status, url) + + # decode response if json. + elif response_type == 'application/json': + content = response.json() + + # Received response from ex libris, but error retrieving data. + if str(status)[0] in ['4', '5']: + try: + if 'web_service_result' in content.keys(): + first_error = content['web_service_result']['errorList']['error'][0] + else: + first_error = content['errorList']['error'][0] + message = first_error['errorCode'] + message += " - " + message += first_error['errorMessage'] + if 'trackingID' in first_error.keys(): + message += "TrackingID: " + message['trackingID'] + message += " See Alma documentation for more information." + except: + message = 'Error ' + str(status) + " - " + str(content) + raise utils.AlmaError(message, status, url) + + else: + content = response + + if str(status)[0] in ['4', '5']: + message = str(status) + " - " + message += str(content.text) + raise utils.AlmaError(message, status, url) + return content diff --git a/almapipy/conf.py b/almapipy/conf.py index 001d375..7aa6452 100644 --- a/almapipy/conf.py +++ b/almapipy/conf.py @@ -1,567 +1,569 @@ -from .client import Client -from . import utils - - -class SubClientConfiguration(Client): - """ - Alma provides a set of Web services for handling Configuration related - information, enabling you to quickly and easily receive configuration details. - These Web services can be used by external systems in order to get list of - possible data. - For more info: https://developers.exlibrisgroup.com/alma/apis/conf - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/conf" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/conf" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/37088dc9-c685-4641-bc7f-60b5ca7cabed.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.units = SubClientConfigurationUnits(self.cnxn_params) - self.general = SubClientConfigurationGeneral(self.cnxn_params) - self.jobs = SubClientConfigurationJobs(self.cnxn_params) - self.sets = SubClientConfigurationSets(self.cnxn_params) - self.deposit_profiles = SubClientConfigurationDeposit(self.cnxn_params) - self.import_profiles = SubClientConfigurationImport(self.cnxn_params) - self.reminders = SubClientConfigurationReminders(self.cnxn_params) - - -class SubClientConfigurationUnits(Client): - """Handles the Organization Unit endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get_libaries(self, library_id=None, q_params={}, raw=False): - """Retrieve a list of libraries or a specific library - - Args: - library_id (str): The code of the library (libraryCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of libraries or single library - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += '/libraries' - if library_id: - url += ("/" + str(library_id)) - - response = self.read(url, args, raw=raw) - return response - - def get_locations(self, library_id, location_id=None, q_params={}, raw=False): - """Retrieve a list of locations for a library - - Args: - library_id (str): The code of the library (libraryCode). - location_id (str): Code for a specific location (locationCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of library locations. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ('/libraries/' + str(library_id) + "/locations") - if location_id: - url += ("/" + str(location_id)) - - response = self.read(url, args, raw=raw) - return response - - def get_departments(self, q_params={}, raw=False): - """Retrieve a list of configured departments - - Args: - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of departments. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += '/departments' - - response = self.read(url, args, raw=raw) - return response - - -class SubClientConfigurationGeneral(Client): - """Handles the General endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, library_id=None, q_params={}, raw=False): - """Retrieve general configuration of the institution - - Args: - library_id (str): The code of the library (libraryCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - General configuration of the institution - """ - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += "/general" - - response = self.read(url, args, raw=raw) - return response - - def get_hours(self, library_id=None, q_params={}, raw=False): - """Retrieve open hours as configured in Alma. - Note that the library-hours do not necessarily reflect when the - library doors are actually open, but rather start and end times that - effect loan period. - This API is limited to one month of days from 1 year ago to - 3 years ahead for a single request. - - Args: - library_id (str): The code of the library (libraryCode). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of open hours - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - if library_id: - url += '/libraries' - url += ("/" + str(library_id)) - - url += '/open-hours' - - response = self.read(url, args, raw=raw) - return response - - def get_code_table(self, table_name, q_params={}, raw=False): - """This API returns all rows defined for a code-table. - - The main usage of this API is for applications that use Alma APIs, - and need to give the user a drop-down of valid values to choose from. - - See Alma documentation for code-table names. - - Args: - table_name (str): Code table name. (codeTableName) - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Code-table rows - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ('/code-tables/' + str(table_name)) - - response = self.read(url, args, raw=raw) - return response - - -class SubClientConfigurationJobs(Client): - """Handles the Jobs endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/jobs' - self.cnxn_params['api_uri_full'] += '/jobs' - - def get(self, job_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a list of jobs that can be submitted or details for a given job. - - Args: - job_id (str): Unique id of the job. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of jobs or a specific job - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - if job_id: - url += ("/" + str(job_id)) - else: - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if job_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='job') - return response - - def get_instances(self, job_id, instance_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve all the job instances (runs) for a given job id, or specific instance. - - Args: - job_id (str): Unique id of the job. - instance_id (str): Unique id of the specific job instance. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of jobs or a specific job - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(job_id) + "/instances") - - if instance_id: - url += ("/" + str(instance_id)) - else: - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if instance_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='job_instance') - return response - - -class SubClientConfigurationSets(Client): - """Handles the Sets endpoints of Configurations API - A set is a collection of items, such as users or the results of a repository search. - Sets may be used for publishing metadata in bulk, moving a group of records, or to run jobs. - """ - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/sets' - self.cnxn_params['api_uri_full'] += '/sets' - - def get(self, set_id=None, content_type=None, set_type=None, - query={}, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieve a list of sets or a single set. - - Args: - set_id (str): A unique identifier of the set. - content_type (str): Content type for filtering. - Valid values are from the SetContentType code table. - set_type (str): Set type for filtering. - Valid values are 'ITEMIZED' or 'LOGICAL'. - query (dict): Search query. Searching for words in created_by or name - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of sets or a specific set. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if set_id: - url += ("/" + str(set_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - if content_type: - args['content_type'] = str(content_type) - if set_type: - args['set_type'] = str(set_type) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - response = self.read(url, args, raw=raw) - if set_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='set') - return response - - def get_members(self, set_id, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves members of a Set given a Set ID. - - Args: - set_id (str): A unique identifier of the set. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of members for a specific set - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(set_id) + "/members") - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='member') - return response - - -class SubClientConfigurationDeposit(Client): - """Handles the Deposit profiles endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/deposit-profiles' - self.cnxn_params['api_uri_full'] += '/deposit-profiles' - - def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves list of deposit profiles or specific profile - - Args: - deposit_profile_id (str): A unique identifier of the deposit profile. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of deposit profiles or specific profile. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if deposit_profile_id: - url += ("/" + str(deposit_profile_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if deposit_profile_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='deposit_profile') - - -class SubClientConfigurationImport(Client): - """Handles the Import profiles endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/md-import-profiles' - self.cnxn_params['api_uri_full'] += '/md-import-profiles' - - def get(self, profile_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves list of import profiles or specific profile - - Args: - profile_id (str): A unique identifier of the import profile. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of import profiles or specific profile. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if profile_id: - url += ("/" + str(profile_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if profile_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='import_profile') - return response - - -class SubClientConfigurationReminders(Client): - """Handles the Reminder endpoints of Configurations API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/reminders' - self.cnxn_params['api_uri_full'] += '/reminders' - - def get(self, reminder_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves list of reminders or specific reminder. - - Args: - reminder_id (str): A unique identifier of the reminder. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of reminders or specific reminder. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if reminder_id: - url += ("/" + str(reminder_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if reminder_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='reminder') - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientConfiguration(Client): + """ + Alma provides a set of Web services for handling Configuration related + information, enabling you to quickly and easily receive configuration details. + These Web services can be used by external systems in order to get list of + possible data. + For more info: https://developers.exlibrisgroup.com/alma/apis/conf + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/conf" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/conf" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/37088dc9-c685-4641-bc7f-60b5ca7cabed.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.units = SubClientConfigurationUnits(self.cnxn_params) + self.general = SubClientConfigurationGeneral(self.cnxn_params) + self.jobs = SubClientConfigurationJobs(self.cnxn_params) + self.sets = SubClientConfigurationSets(self.cnxn_params) + self.deposit_profiles = SubClientConfigurationDeposit(self.cnxn_params) + self.import_profiles = SubClientConfigurationImport(self.cnxn_params) + self.reminders = SubClientConfigurationReminders(self.cnxn_params) + + +class SubClientConfigurationUnits(Client): + """Handles the Organization Unit endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get_libaries(self, library_id=None, q_params={}, raw=False): + """Retrieve a list of libraries or a specific library + + Args: + library_id (str): The code of the library (libraryCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of libraries or single library + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += '/libraries' + if library_id: + url += ("/" + str(library_id)) + + response = self.read(url, args, raw=raw) + return response + + def get_locations(self, library_id, location_id=None, q_params={}, raw=False): + """Retrieve a list of locations for a library + + Args: + library_id (str): The code of the library (libraryCode). + location_id (str): Code for a specific location (locationCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of library locations. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ('/libraries/' + str(library_id) + "/locations") + if location_id: + url += ("/" + str(location_id)) + + response = self.read(url, args, raw=raw) + return response + + def get_departments(self, q_params={}, raw=False): + """Retrieve a list of configured departments + + Args: + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of departments. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += '/departments' + + response = self.read(url, args, raw=raw) + return response + + +class SubClientConfigurationGeneral(Client): + """Handles the General endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, library_id=None, q_params={}, raw=False): + """Retrieve general configuration of the institution + + Args: + library_id (str): The code of the library (libraryCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + General configuration of the institution + """ + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += "/general" + + response = self.read(url, args, raw=raw) + return response + + def get_hours(self, library_id=None, q_params={}, raw=False): + """Retrieve open hours as configured in Alma. + Note that the library-hours do not necessarily reflect when the + library doors are actually open, but rather start and end times that + effect loan period. + This API is limited to one month of days from 1 year ago to + 3 years ahead for a single request. + + Args: + library_id (str): The code of the library (libraryCode). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of open hours + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + if library_id: + url += '/libraries' + url += ("/" + str(library_id)) + + url += '/open-hours' + + response = self.read(url, args, raw=raw) + return response + + def get_code_table(self, table_name, q_params={}, raw=False): + """This API returns all rows defined for a code-table. + + The main usage of this API is for applications that use Alma APIs, + and need to give the user a drop-down of valid values to choose from. + + See Alma documentation for code-table names. + + Args: + table_name (str): Code table name. (codeTableName) + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Code-table rows + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ('/code-tables/' + str(table_name)) + + response = self.read(url, args, raw=raw) + return response + + +class SubClientConfigurationJobs(Client): + """Handles the Jobs endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/jobs' + self.cnxn_params['api_uri_full'] += '/jobs' + + def get(self, job_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a list of jobs that can be submitted or details for a given job. + + Args: + job_id (str): Unique id of the job. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of jobs or a specific job + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + if job_id: + url += ("/" + str(job_id)) + else: + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if job_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='job') + return response + + def get_instances(self, job_id, instance_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve all the job instances (runs) for a given job id, or specific instance. + + Args: + job_id (str): Unique id of the job. + instance_id (str): Unique id of the specific job instance. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of jobs or a specific job + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(job_id) + "/instances") + + if instance_id: + url += ("/" + str(instance_id)) + else: + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if instance_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='job_instance') + return response + + +class SubClientConfigurationSets(Client): + """Handles the Sets endpoints of Configurations API + A set is a collection of items, such as users or the results of a repository search. + Sets may be used for publishing metadata in bulk, moving a group of records, or to run jobs. + """ + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/sets' + self.cnxn_params['api_uri_full'] += '/sets' + + def get(self, set_id=None, content_type=None, set_type=None, + query={}, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieve a list of sets or a single set. + + Args: + set_id (str): A unique identifier of the set. + content_type (str): Content type for filtering. + Valid values are from the SetContentType code table. + set_type (str): Set type for filtering. + Valid values are 'ITEMIZED' or 'LOGICAL'. + query (dict): Search query. Searching for words in created_by or name + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of sets or a specific set. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if set_id: + url += ("/" + str(set_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + if content_type: + args['content_type'] = str(content_type) + if set_type: + args['set_type'] = str(set_type) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + response = self.read(url, args, raw=raw) + if set_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='set') + return response + + def get_members(self, set_id, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves members of a Set given a Set ID. + + Args: + set_id (str): A unique identifier of the set. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of members for a specific set + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(set_id) + "/members") + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='member') + return response + + +class SubClientConfigurationDeposit(Client): + """Handles the Deposit profiles endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/deposit-profiles' + self.cnxn_params['api_uri_full'] += '/deposit-profiles' + + def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves list of deposit profiles or specific profile + + Args: + deposit_profile_id (str): A unique identifier of the deposit profile. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of deposit profiles or specific profile. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if deposit_profile_id: + url += ("/" + str(deposit_profile_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if deposit_profile_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='deposit_profile') + + +class SubClientConfigurationImport(Client): + """Handles the Import profiles endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/md-import-profiles' + self.cnxn_params['api_uri_full'] += '/md-import-profiles' + + def get(self, profile_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves list of import profiles or specific profile + + Args: + profile_id (str): A unique identifier of the import profile. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of import profiles or specific profile. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if profile_id: + url += ("/" + str(profile_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if profile_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='import_profile') + return response + + +class SubClientConfigurationReminders(Client): + """Handles the Reminder endpoints of Configurations API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/reminders' + self.cnxn_params['api_uri_full'] += '/reminders' + + def get(self, reminder_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves list of reminders or specific reminder. + + Args: + reminder_id (str): A unique identifier of the reminder. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of reminders or specific reminder. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if reminder_id: + url += ("/" + str(reminder_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if reminder_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='reminder') + return response diff --git a/almapipy/courses.py b/almapipy/courses.py index 43420ec..945d0e5 100644 --- a/almapipy/courses.py +++ b/almapipy/courses.py @@ -1,231 +1,233 @@ -from .client import Client -from . import utils - - -class SubClientCourses(Client): - """ - The Courses API allows access to courses and reading lists related information. - These Web services can be used by external systems such as Courses Management - Systems to retrieve or update courses and reading lists related data. - For more info: https://developers.exlibrisgroup.com/alma/apis/courses - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/courses" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/courses" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/25ede018-da5d-4780-8fda-a8e5d103faba.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - #self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' - - # Hook in subclients of api - self.reading_lists = SubClientCoursesReadingLists(self.cnxn_params) - self.citations = SubClientCoursesCitations(self.cnxn_params) - self.owners = SubClientCoursesOwners(self.cnxn_params) - self.tags = SubClientCoursesTags(self.cnxn_params) - - def get(self, course_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a courses list or a single course. - - Args: - course_id (str): The identifier of a single course. - Gets more detailed information. - query (dict): Search query for filtering a course list. Optional. - Searching for words from fields: [code, section, name, notes, - instructors, searchable_ids, year, academic_department, - all]. Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'code': 'ECN'} returns a list of econ classes - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of courses or a specific course resource. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if course_id: - url += ("/" + str(course_id)) - else: - # include paramets specific to course list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if course_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='course') - return response - - -class SubClientCoursesReadingLists(Client): - """Handles the reading list endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id=None, view='brief', q_params={}, raw=False): - """Retrieves all Reading Lists, or a specific list, for a Course. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - view (str): 'brief' or 'full' view of reading list. - Only applies when retrieving a single record. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of reading lists or single list - for a given course ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - if reading_list_id: - url += ('/' + str(reading_list_id)) - if view not in ['brief', 'full']: - message = "Valid view arguments are 'brief' or 'full'" - raise utils.ArgError(message) - args['view'] = view - - return self.read(url, args, raw=raw) - - -class SubClientCoursesCitations(Client): - """Handles the citations endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id, citation_id=None, q_params={}, raw=False): - """Retrieves all citations, or a specific citation, for a reading list. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - citation_id (str): The identifier of the citation. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of citations or single citation - for a given reading list ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - url += ('/' + str(reading_list_id)) - url += '/citations' - - if citation_id: - url += ('/' + str(citation_id)) - - return self.read(url, args, raw=raw) - - -class SubClientCoursesOwners(Client): - """Handles the owners endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id, owner_id=None, q_params={}, raw=False): - """Retrieves all owners, or a specific owner, for a reading list. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - owner_id (str): The primary identifier of the user (primary_id). - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of owner or single owner - for a given reading list ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - url += ('/' + str(reading_list_id)) - url += '/owners' - - if owner_id: - url += ('/' + str(owner_id)) - - return self.read(url, args, raw=raw) - - -class SubClientCoursesTags(Client): - """Handles the citation tags endpoints of Courses API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, course_id, reading_list_id, citation_id, q_params={}, raw=False): - """Retrieves a citation's tag list. - - Args: - course_id (str): The identifier of the Course. - reading_list_id (str): The identifier of the Reading List. - citation_id (str): The identifier of the citation. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of a citation's tags in for a given reading list ID. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(course_id) - url += '/reading-lists' - url += ('/' + str(reading_list_id)) - url += '/citations' - url += ('/' + str(citation_id) + "/tags") - - return self.read(url, args, raw=raw) +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientCourses(Client): + """ + The Courses API allows access to courses and reading lists related information. + These Web services can be used by external systems such as Courses Management + Systems to retrieve or update courses and reading lists related data. + For more info: https://developers.exlibrisgroup.com/alma/apis/courses + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/courses" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/courses" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/25ede018-da5d-4780-8fda-a8e5d103faba.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + #self.cnxn_params['xml_ns']['report'] = 'urn:schemas-microsoft-com:xml-analysis:rowset' + + # Hook in subclients of api + self.reading_lists = SubClientCoursesReadingLists(self.cnxn_params) + self.citations = SubClientCoursesCitations(self.cnxn_params) + self.owners = SubClientCoursesOwners(self.cnxn_params) + self.tags = SubClientCoursesTags(self.cnxn_params) + + def get(self, course_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a courses list or a single course. + + Args: + course_id (str): The identifier of a single course. + Gets more detailed information. + query (dict): Search query for filtering a course list. Optional. + Searching for words from fields: [code, section, name, notes, + instructors, searchable_ids, year, academic_department, + all]. Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'code': 'ECN'} returns a list of econ classes + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of courses or a specific course resource. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if course_id: + url += ("/" + str(course_id)) + else: + # include paramets specific to course list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if course_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='course') + return response + + +class SubClientCoursesReadingLists(Client): + """Handles the reading list endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id=None, view='brief', q_params={}, raw=False): + """Retrieves all Reading Lists, or a specific list, for a Course. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + view (str): 'brief' or 'full' view of reading list. + Only applies when retrieving a single record. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of reading lists or single list + for a given course ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + if reading_list_id: + url += ('/' + str(reading_list_id)) + if view not in ['brief', 'full']: + message = "Valid view arguments are 'brief' or 'full'" + raise utils.ArgError(message) + args['view'] = view + + return self.read(url, args, raw=raw) + + +class SubClientCoursesCitations(Client): + """Handles the citations endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id, citation_id=None, q_params={}, raw=False): + """Retrieves all citations, or a specific citation, for a reading list. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + citation_id (str): The identifier of the citation. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of citations or single citation + for a given reading list ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + url += ('/' + str(reading_list_id)) + url += '/citations' + + if citation_id: + url += ('/' + str(citation_id)) + + return self.read(url, args, raw=raw) + + +class SubClientCoursesOwners(Client): + """Handles the owners endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id, owner_id=None, q_params={}, raw=False): + """Retrieves all owners, or a specific owner, for a reading list. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + owner_id (str): The primary identifier of the user (primary_id). + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of owner or single owner + for a given reading list ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + url += ('/' + str(reading_list_id)) + url += '/owners' + + if owner_id: + url += ('/' + str(owner_id)) + + return self.read(url, args, raw=raw) + + +class SubClientCoursesTags(Client): + """Handles the citation tags endpoints of Courses API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, course_id, reading_list_id, citation_id, q_params={}, raw=False): + """Retrieves a citation's tag list. + + Args: + course_id (str): The identifier of the Course. + reading_list_id (str): The identifier of the Reading List. + citation_id (str): The identifier of the citation. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of a citation's tags in for a given reading list ID. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(course_id) + url += '/reading-lists' + url += ('/' + str(reading_list_id)) + url += '/citations' + url += ('/' + str(citation_id) + "/tags") + + return self.read(url, args, raw=raw) diff --git a/almapipy/electronic.py b/almapipy/electronic.py index ea68d53..b045233 100644 --- a/almapipy/electronic.py +++ b/almapipy/electronic.py @@ -1,157 +1,159 @@ -from .client import Client -from . import utils - - -class SubClientElectronic(Client): - """ - Alma provides a set of Web services for handling electronic information, - enabling you to quickly and easily manipulate electronic details. - These Web services can be used by external systems in order to retrieve or - update electronic data. - For more info: https://developers.exlibrisgroup.com/alma/apis/electronic - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/electronic" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/electronic" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/e7cf39e9-adce-4be1-aeb9-a31f452960da.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.collections = SubClientElectronicCollections(self.cnxn_params) - self.services = SubClientElectronicServices(self.cnxn_params) - self.portfolios = SubClientElectronicPortfolios(self.cnxn_params) - - -class SubClientElectronicCollections(Client): - """Handles the e-collections endpoints of Electronic API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/e-collections' - self.cnxn_params['api_uri_full'] += '/e-collections' - - def get(self, collection_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of electronic collections. - - Args: - collection_id (str): Unique ID of the electronic collection. - query (dict): Search query for filtering a course list. Optional. - Searching for words from fields: [interface_name, keywords, - name, po_line_id]. Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of electronic collections or specific collection. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if collection_id: - url += ("/" + str(collection_id)) - else: - # include paramets specific to course list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if collection_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='electronic_collection') - return response - - -class SubClientElectronicServices(Client): - """Handles the e-services endpoints of Electronic API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/e-collections/' - self.cnxn_params['api_uri_full'] += '/e-collections/' - - def get(self, collection_id, service_id=None, q_params={}, raw=False): - """Returns a list of electronic services for a given electronic collection. - - Args: - collection_id (str): Unique ID of the electronic collection. - service_id (str): Unique ID of the electronic service. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of electronic services for a given electronic collection - or a specific e-service. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(collection_id) - url += '/e-services' - if service_id: - url += ('/' + str(service_id)) - - return self.read(url, args, raw=raw) - - -class SubClientElectronicPortfolios(Client): - """Handles the e-services endpoints of Electronic API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/e-collections/' - self.cnxn_params['api_uri_full'] += '/e-collections/' - - def get(self, collection_id, service_id, portfolio_id=None, q_params={}, raw=False): - """Returns a list of portfolios for an electronic services for a given electronic collection. - - Args: - collection_id (str): Unique ID of the electronic collection. - service_id (str): Unique ID of the electronic service. - portfolio_id (str): Unique ID of the portfolio. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of portfolios for an electronic services for a given electronic collection - or a specific portfolio. - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += str(collection_id) - url += '/e-services' - url += ('/' + str(service_id)) - url += "/portfolios" - if portfolio_id: - url += ('/' + str(portfolio_id)) - return self.read(url, args, raw=raw) +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientElectronic(Client): + """ + Alma provides a set of Web services for handling electronic information, + enabling you to quickly and easily manipulate electronic details. + These Web services can be used by external systems in order to retrieve or + update electronic data. + For more info: https://developers.exlibrisgroup.com/alma/apis/electronic + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/electronic" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/electronic" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/e7cf39e9-adce-4be1-aeb9-a31f452960da.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.collections = SubClientElectronicCollections(self.cnxn_params) + self.services = SubClientElectronicServices(self.cnxn_params) + self.portfolios = SubClientElectronicPortfolios(self.cnxn_params) + + +class SubClientElectronicCollections(Client): + """Handles the e-collections endpoints of Electronic API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/e-collections' + self.cnxn_params['api_uri_full'] += '/e-collections' + + def get(self, collection_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of electronic collections. + + Args: + collection_id (str): Unique ID of the electronic collection. + query (dict): Search query for filtering a course list. Optional. + Searching for words from fields: [interface_name, keywords, + name, po_line_id]. Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of electronic collections or specific collection. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if collection_id: + url += ("/" + str(collection_id)) + else: + # include paramets specific to course list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if collection_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='electronic_collection') + return response + + +class SubClientElectronicServices(Client): + """Handles the e-services endpoints of Electronic API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/e-collections/' + self.cnxn_params['api_uri_full'] += '/e-collections/' + + def get(self, collection_id, service_id=None, q_params={}, raw=False): + """Returns a list of electronic services for a given electronic collection. + + Args: + collection_id (str): Unique ID of the electronic collection. + service_id (str): Unique ID of the electronic service. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of electronic services for a given electronic collection + or a specific e-service. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(collection_id) + url += '/e-services' + if service_id: + url += ('/' + str(service_id)) + + return self.read(url, args, raw=raw) + + +class SubClientElectronicPortfolios(Client): + """Handles the e-services endpoints of Electronic API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/e-collections/' + self.cnxn_params['api_uri_full'] += '/e-collections/' + + def get(self, collection_id, service_id, portfolio_id=None, q_params={}, raw=False): + """Returns a list of portfolios for an electronic services for a given electronic collection. + + Args: + collection_id (str): Unique ID of the electronic collection. + service_id (str): Unique ID of the electronic service. + portfolio_id (str): Unique ID of the portfolio. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of portfolios for an electronic services for a given electronic collection + or a specific portfolio. + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += str(collection_id) + url += '/e-services' + url += ('/' + str(service_id)) + url += "/portfolios" + if portfolio_id: + url += ('/' + str(portfolio_id)) + return self.read(url, args, raw=raw) diff --git a/almapipy/partners.py b/almapipy/partners.py index b0491e2..16c64f6 100644 --- a/almapipy/partners.py +++ b/almapipy/partners.py @@ -1,100 +1,102 @@ -from .client import Client -from . import utils - - -class SubClientPartners(Client): - """ - Alma provides a set of Web services for handling Resource Sharing Partner - information, enabling you to quickly and easily manipulate partner details. - These Web services can be used by external systems to retrieve or update - partner data. - For more info: https://developers.exlibrisgroup.com/alma/apis/partners - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/partners" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/partners" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/8883ef41-c3b8-4792-9ff8-cb6b729d6e07.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.lending_requests = SubClientPartnersLending(self.cnxn_params) - - def get(self, partner_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): - """Retrieves a list of Resource Sharing Partners or specific partner. - - Args: - partner_id (str): The code of the Resource Sharing Partner (partner_code). - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of partners or specific partner - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if partner_id: - url += ("/" + str(partner_id)) - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - - if partner_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='partner') - return response - - -class SubClientPartnersLending(Client): - """Handles the Lending Request endpoints of Resource Sharing Partners API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - - def get(self, partner_id, request_id, q_params={}, raw=False): - """Retrieve a lending request from a specific partner. - - Args: - partner_id (str): The code of the Resource Sharing Partner (partner_code). - request_id (str): The ID of the requested lending request. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - Lending request from a specific partner. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += ("/" + str(partner_id) + "/lending-requests") - url += ("/" + str(request_id)) - - response = self.read(url, args, raw=raw) - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientPartners(Client): + """ + Alma provides a set of Web services for handling Resource Sharing Partner + information, enabling you to quickly and easily manipulate partner details. + These Web services can be used by external systems to retrieve or update + partner data. + For more info: https://developers.exlibrisgroup.com/alma/apis/partners + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/partners" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/partners" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/8883ef41-c3b8-4792-9ff8-cb6b729d6e07.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.lending_requests = SubClientPartnersLending(self.cnxn_params) + + def get(self, partner_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): + """Retrieves a list of Resource Sharing Partners or specific partner. + + Args: + partner_id (str): The code of the Resource Sharing Partner (partner_code). + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of partners or specific partner + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if partner_id: + url += ("/" + str(partner_id)) + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + + if partner_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='partner') + return response + + +class SubClientPartnersLending(Client): + """Handles the Lending Request endpoints of Resource Sharing Partners API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + + def get(self, partner_id, request_id, q_params={}, raw=False): + """Retrieve a lending request from a specific partner. + + Args: + partner_id (str): The code of the Resource Sharing Partner (partner_code). + request_id (str): The ID of the requested lending request. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + Lending request from a specific partner. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += ("/" + str(partner_id) + "/lending-requests") + url += ("/" + str(request_id)) + + response = self.read(url, args, raw=raw) + return response diff --git a/almapipy/task_lists.py b/almapipy/task_lists.py index 1e31b69..d748256 100644 --- a/almapipy/task_lists.py +++ b/almapipy/task_lists.py @@ -1,112 +1,114 @@ -from .client import Client -from . import utils - - -class SubClientTaskList(Client): - """ - Alma provides a set of Web services for handling task lists information, - enabling you to quickly and easily manipulate their details. - These Web services can be used by external systems. - For more info: https://developers.exlibrisgroup.com/alma/apis/taskslists - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/task-lists" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/taskslists" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d48a1a58-d90c-4eb2-b69f-c17f7a016fd3.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.resources = SubClientTaskListResources(self.cnxn_params) - self.lending = SubClientTaskListLending(self.cnxn_params) - - -class SubClientTaskListResources(Client): - """Handles the requested resources endpoints of Task List API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/requested-resources' - self.cnxn_params['api_uri_full'] += '/requested-resources' - - def get(self, library_id, circ_desk, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of requested resources to be picked from the shelf in Alma - for a specific library/circ_desk. - - Args: - library_id (str): The library of the given circulation desk or department where the resources are located. - Use conf.units.get_libraries() to retrieve valid arguments. - circ_desk (str): The circulation desk where the action is being performed. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of requested resources. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - args['library'] = str(library_id) - args['circ_desk'] = str(circ_desk) - - response = self.read(url, args, raw=raw) - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, - raw=raw, response=response, - data_key='requested_resource') - return response - - -class SubClientTaskListLending(Client): - """Handles the requested resources endpoints of Task List API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/rs/lending-requests' - self.cnxn_params['api_uri_full'] += '/rs/lending-requests' - - def get(self, library_id, q_params={}, raw=False): - """Retrieve list of lending requests in Alma. - - Args: - library_id (str): The library of the given circulation desk or department where the resources are located. - Use conf.units.get_libraries() to retrieve valid arguments. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of lending requests. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - args['library'] = str(library_id) - - url = self.cnxn_params['api_uri_full'] - - response = self.read(url, args, raw=raw) - - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientTaskList(Client): + """ + Alma provides a set of Web services for handling task lists information, + enabling you to quickly and easily manipulate their details. + These Web services can be used by external systems. + For more info: https://developers.exlibrisgroup.com/alma/apis/taskslists + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/task-lists" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/taskslists" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/d48a1a58-d90c-4eb2-b69f-c17f7a016fd3.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.resources = SubClientTaskListResources(self.cnxn_params) + self.lending = SubClientTaskListLending(self.cnxn_params) + + +class SubClientTaskListResources(Client): + """Handles the requested resources endpoints of Task List API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/requested-resources' + self.cnxn_params['api_uri_full'] += '/requested-resources' + + def get(self, library_id, circ_desk, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of requested resources to be picked from the shelf in Alma + for a specific library/circ_desk. + + Args: + library_id (str): The library of the given circulation desk or department where the resources are located. + Use conf.units.get_libraries() to retrieve valid arguments. + circ_desk (str): The circulation desk where the action is being performed. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of requested resources. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + args['library'] = str(library_id) + args['circ_desk'] = str(circ_desk) + + response = self.read(url, args, raw=raw) + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, + raw=raw, response=response, + data_key='requested_resource') + return response + + +class SubClientTaskListLending(Client): + """Handles the requested resources endpoints of Task List API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/rs/lending-requests' + self.cnxn_params['api_uri_full'] += '/rs/lending-requests' + + def get(self, library_id, q_params={}, raw=False): + """Retrieve list of lending requests in Alma. + + Args: + library_id (str): The library of the given circulation desk or department where the resources are located. + Use conf.units.get_libraries() to retrieve valid arguments. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of lending requests. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + args['library'] = str(library_id) + + url = self.cnxn_params['api_uri_full'] + + response = self.read(url, args, raw=raw) + + return response diff --git a/almapipy/users.py b/almapipy/users.py index 6830315..5a1a53f 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -1,335 +1,340 @@ -from .client import Client -from . import utils - - -class SubClientUsers(Client): - """ - Alma provides a set of Web services for handling user information, - enabling you to quickly and easily manipulate user details. - These Web services can be used by external systems - such as student information systems (SIS) to retrieve or update user data. - For more info: https://developers.exlibrisgroup.com/alma/apis/users - """ - - def __init__(self, cnxn_params={}): - - # Copy cnnection parameters and add info specific to API. - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] = "/almaws/v1/users" - self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/users" - self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/0aa8d36f-53d6-48ff-8996-485b90b103e4.wadl" - self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] - self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] - - # Hook in subclients of api - self.loans = SubClientUsersLoans(self.cnxn_params) - self.requests = SubClientUsersRequests(self.cnxn_params) - self.fees = SubClientUsersFees(self.cnxn_params) - self.deposits = SubClientUsersDeposits(self.cnxn_params) - - def get(self, user_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a user list or a single user. - - Args: - user_id (str): A unique identifier for the user. - Gets more detailed information. - query (dict): Search query for filtering a user list. Optional. - Searching for words from fields: [primary_id, first_name, - last_name, middle_name, email, job_category, identifiers, - general_info and ALL.]. - Only AND operator is supported for multiple filters. - Format {'field': 'value', 'field2', 'value2'}. - e.g. query = {'first_name': 'Sterling', 'last_name': 'Archer'} - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of user or a specific user's details. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - if user_id: - url += ("/" + str(user_id)) - else: - # include paramets specific to user list - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - # add search query if specified in desired format - if query: - args['q'] = self.__format_query__(query) - - response = self.read(url, args, raw=raw) - if user_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user') - return response - - - def post(self, identifier, id_type, user_data={}, raw=False): - """Create a single user if it does not exists yet in Alma - - Args: - id_type (str): The identifier type for the user - Values: from the User Identifier Type code table. - identifier (str): The identifier itself for the user. - user_data (dict): Data for user enrollment. - Setting words for fields: [first_name, last_name, - middle_name, email, job_category, general_info]. - Format {'field': 'value', 'field2', 'value2'}. - e.g. data = {'first_name': 'Sterling', 'last_name': 'Archer'} - raw (bool): If true, returns raw requests object. - - Returns: ¿? - The user (at Alma) if a new user is created. - Empty list if the 'identifier' was already set. - - """ - - args = {} - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - - # Search query for the 'identifier' in Alma - args['id_type'] = id_type - query = {} - query['identifier'] = identifier - args['q'] = self.__format_query__(query) - - # Search for a user with this 'user_identifier' - response = self.read(url, args, raw=raw) - - if not response: - # No user exists with this 'identifier': Let's create it. - args.clear() - args['apikey'] = self.cnxn_params['api_key'] - - user_data['identifier'] = identifier -# TODO: status, segment_type? - user_data['status'] = 'ACTIVE' - user_data['segment_type'] = 'External' - - response = self.create(url, user_data, args, raw=raw) - - return response - - -class SubClientUsersLoans(Client): - """Handles the Loans endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, loan_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of loans for a user. - - Args: - user_id (str): A unique identifier for the user. - loan_id (str): A unique identifier for the loan. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of loans or a specific loan for a given user. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += (str(user_id) + "/loans") - - if loan_id: - url += ('/' + str(loan_id)) - else: - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - if loan_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='item_loan') - return response - - -class SubClientUsersRequests(Client): - """Handles the Requests endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, request_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of requests for a user. - - Args: - user_id (str): A unique identifier for the user. - request_id (str): A unique identifier for the request. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of requests or a specific request for a given user. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += (str(user_id) + "/requests") - - if request_id: - url += ('/' + str(request_id)) - else: - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - if request_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user_request') - return response - - -class SubClientUsersFees(Client): - """Handles the Fines and Fees endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, fee_id=None, q_params={}, raw=False): - """Retrieve a list of fines and fees for a user. - - Args: - user_id (str): A unique identifier for the user. - fee_id (str): A unique identifier for the fee. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of fines/fees or a specific fine/fee for a given user. - - """ - url = self.cnxn_params['api_uri_full'] - url += (str(user_id)) - url += '/fees' - if fee_id: - url += ('/' + str(fee_id)) - - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - return self.read(url, args, raw=raw) - - -class SubClientUsersDeposits(Client): - """Handles the Deposits endpoints of Users API""" - - def __init__(self, cnxn_params={}): - self.cnxn_params = cnxn_params.copy() - self.cnxn_params['api_uri'] += '/' - self.cnxn_params['api_uri_full'] += '/' - - def get(self, user_id, deposit_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): - """Retrieve a list of deposits for a user. - - Args: - user_id (str): A unique identifier for the user. - deposit_id (str): A unique identifier for the deposit. - limit (int): Limits the number of results. - Valid values are 0-100. - offset (int): The row number to start with. - all_records (bool): Return all rows returned by query. - Otherwise returns number specified by limit. - q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. - - Returns: - List of deposits or a specific deposit for a given user. - - """ - args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] - - url = self.cnxn_params['api_uri_full'] - url += (str(user_id) + "/deposits") - - if deposit_id: - url += ('/' + str(deposit_id)) - else: - if int(limit) > 100: - limit = 100 - elif int(limit) < 1: - limit = 1 - else: - limit = int(limit) - args['limit'] = limit - args['offset'] = int(offset) - - response = self.read(url, args, raw=raw) - if deposit_id: - return response - - # make multiple api calls until all records are retrieved - if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user_deposit') - return response +#-*- coding: utf-8-unix -*- + +from .client import Client +from . import utils + + +class SubClientUsers(Client): + """ + Alma provides a set of Web services for handling user information, + enabling you to quickly and easily manipulate user details. + These Web services can be used by external systems + such as student information systems (SIS) to retrieve or update user data. + For more info: https://developers.exlibrisgroup.com/alma/apis/users + """ + + def __init__(self, cnxn_params={}): + + # Copy cnnection parameters and add info specific to API. + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] = "/almaws/v1/users" + self.cnxn_params['web_doc'] = "https://developers.exlibrisgroup.com/alma/apis/users" + self.cnxn_params['wadl_url'] = "https://developers.exlibrisgroup.com/resources/wadl/0aa8d36f-53d6-48ff-8996-485b90b103e4.wadl" + self.cnxn_params['api_uri_full'] = self.cnxn_params['base_uri'] + self.cnxn_params['api_uri_full'] += self.cnxn_params['api_uri'] + + # Hook in subclients of api + self.loans = SubClientUsersLoans(self.cnxn_params) + self.requests = SubClientUsersRequests(self.cnxn_params) + self.fees = SubClientUsersFees(self.cnxn_params) + self.deposits = SubClientUsersDeposits(self.cnxn_params) + + def get(self, user_id=None, query={}, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a user list or a single user. + + Args: + user_id (str): A unique identifier for the user. + Gets more detailed information. + query (dict): Search query for filtering a user list. Optional. + Searching for words from fields: [primary_id, first_name, + last_name, middle_name, email, job_category, identifiers, + general_info and ALL.]. + Only AND operator is supported for multiple filters. + Format {'field': 'value', 'field2', 'value2'}. + e.g. query = {'first_name': 'Sterling', 'last_name': 'Archer'} + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of user or a specific user's details. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + if user_id: + url += ("/" + str(user_id)) + else: + # include paramets specific to user list + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + # add search query if specified in desired format + if query: + args['q'] = self.__format_query__(query) + + response = self.read(url, args, raw=raw) + if user_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='user') + return response + + + def post(self, identifier, id_type, user_data={}, raw=False): + """Create a single user if it does not exists yet in Alma + + Args: + id_type (str): The identifier type for the user + Values: from the code-table: UserIdentifierTypes + See: + identifier (str): The identifier itself for the user. + See: + user_data (dict): Data for user enrollment. + Setting words for fields: [first_name, last_name, + middle_name, email, user_group, ...]. + See + Values(user_group): code-table: UserGroups. + Format {'field': 'value', 'field2', 'value2'}. + raw (bool): If true, returns raw requests object. + + Returns: (?) + The user (at Alma) if a new user is created. + Empty list if the 'identifier' was already set. + + """ + + args = {} + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + + # Search query for the 'identifier' in Alma + args['id_type'] = id_type + query = {} + query['identifier'] = identifier + args['q'] = self.__format_query__(query) + + # Search for a user with this 'user_identifier' + response = self.read(url, args, raw=raw) + + if not response: + # No user exists with this 'identifier': Let's create it. + args.clear() + args['apikey'] = self.cnxn_params['api_key'] + + user_data['identifier'] = identifier +# TODO: status, segment_type? + user_data['status'] = 'ACTIVE' + user_data['segment_type'] = 'External' + + response = self.create(url, user_data, args, raw=raw) + + return response + + +class SubClientUsersLoans(Client): + """Handles the Loans endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, loan_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of loans for a user. + + Args: + user_id (str): A unique identifier for the user. + loan_id (str): A unique identifier for the loan. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of loans or a specific loan for a given user. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += (str(user_id) + "/loans") + + if loan_id: + url += ('/' + str(loan_id)) + else: + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + if loan_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='item_loan') + return response + + +class SubClientUsersRequests(Client): + """Handles the Requests endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, request_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of requests for a user. + + Args: + user_id (str): A unique identifier for the user. + request_id (str): A unique identifier for the request. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of requests or a specific request for a given user. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += (str(user_id) + "/requests") + + if request_id: + url += ('/' + str(request_id)) + else: + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + if request_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='user_request') + return response + + +class SubClientUsersFees(Client): + """Handles the Fines and Fees endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, fee_id=None, q_params={}, raw=False): + """Retrieve a list of fines and fees for a user. + + Args: + user_id (str): A unique identifier for the user. + fee_id (str): A unique identifier for the fee. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of fines/fees or a specific fine/fee for a given user. + + """ + url = self.cnxn_params['api_uri_full'] + url += (str(user_id)) + url += '/fees' + if fee_id: + url += ('/' + str(fee_id)) + + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + return self.read(url, args, raw=raw) + + +class SubClientUsersDeposits(Client): + """Handles the Deposits endpoints of Users API""" + + def __init__(self, cnxn_params={}): + self.cnxn_params = cnxn_params.copy() + self.cnxn_params['api_uri'] += '/' + self.cnxn_params['api_uri_full'] += '/' + + def get(self, user_id, deposit_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): + """Retrieve a list of deposits for a user. + + Args: + user_id (str): A unique identifier for the user. + deposit_id (str): A unique identifier for the deposit. + limit (int): Limits the number of results. + Valid values are 0-100. + offset (int): The row number to start with. + all_records (bool): Return all rows returned by query. + Otherwise returns number specified by limit. + q_params (dict): Any additional query parameters. + raw (bool): If true, returns raw requests object. + + Returns: + List of deposits or a specific deposit for a given user. + + """ + args = q_params.copy() + args['apikey'] = self.cnxn_params['api_key'] + + url = self.cnxn_params['api_uri_full'] + url += (str(user_id) + "/deposits") + + if deposit_id: + url += ('/' + str(deposit_id)) + else: + if int(limit) > 100: + limit = 100 + elif int(limit) < 1: + limit = 1 + else: + limit = int(limit) + args['limit'] = limit + args['offset'] = int(offset) + + response = self.read(url, args, raw=raw) + if deposit_id: + return response + + # make multiple api calls until all records are retrieved + if all_records: + response = self.__read_all__(url=url, args=args, raw=raw, + response=response, data_key='user_deposit') + return response diff --git a/almapipy/utils.py b/almapipy/utils.py index d0ba87c..15625c3 100644 --- a/almapipy/utils.py +++ b/almapipy/utils.py @@ -1,26 +1,28 @@ -""" -Error classes and other helpful functions -""" - - -class Error(Exception): - """Base class for exceptions""" - pass - - -class AlmaError(Error): - """ - Base Exception class for Alma API calls - """ - - def __init__(self, message, response=None, url=None): - super(AlmaError, self).__init__(message) - self.message = message - self.response = response - self.url = url - - -class ArgError(Error): - def __init__(self, message): - super(ArgError, self).__init__(message) - self.message = "Invalid Argument: " + message +#-*- coding: utf-8-unix -*- + +""" +Error classes and other helpful functions +""" + + +class Error(Exception): + """Base class for exceptions""" + pass + + +class AlmaError(Error): + """ + Base Exception class for Alma API calls + """ + + def __init__(self, message, response=None, url=None): + super(AlmaError, self).__init__(message) + self.message = message + self.response = response + self.url = url + + +class ArgError(Error): + def __init__(self, message): + super(ArgError, self).__init__(message) + self.message = "Invalid Argument: " + message From f7cec08eeff78bd50d5cac54f111d05ba002e0b1 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 16:11:14 +0100 Subject: [PATCH 11/47] General 'char coding' change to UTF8 --- setup.py | 64 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/setup.py b/setup.py index 3e94529..990d264 100644 --- a/setup.py +++ b/setup.py @@ -1,31 +1,33 @@ -from setuptools import setup -from setuptools import find_packages - -with open("README.md", "r") as fh: - long_description = fh.read() - -VERSION = "1.0.1" - -setup( - name="almapipy", - version=VERSION, - author="Steve Pelkey", - author_email="spelkey@ucdavis.edu", - description="Python requests wrapper for the Ex Libris Alma API", - long_description=long_description, -# long_description_content_type="text/markdown", - url="https://github.com/UCDavisLibrary/almapipy", - install_requires=['requests'], - keywords='alma exlibris exlibrisgroup api bibliographic', - packages=find_packages(), - classifiers=[ - "Programming Language :: Python :: 3" - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: Science/Research", - "Natural Language :: English", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) +#-*- coding: utf-8-unix -*- + +from setuptools import setup +from setuptools import find_packages + +with open("README.md", "r") as fh: + long_description = fh.read() + +VERSION = "1.0.1" + +setup( + name="almapipy", + version=VERSION, + author="Steve Pelkey", + author_email="spelkey@ucdavis.edu", + description="Python requests wrapper for the Ex Libris Alma API", + long_description=long_description, +# long_description_content_type="text/markdown", + url="https://github.com/UCDavisLibrary/almapipy", + install_requires=['requests'], + keywords='alma exlibris exlibrisgroup api bibliographic', + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3" + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Topic :: Software Development :: Libraries :: Python Modules", + ], +) From dc6ee7cc283ac8359a3c25c8100a19f4bd756168 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 16:11:14 +0100 Subject: [PATCH 12/47] General 'char coding' change to UTF8 --- setup.py | 64 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/setup.py b/setup.py index 3e94529..990d264 100644 --- a/setup.py +++ b/setup.py @@ -1,31 +1,33 @@ -from setuptools import setup -from setuptools import find_packages - -with open("README.md", "r") as fh: - long_description = fh.read() - -VERSION = "1.0.1" - -setup( - name="almapipy", - version=VERSION, - author="Steve Pelkey", - author_email="spelkey@ucdavis.edu", - description="Python requests wrapper for the Ex Libris Alma API", - long_description=long_description, -# long_description_content_type="text/markdown", - url="https://github.com/UCDavisLibrary/almapipy", - install_requires=['requests'], - keywords='alma exlibris exlibrisgroup api bibliographic', - packages=find_packages(), - classifiers=[ - "Programming Language :: Python :: 3" - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: Science/Research", - "Natural Language :: English", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) +#-*- coding: utf-8-unix -*- + +from setuptools import setup +from setuptools import find_packages + +with open("README.md", "r") as fh: + long_description = fh.read() + +VERSION = "1.0.1" + +setup( + name="almapipy", + version=VERSION, + author="Steve Pelkey", + author_email="spelkey@ucdavis.edu", + description="Python requests wrapper for the Ex Libris Alma API", + long_description=long_description, +# long_description_content_type="text/markdown", + url="https://github.com/UCDavisLibrary/almapipy", + install_requires=['requests'], + keywords='alma exlibris exlibrisgroup api bibliographic', + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3" + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Topic :: Software Development :: Libraries :: Python Modules", + ], +) From 4f3d24bc616c39ee71b1deb89b8f9ce1dcab71d4 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 16:46:44 +0100 Subject: [PATCH 13/47] Remove 'intruder' directory --- almapipy.egg-info/PKG-INFO | 220 ------------------------- almapipy.egg-info/SOURCES.txt | 20 --- almapipy.egg-info/dependency_links.txt | 1 - almapipy.egg-info/requires.txt | 1 - almapipy.egg-info/top_level.txt | 1 - 5 files changed, 243 deletions(-) delete mode 100644 almapipy.egg-info/PKG-INFO delete mode 100644 almapipy.egg-info/SOURCES.txt delete mode 100644 almapipy.egg-info/dependency_links.txt delete mode 100644 almapipy.egg-info/requires.txt delete mode 100644 almapipy.egg-info/top_level.txt diff --git a/almapipy.egg-info/PKG-INFO b/almapipy.egg-info/PKG-INFO deleted file mode 100644 index 0a7c528..0000000 --- a/almapipy.egg-info/PKG-INFO +++ /dev/null @@ -1,220 +0,0 @@ -Metadata-Version: 1.1 -Name: almapipy -Version: 1.0.1 -Summary: Python requests wrapper for the Ex Libris Alma API -Home-page: https://github.com/UCDavisLibrary/almapipy -Author: Steve Pelkey -Author-email: spelkey@ucdavis.edu -License: UNKNOWN -Description: # almapipy: Python Wrapper for Alma API - - almapipy is python requests wrapper for easily accessing the Ex Libris Alma API. It is designed to be lightweight, and imposes a structure that mimics the [Alma API architecture](https://developers.exlibrisgroup.com/alma/apis). - - ## Installation - ```pip install almapipy``` - - ## Progress and Roadmap - Get functionality has been developed around all the Alma APIs (listed below). - Post, Put and Delete functions will be gradually added in future releases. - - | API | Get | Post | Put | Delete | - | --- | :---: | :---: | :---: | :---: | - | [bibs](#access-bibliographic-data) | X | | | | - | [analytics](#access-reports) | X | NA | NA | NA | - | [acquisitions](#access-acquisitions) | X | | | | - | [configuration](#access-configuration-settings) | X | | | | - | [courses](#access-courses) | X | | | | - | [resource sharing partners](#access-resource-sharing-partners) | X | | | | - | [task-lists](#access-task-lists) | X | | | | - | [users](#access-users) | X | In Progress | | | - | [electronic](#access-electronic) | X | | | | - - ## Use - - ### Import - ```python - # Import and call primary Client class - from almapipy import AlmaCnxn - alma = AlmaCnxn('your_api_key', location='Europe', data_format='json') - ``` - ### Access Bibliographic Data - Alma provides a set of Web services for handling bibliographic records related information, enabling you to quickly and easily manipulate bibliographic records related details. These Web services can be used by external systems to retrieve or update bibliographic records related data. - ```python - # Use Alma mms_id for retrieving bib records - harry_potter = "9980963346303126" - bib_record = alma.bibs.catalog.get(harry_potter) - - # get holding items for a bib record - holdings = alma.bibs.catalog.get_holdings(harry_potter) - - # get loans by title - loans = alma.bibs.loans.get_by_title(harry_potter) - # or by a specific holding item - loans = alma.bibs.loans.get_by_item(harry_potter, holding_id, item_id) - - # get requests or availability of bib - alma.bibs.requests.get_by_title(harry_potter) - alma.bibs.requests.get_by_item(harry_potter, holding_id, item_id) - alma.bibs.requests.get_availability(harry_potter, period=20) - - # get digital representations - alma.bibs.representations.get(harry_potter) - - # get linked data - alma.bibs.linked_data.get(harry_potter) - ``` - - ### Access Reports - The Analytics API returns an Alma report. - ```python - # Find the system path to the report if don't know path - alma.analytics.paths.get('/shared') - - # retrieve the report as an XML ET element (native response) - report = alma.analytics.reports.get('path_to_report') - - # or convert the xml to json after API call - report = alma.analytics.reports.get('path_to_report', return_json = True) - ``` - - ### Access Courses - Alma provides a set of Web services for handling courses and reading lists related information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems such as Courses Management Systems to retrieve or update courses and reading lists related data. - ```python - # Get a complete list of courses. Makes multiple calls if necessary. - course_list = alma.courses.get(all_records = True) - - # or filter on search parameters - econ_courses = alma.courses.get(query = {'code': 'ECN'}) - - # get reading lists for a course - course_id = econ_courses['course'][0]['id'] - reading_lists = alma.courses.reading_lists.get(course_id) - - # get more detailed information about a specific reading list - reading_list_id = reading_lists['reading_list'][0]['id'] - alma.courses.reading_lists(course_id, reading_list_id, view = 'full') - - # get citations for a reading list - alma.courses.citations(course_id, reading_list_id) - ``` - - ### Access Users - Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems—such as student information systems (SIS)—to retrieve or update user data. - ```python - # Get a list of users or filter on search parameters - users = alma.users.get(query = {'first_name': 'Sterling', 'last_name': 'Archer'}) - - # get more information on that user - user_id = users['user'][0]['primary_id'] - alma.users.get(user_id) - - # get all loans or requests for a user. Makes multiple calls if necessary. - loans = alma.user.loans.get(user_id, all_records = True) - requests = alma.user.requests.get(user_id, all_records = True) - - # get deposits or fees for a user - deposits = alma.users.deposits.get(user_id) - fees = alma.users.fees.get(user_id) - ``` - ### Access Acquisitions - Alma provides a set of Web services for handling acquisitions information, enabling you to quickly and easily manipulate acquisitions details. These Web services can be used by external systems - such as subscription agent systems - to retrieve or update acquisitions data. - ```python - # get all funds - alma.acq.funds.get(all_records=True) - - # get po_lines by search - amazon_lines = alma.acq.po_lines.get(query={'vendor_account': 'AMAZON'}) - single_line_id = amazon_lines['po_line'][0]['number'] - # or by a specific line number - alma.acq.po_lines.get(single_line_id) - - # search for a vendor - alma.acq.vendors.get(status='active', query={'name':'AMAZON'}) - # or get a specific vendor - alma.acq.vendors.get('AMAZON.COM') - - # get invoices or polines for a specific vendor - alma.acq.vendors.get_invoices('AMAZON.COM') - alma.acq.vendors.get_po_lines('AMAZON.COM') - - # or get specific invoices - alma.acq.invoices.get('invoice_id') - - # get all licenses - alma.acq.licenses.get(all_records=True) - ``` - ### Access Configuration Settings - Alma provides a set of Web services for handling Configuration related information, enabling you to quickly and easily receive configuration details. These Web services can be used by external systems in order to get list of possible data. - ```python - # Get libraries, locations, departments, and hours - libraries = alma.conf.units.get_libaries() - library_id = libraries['library'][0]['code'] - locations = alma.conf.units.get_locations(library_id) - hours = alma.conf.general.get_hours(library_id) - departments = alma.conf.units.get_departments() - - # Get system code tables - table = 'UserGroups' - alma.conf.general.get_code_table(table) - - # Get scheduled jobs and run history - jobs = alma.conf.jobs.get() - job_id = jobs['job'][0]['id'] - run_history = alma.conf.jobs.get_instances(job_id) - - # Get sets and set members - sets = alma.conf.sets.get() - set_id = sets['set'][0]['id'] - set_members = alma.conf.sets.get_members(set_id) - - # get profiles and reminders - depost_profiles = alma.conf.deposit_profiles.get() - import_profiles = alma.conf.import_profiles.get() - reminders = alma.conf.reminders.get() - ``` - ### Access Resource Sharing Partners - Alma provides a set of Web services for handling Resource Sharing Partner information, enabling you to quickly and easily manipulate partner details. These Web services can be used by external systems to retrieve or update partner data. - ```python - # get partners - partners = alma.partners.get() - ``` - ### Access Electronic - Alma provides a set of Web services for handling electronic information, enabling you to quickly and easily manipulate electronic details. These Web services can be used by external systems in order to retrieve or update electronic data. - ```python - # get e-collections - collections = alma.electronic.collections.get() - collection_id = collections['electronic_collection'][0]['id'] - - # get services for a collection - services = alma.electronic.services.get(collection_id) - service_id = services['electronic_service'][0]['id'] - - # get portfolios for a service - alma.electronic.portfolios.get(collection_id, service_id) - - ``` - ### Access Task Lists - Alma provides a set of Web services for handling task lists information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems. - ```python - # get requested resources for a specific circulation desk - alma.task_lists.resources.get(library_id, circ_desk) - - # get lending requests for a specific library - alma.task_lists.lending.get(library_id) - - ``` - - ## Attribution and Contact - - - * **Author**: [Steve Pelkey](mailto:spelkey@ucdavis.edu) - -Keywords: alma exlibris exlibrisgroup api bibliographic -Platform: UNKNOWN -Classifier: Programming Language :: Python :: 3License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Education -Classifier: Intended Audience :: Science/Research -Classifier: Natural Language :: English -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/almapipy.egg-info/SOURCES.txt b/almapipy.egg-info/SOURCES.txt deleted file mode 100644 index ff6b7d0..0000000 --- a/almapipy.egg-info/SOURCES.txt +++ /dev/null @@ -1,20 +0,0 @@ -MANIFEST.in -README.md -setup.py -almapipy/__init__.py -almapipy/acquisitions.py -almapipy/analytics.py -almapipy/bibs.py -almapipy/client.py -almapipy/conf.py -almapipy/courses.py -almapipy/electronic.py -almapipy/partners.py -almapipy/task_lists.py -almapipy/users.py -almapipy/utils.py -almapipy.egg-info/PKG-INFO -almapipy.egg-info/SOURCES.txt -almapipy.egg-info/dependency_links.txt -almapipy.egg-info/requires.txt -almapipy.egg-info/top_level.txt \ No newline at end of file diff --git a/almapipy.egg-info/dependency_links.txt b/almapipy.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/almapipy.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/almapipy.egg-info/requires.txt b/almapipy.egg-info/requires.txt deleted file mode 100644 index f229360..0000000 --- a/almapipy.egg-info/requires.txt +++ /dev/null @@ -1 +0,0 @@ -requests diff --git a/almapipy.egg-info/top_level.txt b/almapipy.egg-info/top_level.txt deleted file mode 100644 index 468e7ed..0000000 --- a/almapipy.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -almapipy From 9bd26a20f1aa0af9fe58fd1d628ffd45f12bc546 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sat, 17 Nov 2018 16:46:44 +0100 Subject: [PATCH 14/47] Remove 'intruder' directory From 80fdd2b0480b873c302a890ffccc5f9c9508076e Mon Sep 17 00:00:00 2001 From: pac0san Date: Mon, 19 Nov 2018 21:16:29 +0100 Subject: [PATCH 15/47] Just Testing POST Access --- almapipy/client.py | 14 ++++++++++---- almapipy/users.py | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index 81666de..9c6c235 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -21,7 +21,8 @@ def __init__(self, cnxn_params={}): # instantiate dictionary for storing alma api connection parameters self.cnxn_params = cnxn_params - def create(self, url, data, args, object_type, raw=False): +# def create(self, url, data, args, object_type, raw=False): + def create(self, url, data, args, raw=False): """ Uses requests library to make Exlibris API Post call. @@ -29,13 +30,12 @@ def create(self, url, data, args, object_type, raw=False): url (str): Exlibris API endpoint url. data (dict): Data to be posted. args (dict): Query string parameters for API call. - object_type (str): Type of object to be posted (see alma docs) +# object_type (str): Type of object to be posted (see alma docs) raw (bool): If true, returns raw response. Returns: JSON-esque, xml, or raw response. """ -# print(url) # Determine format of data to be posted according to order of importance: # 1) Local declaration, 2) dtype of data parameter, 3) global setting. @@ -66,7 +66,13 @@ def create(self, url, data, args, object_type, raw=False): else: message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) - + """ + print("Debug: client.py Create") + print(url) + print(data) + print(args) + print(headers) + """ # Send request and parse response response = requests.post(url, data=data, params=args, headers=headers) if raw: diff --git a/almapipy/users.py b/almapipy/users.py index 5a1a53f..668e9c1 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -79,7 +79,12 @@ def get(self, user_id=None, query={}, limit=10, offset=0, response = self.read(url, args, raw=raw) if user_id: return response - + """ + print("Debug: users.py GET") + print(url) + print(args) + print(response) + """ # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, raw=raw, @@ -93,48 +98,62 @@ def post(self, identifier, id_type, user_data={}, raw=False): Args: id_type (str): The identifier type for the user Values: from the code-table: UserIdentifierTypes + See: identifier (str): The identifier itself for the user. See: user_data (dict): Data for user enrollment. Setting words for fields: [first_name, last_name, middle_name, email, user_group, ...]. - See - Values(user_group): code-table: UserGroups. Format {'field': 'value', 'field2', 'value2'}. + Values(user_group): code-table: UserGroups. + + See raw (bool): If true, returns raw requests object. Returns: (?) The user (at Alma) if a new user is created. - Empty list if the 'identifier' was already set. + "{'total_record_count': 0}" if the 'identifier' was already set. """ args = {} + args['id_type'] = id_type args['apikey'] = self.cnxn_params['api_key'] url = self.cnxn_params['api_uri_full'] # Search query for the 'identifier' in Alma - args['id_type'] = id_type query = {} - query['identifier'] = identifier + query['identifiers'] = identifier args['q'] = self.__format_query__(query) # Search for a user with this 'user_identifier' response = self.read(url, args, raw=raw) - - if not response: + """ + print("Debug: users.py POST") + print(url) + print(args) + print(response) + """ + if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. args.clear() args['apikey'] = self.cnxn_params['api_key'] - user_data['identifier'] = identifier -# TODO: status, segment_type? - user_data['status'] = 'ACTIVE' - user_data['segment_type'] = 'External' + # 'user_identifier' chunk + aux_dict = {} + aux_dict['value'] = identifier + aux_dict['id_type'] = {} + aux_dict['id_type']['value'] = id_type + aux_dict['status'] = 'ACTIVE' + aux_dict['segment_type'] = 'External' + user_data['user_identifier'] = [ aux_dict ] response = self.create(url, user_data, args, raw=raw) + else: + # User already exist in Alma. + response = {'total_record_count': 0} return response From 2af0d2a919efcca63fe3f8df083e66472ef1688a Mon Sep 17 00:00:00 2001 From: pac0san Date: Mon, 19 Nov 2018 21:16:29 +0100 Subject: [PATCH 16/47] Just Testing POST Access --- almapipy/client.py | 14 ++++++++++---- almapipy/users.py | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index 81666de..9c6c235 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -21,7 +21,8 @@ def __init__(self, cnxn_params={}): # instantiate dictionary for storing alma api connection parameters self.cnxn_params = cnxn_params - def create(self, url, data, args, object_type, raw=False): +# def create(self, url, data, args, object_type, raw=False): + def create(self, url, data, args, raw=False): """ Uses requests library to make Exlibris API Post call. @@ -29,13 +30,12 @@ def create(self, url, data, args, object_type, raw=False): url (str): Exlibris API endpoint url. data (dict): Data to be posted. args (dict): Query string parameters for API call. - object_type (str): Type of object to be posted (see alma docs) +# object_type (str): Type of object to be posted (see alma docs) raw (bool): If true, returns raw response. Returns: JSON-esque, xml, or raw response. """ -# print(url) # Determine format of data to be posted according to order of importance: # 1) Local declaration, 2) dtype of data parameter, 3) global setting. @@ -66,7 +66,13 @@ def create(self, url, data, args, object_type, raw=False): else: message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) - + """ + print("Debug: client.py Create") + print(url) + print(data) + print(args) + print(headers) + """ # Send request and parse response response = requests.post(url, data=data, params=args, headers=headers) if raw: diff --git a/almapipy/users.py b/almapipy/users.py index 5a1a53f..668e9c1 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -79,7 +79,12 @@ def get(self, user_id=None, query={}, limit=10, offset=0, response = self.read(url, args, raw=raw) if user_id: return response - + """ + print("Debug: users.py GET") + print(url) + print(args) + print(response) + """ # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, raw=raw, @@ -93,48 +98,62 @@ def post(self, identifier, id_type, user_data={}, raw=False): Args: id_type (str): The identifier type for the user Values: from the code-table: UserIdentifierTypes + See: identifier (str): The identifier itself for the user. See: user_data (dict): Data for user enrollment. Setting words for fields: [first_name, last_name, middle_name, email, user_group, ...]. - See - Values(user_group): code-table: UserGroups. Format {'field': 'value', 'field2', 'value2'}. + Values(user_group): code-table: UserGroups. + + See raw (bool): If true, returns raw requests object. Returns: (?) The user (at Alma) if a new user is created. - Empty list if the 'identifier' was already set. + "{'total_record_count': 0}" if the 'identifier' was already set. """ args = {} + args['id_type'] = id_type args['apikey'] = self.cnxn_params['api_key'] url = self.cnxn_params['api_uri_full'] # Search query for the 'identifier' in Alma - args['id_type'] = id_type query = {} - query['identifier'] = identifier + query['identifiers'] = identifier args['q'] = self.__format_query__(query) # Search for a user with this 'user_identifier' response = self.read(url, args, raw=raw) - - if not response: + """ + print("Debug: users.py POST") + print(url) + print(args) + print(response) + """ + if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. args.clear() args['apikey'] = self.cnxn_params['api_key'] - user_data['identifier'] = identifier -# TODO: status, segment_type? - user_data['status'] = 'ACTIVE' - user_data['segment_type'] = 'External' + # 'user_identifier' chunk + aux_dict = {} + aux_dict['value'] = identifier + aux_dict['id_type'] = {} + aux_dict['id_type']['value'] = id_type + aux_dict['status'] = 'ACTIVE' + aux_dict['segment_type'] = 'External' + user_data['user_identifier'] = [ aux_dict ] response = self.create(url, user_data, args, raw=raw) + else: + # User already exist in Alma. + response = {'total_record_count': 0} return response From 7f9facb0848847334938832b68b702dd61d8dbbf Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 20 Nov 2018 21:39:18 +0100 Subject: [PATCH 17/47] 'post' Changes --- almapipy/users.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/almapipy/users.py b/almapipy/users.py index 668e9c1..55210a9 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -2,7 +2,7 @@ from .client import Client from . import utils - +from json import loads class SubClientUsers(Client): """ @@ -142,6 +142,8 @@ def post(self, identifier, id_type, user_data={}, raw=False): args['apikey'] = self.cnxn_params['api_key'] # 'user_identifier' chunk + + """ aux_dict = {} aux_dict['value'] = identifier aux_dict['id_type'] = {} @@ -149,6 +151,10 @@ def post(self, identifier, id_type, user_data={}, raw=False): aux_dict['status'] = 'ACTIVE' aux_dict['segment_type'] = 'External' user_data['user_identifier'] = [ aux_dict ] + """ + + aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" + user_data = loads(aux_dict.replace("'", "\"")) response = self.create(url, user_data, args, raw=raw) else: From 618fd81a40732775c24ecac67e3bbc92dc5c1b22 Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 20 Nov 2018 21:39:18 +0100 Subject: [PATCH 18/47] 'post' Changes --- almapipy/users.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/almapipy/users.py b/almapipy/users.py index 668e9c1..55210a9 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -2,7 +2,7 @@ from .client import Client from . import utils - +from json import loads class SubClientUsers(Client): """ @@ -142,6 +142,8 @@ def post(self, identifier, id_type, user_data={}, raw=False): args['apikey'] = self.cnxn_params['api_key'] # 'user_identifier' chunk + + """ aux_dict = {} aux_dict['value'] = identifier aux_dict['id_type'] = {} @@ -149,6 +151,10 @@ def post(self, identifier, id_type, user_data={}, raw=False): aux_dict['status'] = 'ACTIVE' aux_dict['segment_type'] = 'External' user_data['user_identifier'] = [ aux_dict ] + """ + + aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" + user_data = loads(aux_dict.replace("'", "\"")) response = self.create(url, user_data, args, raw=raw) else: From 1a33e592b31ccb681bc86fe3d85640c0c7ffac22 Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 20 Nov 2018 22:24:54 +0100 Subject: [PATCH 19/47] 'requests.delete' skelleton init --- almapipy/client.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/almapipy/client.py b/almapipy/client.py index 9c6c235..ca8210e 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -25,6 +25,7 @@ def __init__(self, cnxn_params={}): def create(self, url, data, args, raw=False): """ Uses requests library to make Exlibris API Post call. + Returns data of type specified during init of base class. Args: url (str): Exlibris API endpoint url. @@ -73,14 +74,40 @@ def create(self, url, data, args, raw=False): print(args) print(headers) """ - # Send request and parse response + # Send request response = requests.post(url, data=data, params=args, headers=headers) if raw: return response + + # Parse content content = self.__parse_response__(response) return content + def delete(self, url, data, args, raw=False): + """ + Uses requests library to make Exlibris API Delete call. + Returns data of type specified during init of base class. + + Args: + url (str): Exlibris API endpoint url. + data (dict): Data to be deleted.. + args (dict): Query string parameters for API call. + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ + + # Send request + response = requests.delete(url, headers=args) + if raw: + return response + + # Parse content + content = self.__parse_response__(response) + + return content def read(self, url, args, raw=False): """ From ba1127a01681811975db1acf6a8985044d8ce283 Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 20 Nov 2018 22:24:54 +0100 Subject: [PATCH 20/47] 'requests.delete' skelleton init --- almapipy/client.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/almapipy/client.py b/almapipy/client.py index 9c6c235..ca8210e 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -25,6 +25,7 @@ def __init__(self, cnxn_params={}): def create(self, url, data, args, raw=False): """ Uses requests library to make Exlibris API Post call. + Returns data of type specified during init of base class. Args: url (str): Exlibris API endpoint url. @@ -73,14 +74,40 @@ def create(self, url, data, args, raw=False): print(args) print(headers) """ - # Send request and parse response + # Send request response = requests.post(url, data=data, params=args, headers=headers) if raw: return response + + # Parse content content = self.__parse_response__(response) return content + def delete(self, url, data, args, raw=False): + """ + Uses requests library to make Exlibris API Delete call. + Returns data of type specified during init of base class. + + Args: + url (str): Exlibris API endpoint url. + data (dict): Data to be deleted.. + args (dict): Query string parameters for API call. + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ + + # Send request + response = requests.delete(url, headers=args) + if raw: + return response + + # Parse content + content = self.__parse_response__(response) + + return content def read(self, url, args, raw=False): """ From 078d638d54e0fe01e2d95a11273830124078c740 Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 21 Nov 2018 12:35:55 +0100 Subject: [PATCH 21/47] Avoid sending 'API Key' in URL, but in Headers --- almapipy/client.py | 31 +++++++++++++++++-------------- almapipy/users.py | 35 +++++++++++++++++------------------ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index ca8210e..f2be65e 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -22,7 +22,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params # def create(self, url, data, args, object_type, raw=False): - def create(self, url, data, args, raw=False): + def create(self, url, data, args, headers, raw=False): """ Uses requests library to make Exlibris API Post call. Returns data of type specified during init of base class. @@ -31,6 +31,7 @@ def create(self, url, data, args, raw=False): url (str): Exlibris API endpoint url. data (dict): Data to be posted. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. # object_type (str): Type of object to be posted (see alma docs) raw (bool): If true, returns raw response. @@ -40,7 +41,6 @@ def create(self, url, data, args, raw=False): # Determine format of data to be posted according to order of importance: # 1) Local declaration, 2) dtype of data parameter, 3) global setting. - headers = {} if 'format' not in args.keys(): if type(data) == ET or type(data) == ET.Element: content_type = 'xml' @@ -52,13 +52,14 @@ def create(self, url, data, args, raw=False): else: content_type = args['format'] - # Declare data type in header, convert to string if necessary. + # Preserve Auth, declare data type in header, convert to string if necessary. + headers_aux = headers.copy() if content_type == 'json': - headers['content-type'] = 'application/json' + headers_aux['content-type'] = 'application/json' if type(data) != str: data = json.dumps(data) elif content_type == 'xml': - headers['content-type'] = 'application/xml' + headers_aux['content-type'] = 'application/xml' if type(data) == ET or type(data) == ET.Element: data = ET.tostring(data, encoding='unicode') elif type(data) != str: @@ -72,10 +73,10 @@ def create(self, url, data, args, raw=False): print(url) print(data) print(args) - print(headers) + print(headers_aux) """ # Send request - response = requests.post(url, data=data, params=args, headers=headers) + response = requests.post(url, data=data, params=args, headers=headers_aux) if raw: return response @@ -84,15 +85,15 @@ def create(self, url, data, args, raw=False): return content - def delete(self, url, data, args, raw=False): + def delete(self, url, args, headers, raw=False): """ Uses requests library to make Exlibris API Delete call. Returns data of type specified during init of base class. Args: url (str): Exlibris API endpoint url. - data (dict): Data to be deleted.. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. raw (bool): If true, returns raw response. Returns: @@ -100,7 +101,7 @@ def delete(self, url, data, args, raw=False): """ # Send request - response = requests.delete(url, headers=args) + response = requests.delete(url, params=args, headers=headers) if raw: return response @@ -109,7 +110,7 @@ def delete(self, url, data, args, raw=False): return content - def read(self, url, args, raw=False): + def read(self, url, args, headers, raw=False): """ Uses requests library to make Exlibris API Get call. Returns data of type specified during init of base class. @@ -117,6 +118,7 @@ def read(self, url, args, raw=False): Args: url (str): Exlibris API endpoint url. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. raw (bool): If true, returns raw response. Returns: @@ -131,7 +133,7 @@ def read(self, url, args, raw=False): data_format = args['format'] # Send request. - response = requests.get(url, params=args) + response = requests.get(url, params=args, headers=headers) if raw: return response @@ -165,13 +167,14 @@ def __format_query__(self, query): return q_str - def __read_all__(self, url, args, raw, response, data_key, max_limit=100): + def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=100): """Makes multiple API calls until all records for a query are retrieved. Called by the 'all_records' parameter. Args: url (str): Exlibris API endpoint url. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. raw (bool): If true, returns raw response. response (xml, raw, or json): First API call. data_key (str): Dictionary key for accessing data. @@ -207,7 +210,7 @@ def __read_all__(self, url, args, raw, response, data_key, max_limit=100): break # make call and increment counter variables - new_response = self.read(url, args, raw=raw) + new_response = self.read(url, args=args, headers=headers, raw=raw) records_retrieved += limit args['offset'] += limit diff --git a/almapipy/users.py b/almapipy/users.py index 55210a9..1c4988c 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -55,8 +55,10 @@ def get(self, user_id=None, query={}, limit=10, offset=0, List of user or a specific user's details. """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] url = self.cnxn_params['api_uri_full'] if user_id: @@ -76,7 +78,7 @@ def get(self, user_id=None, query={}, limit=10, offset=0, if query: args['q'] = self.__format_query__(query) - response = self.read(url, args, raw=raw) + response = self.read(url, args=args, headers=headers, raw=raw) if user_id: return response """ @@ -87,12 +89,12 @@ def get(self, user_id=None, query={}, limit=10, offset=0, """ # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='user') return response - def post(self, identifier, id_type, user_data={}, raw=False): + def post(self, identifier, id_type, user_data, raw=False): """Create a single user if it does not exists yet in Alma Args: @@ -117,19 +119,18 @@ def post(self, identifier, id_type, user_data={}, raw=False): """ - args = {} - args['id_type'] = id_type - args['apikey'] = self.cnxn_params['api_key'] + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + args = {'id_type': '{}'.format(id_type)} url = self.cnxn_params['api_uri_full'] # Search query for the 'identifier' in Alma - query = {} - query['identifiers'] = identifier + query = {'identifiers': '{}'.format(identifier)} args['q'] = self.__format_query__(query) # Search for a user with this 'user_identifier' - response = self.read(url, args, raw=raw) + response = self.read(url, args=args, headers=headers, raw=raw) """ print("Debug: users.py POST") print(url) @@ -138,11 +139,8 @@ def post(self, identifier, id_type, user_data={}, raw=False): """ if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. - args.clear() - args['apikey'] = self.cnxn_params['api_key'] # 'user_identifier' chunk - """ aux_dict = {} aux_dict['value'] = identifier @@ -152,11 +150,10 @@ def post(self, identifier, id_type, user_data={}, raw=False): aux_dict['segment_type'] = 'External' user_data['user_identifier'] = [ aux_dict ] """ - aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" user_data = loads(aux_dict.replace("'", "\"")) - response = self.create(url, user_data, args, raw=raw) + response = self.create(url, data=user_data, args=args, headers=headers, raw=raw) else: # User already exist in Alma. response = {'total_record_count': 0} @@ -191,8 +188,10 @@ def get(self, user_id, loan_id=None, limit=10, offset=0, List of loans or a specific loan for a given user. """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] url = self.cnxn_params['api_uri_full'] url += (str(user_id) + "/loans") @@ -209,13 +208,13 @@ def get(self, user_id, loan_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.read(url, args=args, headers=headers, raw=raw) if loan_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='item_loan') return response From f05fcd27e0b8add1044eb4897a6bb53341d8f65b Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 21 Nov 2018 12:35:55 +0100 Subject: [PATCH 22/47] Avoid sending 'API Key' in URL, but in Headers --- almapipy/client.py | 31 +++++++++++++++++-------------- almapipy/users.py | 35 +++++++++++++++++------------------ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index ca8210e..f2be65e 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -22,7 +22,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params # def create(self, url, data, args, object_type, raw=False): - def create(self, url, data, args, raw=False): + def create(self, url, data, args, headers, raw=False): """ Uses requests library to make Exlibris API Post call. Returns data of type specified during init of base class. @@ -31,6 +31,7 @@ def create(self, url, data, args, raw=False): url (str): Exlibris API endpoint url. data (dict): Data to be posted. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. # object_type (str): Type of object to be posted (see alma docs) raw (bool): If true, returns raw response. @@ -40,7 +41,6 @@ def create(self, url, data, args, raw=False): # Determine format of data to be posted according to order of importance: # 1) Local declaration, 2) dtype of data parameter, 3) global setting. - headers = {} if 'format' not in args.keys(): if type(data) == ET or type(data) == ET.Element: content_type = 'xml' @@ -52,13 +52,14 @@ def create(self, url, data, args, raw=False): else: content_type = args['format'] - # Declare data type in header, convert to string if necessary. + # Preserve Auth, declare data type in header, convert to string if necessary. + headers_aux = headers.copy() if content_type == 'json': - headers['content-type'] = 'application/json' + headers_aux['content-type'] = 'application/json' if type(data) != str: data = json.dumps(data) elif content_type == 'xml': - headers['content-type'] = 'application/xml' + headers_aux['content-type'] = 'application/xml' if type(data) == ET or type(data) == ET.Element: data = ET.tostring(data, encoding='unicode') elif type(data) != str: @@ -72,10 +73,10 @@ def create(self, url, data, args, raw=False): print(url) print(data) print(args) - print(headers) + print(headers_aux) """ # Send request - response = requests.post(url, data=data, params=args, headers=headers) + response = requests.post(url, data=data, params=args, headers=headers_aux) if raw: return response @@ -84,15 +85,15 @@ def create(self, url, data, args, raw=False): return content - def delete(self, url, data, args, raw=False): + def delete(self, url, args, headers, raw=False): """ Uses requests library to make Exlibris API Delete call. Returns data of type specified during init of base class. Args: url (str): Exlibris API endpoint url. - data (dict): Data to be deleted.. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. raw (bool): If true, returns raw response. Returns: @@ -100,7 +101,7 @@ def delete(self, url, data, args, raw=False): """ # Send request - response = requests.delete(url, headers=args) + response = requests.delete(url, params=args, headers=headers) if raw: return response @@ -109,7 +110,7 @@ def delete(self, url, data, args, raw=False): return content - def read(self, url, args, raw=False): + def read(self, url, args, headers, raw=False): """ Uses requests library to make Exlibris API Get call. Returns data of type specified during init of base class. @@ -117,6 +118,7 @@ def read(self, url, args, raw=False): Args: url (str): Exlibris API endpoint url. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. raw (bool): If true, returns raw response. Returns: @@ -131,7 +133,7 @@ def read(self, url, args, raw=False): data_format = args['format'] # Send request. - response = requests.get(url, params=args) + response = requests.get(url, params=args, headers=headers) if raw: return response @@ -165,13 +167,14 @@ def __format_query__(self, query): return q_str - def __read_all__(self, url, args, raw, response, data_key, max_limit=100): + def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=100): """Makes multiple API calls until all records for a query are retrieved. Called by the 'all_records' parameter. Args: url (str): Exlibris API endpoint url. args (dict): Query string parameters for API call. + headers (dict): API Key Auth in Headers. raw (bool): If true, returns raw response. response (xml, raw, or json): First API call. data_key (str): Dictionary key for accessing data. @@ -207,7 +210,7 @@ def __read_all__(self, url, args, raw, response, data_key, max_limit=100): break # make call and increment counter variables - new_response = self.read(url, args, raw=raw) + new_response = self.read(url, args=args, headers=headers, raw=raw) records_retrieved += limit args['offset'] += limit diff --git a/almapipy/users.py b/almapipy/users.py index 55210a9..1c4988c 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -55,8 +55,10 @@ def get(self, user_id=None, query={}, limit=10, offset=0, List of user or a specific user's details. """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] url = self.cnxn_params['api_uri_full'] if user_id: @@ -76,7 +78,7 @@ def get(self, user_id=None, query={}, limit=10, offset=0, if query: args['q'] = self.__format_query__(query) - response = self.read(url, args, raw=raw) + response = self.read(url, args=args, headers=headers, raw=raw) if user_id: return response """ @@ -87,12 +89,12 @@ def get(self, user_id=None, query={}, limit=10, offset=0, """ # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='user') return response - def post(self, identifier, id_type, user_data={}, raw=False): + def post(self, identifier, id_type, user_data, raw=False): """Create a single user if it does not exists yet in Alma Args: @@ -117,19 +119,18 @@ def post(self, identifier, id_type, user_data={}, raw=False): """ - args = {} - args['id_type'] = id_type - args['apikey'] = self.cnxn_params['api_key'] + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + args = {'id_type': '{}'.format(id_type)} url = self.cnxn_params['api_uri_full'] # Search query for the 'identifier' in Alma - query = {} - query['identifiers'] = identifier + query = {'identifiers': '{}'.format(identifier)} args['q'] = self.__format_query__(query) # Search for a user with this 'user_identifier' - response = self.read(url, args, raw=raw) + response = self.read(url, args=args, headers=headers, raw=raw) """ print("Debug: users.py POST") print(url) @@ -138,11 +139,8 @@ def post(self, identifier, id_type, user_data={}, raw=False): """ if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. - args.clear() - args['apikey'] = self.cnxn_params['api_key'] # 'user_identifier' chunk - """ aux_dict = {} aux_dict['value'] = identifier @@ -152,11 +150,10 @@ def post(self, identifier, id_type, user_data={}, raw=False): aux_dict['segment_type'] = 'External' user_data['user_identifier'] = [ aux_dict ] """ - aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" user_data = loads(aux_dict.replace("'", "\"")) - response = self.create(url, user_data, args, raw=raw) + response = self.create(url, data=user_data, args=args, headers=headers, raw=raw) else: # User already exist in Alma. response = {'total_record_count': 0} @@ -191,8 +188,10 @@ def get(self, user_id, loan_id=None, limit=10, offset=0, List of loans or a specific loan for a given user. """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + args = q_params.copy() - args['apikey'] = self.cnxn_params['api_key'] url = self.cnxn_params['api_uri_full'] url += (str(user_id) + "/loans") @@ -209,13 +208,13 @@ def get(self, user_id, loan_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.read(url, args=args, headers=headers, raw=raw) if loan_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='item_loan') return response From ddef942ff3f109c20325d6fa5aa5e506b7f018ff Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 21 Nov 2018 15:22:45 +0100 Subject: [PATCH 23/47] Include 'User-Agent' in Headers --- almapipy/__init__.py | 6 +++++- almapipy/client.py | 29 ++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index f85186a..f8e00bf 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -4,7 +4,9 @@ Python client for Ex Libris Alma """ __author__ = "Steve Pelkey (spelkey@ucdavis.edu)" -__version__ = "0.0.9" +__name__ = "almapipy" +__version__ = "1.0.1" + import os @@ -68,6 +70,8 @@ def __init__(self, apikey, location='America', data_format='json'): # call __validate_key__ self.cnxn_params['api_key'] = apikey + self.cnxn_params['User-Agent'] = '{}/{}'.format(__name__,__version__) + # Hook in the various Alma APIs based on what API key can access self.bibs = SubClientBibs(self.cnxn_params) self.analytics = SubClientAnalytics(self.cnxn_params) diff --git a/almapipy/client.py b/almapipy/client.py index f2be65e..9229c1e 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -52,8 +52,8 @@ def create(self, url, data, args, headers, raw=False): else: content_type = args['format'] - # Preserve Auth, declare data type in header, convert to string if necessary. - headers_aux = headers.copy() + # Preserve User-Agent, Auth, declare data type in header, convert to string if necessary. + headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) if content_type == 'json': headers_aux['content-type'] = 'application/json' if type(data) != str: @@ -100,8 +100,16 @@ def delete(self, url, args, headers, raw=False): JSON-esque, xml, or raw response. """ + # Preserve User-Agent and Auth in headers + headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + """ + print("Debug: client.py delete") + print(url) + print(args) + print(headers_aux) + """ # Send request - response = requests.delete(url, params=args, headers=headers) + response = requests.delete(url, params=args, headers=headers_aux) if raw: return response @@ -132,8 +140,16 @@ def read(self, url, args, headers, raw=False): args['format'] = data_format data_format = args['format'] + # Preserve User-Agent and Auth in headers + headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + """ + print("Debug: client.py read") + print(url) + print(args) + print(headers_aux) + """ # Send request. - response = requests.get(url, params=args, headers=headers) + response = requests.get(url, params=args, headers=headers_aux) if raw: return response @@ -205,12 +221,15 @@ def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=10 args['limit'] = max_limit limit = max_limit + # Preserve User-Agent and Auth in headers + headers_aux = dict(__headers__, headers) + while True: if total_records <= records_retrieved: break # make call and increment counter variables - new_response = self.read(url, args=args, headers=headers, raw=raw) + new_response = self.read(url, args=args, headers=headers_aux, raw=raw) records_retrieved += limit args['offset'] += limit From b93010423f7f3e400f35e5ca55b0decc55dee36b Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 21 Nov 2018 15:22:45 +0100 Subject: [PATCH 24/47] Include 'User-Agent' in Headers --- almapipy/__init__.py | 6 +++++- almapipy/client.py | 29 ++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index f85186a..f8e00bf 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -4,7 +4,9 @@ Python client for Ex Libris Alma """ __author__ = "Steve Pelkey (spelkey@ucdavis.edu)" -__version__ = "0.0.9" +__name__ = "almapipy" +__version__ = "1.0.1" + import os @@ -68,6 +70,8 @@ def __init__(self, apikey, location='America', data_format='json'): # call __validate_key__ self.cnxn_params['api_key'] = apikey + self.cnxn_params['User-Agent'] = '{}/{}'.format(__name__,__version__) + # Hook in the various Alma APIs based on what API key can access self.bibs = SubClientBibs(self.cnxn_params) self.analytics = SubClientAnalytics(self.cnxn_params) diff --git a/almapipy/client.py b/almapipy/client.py index f2be65e..9229c1e 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -52,8 +52,8 @@ def create(self, url, data, args, headers, raw=False): else: content_type = args['format'] - # Preserve Auth, declare data type in header, convert to string if necessary. - headers_aux = headers.copy() + # Preserve User-Agent, Auth, declare data type in header, convert to string if necessary. + headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) if content_type == 'json': headers_aux['content-type'] = 'application/json' if type(data) != str: @@ -100,8 +100,16 @@ def delete(self, url, args, headers, raw=False): JSON-esque, xml, or raw response. """ + # Preserve User-Agent and Auth in headers + headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + """ + print("Debug: client.py delete") + print(url) + print(args) + print(headers_aux) + """ # Send request - response = requests.delete(url, params=args, headers=headers) + response = requests.delete(url, params=args, headers=headers_aux) if raw: return response @@ -132,8 +140,16 @@ def read(self, url, args, headers, raw=False): args['format'] = data_format data_format = args['format'] + # Preserve User-Agent and Auth in headers + headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + """ + print("Debug: client.py read") + print(url) + print(args) + print(headers_aux) + """ # Send request. - response = requests.get(url, params=args, headers=headers) + response = requests.get(url, params=args, headers=headers_aux) if raw: return response @@ -205,12 +221,15 @@ def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=10 args['limit'] = max_limit limit = max_limit + # Preserve User-Agent and Auth in headers + headers_aux = dict(__headers__, headers) + while True: if total_records <= records_retrieved: break # make call and increment counter variables - new_response = self.read(url, args=args, headers=headers, raw=raw) + new_response = self.read(url, args=args, headers=headers_aux, raw=raw) records_retrieved += limit args['offset'] += limit From 7552070e2f0b80584ea43073e3187d1f71b115c1 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sun, 25 Nov 2018 18:48:16 +0100 Subject: [PATCH 25/47] Changed some functions names - Be carefull --- almapipy/conf.py | 74 ++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/almapipy/conf.py b/almapipy/conf.py index 7aa6452..cd45884 100644 --- a/almapipy/conf.py +++ b/almapipy/conf.py @@ -39,7 +39,7 @@ class SubClientConfigurationUnits(Client): def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params.copy() - def get_libaries(self, library_id=None, q_params={}, raw=False): + def retrieve_libaries(self, library_id=None, q_params={}, raw=False): """Retrieve a list of libraries or a specific library Args: @@ -59,10 +59,10 @@ def get_libaries(self, library_id=None, q_params={}, raw=False): if library_id: url += ("/" + str(library_id)) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_locations(self, library_id, location_id=None, q_params={}, raw=False): + def retrieve_locations(self, library_id, location_id=None, q_params={}, raw=False): """Retrieve a list of locations for a library Args: @@ -83,10 +83,10 @@ def get_locations(self, library_id, location_id=None, q_params={}, raw=False): if location_id: url += ("/" + str(location_id)) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_departments(self, q_params={}, raw=False): + def retrieve_departments(self, q_params={}, raw=False): """Retrieve a list of configured departments Args: @@ -103,7 +103,7 @@ def get_departments(self, q_params={}, raw=False): url = self.cnxn_params['api_uri_full'] url += '/departments' - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response @@ -113,7 +113,7 @@ class SubClientConfigurationGeneral(Client): def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params.copy() - def get(self, library_id=None, q_params={}, raw=False): + def retrieve(self, library_id=None, q_params={}, raw=False): """Retrieve general configuration of the institution Args: @@ -131,10 +131,10 @@ def get(self, library_id=None, q_params={}, raw=False): url = self.cnxn_params['api_uri_full'] url += "/general" - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_hours(self, library_id=None, q_params={}, raw=False): + def retrieve_hours(self, library_id=None, q_params={}, raw=False): """Retrieve open hours as configured in Alma. Note that the library-hours do not necessarily reflect when the library doors are actually open, but rather start and end times that @@ -162,10 +162,10 @@ def get_hours(self, library_id=None, q_params={}, raw=False): url += '/open-hours' - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_code_table(self, table_name, q_params={}, raw=False): + def retrieve_code_table(self, table_name, q_params={}, raw=False): """This API returns all rows defined for a code-table. The main usage of this API is for applications that use Alma APIs, @@ -176,7 +176,7 @@ def get_code_table(self, table_name, q_params={}, raw=False): Args: table_name (str): Code table name. (codeTableName) q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. + raw (bool): If true, returns raw requests object. Returns: Code-table rows @@ -188,7 +188,7 @@ def get_code_table(self, table_name, q_params={}, raw=False): url = self.cnxn_params['api_uri_full'] url += ('/code-tables/' + str(table_name)) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response @@ -200,7 +200,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/jobs' self.cnxn_params['api_uri_full'] += '/jobs' - def get(self, job_id=None, limit=10, offset=0, all_records=False, + def retrieve(self, job_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of jobs that can be submitted or details for a given job. @@ -236,19 +236,19 @@ def get(self, job_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if job_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='job') return response - def get_instances(self, job_id, instance_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): + def retrieve_instances(self, job_id, instance_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): """Retrieve all the job instances (runs) for a given job id, or specific instance. Args: @@ -285,14 +285,14 @@ def get_instances(self, job_id, instance_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if instance_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='job_instance') return response @@ -308,7 +308,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/sets' self.cnxn_params['api_uri_full'] += '/sets' - def get(self, set_id=None, content_type=None, set_type=None, + def retrieve(self, set_id=None, content_type=None, set_type=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of sets or a single set. @@ -356,17 +356,17 @@ def get(self, set_id=None, content_type=None, set_type=None, # add search query if specified in desired format if query: args['q'] = self.__format_query__(query) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if set_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='set') return response - def get_members(self, set_id, limit=10, offset=0, all_records=False, + def retrieve_members(self, set_id, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves members of a Set given a Set ID. @@ -399,11 +399,11 @@ def get_members(self, set_id, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='member') return response @@ -416,7 +416,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/deposit-profiles' self.cnxn_params['api_uri_full'] += '/deposit-profiles' - def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, + def retrieve(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves list of deposit profiles or specific profile @@ -450,14 +450,14 @@ def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if deposit_profile_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='deposit_profile') @@ -469,8 +469,8 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/md-import-profiles' self.cnxn_params['api_uri_full'] += '/md-import-profiles' - def get(self, profile_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): + def retrieve(self, profile_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): """Retrieves list of import profiles or specific profile Args: @@ -503,14 +503,14 @@ def get(self, profile_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if profile_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='import_profile') return response @@ -523,8 +523,8 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/reminders' self.cnxn_params['api_uri_full'] += '/reminders' - def get(self, reminder_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): + def retrieve(self, reminder_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): """Retrieves list of reminders or specific reminder. Args: @@ -557,13 +557,13 @@ def get(self, reminder_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if reminder_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='reminder') return response From 444fd13dcc84902f1662b5adf5f54aa093836af0 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sun, 25 Nov 2018 18:48:16 +0100 Subject: [PATCH 26/47] Changed some functions names - Be carefull --- almapipy/conf.py | 74 ++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/almapipy/conf.py b/almapipy/conf.py index 7aa6452..cd45884 100644 --- a/almapipy/conf.py +++ b/almapipy/conf.py @@ -39,7 +39,7 @@ class SubClientConfigurationUnits(Client): def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params.copy() - def get_libaries(self, library_id=None, q_params={}, raw=False): + def retrieve_libaries(self, library_id=None, q_params={}, raw=False): """Retrieve a list of libraries or a specific library Args: @@ -59,10 +59,10 @@ def get_libaries(self, library_id=None, q_params={}, raw=False): if library_id: url += ("/" + str(library_id)) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_locations(self, library_id, location_id=None, q_params={}, raw=False): + def retrieve_locations(self, library_id, location_id=None, q_params={}, raw=False): """Retrieve a list of locations for a library Args: @@ -83,10 +83,10 @@ def get_locations(self, library_id, location_id=None, q_params={}, raw=False): if location_id: url += ("/" + str(location_id)) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_departments(self, q_params={}, raw=False): + def retrieve_departments(self, q_params={}, raw=False): """Retrieve a list of configured departments Args: @@ -103,7 +103,7 @@ def get_departments(self, q_params={}, raw=False): url = self.cnxn_params['api_uri_full'] url += '/departments' - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response @@ -113,7 +113,7 @@ class SubClientConfigurationGeneral(Client): def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params.copy() - def get(self, library_id=None, q_params={}, raw=False): + def retrieve(self, library_id=None, q_params={}, raw=False): """Retrieve general configuration of the institution Args: @@ -131,10 +131,10 @@ def get(self, library_id=None, q_params={}, raw=False): url = self.cnxn_params['api_uri_full'] url += "/general" - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_hours(self, library_id=None, q_params={}, raw=False): + def retrieve_hours(self, library_id=None, q_params={}, raw=False): """Retrieve open hours as configured in Alma. Note that the library-hours do not necessarily reflect when the library doors are actually open, but rather start and end times that @@ -162,10 +162,10 @@ def get_hours(self, library_id=None, q_params={}, raw=False): url += '/open-hours' - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response - def get_code_table(self, table_name, q_params={}, raw=False): + def retrieve_code_table(self, table_name, q_params={}, raw=False): """This API returns all rows defined for a code-table. The main usage of this API is for applications that use Alma APIs, @@ -176,7 +176,7 @@ def get_code_table(self, table_name, q_params={}, raw=False): Args: table_name (str): Code table name. (codeTableName) q_params (dict): Any additional query parameters. - raw (bool): If true, returns raw requests object. + raw (bool): If true, returns raw requests object. Returns: Code-table rows @@ -188,7 +188,7 @@ def get_code_table(self, table_name, q_params={}, raw=False): url = self.cnxn_params['api_uri_full'] url += ('/code-tables/' + str(table_name)) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) return response @@ -200,7 +200,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/jobs' self.cnxn_params['api_uri_full'] += '/jobs' - def get(self, job_id=None, limit=10, offset=0, all_records=False, + def retrieve(self, job_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of jobs that can be submitted or details for a given job. @@ -236,19 +236,19 @@ def get(self, job_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if job_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='job') return response - def get_instances(self, job_id, instance_id=None, limit=10, offset=0, - all_records=False, q_params={}, raw=False): + def retrieve_instances(self, job_id, instance_id=None, limit=10, offset=0, + all_records=False, q_params={}, raw=False): """Retrieve all the job instances (runs) for a given job id, or specific instance. Args: @@ -285,14 +285,14 @@ def get_instances(self, job_id, instance_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if instance_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='job_instance') return response @@ -308,7 +308,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/sets' self.cnxn_params['api_uri_full'] += '/sets' - def get(self, set_id=None, content_type=None, set_type=None, + def retrieve(self, set_id=None, content_type=None, set_type=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of sets or a single set. @@ -356,17 +356,17 @@ def get(self, set_id=None, content_type=None, set_type=None, # add search query if specified in desired format if query: args['q'] = self.__format_query__(query) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if set_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='set') return response - def get_members(self, set_id, limit=10, offset=0, all_records=False, + def retrieve_members(self, set_id, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves members of a Set given a Set ID. @@ -399,11 +399,11 @@ def get_members(self, set_id, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='member') return response @@ -416,7 +416,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/deposit-profiles' self.cnxn_params['api_uri_full'] += '/deposit-profiles' - def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, + def retrieve(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves list of deposit profiles or specific profile @@ -450,14 +450,14 @@ def get(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if deposit_profile_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='deposit_profile') @@ -469,8 +469,8 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/md-import-profiles' self.cnxn_params['api_uri_full'] += '/md-import-profiles' - def get(self, profile_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): + def retrieve(self, profile_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): """Retrieves list of import profiles or specific profile Args: @@ -503,14 +503,14 @@ def get(self, profile_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if profile_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='import_profile') return response @@ -523,8 +523,8 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/reminders' self.cnxn_params['api_uri_full'] += '/reminders' - def get(self, reminder_id=None, limit=10, offset=0, all_records=False, - q_params={}, raw=False): + def retrieve(self, reminder_id=None, limit=10, offset=0, all_records=False, + q_params={}, raw=False): """Retrieves list of reminders or specific reminder. Args: @@ -557,13 +557,13 @@ def get(self, reminder_id=None, limit=10, offset=0, all_records=False, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if reminder_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='reminder') return response From e01a5ad6c5cf628cf1630ded10493bf77976e435 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sun, 25 Nov 2018 18:51:55 +0100 Subject: [PATCH 27/47] Following with the efforts in POST and DELETE and Changed some functions names - Be carefull --- almapipy/client.py | 45 +++++++----- almapipy/users.py | 174 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 164 insertions(+), 55 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index 9229c1e..c173a52 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -21,8 +21,8 @@ def __init__(self, cnxn_params={}): # instantiate dictionary for storing alma api connection parameters self.cnxn_params = cnxn_params -# def create(self, url, data, args, object_type, raw=False): - def create(self, url, data, args, headers, raw=False): +# def post(self, url, data, args, object_type, raw=False): + def post(self, url, data, args, headers, raw=False): """ Uses requests library to make Exlibris API Post call. Returns data of type specified during init of base class. @@ -52,8 +52,9 @@ def create(self, url, data, args, headers, raw=False): else: content_type = args['format'] - # Preserve User-Agent, Auth, declare data type in header, convert to string if necessary. - headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + # Preserve Auth and add 'User-Agent' and 'content-type' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] if content_type == 'json': headers_aux['content-type'] = 'application/json' if type(data) != str: @@ -69,7 +70,7 @@ def create(self, url, data, args, headers, raw=False): message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) """ - print("Debug: client.py Create") + print("\nDebug: client.py Post #1") print(url) print(data) print(args) @@ -100,16 +101,22 @@ def delete(self, url, args, headers, raw=False): JSON-esque, xml, or raw response. """ - # Preserve User-Agent and Auth in headers - headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) - """ - print("Debug: client.py delete") + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] + + print("\nDebug: client.py Delete #1") print(url) print(args) print(headers_aux) - """ + print("") + # Send request response = requests.delete(url, params=args, headers=headers_aux) + + print(response) + print("") + if raw: return response @@ -118,7 +125,7 @@ def delete(self, url, args, headers, raw=False): return content - def read(self, url, args, headers, raw=False): + def get(self, url, args, headers, raw=False): """ Uses requests library to make Exlibris API Get call. Returns data of type specified during init of base class. @@ -140,10 +147,11 @@ def read(self, url, args, headers, raw=False): args['format'] = data_format data_format = args['format'] - # Preserve User-Agent and Auth in headers - headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] """ - print("Debug: client.py read") + print("\nDebug: client.py Get #1") print(url) print(args) print(headers_aux) @@ -183,7 +191,7 @@ def __format_query__(self, query): return q_str - def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=100): + def __get_all__(self, url, args, headers, raw, response, data_key, max_limit=100): """Makes multiple API calls until all records for a query are retrieved. Called by the 'all_records' parameter. @@ -221,15 +229,16 @@ def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=10 args['limit'] = max_limit limit = max_limit - # Preserve User-Agent and Auth in headers - headers_aux = dict(__headers__, headers) + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] while True: if total_records <= records_retrieved: break # make call and increment counter variables - new_response = self.read(url, args=args, headers=headers_aux, raw=raw) + new_response = self.get(url, args=args, headers=headers_aux, raw=raw) records_retrieved += limit args['offset'] += limit diff --git a/almapipy/users.py b/almapipy/users.py index 1c4988c..188d52c 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -29,12 +29,10 @@ def __init__(self, cnxn_params={}): self.fees = SubClientUsersFees(self.cnxn_params) self.deposits = SubClientUsersDeposits(self.cnxn_params) - def get(self, user_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): + def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a user list or a single user. - Args: - user_id (str): A unique identifier for the user. + Args: user_id (str): A unique identifier for the user. Gets more detailed information. query (dict): Search query for filtering a user list. Optional. Searching for words from fields: [primary_id, first_name, @@ -60,6 +58,17 @@ def get(self, user_id=None, query={}, limit=10, offset=0, args = q_params.copy() + print("\nDebug: users.py Retrieve #1") + print(query) + print(args) + print("") + + # Avoid sending 'primary_id' as 'id_type' + if ('id_type' in args.keys()) and (args['id_type'] == 'primary_id'): + args.pop('id_type', None) + args['primary_id'] = args.pop('identifiers', None) + print(args) + url = self.cnxn_params['api_uri_full'] if user_id: url += ("/" + str(user_id)) @@ -78,24 +87,25 @@ def get(self, user_id=None, query={}, limit=10, offset=0, if query: args['q'] = self.__format_query__(query) - response = self.read(url, args=args, headers=headers, raw=raw) + response = self.get(url, args=args, headers=headers, raw=raw) if user_id: return response - """ - print("Debug: users.py GET") + + print("\nDebug: users.py Retrieve #2") print(url) print(args) + print(headers) print(response) - """ + print("") + # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='user') return response - - def post(self, identifier, id_type, user_data, raw=False): - """Create a single user if it does not exists yet in Alma + def create(self, identifier, id_type, user_data, raw=False): + """Create a single user if it does not exist yet in Alma Args: id_type (str): The identifier type for the user @@ -120,23 +130,34 @@ def post(self, identifier, id_type, user_data, raw=False): """ headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + data=user_data.copy() + + # Avoid sending 'primary_id' as 'id_type' + args = {} + query = {} + if id_type != 'primary_id': + args['id_type'] = id_type + query = {'identifiers': '{}'.format(identifier)} +# TODO: add 'user_identifier' stuff into 'user_data' + else: + query = {'primary_id': '{}'.format(identifier)} + data['primary_id'] = identifier - args = {'id_type': '{}'.format(id_type)} - - url = self.cnxn_params['api_uri_full'] - - # Search query for the 'identifier' in Alma - query = {'identifiers': '{}'.format(identifier)} args['q'] = self.__format_query__(query) + + url = self.cnxn_params['api_uri_full'] # Search for a user with this 'user_identifier' - response = self.read(url, args=args, headers=headers, raw=raw) - """ - print("Debug: users.py POST") + response = self.get(url, args=args, headers=headers, raw=raw) + + print("\nDebug: users.py Create #1") + print(headers) print(url) print(args) print(response) - """ + print("") + if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. @@ -148,18 +169,97 @@ def post(self, identifier, id_type, user_data, raw=False): aux_dict['id_type']['value'] = id_type aux_dict['status'] = 'ACTIVE' aux_dict['segment_type'] = 'External' - user_data['user_identifier'] = [ aux_dict ] + data['user_identifier'] = [ aux_dict ] """ - aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" - user_data = loads(aux_dict.replace("'", "\"")) +# aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" +# data = loads(aux_dict.replace("'", "\"")) + + args.pop('q', None) + + print("\nDebug: users.py Create #2") + print(headers) + print(url) + print(args) + print(data) + + response = self.post(url, data=data, args=args, headers=headers, raw=raw) + + print(response) + print("") - response = self.create(url, data=user_data, args=args, headers=headers, raw=raw) else: # User already exist in Alma. response = {'total_record_count': 0} return response + def remove(self, identifier, id_type, raw=False): + """Remove a single user if it does exist in Alma + + Args: + id_type (str): The identifier type for the user + Values: from the code-table: UserIdentifierTypes + + See: + identifier (str): The identifier itself for the user. + See: + raw (bool): If true, returns raw requests object. + + Returns: (?) + The user (at Alma) if a new user is removed. + "{'total_record_count': 0}" if the 'identifier' is not present in Alma. + + """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + # Avoid sending 'primary_id' as 'id_type' + args = {} + query = {} + if id_type != 'primary_id': + args['id_type'] = id_type + query = {'identifiers': '{}'.format(identifier)} +# TODO: add 'user_identifier' stuff into 'user_data' + else: + query = {'primary_id': '{}'.format(identifier)} + + args['q'] = self.__format_query__(query) + + url = self.cnxn_params['api_uri_full'] + + # Search for a user with this 'user_identifier' + response = self.get(url, args=args, headers=headers, raw=raw) + + print("\nDebug: users.py Delete #1") + print(headers) + print(url) + print(args) + print(response) + print("") + + if response['total_record_count'] == 1: + # A single user exists with this 'identifier': Let's remove it. + args.clear() + args['primary_id'] = response['user'][0]['primary_id'] + url += (str('/' + args['primary_id'])) + + print("\nDebug: users.py Delete #2") + print(headers) + print(url) + print(args) + + # Send request + response = self.delete(url, args=args, headers=headers, raw=raw) + + print(response) + print("") + + else: + # No a "single" user exists in Alma. + response = response['total_record_count'] + + return response + class SubClientUsersLoans(Client): """Handles the Loans endpoints of Users API""" @@ -169,7 +269,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, loan_id=None, limit=10, offset=0, + def retrieve(self, user_id, loan_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of loans for a user. @@ -208,13 +308,13 @@ def get(self, user_id, loan_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args=args, headers=headers, raw=raw) + response = self.get(url, args=args, headers=headers, raw=raw) if loan_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, + response = self.__get_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='item_loan') return response @@ -227,7 +327,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, request_id=None, limit=10, offset=0, + def retrieve(self, user_id, request_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of requests for a user. @@ -264,13 +364,13 @@ def get(self, user_id, request_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if request_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='user_request') return response @@ -283,7 +383,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, fee_id=None, q_params={}, raw=False): + def retrieve(self, user_id, fee_id=None, q_params={}, raw=False): """Retrieve a list of fines and fees for a user. Args: @@ -305,7 +405,7 @@ def get(self, user_id, fee_id=None, q_params={}, raw=False): args = q_params.copy() args['apikey'] = self.cnxn_params['api_key'] - return self.read(url, args, raw=raw) + return self.get(url, args, raw=raw) class SubClientUsersDeposits(Client): @@ -316,7 +416,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, deposit_id=None, limit=10, offset=0, + def retrieve(self, user_id, deposit_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of deposits for a user. @@ -353,12 +453,12 @@ def get(self, user_id, deposit_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if deposit_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user_deposit') + response = self.__get_all__(url=url, args=args, raw=raw, + response=response, data_key='user_deposit') return response From 0749198b5ce143caeebd611aae9ee8cdb3b0839f Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sun, 25 Nov 2018 18:51:55 +0100 Subject: [PATCH 28/47] Following with the efforts in POST and DELETE and Changed some functions names - Be carefull --- almapipy/client.py | 45 +++++++----- almapipy/users.py | 174 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 164 insertions(+), 55 deletions(-) diff --git a/almapipy/client.py b/almapipy/client.py index 9229c1e..c173a52 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -21,8 +21,8 @@ def __init__(self, cnxn_params={}): # instantiate dictionary for storing alma api connection parameters self.cnxn_params = cnxn_params -# def create(self, url, data, args, object_type, raw=False): - def create(self, url, data, args, headers, raw=False): +# def post(self, url, data, args, object_type, raw=False): + def post(self, url, data, args, headers, raw=False): """ Uses requests library to make Exlibris API Post call. Returns data of type specified during init of base class. @@ -52,8 +52,9 @@ def create(self, url, data, args, headers, raw=False): else: content_type = args['format'] - # Preserve User-Agent, Auth, declare data type in header, convert to string if necessary. - headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + # Preserve Auth and add 'User-Agent' and 'content-type' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] if content_type == 'json': headers_aux['content-type'] = 'application/json' if type(data) != str: @@ -69,7 +70,7 @@ def create(self, url, data, args, headers, raw=False): message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) """ - print("Debug: client.py Create") + print("\nDebug: client.py Post #1") print(url) print(data) print(args) @@ -100,16 +101,22 @@ def delete(self, url, args, headers, raw=False): JSON-esque, xml, or raw response. """ - # Preserve User-Agent and Auth in headers - headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) - """ - print("Debug: client.py delete") + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] + + print("\nDebug: client.py Delete #1") print(url) print(args) print(headers_aux) - """ + print("") + # Send request response = requests.delete(url, params=args, headers=headers_aux) + + print(response) + print("") + if raw: return response @@ -118,7 +125,7 @@ def delete(self, url, args, headers, raw=False): return content - def read(self, url, args, headers, raw=False): + def get(self, url, args, headers, raw=False): """ Uses requests library to make Exlibris API Get call. Returns data of type specified during init of base class. @@ -140,10 +147,11 @@ def read(self, url, args, headers, raw=False): args['format'] = data_format data_format = args['format'] - # Preserve User-Agent and Auth in headers - headers_aux = dict({'User-Agent': '{}'.format(self.cnxn_params['User-Agent'])}, **headers) + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] """ - print("Debug: client.py read") + print("\nDebug: client.py Get #1") print(url) print(args) print(headers_aux) @@ -183,7 +191,7 @@ def __format_query__(self, query): return q_str - def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=100): + def __get_all__(self, url, args, headers, raw, response, data_key, max_limit=100): """Makes multiple API calls until all records for a query are retrieved. Called by the 'all_records' parameter. @@ -221,15 +229,16 @@ def __read_all__(self, url, args, headers, raw, response, data_key, max_limit=10 args['limit'] = max_limit limit = max_limit - # Preserve User-Agent and Auth in headers - headers_aux = dict(__headers__, headers) + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] while True: if total_records <= records_retrieved: break # make call and increment counter variables - new_response = self.read(url, args=args, headers=headers_aux, raw=raw) + new_response = self.get(url, args=args, headers=headers_aux, raw=raw) records_retrieved += limit args['offset'] += limit diff --git a/almapipy/users.py b/almapipy/users.py index 1c4988c..188d52c 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -29,12 +29,10 @@ def __init__(self, cnxn_params={}): self.fees = SubClientUsersFees(self.cnxn_params) self.deposits = SubClientUsersDeposits(self.cnxn_params) - def get(self, user_id=None, query={}, limit=10, offset=0, - all_records=False, q_params={}, raw=False): + def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a user list or a single user. - Args: - user_id (str): A unique identifier for the user. + Args: user_id (str): A unique identifier for the user. Gets more detailed information. query (dict): Search query for filtering a user list. Optional. Searching for words from fields: [primary_id, first_name, @@ -60,6 +58,17 @@ def get(self, user_id=None, query={}, limit=10, offset=0, args = q_params.copy() + print("\nDebug: users.py Retrieve #1") + print(query) + print(args) + print("") + + # Avoid sending 'primary_id' as 'id_type' + if ('id_type' in args.keys()) and (args['id_type'] == 'primary_id'): + args.pop('id_type', None) + args['primary_id'] = args.pop('identifiers', None) + print(args) + url = self.cnxn_params['api_uri_full'] if user_id: url += ("/" + str(user_id)) @@ -78,24 +87,25 @@ def get(self, user_id=None, query={}, limit=10, offset=0, if query: args['q'] = self.__format_query__(query) - response = self.read(url, args=args, headers=headers, raw=raw) + response = self.get(url, args=args, headers=headers, raw=raw) if user_id: return response - """ - print("Debug: users.py GET") + + print("\nDebug: users.py Retrieve #2") print(url) print(args) + print(headers) print(response) - """ + print("") + # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='user') return response - - def post(self, identifier, id_type, user_data, raw=False): - """Create a single user if it does not exists yet in Alma + def create(self, identifier, id_type, user_data, raw=False): + """Create a single user if it does not exist yet in Alma Args: id_type (str): The identifier type for the user @@ -120,23 +130,34 @@ def post(self, identifier, id_type, user_data, raw=False): """ headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + data=user_data.copy() + + # Avoid sending 'primary_id' as 'id_type' + args = {} + query = {} + if id_type != 'primary_id': + args['id_type'] = id_type + query = {'identifiers': '{}'.format(identifier)} +# TODO: add 'user_identifier' stuff into 'user_data' + else: + query = {'primary_id': '{}'.format(identifier)} + data['primary_id'] = identifier - args = {'id_type': '{}'.format(id_type)} - - url = self.cnxn_params['api_uri_full'] - - # Search query for the 'identifier' in Alma - query = {'identifiers': '{}'.format(identifier)} args['q'] = self.__format_query__(query) + + url = self.cnxn_params['api_uri_full'] # Search for a user with this 'user_identifier' - response = self.read(url, args=args, headers=headers, raw=raw) - """ - print("Debug: users.py POST") + response = self.get(url, args=args, headers=headers, raw=raw) + + print("\nDebug: users.py Create #1") + print(headers) print(url) print(args) print(response) - """ + print("") + if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. @@ -148,18 +169,97 @@ def post(self, identifier, id_type, user_data, raw=False): aux_dict['id_type']['value'] = id_type aux_dict['status'] = 'ACTIVE' aux_dict['segment_type'] = 'External' - user_data['user_identifier'] = [ aux_dict ] + data['user_identifier'] = [ aux_dict ] """ - aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" - user_data = loads(aux_dict.replace("'", "\"")) +# aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" +# data = loads(aux_dict.replace("'", "\"")) + + args.pop('q', None) + + print("\nDebug: users.py Create #2") + print(headers) + print(url) + print(args) + print(data) + + response = self.post(url, data=data, args=args, headers=headers, raw=raw) + + print(response) + print("") - response = self.create(url, data=user_data, args=args, headers=headers, raw=raw) else: # User already exist in Alma. response = {'total_record_count': 0} return response + def remove(self, identifier, id_type, raw=False): + """Remove a single user if it does exist in Alma + + Args: + id_type (str): The identifier type for the user + Values: from the code-table: UserIdentifierTypes + + See: + identifier (str): The identifier itself for the user. + See: + raw (bool): If true, returns raw requests object. + + Returns: (?) + The user (at Alma) if a new user is removed. + "{'total_record_count': 0}" if the 'identifier' is not present in Alma. + + """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + # Avoid sending 'primary_id' as 'id_type' + args = {} + query = {} + if id_type != 'primary_id': + args['id_type'] = id_type + query = {'identifiers': '{}'.format(identifier)} +# TODO: add 'user_identifier' stuff into 'user_data' + else: + query = {'primary_id': '{}'.format(identifier)} + + args['q'] = self.__format_query__(query) + + url = self.cnxn_params['api_uri_full'] + + # Search for a user with this 'user_identifier' + response = self.get(url, args=args, headers=headers, raw=raw) + + print("\nDebug: users.py Delete #1") + print(headers) + print(url) + print(args) + print(response) + print("") + + if response['total_record_count'] == 1: + # A single user exists with this 'identifier': Let's remove it. + args.clear() + args['primary_id'] = response['user'][0]['primary_id'] + url += (str('/' + args['primary_id'])) + + print("\nDebug: users.py Delete #2") + print(headers) + print(url) + print(args) + + # Send request + response = self.delete(url, args=args, headers=headers, raw=raw) + + print(response) + print("") + + else: + # No a "single" user exists in Alma. + response = response['total_record_count'] + + return response + class SubClientUsersLoans(Client): """Handles the Loans endpoints of Users API""" @@ -169,7 +269,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, loan_id=None, limit=10, offset=0, + def retrieve(self, user_id, loan_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of loans for a user. @@ -208,13 +308,13 @@ def get(self, user_id, loan_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args=args, headers=headers, raw=raw) + response = self.get(url, args=args, headers=headers, raw=raw) if loan_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, + response = self.__get_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='item_loan') return response @@ -227,7 +327,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, request_id=None, limit=10, offset=0, + def retrieve(self, user_id, request_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of requests for a user. @@ -264,13 +364,13 @@ def get(self, user_id, request_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if request_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, + response = self.__get_all__(url=url, args=args, raw=raw, response=response, data_key='user_request') return response @@ -283,7 +383,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, fee_id=None, q_params={}, raw=False): + def retrieve(self, user_id, fee_id=None, q_params={}, raw=False): """Retrieve a list of fines and fees for a user. Args: @@ -305,7 +405,7 @@ def get(self, user_id, fee_id=None, q_params={}, raw=False): args = q_params.copy() args['apikey'] = self.cnxn_params['api_key'] - return self.read(url, args, raw=raw) + return self.get(url, args, raw=raw) class SubClientUsersDeposits(Client): @@ -316,7 +416,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def get(self, user_id, deposit_id=None, limit=10, offset=0, + def retrieve(self, user_id, deposit_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of deposits for a user. @@ -353,12 +453,12 @@ def get(self, user_id, deposit_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.read(url, args, raw=raw) + response = self.get(url, args, raw=raw) if deposit_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__read_all__(url=url, args=args, raw=raw, - response=response, data_key='user_deposit') + response = self.__get_all__(url=url, args=args, raw=raw, + response=response, data_key='user_deposit') return response From 87993400588fea2298797687b6f18eee855f0550 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sun, 25 Nov 2018 23:06:00 +0100 Subject: [PATCH 29/47] Remove intruder directory --- almapipy/__pycache__/__init__.cpython-37.pyc | Bin 2837 -> 0 bytes .../__pycache__/acquisitions.cpython-37.pyc | Bin 15333 -> 0 bytes almapipy/__pycache__/analytics.cpython-37.pyc | Bin 5845 -> 0 bytes almapipy/__pycache__/bibs.cpython-37.pyc | Bin 17436 -> 0 bytes almapipy/__pycache__/client.cpython-37.pyc | Bin 6217 -> 0 bytes almapipy/__pycache__/conf.cpython-37.pyc | Bin 17458 -> 0 bytes almapipy/__pycache__/courses.cpython-37.pyc | Bin 8335 -> 0 bytes almapipy/__pycache__/electronic.cpython-37.pyc | Bin 6000 -> 0 bytes almapipy/__pycache__/partners.cpython-37.pyc | Bin 3601 -> 0 bytes almapipy/__pycache__/task_lists.cpython-37.pyc | Bin 4311 -> 0 bytes almapipy/__pycache__/users.cpython-37.pyc | Bin 10320 -> 0 bytes almapipy/__pycache__/utils.cpython-37.pyc | Bin 1196 -> 0 bytes 12 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 almapipy/__pycache__/__init__.cpython-37.pyc delete mode 100644 almapipy/__pycache__/acquisitions.cpython-37.pyc delete mode 100644 almapipy/__pycache__/analytics.cpython-37.pyc delete mode 100644 almapipy/__pycache__/bibs.cpython-37.pyc delete mode 100644 almapipy/__pycache__/client.cpython-37.pyc delete mode 100644 almapipy/__pycache__/conf.cpython-37.pyc delete mode 100644 almapipy/__pycache__/courses.cpython-37.pyc delete mode 100644 almapipy/__pycache__/electronic.cpython-37.pyc delete mode 100644 almapipy/__pycache__/partners.cpython-37.pyc delete mode 100644 almapipy/__pycache__/task_lists.cpython-37.pyc delete mode 100644 almapipy/__pycache__/users.cpython-37.pyc delete mode 100644 almapipy/__pycache__/utils.cpython-37.pyc diff --git a/almapipy/__pycache__/__init__.cpython-37.pyc b/almapipy/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index e58046b27acf9ce0779f784040d176d2815b9a7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2837 zcma)8OOM+|5^hSQL_O!}$2f78>mWO$g_p`Lh~XHS%+4A{3}XO$11xhOde|+Ac9WE= zn_f#8Ut$A3+ykS=?3qY!D4B?x|t65p7u9SDV>Zv}N%?ZD%{tj>SW@n_Y@7S-hbxXIG*t z7H_Jn*bm|2kriArT|f=L0v@$=w*dJ3ry z4pqjx%Nq}kd?tevsh-G1aG>WFd|f7jKTCBeMLAk}pNF4^U;cs)U=MzAa2z?S){(V| zXz;K+tscHfPc;g*Yr$d8)xx9+NjGchQBi`HBpcSUziSC3*{vmq$N)C*;8f<4<2z=CbH(XnJVwBiqyUlT z)4&vgD3UV6uv$l0bKC?oW9ItScq}les+glULit?TfKQ<)=V4N0V~%?qb6oR$8^_@^ zvXe3Vc3G6O@vguFii5Q+>-?C2yQBgCsOCx)uBbmnP2y-^xX;bHBhfG4N}pyRx>d z)>FkKpc8XgRHq`_0(TlC|1wLjpc*c9<UF$=LZM1+2gk8-DYQKVUO9TK0YR%!N~_jP zav@=PK(WBGL)B$nZ^k8L<0)1#;A)I-ol2hTsD&uT|Mt45Tw8g%#6kr_+KnH_X`Y%m zUb$FtH0DQ<=73F^MLt%GV}JspUV^15o^#-t2Fjh4OXUbWf*tf#)pUSTY1K}ub_0}L zSnR3MfR8w8^5iLWNa`loRDeDLRz8}wT?Bgc*tLmn`v>%`z+mTy6}ApNYOh=@1d~+@ z&w{&!ZB&SQ7fMakS^HhX7QNlD&8!p0iQ-zv@z3l((b)FB@gyli>d?m*a zyuKG2yECBwxsrN(2+1scR*b9iVoUivoiE;jwFATX0I|#@AyvN zah+`(yUwubBktnQce-x3*+=Qc-*Ri+)xx#)1iPEP=$}3bj_}qL3!9$K1NakShg@C0FM&?Ks9>8OPQga6pR>&LfaWrEsF&+Q@;=$U5!N67n(vO+4~9+{SQZWuuSa zCQ4{UUgGx^;EFvw4F(B26FxKw*} zQv20oNeZ9PB#{*UKm&0U0vd>(@JAXfFPTjeyT5nGx!`X9cIBNrwzrT~7h~=={Kmyd H*SPs#2%j82 diff --git a/almapipy/__pycache__/acquisitions.cpython-37.pyc b/almapipy/__pycache__/acquisitions.cpython-37.pyc deleted file mode 100644 index a73ae700068337e6d895d6e10a2d20257d4e11ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15333 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 diff --git a/almapipy/__pycache__/analytics.cpython-37.pyc b/almapipy/__pycache__/analytics.cpython-37.pyc deleted file mode 100644 index dda3f3e83fdf410ef595c7a4e4a21c6840f7017d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5845 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 diff --git a/almapipy/__pycache__/bibs.cpython-37.pyc b/almapipy/__pycache__/bibs.cpython-37.pyc deleted file mode 100644 index 06bcca7966e964ad0557851ff1b700418f5c26fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17436 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 diff --git a/almapipy/__pycache__/client.cpython-37.pyc b/almapipy/__pycache__/client.cpython-37.pyc deleted file mode 100644 index 5fd11b72342200dcee422b266118c2f858a58276..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6217 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 F{{r0hZ;Su{ diff --git a/almapipy/__pycache__/conf.cpython-37.pyc b/almapipy/__pycache__/conf.cpython-37.pyc deleted file mode 100644 index f1ce292535ea8b08974acd3ffa64b1bdd71b603e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17458 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 Fe*jJ?AnO1C diff --git a/almapipy/__pycache__/partners.cpython-37.pyc b/almapipy/__pycache__/partners.cpython-37.pyc deleted file mode 100644 index 0810f5aef9c1a5fda89d96c248752a16a50c5021..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3601 zcmd5<&2QYs73Yv#F122#N|PYA8x+%`AKD_)+Kv&)f>79vq1J%w7*RihEedv)Gi%Z2 zlIs~#@-C>8bLgS@A6lS?_RxQb*Y@H|dk=Ex?+v*t$*zL7r_6$nZ-z5(-n@_BoBhe< z%RL9}%YXjsH~)CUasE!7W%I$@!DIG7q$64COxcJr;@#ApdLxfHKXs%h{m&ffEB3-0 z`RI3KfPP^69oao_2H{Hv5d&5SAEk-P3UHmWNKzAHc+~=Mckq~-Aj%mr>5N>-Mqc7f zm~^H085{Z1C;76QII=qlWGL4#+L1nX>D6x@l;dWHM?x2w(nkCoJNG!+-E=DWOy@_5 zRE7&~RKfEJ|Fs%})kjH;(MhiPp~z&KWRLkL%H*Yv6+bu>ng&{#R$7u_?o<>>p1sdi zCdPKjJTG~X^JireKTYQxlDHBk0Xr30GAmP2C_Zac<5Cp@CfKs_esQRbI(tDR57RAi$ z4u=wJ(|iUyZK>yJGS-QCtn+fV73b5TfWd}hmY88H&sgEH3-3^)cL&c$c+BrWD)vX` zPfq1TOtQ*(0+;O}I4tLb>sT(>!wh`uUTxlC+YdWVJK|n-wjIyea|)MczJRRAlMWob z_Hgl*RqNOck8Z5gs=LrKDs{5h{yz^^caPOLl6k!N?SCafx1nK%$3mvVot>Qz)a2HU z&G>`y&gQM{+c!6FPbNE?@%Y2>_RZV!Lv?+7i)Pj#QK$-TEQ}(h>wc?!G%3?`;Mabf z&*t@|ID4K&u)LU>`pRo@{Xk_BF*#aK8z$1~S*c8625#*el}_{}c;v5+Mp8RpjE28N zjGN&k&bN%n;zP9<9?Y|NcrTC3DH1X_{1GPPh%aS^cXfQ29Oc8=yg1A=Yw=k!o1Zev zfY^efVH71dz^Q!wXYAZ*zEx|oCog^8 z!FPNc^u_Oy6rZ$7Ng$IZ#-hwcSYjx1%}m9~B#D>Eu+_7PWq z5!rFBrQsXnJWrt=>|5%L;}Z!n1cK-{Ac-8%^YbD|$&YaqeVhPco5^Omb!+-&v9-bZ z#n0LdfT{r*$%oW@QAi%-?O4uCLt>op|P7i{=a;;pG7*ByK2I%xcZvg|>v zQMcV-(5XGbDqkQB^;KFBA`E7EW>g)b+=vKb2Z5%PwSZH5h{@VR7}dU3Le~8V{u44H zM@EC5zDz=T%Ro$|ewUVBq0vicoLz@Y%iC@Bw`kTiBJU9)lxfH}z}4PkRnQFc0}$t% zA$!B^F`uoweHJi0ZpcE`ch_B?`JQjFY4OhMs01m4fbcAXH>0TTMbR{uWlB8G@vIbS z`=uLEvB1&Rv7i1vkyDi{M|x?xKv9J>AxPNu-Tvi}+0hsStLA~bgU9>`H{ Xas^FwxYt12kEsk;k$u+p*6;lrSy{-J diff --git a/almapipy/__pycache__/task_lists.cpython-37.pyc b/almapipy/__pycache__/task_lists.cpython-37.pyc deleted file mode 100644 index d7efb9a0e84c450ac2ef377b0cba90fca0c339d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4311 zcmds5TW=f372YekqNG%DW7JO5bkZBdHX6FZ~PsDf`+dzqIc~ANrlyC3TC7G*3ZC*x56)b2(?uIo~(buQoS3 z4$8m(@y}2G^s?jpof^v$KzNA99H0`8a8qaE4qTUHFZCw=z;~TrJHi*iXO0M@`^+B% zXosSOcFVRywA&&=JF@MTSbOYrJI`G#*>$VdyQz{{0VymCm6`mJ66f64Xxu+tNFdJZD`d# zmBU!%$^4IBX$>t+UTOqG|CEcgFW!2G@9|%}vm@^9COdD*;hmk~+xJF0$===3UB0{b z_DKA^MFdekhTIhOJU}QSd>+s)S9sMo5{`H6c?-1OkKT7hR;aM`4^Zw&mmh|7t zlX8Nb%MJSg19Ai!Oy1YYSe@kk>8u#%nf1w3O=sr<1yF2#(}N*V9IH$falA=!vxUm> zJ8u2A<=*l)-1&9$ddo?NNLZ|p@rjJ%suRbPT$Cxv8*%)kEfLf!_;*~oHYoyW}m^Q!v8^Q+Q1QMEQ z`J|M{OaZA083s>GbCm%xfGh+->;U+|_CI+4QmT=fHr4JxI8Qa3OfDsl@anX&J^sZW zn=rwlCVS?u^jAvktd!7e_|)|@A-atP$S*DBt0zZC`h7g6h01~JZn*OwUJJEVM6P`I zoK}BMa~>rTy-+Cb+IXyGe}S-Q5r~{cwOxROh+w3)>9%vW22k*yxgt0Y^c@kL`Pr*Y zPk8L;_e6-EsO~xQ7d~bKaL^6_BavuB_7?;xkpS>O_N=^ScL)}1i)hgnozMNV&LY%b zVArtjTXe*_*!Vm|e+S^R$!EUfd?*X8fT4_A#3%Qyj`o6of%HJ=DX<=HAL)EzBf*T3 zb^v5cF&0>fZ>SKTaAu}5Q6rUL>7nL&)=!j9VzAJ0ud^DyuaC_ARf{G!qhlr5wkdS? zJ_Bhidr3oz!XtGeGe!#%)qtNsGYC8ED|2cO}hsmk3WU5A1}eP>|~*3nvXrE!SBYdeMSw%ATvo6KjGp zIA)??5E>EhRTWb;Q3cyZqTx^us8Oe6R!)Xe*UmF#3e7JaC~5VvRToMlccHapyE}Wk zyO+o1qY+&Wn)T_>b$-g4#bG;c6&i=Cit*)nJWXRQlUxhKwugD1LOTRjsWXO8w1}Z| zL%U{p;D?@FUV>EoEgYp!6II1VxGq^nYNy8~@Td0JNW0Fzg2H@olPJq3R}8mPda^(zym!vSdn3N0u1Eb!_Bqgn?`MFG0tu05oet z{V9YO5h|!O7;iy~u zZ%82|XH9K%Z6j=KtUS->hPf_e<^LraEpzdk1&>#~als4v7NS7krs{iC*^8ZPp`;s} zLzezAef$ZkZo9teZBqy36K@Lb?B#HlzDy z4K5ZB4!iBDmDv9RX!cCqU5!)b$-Bn{tYsYcyMcR5}XA=X46J+xR1p&d;jCN^L zq&!1;BUOtYl0yqLhxU+L3+Gt0=b|VI6fJt`zrbrxzV+ImhyLD>l*IK%9NR6NHd=5v zpKre2{C@8Z_vQWj8wP%r1OIq`<(Ogoof5^*M&t}06(ZmUH$$Uuc1)Avm9WycI+kg? zYH*9&9~;~j=0{e?M!L!!q#d2EB34r|%klx1^kY3Pfm+#*&ng_0% z=(1_1&e<>!QG!@?m;|BfB76F)A$A6jdKrN*Iwm(d6>fH{!04I0!mW?Zj?L{rkBHas zuJte$UPpdarz*U`_aWtQ8@(>12R4SAS&wfkA(h|3Fd7WMwXpBAfsC&PTqx!WndMzM~Pek_HSv&y!UWS~~t zZH`7^JixlO#D`(9DFd}7^(|9d$r|~*Bpbqtm<3&C09o4>iEE{)P!yLS!48|w!X4+|OM{IgL?v6jWqvlem zcqpM;2`zn2?tp68cKT>Hm)UQX%Wp9crL!lCt zy0VBJEZ0~ULUrsO!gNpDb7ByyAWr>e7b>o)u zqwcmCw>L&nw|y?|4*O8#Sg|*tOfmEwOSC4t+ribiJs2h1ain){5DZ4Mh}OIo=InZ2 z5Cw_n9Uz5!7=d9m%2LgKc|NV8)y3~lD$w{5t&`oIOqtMjddh}*uO{|YhUcGF~*LZc;iVo*F z&SgV(xPu%w%h^Ru$gDxTt%(iYb~&AZiu}pMxMoi(pHWU-_gJ0OC+?)i8=qLa4fK0$ z(%3L2Rr!02#>vVi07Aa-NfqTNYwq_s(ErT>3j7Rj#{NBCE$lAK-q3U5YooE|7LB zmZKwcYol(Iv?U@T{SXS=i#2boUAWNt;Ks4P9)V2OE}mnA$bN$H2OxmqU=Yg$Ye*C7 z55puFU>mcA$vI|z*z20U*pHUbQD|LU zafs33)C^_BP((r$5h~JHbI`L^#Ly=Qf#zr%`ApYO3m4#kn_2&k}tb{21565V9(sArShbJ{Ox zeCTCh0R;n2WaIDZtR=;!*35KsGe*5EMyUgS!dun>o}ZiT>l|-Mb|4stHavB*T}sU> zum>%SFSQ9@n$^@Irn7y(k9>+6fRof9j+97U7%;vEvCWz!)Yb%(T7aX}f-eY%Pbgw5lu1CrIFvRI+bQ5mL9<6+IaF4Apv? zf@dipYRdv5p48eB$+r+u-$P&=cFhBohH0AzK|&5l$f>xdYc5ze@)wbBBSu6u-b1!6 zi0P6IML$|W2hUkN>Qe-Pu}d}+OnC56HRmB%RNx_JQb7)^D%7b>Ot3_iIO7^(U=Wz{ znn5ZPXHuQmyaonwC$9R%q!Qnb9|$V2>%Mw znd)KzeLxJ@Hme!dQ?0kjnm<3IthY#dH|ByRFz7}3OwM(c)83u|0J~u7+=lnGnvvp8 zkM%i9(3q)+iAcs_NET4JXBz*8ND^YKoUPDgN>+0OHAhG{%>IiBYMz5`LOu6`HT3N7 zl{D@HzOldj>4&8iqP^6curU{5CwFy(^{_IX=5fp5{5KQjW``=?d)6U-+3ieY`K*c4Ps~oJ1_jn*JYTc}n#ZHD5;JM*> z{g@A7L3sqySB8F=m()D+tbp6!L_e}c!3hdx1JW@dz^|m!L8Yqg+)ks#tkYMBbm z)<`c8t>Dk_D4MX|dd?aUp5GqVmC zsfygn)I~Zob%<>Fo_2@|7pVpp=@PHq;3B~-nz~4tLu8j6qKf=g?hySB3z<7hH#kJU z#4QD^p6qVK&kiznPCLgMnlqVgooovJNX zGNdy^|2ASxXNDZ|J5=;t3Wzmilgcv_S&|i-)<_sMb=H?WLKTlvpyl4yCf3;fbJSHN z-gqdpi9r?S!Ai+0E?dNVu4^t}42sNR4E+V__#_1{QgDib8B5H-Z$=P=+CRmk=za%m zRavMyrpEI7Ob>JgHGXcM9*Uc_2c9ARO6xPn5KomEqL|136G{B`<|HxoqsfH-kMl&{ z+nrKHUcC&9U$ezmh%Kh-RVIu)`^#`fymf2L8C_=9pw{9z&E3{@k3ytR8lpD=qCxft~6Qacu|Ttt)cv3cfL4sksuSy41>a`q>mNQ`<4(-UZoMwi4V8m?F6R>q_fs|hQ4myqiPw=A50@xuVP!TU>UfmfbwsPT;$J=tGaoHRnuFN+^}uhnQyDDRekqM zh=6-w?e>zP+HtZ)oij{l_V3Ip)GjgICz#L%MaRzA-@x50U1BWi>x=Q851i|yx_1j5 z)WJXT=%bODp8BkpfP;?B8LD*G_Y&Vg>$2fle3pISf#XTqhdJQbV|W(xn1RRt{_VtX zZf;5S8~f2UEBE-kaAhBh}Y-=U2$?oN8AYzVqzcqW2W6^}Xb{j{hFY{wL%P z?asXVeX=_{ja=S6+MUl3Fx`vYc`jdtyCx4p^}{rZlHEzU7n^ll?CXE0lK=}NH6&S| t`pR2hY)GkKQ^k1r||dmzX9(334j0q diff --git a/almapipy/__pycache__/utils.cpython-37.pyc b/almapipy/__pycache__/utils.cpython-37.pyc deleted file mode 100644 index 6dceea66d045e56550f9dd1c6d000a15886078d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1196 zcmZ8gL5tHs6rM>YZCYFH?qwB3f(Ic88@!2#Y!??5UsZ;Jbo>&!hvs3%(~h;CHOgME8<}{_=VMOzT1isY;BICP;D-6xB@X zU?x>LtyM6s^R&u}-1JjOwKWQ>6L`!BK$4gO2}jV_6%GV;8_&jFJ~~Z|Tt^S4kVk$= zWog4rLe}`4=gC}h-t;-27ot|U9&rA-PSj?n%eg30&VLZyfm9a$e2hLAsZBIZi(`}I z=}a!8%SE0>Z;P~^%e*Q~Z~+I3JWmn)_)vU;)M9q^jD;K*(U1iD~mkW($ zVH$%1`;<}p&tB!YgJzxr>{agFk}0*a_hNQS7>Pa5qSN%okZ`TXi+%uRMLBDw_)i`@ z7>qB@gEUdf*t3(gO^9!I^O$F_s7OUuBU2hY@{ z)Y^y7Q1&d(Dv-K6m&PPlvhlSvB{W$!Zmkuxh8d}*`W_tD1B4+!NShw#c2qcDccY7Y zIHouT2BQ-?q8<1x54TkxxA{V60$el78<^{IzIvDuFit4=n+A! zt-j7=d3c^*Cn^&G1hez^DhQeG!MFMV0jslOhK|0!U2m$gW4#sfA#B^yP{`#$%WkKk zcUM0f9QEH_kUhbW!4XEq!hC8OO+wG=-t@L(XeZrjx#nzX?Encj{_FKEeS`WK++^;^ H8x8&egU$S{ From 9d100c5f7613391b377dbbab6cf364bf05cdccfd Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Sun, 25 Nov 2018 23:06:00 +0100 Subject: [PATCH 30/47] Remove intruder directory From b8e700d89f6c48a836f0d91069b0c277aa7b1d36 Mon Sep 17 00:00:00 2001 From: pac0san <7056343+pac0san@users.noreply.github.com> Date: Mon, 26 Nov 2018 01:27:26 +0100 Subject: [PATCH 31/47] Changes in comments and other minor issues. --- README.md | 421 ++++++++++++++++++++++--------------------- almapipy/__init__.py | 2 +- almapipy/users.py | 63 ++++--- setup.py | 2 +- 4 files changed, 252 insertions(+), 236 deletions(-) diff --git a/README.md b/README.md index 5836079..ae51263 100644 --- a/README.md +++ b/README.md @@ -1,202 +1,219 @@ -# almapipy: Python Wrapper for Alma API - -almapipy is python requests wrapper for easily accessing the Ex Libris Alma API. It is designed to be lightweight, and imposes a structure that mimics the [Alma API architecture](https://developers.exlibrisgroup.com/alma/apis). - -## Installation -```pip install almapipy``` - -## Progress and Roadmap -Get functionality has been developed around all the Alma APIs (listed below). -Post, Put and Delete functions will be gradually added in future releases. - -| API | Get | Post | Put | Delete | -| --- | :---: | :---: | :---: | :---: | -| [bibs](#access-bibliographic-data) | X | | | | -| [analytics](#access-reports) | X | NA | NA | NA | -| [acquisitions](#access-acquisitions) | X | | | | -| [configuration](#access-configuration-settings) | X | | | | -| [courses](#access-courses) | X | | | | -| [resource sharing partners](#access-resource-sharing-partners) | X | | | | -| [task-lists](#access-task-lists) | X | | | | -| [users](#access-users) | X | In Progress | | | -| [electronic](#access-electronic) | X | | | | - -## Use - -### Import -```python -# Import and call primary Client class -from almapipy import AlmaCnxn -alma = AlmaCnxn('your_api_key', location='Europe', data_format='json') -``` -### Access Bibliographic Data -Alma provides a set of Web services for handling bibliographic records related information, enabling you to quickly and easily manipulate bibliographic records related details. These Web services can be used by external systems to retrieve or update bibliographic records related data. -```python -# Use Alma mms_id for retrieving bib records -harry_potter = "9980963346303126" -bib_record = alma.bibs.catalog.get(harry_potter) - -# get holding items for a bib record -holdings = alma.bibs.catalog.get_holdings(harry_potter) - -# get loans by title -loans = alma.bibs.loans.get_by_title(harry_potter) -# or by a specific holding item -loans = alma.bibs.loans.get_by_item(harry_potter, holding_id, item_id) - -# get requests or availability of bib -alma.bibs.requests.get_by_title(harry_potter) -alma.bibs.requests.get_by_item(harry_potter, holding_id, item_id) -alma.bibs.requests.get_availability(harry_potter, period=20) - -# get digital representations -alma.bibs.representations.get(harry_potter) - -# get linked data -alma.bibs.linked_data.get(harry_potter) -``` - -### Access Reports -The Analytics API returns an Alma report. -```python -# Find the system path to the report if don't know path -alma.analytics.paths.get('/shared') - -# retrieve the report as an XML ET element (native response) -report = alma.analytics.reports.get('path_to_report') - -# or convert the xml to json after API call -report = alma.analytics.reports.get('path_to_report', return_json = True) -``` - -### Access Courses -Alma provides a set of Web services for handling courses and reading lists related information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems such as Courses Management Systems to retrieve or update courses and reading lists related data. -```python -# Get a complete list of courses. Makes multiple calls if necessary. -course_list = alma.courses.get(all_records = True) - -# or filter on search parameters -econ_courses = alma.courses.get(query = {'code': 'ECN'}) - -# get reading lists for a course -course_id = econ_courses['course'][0]['id'] -reading_lists = alma.courses.reading_lists.get(course_id) - -# get more detailed information about a specific reading list -reading_list_id = reading_lists['reading_list'][0]['id'] -alma.courses.reading_lists(course_id, reading_list_id, view = 'full') - -# get citations for a reading list -alma.courses.citations(course_id, reading_list_id) -``` - -### Access Users -Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems—such as student information systems (SIS)—to retrieve or update user data. -```python -# Get a list of users or filter on search parameters -users = alma.users.get(query = {'first_name': 'Sterling', 'last_name': 'Archer'}) - -# get more information on that user -user_id = users['user'][0]['primary_id'] -alma.users.get(user_id) - -# get all loans or requests for a user. Makes multiple calls if necessary. -loans = alma.user.loans.get(user_id, all_records = True) -requests = alma.user.requests.get(user_id, all_records = True) - -# get deposits or fees for a user -deposits = alma.users.deposits.get(user_id) -fees = alma.users.fees.get(user_id) -``` -### Access Acquisitions -Alma provides a set of Web services for handling acquisitions information, enabling you to quickly and easily manipulate acquisitions details. These Web services can be used by external systems - such as subscription agent systems - to retrieve or update acquisitions data. -```python -# get all funds -alma.acq.funds.get(all_records=True) - -# get po_lines by search -amazon_lines = alma.acq.po_lines.get(query={'vendor_account': 'AMAZON'}) -single_line_id = amazon_lines['po_line'][0]['number'] -# or by a specific line number -alma.acq.po_lines.get(single_line_id) - -# search for a vendor -alma.acq.vendors.get(status='active', query={'name':'AMAZON'}) -# or get a specific vendor -alma.acq.vendors.get('AMAZON.COM') - -# get invoices or polines for a specific vendor -alma.acq.vendors.get_invoices('AMAZON.COM') -alma.acq.vendors.get_po_lines('AMAZON.COM') - -# or get specific invoices -alma.acq.invoices.get('invoice_id') - -# get all licenses -alma.acq.licenses.get(all_records=True) -``` -### Access Configuration Settings -Alma provides a set of Web services for handling Configuration related information, enabling you to quickly and easily receive configuration details. These Web services can be used by external systems in order to get list of possible data. -```python -# Get libraries, locations, departments, and hours -libraries = alma.conf.units.get_libaries() -library_id = libraries['library'][0]['code'] -locations = alma.conf.units.get_locations(library_id) -hours = alma.conf.general.get_hours(library_id) -departments = alma.conf.units.get_departments() - -# Get system code tables -table = 'UserGroups' -alma.conf.general.get_code_table(table) - -# Get scheduled jobs and run history -jobs = alma.conf.jobs.get() -job_id = jobs['job'][0]['id'] -run_history = alma.conf.jobs.get_instances(job_id) - -# Get sets and set members -sets = alma.conf.sets.get() -set_id = sets['set'][0]['id'] -set_members = alma.conf.sets.get_members(set_id) - -# get profiles and reminders -depost_profiles = alma.conf.deposit_profiles.get() -import_profiles = alma.conf.import_profiles.get() -reminders = alma.conf.reminders.get() -``` -### Access Resource Sharing Partners -Alma provides a set of Web services for handling Resource Sharing Partner information, enabling you to quickly and easily manipulate partner details. These Web services can be used by external systems to retrieve or update partner data. -```python -# get partners -partners = alma.partners.get() -``` -### Access Electronic -Alma provides a set of Web services for handling electronic information, enabling you to quickly and easily manipulate electronic details. These Web services can be used by external systems in order to retrieve or update electronic data. -```python -# get e-collections -collections = alma.electronic.collections.get() -collection_id = collections['electronic_collection'][0]['id'] - -# get services for a collection -services = alma.electronic.services.get(collection_id) -service_id = services['electronic_service'][0]['id'] - -# get portfolios for a service -alma.electronic.portfolios.get(collection_id, service_id) - -``` -### Access Task Lists -Alma provides a set of Web services for handling task lists information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems. -```python -# get requested resources for a specific circulation desk -alma.task_lists.resources.get(library_id, circ_desk) - -# get lending requests for a specific library -alma.task_lists.lending.get(library_id) - -``` - -## Attribution and Contact - - -* **Author**: [Steve Pelkey](mailto:spelkey@ucdavis.edu) +#-*- coding: utf-8-unix -*- + +# almapipy: Python Wrapper for Alma API + +almapipy is python requests wrapper for easily accessing the Ex Libris Alma API. It is designed to be lightweight, and imposes a structure that mimics the [Alma API architecture](https://developers.exlibrisgroup.com/alma/apis). + +## Installation +```pip install almapipy``` + +## Progress and Roadmap +Get functionality has been developed around all the Alma APIs (listed below). +Post, Put and Delete functions will be gradually added in future releases. + +| API | Get | Post | Put | Delete | +| --- | :---: | :---: | :---: | :---: | +| [bibs](#access-bibliographic-data) | X | | | | +| [analytics](#access-reports) | X | NA | NA | NA | +| [acquisitions](#access-acquisitions) | X | | | | +| [configuration](#access-configuration-settings) | X | | | | +| [courses](#access-courses) | X | | | | +| [resource sharing partners](#access-resource-sharing-partners) | X | | | | +| [task-lists](#access-task-lists) | X | | | | +| [users](#access-users) | X | In Progress | | In Progress | +| [electronic](#access-electronic) | X | | | | + +## Use + +### Import +```python +# Import and call primary Client class +from almapipy import AlmaCnxn +alma = AlmaCnxn('your_api_key', location='Europe', data_format='json') +``` +### Access Bibliographic Data +Alma provides a set of Web services for handling bibliographic records related information, enabling you to quickly and easily manipulate bibliographic records related details. These Web services can be used by external systems to retrieve or update bibliographic records related data. +```python +# Use Alma mms_id for retrieving bib records +harry_potter = "9980963346303126" +bib_record = alma.bibs.catalog.get(harry_potter) + +# get holding items for a bib record +holdings = alma.bibs.catalog.get_holdings(harry_potter) + +# get loans by title +loans = alma.bibs.loans.get_by_title(harry_potter) +# or by a specific holding item +loans = alma.bibs.loans.get_by_item(harry_potter, holding_id, item_id) + +# get requests or availability of bib +alma.bibs.requests.get_by_title(harry_potter) +alma.bibs.requests.get_by_item(harry_potter, holding_id, item_id) +alma.bibs.requests.get_availability(harry_potter, period=20) + +# get digital representations +alma.bibs.representations.get(harry_potter) + +# get linked data +alma.bibs.linked_data.get(harry_potter) +``` + +### Access Reports +The Analytics API returns an Alma report. +```python +# Find the system path to the report if don't know path +alma.analytics.paths.get('/shared') + +# retrieve the report as an XML ET element (native response) +report = alma.analytics.reports.get('path_to_report') + +# or convert the xml to json after API call +report = alma.analytics.reports.get('path_to_report', return_json = True) +``` + +### Access Courses +Alma provides a set of Web services for handling courses and reading lists related information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems such as Courses Management Systems to retrieve or update courses and reading lists related data. +```python +# Get a complete list of courses. Makes multiple calls if necessary. +course_list = alma.courses.get(all_records = True) + +# or filter on search parameters +econ_courses = alma.courses.get(query = {'code': 'ECN'}) + +# get reading lists for a course +course_id = econ_courses['course'][0]['id'] +reading_lists = alma.courses.reading_lists.get(course_id) + +# get more detailed information about a specific reading list +reading_list_id = reading_lists['reading_list'][0]['id'] +alma.courses.reading_lists(course_id, reading_list_id, view = 'full') + +# get citations for a reading list +alma.courses.citations(course_id, reading_list_id) +``` + +### Access Users +Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems—such as student information systems (SIS)—to retrieve or update user data. +```python +# Retrieve a list of users or filter on search parameters +users = alma.users.retrieve(query = {'first_name': 'Sterling', 'last_name': 'Archer'}) + +# Retrieve more information on that user +user_id = users['user'][0]['primary_id'] +user = alma.users.retrieve(user_id) + +# Create a user, providing an identifier and some necessary or even additional data (according to each Alma implementation) +data = {'first_name': 'Tester #001', 'last_name': 'from Alma', + 'account_type': {'value': 'EXTERNAL', 'desc': 'External'}, + 'external_id': 'SIS', + 'contact_info': { + 'email': [ + {'email_address': 'alma.tester.001@alma.example', + 'email_type': [{'value': 'work', 'desc': 'Work'}]}]}} +user = alma.users.create(identifier = 'alma.tester.001', id_type = 'primary_id', user_data = data) +user = alma.users.create(identifier = '20181126001650001', id_type = 'OTHER_ID_1', user_data = data) + +# Remove a user, providing an identifier. +response = alma.users.remove(identifier = 'alma.tester.001', id_type = 'primary_id') +response = alma.users.remove(identifier = '20181126001650001', id_type = 'OTHER_ID_1') + +# Retrieve all loans or requests for a user. Makes multiple calls if necessary. +loans = alma.user.loans.retrieve(user_id, all_records = True) +requests = alma.user.requests.retrieve(user_id, all_records = True) + +# Retrieve deposits or fees for a user +deposits = alma.users.deposits.Retrieve(user_id) +fees = alma.users.fees.retrieve(user_id) +``` +### Access Acquisitions +Alma provides a set of Web services for handling acquisitions information, enabling you to quickly and easily manipulate acquisitions details. These Web services can be used by external systems - such as subscription agent systems - to retrieve or update acquisitions data. +```python +# get all funds +alma.acq.funds.get(all_records=True) + +# get po_lines by search +amazon_lines = alma.acq.po_lines.get(query={'vendor_account': 'AMAZON'}) +single_line_id = amazon_lines['po_line'][0]['number'] +# or by a specific line number +alma.acq.po_lines.get(single_line_id) + +# search for a vendor +alma.acq.vendors.get(status='active', query={'name':'AMAZON'}) +# or get a specific vendor +alma.acq.vendors.get('AMAZON.COM') + +# get invoices or polines for a specific vendor +alma.acq.vendors.get_invoices('AMAZON.COM') +alma.acq.vendors.get_po_lines('AMAZON.COM') + +# or get specific invoices +alma.acq.invoices.get('invoice_id') + +# get all licenses +alma.acq.licenses.get(all_records=True) +``` +### Access Configuration Settings +Alma provides a set of Web services for handling Configuration related information, enabling you to quickly and easily receive configuration details. These Web services can be used by external systems in order to get list of possible data. +```python +# Restrieve libraries, locations, departments, and hours +libraries = alma.conf.units.retrieve_libaries() +library_id = libraries['library'][0]['code'] +locations = alma.conf.units.retrieve_locations(library_id) +hours = alma.conf.general.retrieve_hours(library_id) +departments = alma.conf.units.retrieve_departments() + +# Retrieve system code tables +table = 'UserGroups' +alma.conf.general.retrieve_code_table(table) + +# Retrieve scheduled jobs and run history +jobs = alma.conf.jobs.retrieve() +job_id = jobs['job'][0]['id'] +run_history = alma.conf.jobs.retrieve_instances(job_id) + +# Retrieve sets and set members +sets = alma.conf.sets.get() +set_id = sets['set'][0]['id'] +set_members = alma.conf.sets.retrieve_members(set_id) + +# Retrieve profiles and reminders +depost_profiles = alma.conf.deposit_profiles.get() +import_profiles = alma.conf.import_profiles.get() +reminders = alma.conf.reminders.retrieve() +``` +### Access Resource Sharing Partners +Alma provides a set of Web services for handling Resource Sharing Partner information, enabling you to quickly and easily manipulate partner details. These Web services can be used by external systems to retrieve or update partner data. +```python +# get partners +partners = alma.partners.get() +``` +### Access Electronic +Alma provides a set of Web services for handling electronic information, enabling you to quickly and easily manipulate electronic details. These Web services can be used by external systems in order to retrieve or update electronic data. +```python +# get e-collections +collections = alma.electronic.collections.get() +collection_id = collections['electronic_collection'][0]['id'] + +# get services for a collection +services = alma.electronic.services.get(collection_id) +service_id = services['electronic_service'][0]['id'] + +# get portfolios for a service +alma.electronic.portfolios.get(collection_id, service_id) + +``` +### Access Task Lists +Alma provides a set of Web services for handling task lists information, enabling you to quickly and easily manipulate their details. These Web services can be used by external systems. +```python +# get requested resources for a specific circulation desk +alma.task_lists.resources.get(library_id, circ_desk) + +# get lending requests for a specific library +alma.task_lists.lending.get(library_id) + +``` + +## Attribution and Contact + + +* **Author**: [Steve Pelkey](mailto:spelkey@ucdavis.edu) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index f8e00bf..84d032e 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -5,7 +5,7 @@ """ __author__ = "Steve Pelkey (spelkey@ucdavis.edu)" __name__ = "almapipy" -__version__ = "1.0.1" +__version__ = "1.1.0" import os diff --git a/almapipy/users.py b/almapipy/users.py index 188d52c..28ce240 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -32,7 +32,8 @@ def __init__(self, cnxn_params={}): def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a user list or a single user. - Args: user_id (str): A unique identifier for the user. + Args: + user_id (str): A unique identifier for the user. Gets more detailed information. query (dict): Search query for filtering a user list. Optional. Searching for words from fields: [primary_id, first_name, @@ -57,18 +58,18 @@ def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} args = q_params.copy() - + """ print("\nDebug: users.py Retrieve #1") print(query) print(args) print("") - + """ + """ # Avoid sending 'primary_id' as 'id_type' if ('id_type' in args.keys()) and (args['id_type'] == 'primary_id'): args.pop('id_type', None) args['primary_id'] = args.pop('identifiers', None) - print(args) - + """ url = self.cnxn_params['api_uri_full'] if user_id: url += ("/" + str(user_id)) @@ -90,14 +91,14 @@ def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False response = self.get(url, args=args, headers=headers, raw=raw) if user_id: return response - + """ print("\nDebug: users.py Retrieve #2") print(url) print(args) print(headers) print(response) print("") - + """ # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, @@ -108,12 +109,12 @@ def create(self, identifier, id_type, user_data, raw=False): """Create a single user if it does not exist yet in Alma Args: + identifier (str): The identifier itself for the user. + See: id_type (str): The identifier type for the user Values: from the code-table: UserIdentifierTypes See: - identifier (str): The identifier itself for the user. - See: user_data (dict): Data for user enrollment. Setting words for fields: [first_name, last_name, middle_name, email, user_group, ...]. @@ -150,14 +151,14 @@ def create(self, identifier, id_type, user_data, raw=False): # Search for a user with this 'user_identifier' response = self.get(url, args=args, headers=headers, raw=raw) - + """ print("\nDebug: users.py Create #1") print(headers) print(url) print(args) print(response) print("") - + """ if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. @@ -171,43 +172,44 @@ def create(self, identifier, id_type, user_data, raw=False): aux_dict['segment_type'] = 'External' data['user_identifier'] = [ aux_dict ] """ -# aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" -# data = loads(aux_dict.replace("'", "\"")) - + """ + aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" + data = loads(aux_dict.replace("'", "\"")) + """ args.pop('q', None) - + """ print("\nDebug: users.py Create #2") print(headers) print(url) print(args) print(data) - + """ response = self.post(url, data=data, args=args, headers=headers, raw=raw) - + """ print(response) print("") - + """ else: # User already exist in Alma. response = {'total_record_count': 0} return response - + def remove(self, identifier, id_type, raw=False): """Remove a single user if it does exist in Alma Args: + identifier (str): The identifier itself for the user. + See: id_type (str): The identifier type for the user Values: from the code-table: UserIdentifierTypes See: - identifier (str): The identifier itself for the user. - See: raw (bool): If true, returns raw requests object. Returns: (?) - The user (at Alma) if a new user is removed. - "{'total_record_count': 0}" if the 'identifier' is not present in Alma. + "{'total_record_count': 1}" if the user has been successfully removed. + "{'total_record_count': 0}" if the 'identifier' was not present in Alma. """ @@ -229,34 +231,31 @@ def remove(self, identifier, id_type, raw=False): # Search for a user with this 'user_identifier' response = self.get(url, args=args, headers=headers, raw=raw) - + """ print("\nDebug: users.py Delete #1") print(headers) print(url) print(args) print(response) print("") - + """ if response['total_record_count'] == 1: # A single user exists with this 'identifier': Let's remove it. args.clear() args['primary_id'] = response['user'][0]['primary_id'] url += (str('/' + args['primary_id'])) - + """ print("\nDebug: users.py Delete #2") print(headers) print(url) print(args) - + """ # Send request response = self.delete(url, args=args, headers=headers, raw=raw) - + """ print(response) print("") - - else: - # No a "single" user exists in Alma. - response = response['total_record_count'] + """ return response diff --git a/setup.py b/setup.py index 990d264..cb2ebbb 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md", "r") as fh: long_description = fh.read() -VERSION = "1.0.1" +VERSION = "1.1.0" setup( name="almapipy", From 7d72dc758451d8dc676f8c30e13eeaabb5ef9ba8 Mon Sep 17 00:00:00 2001 From: pac0san Date: Mon, 26 Nov 2018 01:53:39 +0100 Subject: [PATCH 32/47] Update users.py --- almapipy/users.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/almapipy/users.py b/almapipy/users.py index e06b5d2..4ed58a6 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -191,11 +191,6 @@ def create(self, identifier, id_type, user_data, raw=False): print(response) print("") """ - response = self.post(url, data=data, args=args, headers=headers, raw=raw) - - print(response) - print("") - else: # User already exist in Alma. response = {'total_record_count': 0} From 078e8667109ac56fa1ede852bb8c52ab91371127 Mon Sep 17 00:00:00 2001 From: pac0san Date: Mon, 26 Nov 2018 02:06:36 +0100 Subject: [PATCH 33/47] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cb2ebbb..b497ea4 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ long_description=long_description, # long_description_content_type="text/markdown", url="https://github.com/UCDavisLibrary/almapipy", - install_requires=['requests'], + install_requires=['json', 'os', 'requests', 'xml.etree.ElementTree'], keywords='alma exlibris exlibrisgroup api bibliographic', packages=find_packages(), classifiers=[ From c52f4a328a3c7eb375c71623809cbb6f62242caf Mon Sep 17 00:00:00 2001 From: pac0san Date: Mon, 26 Nov 2018 02:12:17 +0100 Subject: [PATCH 34/47] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b497ea4..cb2ebbb 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ long_description=long_description, # long_description_content_type="text/markdown", url="https://github.com/UCDavisLibrary/almapipy", - install_requires=['json', 'os', 'requests', 'xml.etree.ElementTree'], + install_requires=['requests'], keywords='alma exlibris exlibrisgroup api bibliographic', packages=find_packages(), classifiers=[ From 30ec4ad05d83c3060b3e8e5a644afce0f3f4f524 Mon Sep 17 00:00:00 2001 From: pac0san Date: Mon, 26 Nov 2018 02:13:44 +0100 Subject: [PATCH 35/47] Update __init__.py --- almapipy/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index 84d032e..b59104a 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -8,8 +8,6 @@ __version__ = "1.1.0" -import os - from .client import Client from .bibs import SubClientBibs from .analytics import SubClientAnalytics From c5b2045d0f4a037448ab8e0d611fa071c2271a53 Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 27 Nov 2018 11:51:48 +0100 Subject: [PATCH 36/47] Style format adjustments --- almapipy/__init__.py | 21 +++++++++++++-------- almapipy/acquisitions.py | 2 +- almapipy/analytics.py | 2 +- almapipy/bibs.py | 2 +- almapipy/client.py | 2 +- almapipy/conf.py | 2 +- almapipy/courses.py | 2 +- almapipy/electronic.py | 2 +- almapipy/partners.py | 2 +- almapipy/task_lists.py | 2 +- almapipy/users.py | 2 +- almapipy/utils.py | 2 +- setup.py | 33 ++++++++++++++++++++------------- 13 files changed, 44 insertions(+), 32 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index 84d032e..66f826b 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -1,14 +1,8 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- """ -Python client for Ex Libris Alma +Python requests wrapper for the Ex Libris Alma API """ -__author__ = "Steve Pelkey (spelkey@ucdavis.edu)" -__name__ = "almapipy" -__version__ = "1.1.0" - - -import os from .client import Client from .bibs import SubClientBibs @@ -23,6 +17,16 @@ from . import utils +__author__ = "Steve Pelkey" +__author_email__ = "spelkey@ucdavis.edu" +__project_name__ = "almapipy" +__project_description__ = "Python requests wrapper for the Ex Libris Alma API" +__project_url__ = "https://github.com/UCDavisLibrary/almapipy" +__license__ = "MIT License" +__version__ = "0.1.2.dev0" +__status__ = "Development" + + class AlmaCnxn(Client): """"Interface with Alma APIs. @@ -70,6 +74,7 @@ def __init__(self, apikey, location='America', data_format='json'): # call __validate_key__ self.cnxn_params['api_key'] = apikey + # Set 'User-Agent' for REST queries self.cnxn_params['User-Agent'] = '{}/{}'.format(__name__,__version__) # Hook in the various Alma APIs based on what API key can access diff --git a/almapipy/acquisitions.py b/almapipy/acquisitions.py index eb75eac..4612645 100644 --- a/almapipy/acquisitions.py +++ b/almapipy/acquisitions.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/analytics.py b/almapipy/analytics.py index ecfa912..26accc8 100644 --- a/almapipy/analytics.py +++ b/almapipy/analytics.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/bibs.py b/almapipy/bibs.py index d8fbbc0..a4f46b3 100644 --- a/almapipy/bibs.py +++ b/almapipy/bibs.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/client.py b/almapipy/client.py index c173a52..9f3794c 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- """ Common Client for interacting with Alma API diff --git a/almapipy/conf.py b/almapipy/conf.py index cd45884..d7fd979 100644 --- a/almapipy/conf.py +++ b/almapipy/conf.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/courses.py b/almapipy/courses.py index 945d0e5..3b48719 100644 --- a/almapipy/courses.py +++ b/almapipy/courses.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/electronic.py b/almapipy/electronic.py index b045233..85ab8b7 100644 --- a/almapipy/electronic.py +++ b/almapipy/electronic.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/partners.py b/almapipy/partners.py index 16c64f6..8223df3 100644 --- a/almapipy/partners.py +++ b/almapipy/partners.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/task_lists.py b/almapipy/task_lists.py index d748256..984c364 100644 --- a/almapipy/task_lists.py +++ b/almapipy/task_lists.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/users.py b/almapipy/users.py index 4ed58a6..a3658cf 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from .client import Client from . import utils diff --git a/almapipy/utils.py b/almapipy/utils.py index 15625c3..09fa040 100644 --- a/almapipy/utils.py +++ b/almapipy/utils.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- """ Error classes and other helpful functions diff --git a/setup.py b/setup.py index b497ea4..ba213de 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- from setuptools import setup from setuptools import find_packages @@ -6,21 +6,27 @@ with open("README.md", "r") as fh: long_description = fh.read() -VERSION = "1.1.0" +from almapipy.__init__ import __version__ as version +from almapipy.__init__ import __author__ as author +from almapipy.__init__ import __author_email__ as author_email +from almapipy.__init__ import __project_url__ as url +from almapipy.__init__ import __project_description__ as description +from almapipy.__init__ import __license__ as license + setup( - name="almapipy", - version=VERSION, - author="Steve Pelkey", - author_email="spelkey@ucdavis.edu", - description="Python requests wrapper for the Ex Libris Alma API", - long_description=long_description, -# long_description_content_type="text/markdown", - url="https://github.com/UCDavisLibrary/almapipy", - install_requires=['json', 'os', 'requests', 'xml.etree.ElementTree'], - keywords='alma exlibris exlibrisgroup api bibliographic', + name = "almapipy", + version = version, + author = author, + author_email = author_email, + url = url, + description = description, + long_description = long_description, +# long_description_content_type = "text/markdown", + install_requires = ['requests'], + keywords = 'alma exlibris exlibrisgroup api bibliographic', packages=find_packages(), - classifiers=[ + classifiers = [ "Programming Language :: Python :: 3" "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", @@ -30,4 +36,5 @@ "Natural Language :: English", "Topic :: Software Development :: Libraries :: Python Modules", ], + license = license, ) From aa2258eeb9e84e4f7a8b692a6d318b1e3efc4915 Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 27 Nov 2018 22:24:44 +0100 Subject: [PATCH 37/47] CRUD getting on --- README.md | 48 ++++---- almapipy/__init__.py | 2 +- almapipy/client.py | 189 ++++++++++++++++++++++++++------ almapipy/conf.py | 26 ++--- almapipy/users.py | 256 +++++++++++++++++++++++++++++-------------- setup.py | 4 +- 6 files changed, 366 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index ae51263..e8b42f4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#-*- coding: utf-8-unix -*- +# -*- coding: utf-8 -*- # almapipy: Python Wrapper for Alma API @@ -95,12 +95,6 @@ alma.courses.citations(course_id, reading_list_id) ### Access Users Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems—such as student information systems (SIS)—to retrieve or update user data. ```python -# Retrieve a list of users or filter on search parameters -users = alma.users.retrieve(query = {'first_name': 'Sterling', 'last_name': 'Archer'}) - -# Retrieve more information on that user -user_id = users['user'][0]['primary_id'] -user = alma.users.retrieve(user_id) # Create a user, providing an identifier and some necessary or even additional data (according to each Alma implementation) data = {'first_name': 'Tester #001', 'last_name': 'from Alma', @@ -113,17 +107,27 @@ data = {'first_name': 'Tester #001', 'last_name': 'from Alma', user = alma.users.create(identifier = 'alma.tester.001', id_type = 'primary_id', user_data = data) user = alma.users.create(identifier = '20181126001650001', id_type = 'OTHER_ID_1', user_data = data) +# Retrieve a list of users or filter on search parameters +users = alma.users.read(query = {'first_name': 'Sterling', 'last_name': 'Archer'}) + +# Retrieve more information on that user +user_id = users['user'][0]['primary_id'] +user = alma.users.read(user_id) + +# Update an user (This function is for advanced users) +response = alma.users.update(primary_id = 'alma.tester.001', user_data = full_user_data) + # Remove a user, providing an identifier. -response = alma.users.remove(identifier = 'alma.tester.001', id_type = 'primary_id') -response = alma.users.remove(identifier = '20181126001650001', id_type = 'OTHER_ID_1') +response = alma.users.delete(identifier = 'alma.tester.001', id_type = 'primary_id') +response = alma.users.delete(identifier = '20181126001650001', id_type = 'OTHER_ID_1') # Retrieve all loans or requests for a user. Makes multiple calls if necessary. -loans = alma.user.loans.retrieve(user_id, all_records = True) -requests = alma.user.requests.retrieve(user_id, all_records = True) +loans = alma.user.loans.read(user_id, all_records = True) +requests = alma.user.requests.read(user_id, all_records = True) # Retrieve deposits or fees for a user -deposits = alma.users.deposits.Retrieve(user_id) -fees = alma.users.fees.retrieve(user_id) +deposits = alma.users.deposits.read(user_id) +fees = alma.users.fees.read(user_id) ``` ### Access Acquisitions Alma provides a set of Web services for handling acquisitions information, enabling you to quickly and easily manipulate acquisitions details. These Web services can be used by external systems - such as subscription agent systems - to retrieve or update acquisitions data. @@ -156,30 +160,30 @@ alma.acq.licenses.get(all_records=True) Alma provides a set of Web services for handling Configuration related information, enabling you to quickly and easily receive configuration details. These Web services can be used by external systems in order to get list of possible data. ```python # Restrieve libraries, locations, departments, and hours -libraries = alma.conf.units.retrieve_libaries() +libraries = alma.conf.units.read_libaries() library_id = libraries['library'][0]['code'] -locations = alma.conf.units.retrieve_locations(library_id) -hours = alma.conf.general.retrieve_hours(library_id) -departments = alma.conf.units.retrieve_departments() +locations = alma.conf.units.read_locations(library_id) +hours = alma.conf.general.read_hours(library_id) +departments = alma.conf.units.read_departments() # Retrieve system code tables table = 'UserGroups' -alma.conf.general.retrieve_code_table(table) +alma.conf.general.read_code_table(table) # Retrieve scheduled jobs and run history -jobs = alma.conf.jobs.retrieve() +jobs = alma.conf.jobs.read() job_id = jobs['job'][0]['id'] -run_history = alma.conf.jobs.retrieve_instances(job_id) +run_history = alma.conf.jobs.read_instances(job_id) # Retrieve sets and set members sets = alma.conf.sets.get() set_id = sets['set'][0]['id'] -set_members = alma.conf.sets.retrieve_members(set_id) +set_members = alma.conf.sets.read_members(set_id) # Retrieve profiles and reminders depost_profiles = alma.conf.deposit_profiles.get() import_profiles = alma.conf.import_profiles.get() -reminders = alma.conf.reminders.retrieve() +reminders = alma.conf.reminders.read() ``` ### Access Resource Sharing Partners Alma provides a set of Web services for handling Resource Sharing Partner information, enabling you to quickly and easily manipulate partner details. These Web services can be used by external systems to retrieve or update partner data. diff --git a/almapipy/__init__.py b/almapipy/__init__.py index aeef7a0..4866c84 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -24,7 +24,7 @@ __project_description__ = "Python requests wrapper for the Ex Libris Alma API" __project_url__ = "https://github.com/UCDavisLibrary/almapipy" __license__ = "MIT License" -__version__ = "0.1.2.dev0" +__version__ = "0.1.2.dev2" __status__ = "Development" diff --git a/almapipy/client.py b/almapipy/client.py index 9f3794c..37b6494 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -22,7 +22,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params # def post(self, url, data, args, object_type, raw=False): - def post(self, url, data, args, headers, raw=False): + def Post(self, url, data, args, headers, raw=False): """ Uses requests library to make Exlibris API Post call. Returns data of type specified during init of base class. @@ -39,45 +39,52 @@ def post(self, url, data, args, headers, raw=False): JSON-esque, xml, or raw response. """ + data_aux = data.copy() + args_aux = args.copy() + + # Preserve Auth and add 'User-Agent' and 'content-type' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] + # Determine format of data to be posted according to order of importance: # 1) Local declaration, 2) dtype of data parameter, 3) global setting. - if 'format' not in args.keys(): - if type(data) == ET or type(data) == ET.Element: + if 'format' not in args_aux.keys(): + if type(data_aux) == ET or type(data_aux) == ET.Element: content_type = 'xml' - elif type(data) == dict: + elif type(data_aux) == dict: content_type = 'json' else: content_type = self.cnxn_params['format'] - args['format'] = self.cnxn_params['format'] + args_aux['format'] = self.cnxn_params['format'] else: - content_type = args['format'] + content_type = args_aux['format'] - # Preserve Auth and add 'User-Agent' and 'content-type' in headers - headers_aux = headers.copy() - headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] + # Determine 'content-type' and set 'data_aux' format in consecuence if content_type == 'json': headers_aux['content-type'] = 'application/json' - if type(data) != str: - data = json.dumps(data) + if type(data_aux) != str: + data_aux = json.dumps(data_aux) elif content_type == 'xml': headers_aux['content-type'] = 'application/xml' - if type(data) == ET or type(data) == ET.Element: - data = ET.tostring(data, encoding='unicode') - elif type(data) != str: + if type(data_aux) == ET or type(data_aux) == ET.Element: + data_aux = ET.tostring(data_aux, encoding='unicode') + elif type(data_aux) != str: message = "XML payload must be either string or ElementTree." raise utils.ArgError(message) else: message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) + """ print("\nDebug: client.py Post #1") print(url) - print(data) - print(args) + print(data_aux) + print(args_aux) print(headers_aux) """ + # Send request - response = requests.post(url, data=data, params=args, headers=headers_aux) + response = requests.post(url, data=data_aux, params=args_aux, headers=headers_aux) if raw: return response @@ -86,9 +93,9 @@ def post(self, url, data, args, headers, raw=False): return content - def delete(self, url, args, headers, raw=False): + def Get(self, url, args, headers, raw=False): """ - Uses requests library to make Exlibris API Delete call. + Uses requests library to make Exlibris API Get call. Returns data of type specified during init of base class. Args: @@ -100,22 +107,111 @@ def delete(self, url, args, headers, raw=False): Returns: JSON-esque, xml, or raw response. """ + + args_aux = args.copy() + headers_aux = headers.copy() + + # handle data format. Allow for overriding of global setting. + data_format = self.cnxn_params['format'] + if 'format' not in args_aux.keys(): + args_aux['format'] = data_format # Preserve Auth and add 'User-Agent' in headers - headers_aux = headers.copy() headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] - print("\nDebug: client.py Delete #1") + """ + print("\nDebug: client.py Get #1") print(url) - print(args) + print(args_aux) print(headers_aux) + """ + + # Send request. + response = requests.get(url, params=args_aux, headers=headers_aux) + + """ + print("\nDebug: client.py Get #2") + print(url) + print(args_aux) + print(headers_aux) + print(response) + print(raw) print("") + """ + + if raw: + return response + + # Parse content + content = self.__parse_response__(response) + + return content + + def Put(self, url, data, headers, raw=False): + """ + Uses requests library to make Exlibris API Put call. + Returns data of type specified during init of base class. + + Args: + url (str): Exlibris API endpoint url. + data (dict): Data to be puted. + headers (dict): API Key Auth in Headers. +# object_type (str): Type of object to be puted (see alma docs) + raw (bool): If true, returns raw response. + + Returns: + JSON-esque, xml, or raw response. + """ + + data_aux = data.copy() + + # Preserve Auth and add 'User-Agent' in headers + headers_aux = headers.copy() + headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] + + # Determine format of data to be puted according to order of importance: + # 1) Local declaration, 2) dtype of data parameter, 3) global setting. + if type(data_aux) == ET or type(data_aux) == ET.Element: + content_type = 'xml' + elif type(data_aux) == dict: + content_type = 'json' + else: + content_type = self.cnxn_params['format'] + + # Determine 'content-type' and set 'data_aux' format in consecuence + if content_type == 'json': + headers_aux['content-type'] = 'application/json' + if type(data_aux) != str: + data_aux = json.dumps(data_aux) + elif content_type == 'xml': + headers_aux['content-type'] = 'application/xml' + if type(data_aux) == ET or type(data_aux) == ET.Element: + data_aux = ET.tostring(data_aux, encoding='unicode') + elif type(data_aux) != str: + message = "XML payload must be either string or ElementTree." + raise utils.ArgError(message) + else: + message = "Put content type must be either 'json' or 'xml'" + raise utils.ArgError(message) + + """ + print("\nDebug: client.py Put #1") + print(url) + print(data_aux) + print(headers_aux) + """ # Send request - response = requests.delete(url, params=args, headers=headers_aux) + response = requests.put(url, data=data_aux, headers=headers_aux) + """ + print("\nDebug: client.py Put #2") + print(url) + print(data_aux) + print(headers_aux) print(response) print("") + """ if raw: return response @@ -125,9 +221,9 @@ def delete(self, url, args, headers, raw=False): return content - def get(self, url, args, headers, raw=False): + def Delete(self, url, args, headers, raw=False): """ - Uses requests library to make Exlibris API Get call. + Uses requests library to make Exlibris API Delete call. Returns data of type specified during init of base class. Args: @@ -139,32 +235,53 @@ def get(self, url, args, headers, raw=False): Returns: JSON-esque, xml, or raw response. """ -# print(url) - # handle data format. Allow for overriding of global setting. - data_format = self.cnxn_params['format'] - if 'format' not in args.keys(): - args['format'] = data_format - data_format = args['format'] + args_aux = args.copy() # Preserve Auth and add 'User-Agent' in headers headers_aux = headers.copy() headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] + + # Determine format of data to be posted + if 'format' not in args_aux.keys(): + args_aux['format'] = self.cnxn_params['format'] + + # Determine 'content-type' + if args_aux['format'] == 'json': + headers_aux['content-type'] = 'application/json' + elif args_aux['format'] == 'xml': + headers_aux['content-type'] = 'application/xml' + else: + message = "Post content type must be either 'json' or 'xml'" + raise utils.ArgError(message) + """ - print("\nDebug: client.py Get #1") + print("\nDebug: client.py Delete #1") print(url) print(args) print(headers_aux) """ - # Send request. - response = requests.get(url, params=args, headers=headers_aux) + + # Send request + response = requests.delete(url, params=args_aux, headers=headers_aux) + + """ + print("\nDebug: client.py Delete #2") + print(response) + print("") + """ + if raw: return response +#TODO: Eval exception 204 + """ # Parse content content = self.__parse_response__(response) return content + """ + return response def __format_query__(self, query): """Converts dictionary of brief search query to a formated string. @@ -191,7 +308,7 @@ def __format_query__(self, query): return q_str - def __get_all__(self, url, args, headers, raw, response, data_key, max_limit=100): + def __Get_all__(self, url, args, headers, raw, response, data_key, max_limit=100): """Makes multiple API calls until all records for a query are retrieved. Called by the 'all_records' parameter. @@ -268,7 +385,7 @@ def __parse_response__(self, response): status = response.status_code url = response.url try: - response_type = response.headers['Content-Type'] + response_type = response.headers['content-type'] if ";" in response_type: response_type, charset = response_type.split(";") except: diff --git a/almapipy/conf.py b/almapipy/conf.py index d7fd979..70575bd 100644 --- a/almapipy/conf.py +++ b/almapipy/conf.py @@ -39,7 +39,7 @@ class SubClientConfigurationUnits(Client): def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params.copy() - def retrieve_libaries(self, library_id=None, q_params={}, raw=False): + def read_libaries(self, library_id=None, q_params={}, raw=False): """Retrieve a list of libraries or a specific library Args: @@ -62,7 +62,7 @@ def retrieve_libaries(self, library_id=None, q_params={}, raw=False): response = self.get(url, args, raw=raw) return response - def retrieve_locations(self, library_id, location_id=None, q_params={}, raw=False): + def read_locations(self, library_id, location_id=None, q_params={}, raw=False): """Retrieve a list of locations for a library Args: @@ -86,7 +86,7 @@ def retrieve_locations(self, library_id, location_id=None, q_params={}, raw=Fals response = self.get(url, args, raw=raw) return response - def retrieve_departments(self, q_params={}, raw=False): + def read_departments(self, q_params={}, raw=False): """Retrieve a list of configured departments Args: @@ -113,7 +113,7 @@ class SubClientConfigurationGeneral(Client): def __init__(self, cnxn_params={}): self.cnxn_params = cnxn_params.copy() - def retrieve(self, library_id=None, q_params={}, raw=False): + def read(self, library_id=None, q_params={}, raw=False): """Retrieve general configuration of the institution Args: @@ -134,7 +134,7 @@ def retrieve(self, library_id=None, q_params={}, raw=False): response = self.get(url, args, raw=raw) return response - def retrieve_hours(self, library_id=None, q_params={}, raw=False): + def read_hours(self, library_id=None, q_params={}, raw=False): """Retrieve open hours as configured in Alma. Note that the library-hours do not necessarily reflect when the library doors are actually open, but rather start and end times that @@ -165,7 +165,7 @@ def retrieve_hours(self, library_id=None, q_params={}, raw=False): response = self.get(url, args, raw=raw) return response - def retrieve_code_table(self, table_name, q_params={}, raw=False): + def read_code_table(self, table_name, q_params={}, raw=False): """This API returns all rows defined for a code-table. The main usage of this API is for applications that use Alma APIs, @@ -200,7 +200,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/jobs' self.cnxn_params['api_uri_full'] += '/jobs' - def retrieve(self, job_id=None, limit=10, offset=0, all_records=False, + def read(self, job_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of jobs that can be submitted or details for a given job. @@ -247,7 +247,7 @@ def retrieve(self, job_id=None, limit=10, offset=0, all_records=False, response=response, data_key='job') return response - def retrieve_instances(self, job_id, instance_id=None, limit=10, offset=0, + def read_instances(self, job_id, instance_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve all the job instances (runs) for a given job id, or specific instance. @@ -308,7 +308,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/sets' self.cnxn_params['api_uri_full'] += '/sets' - def retrieve(self, set_id=None, content_type=None, set_type=None, + def read(self, set_id=None, content_type=None, set_type=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of sets or a single set. @@ -366,7 +366,7 @@ def retrieve(self, set_id=None, content_type=None, set_type=None, response=response, data_key='set') return response - def retrieve_members(self, set_id, limit=10, offset=0, all_records=False, + def read_members(self, set_id, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves members of a Set given a Set ID. @@ -416,7 +416,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/deposit-profiles' self.cnxn_params['api_uri_full'] += '/deposit-profiles' - def retrieve(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, + def read(self, deposit_profile_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves list of deposit profiles or specific profile @@ -469,7 +469,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/md-import-profiles' self.cnxn_params['api_uri_full'] += '/md-import-profiles' - def retrieve(self, profile_id=None, limit=10, offset=0, all_records=False, + def read(self, profile_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves list of import profiles or specific profile @@ -523,7 +523,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/reminders' self.cnxn_params['api_uri_full'] += '/reminders' - def retrieve(self, reminder_id=None, limit=10, offset=0, all_records=False, + def read(self, reminder_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieves list of reminders or specific reminder. diff --git a/almapipy/users.py b/almapipy/users.py index a3658cf..a1c8bfe 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -2,7 +2,8 @@ from .client import Client from . import utils -from json import loads + +#from json import loads class SubClientUsers(Client): """ @@ -29,7 +30,106 @@ def __init__(self, cnxn_params={}): self.fees = SubClientUsersFees(self.cnxn_params) self.deposits = SubClientUsersDeposits(self.cnxn_params) - def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): + def create(self, identifier, id_type, user_data, raw=False): + """Create a single user if it does not exist yet in Alma + + Args: + identifier (str): The identifier itself for the user. + See: + id_type (str): The identifier type for the user + Values: from the code-table: UserIdentifierTypes + + See: + user_data (dict): Data for user enrollment. + Setting words for fields: [first_name, last_name, + middle_name, email, user_group, ...]. + Format {'field': 'value', 'field2', 'value2'}. + Values(user_group): code-table: UserGroups. + + See + raw (bool): If true, returns raw requests object. + + Returns: (?) + The user (at Alma) if a new user is created. + "{'total_record_count': 0}" if the 'identifier' was already set. + + """ + + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + + data=user_data.copy() + + # Avoid sending 'primary_id' as 'id_type' + args = {} + query = {} + if id_type != 'primary_id': + args['id_type'] = id_type + query['identifiers'] = identifier +# TODO: add 'user_identifier' stuff into 'user_data' + else: + query['primary_id'] = identifier + data['primary_id'] = identifier + + args['q'] = self.__format_query__(query) + + url = self.cnxn_params['api_uri_full'] + + # Search for a user with this 'user_identifier' + response = self.Get(url, args=args, headers=headers, raw=raw) + + """ + print("\nDebug: users.py Create #1") + print(headers) + print(url) + print(args) + print(response) + print("") + """ + +# TODO: ¿what happens when no response? Parse 'requests.models.Response' + if response['total_record_count'] == 0: + # No user exists with this 'identifier': Let's create it. + + """ + # 'user_identifier' chunk + aux_dict = {} + aux_dict['value'] = identifier + aux_dict['id_type'] = {} + aux_dict['id_type']['value'] = id_type + aux_dict['status'] = 'ACTIVE' + aux_dict['segment_type'] = 'External' + data['user_identifier'] = [ aux_dict ] + """ + """ + aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" + data = loads(aux_dict.replace("'", "\"")) + """ + + args.pop('q', None) + + """ + print("\nDebug: users.py Create #2") + print(headers) + print(url) + print(args) + print(data) + """ + + # Send request + response = self.Post(url, data=data, args=args, headers=headers, raw=raw) + + """ + print(response) + print("") + """ + + else: + # User already exist in Alma. + response = {'total_record_count': 0} + + return response + + def read(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a user list or a single user. Args: @@ -55,22 +155,16 @@ def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False """ + args = q_params.copy() + headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} - args = q_params.copy() """ - print("\nDebug: users.py Retrieve #1") + print("\nDebug: users.py Read #1") print(query) print(args) print("") """ - """ - # Avoid sending 'primary_id' as 'id_type' - if ('id_type' in args.keys()) and (args['id_type'] == 'primary_id'): - args.pop('id_type', None) - args['primary_id'] = args.pop('identifiers', None) - """ - print(args) url = self.cnxn_params['api_uri_full'] if user_id: @@ -90,32 +184,33 @@ def retrieve(self, user_id=None, query={}, limit=10, offset=0, all_records=False if query: args['q'] = self.__format_query__(query) - response = self.get(url, args=args, headers=headers, raw=raw) + response = self.Get(url, args=args, headers=headers, raw=raw) if user_id: return response + """ - print("\nDebug: users.py Retrieve #2") + print("\nDebug: users.py Read #2") print(url) print(args) print(headers) print(response) print("") """ + # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='user') return response - def create(self, identifier, id_type, user_data, raw=False): - """Create a single user if it does not exist yet in Alma + def update(self, primary_id, user_data, raw=False): + """Update a single user if it does exist yet in Alma + + WARNING: This function is only for Alma Sorcerer's Apprentices. + Advanced users' data structure knowledge is required. Args: - identifier (str): The identifier itself for the user. - See: - id_type (str): The identifier type for the user - Values: from the code-table: UserIdentifierTypes - + primary_id (str): The primary ididentifier of the user. See: user_data (dict): Data for user enrollment. Setting words for fields: [first_name, last_name, @@ -127,77 +222,55 @@ def create(self, identifier, id_type, user_data, raw=False): raw (bool): If true, returns raw requests object. Returns: (?) - The user (at Alma) if a new user is created. - "{'total_record_count': 0}" if the 'identifier' was already set. + "{'total_record_count': 1}" if the user was updated successfully. + "{'total_record_count': 0}" if there were any trouble. """ headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} - - data=user_data.copy() - - # Avoid sending 'primary_id' as 'id_type' - args = {} - query = {} - if id_type != 'primary_id': - args['id_type'] = id_type - query = {'identifiers': '{}'.format(identifier)} -# TODO: add 'user_identifier' stuff into 'user_data' - else: - query = {'primary_id': '{}'.format(identifier)} - data['primary_id'] = identifier + url = self.cnxn_params['api_uri_full'] + "/" + str(primary_id) - args['q'] = self.__format_query__(query) - - url = self.cnxn_params['api_uri_full'] + # Search for a user with this 'primary_id' + response = self.Get(url, args={}, headers=headers, raw=raw) - # Search for a user with this 'user_identifier' - response = self.get(url, args=args, headers=headers, raw=raw) """ - print("\nDebug: users.py Create #1") + print("\nDebug: users.py Update #1") print(headers) print(url) - print(args) + print(user_data) print(response) - print("") """ - if response['total_record_count'] == 0: - # No user exists with this 'identifier': Let's create it. - # 'user_identifier' chunk - """ - aux_dict = {} - aux_dict['value'] = identifier - aux_dict['id_type'] = {} - aux_dict['id_type']['value'] = id_type - aux_dict['status'] = 'ACTIVE' - aux_dict['segment_type'] = 'External' - data['user_identifier'] = [ aux_dict ] - """ +# TODO: ¿what happens when no response? Parse 'requests.models.Response' + if response['primary_id']: + # Found an user with this 'primary_id': Let's update it. + """ - aux_dict = "{ 'user_identifier': [{ 'value': '" + identifier + "', 'id_type': { 'value': '" + id_type + "' }, 'status': 'ACTIVE', 'segment_type': 'External' }] }" - data = loads(aux_dict.replace("'", "\"")) + print("\nDebug: users.py Update #3") + print(user_data) """ - args.pop('q', None) + + # Send request + self.Put(url, data=user_data, headers=headers, raw=raw) + response = {'total_record_count': 1} + """ - print("\nDebug: users.py Create #2") + print("\nDebug: users.py Update #4") print(headers) print(url) - print(args) - print(data) - """ - response = self.post(url, data=data, args=args, headers=headers, raw=raw) - """ + print(user_data) print(response) + print(raw) print("") """ + else: - # User already exist in Alma. + # (a single) User not found in Alma. response = {'total_record_count': 0} return response - def remove(self, identifier, id_type, raw=False): + def delete(self, identifier, id_type, raw=False): """Remove a single user if it does exist in Alma Args: @@ -222,43 +295,56 @@ def remove(self, identifier, id_type, raw=False): query = {} if id_type != 'primary_id': args['id_type'] = id_type - query = {'identifiers': '{}'.format(identifier)} + query['identifiers'] = identifier # TODO: add 'user_identifier' stuff into 'user_data' else: - query = {'primary_id': '{}'.format(identifier)} + query['primary_id'] = identifier args['q'] = self.__format_query__(query) url = self.cnxn_params['api_uri_full'] # Search for a user with this 'user_identifier' - response = self.get(url, args=args, headers=headers, raw=raw) + response = self.Get(url, args=args, headers=headers, raw=raw) + """ - print("\nDebug: users.py Delete #1") + print("\nDebug: users.py delete #1") print(headers) print(url) print(args) print(response) - print("") """ + +# TODO: ¿what happens when no response? Parse 'requests.models.Response' if response['total_record_count'] == 1: # A single user exists with this 'identifier': Let's remove it. args.clear() - args['primary_id'] = response['user'][0]['primary_id'] + args['primary_id'] = query['primary_id'] url += (str('/' + args['primary_id'])) + """ - print("\nDebug: users.py Delete #2") + print("\nDebug: users.py delete #2") print(headers) print(url) print(args) """ + # Send request - response = self.delete(url, args=args, headers=headers, raw=raw) +# SEE: "TODO: Eval exception 204" at the end of Delete 'function' in 'client.py' +# response = self.Delete(url, args=args, headers=headers, raw=raw) + self.Delete(url, args=args, headers=headers, raw=raw) + response = {'total_record_count': 1} + """ + print("\nDebug: users.py delete #3") print(response) print("") """ + else: + # (a single) User not found in Alma. + response = {'total_record_count': 0} + return response @@ -270,7 +356,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def retrieve(self, user_id, loan_id=None, limit=10, offset=0, + def read(self, user_id, loan_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of loans for a user. @@ -309,13 +395,13 @@ def retrieve(self, user_id, loan_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.get(url, args=args, headers=headers, raw=raw) + response = self.Get(url, args=args, headers=headers, raw=raw) if loan_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__get_all__(url=url, args=args, headers=headers, raw=raw, + response = self.__Get_all__(url=url, args=args, headers=headers, raw=raw, response=response, data_key='item_loan') return response @@ -328,7 +414,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def retrieve(self, user_id, request_id=None, limit=10, offset=0, + def read(self, user_id, request_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of requests for a user. @@ -365,13 +451,13 @@ def retrieve(self, user_id, request_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.get(url, args, raw=raw) + response = self.Get(url, args, raw=raw) if request_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__get_all__(url=url, args=args, raw=raw, + response = self.__Get_all__(url=url, args=args, raw=raw, response=response, data_key='user_request') return response @@ -384,7 +470,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def retrieve(self, user_id, fee_id=None, q_params={}, raw=False): + def read(self, user_id, fee_id=None, q_params={}, raw=False): """Retrieve a list of fines and fees for a user. Args: @@ -406,7 +492,7 @@ def retrieve(self, user_id, fee_id=None, q_params={}, raw=False): args = q_params.copy() args['apikey'] = self.cnxn_params['api_key'] - return self.get(url, args, raw=raw) + return self.Get(url, args, raw=raw) class SubClientUsersDeposits(Client): @@ -417,7 +503,7 @@ def __init__(self, cnxn_params={}): self.cnxn_params['api_uri'] += '/' self.cnxn_params['api_uri_full'] += '/' - def retrieve(self, user_id, deposit_id=None, limit=10, offset=0, + def read(self, user_id, deposit_id=None, limit=10, offset=0, all_records=False, q_params={}, raw=False): """Retrieve a list of deposits for a user. @@ -454,12 +540,12 @@ def retrieve(self, user_id, deposit_id=None, limit=10, offset=0, args['limit'] = limit args['offset'] = int(offset) - response = self.get(url, args, raw=raw) + response = self.Get(url, args, raw=raw) if deposit_id: return response # make multiple api calls until all records are retrieved if all_records: - response = self.__get_all__(url=url, args=args, raw=raw, + response = self.__Get_all__(url=url, args=args, raw=raw, response=response, data_key='user_deposit') return response diff --git a/setup.py b/setup.py index ba213de..9f0117b 100644 --- a/setup.py +++ b/setup.py @@ -12,10 +12,10 @@ from almapipy.__init__ import __project_url__ as url from almapipy.__init__ import __project_description__ as description from almapipy.__init__ import __license__ as license - +from almapipy.__init__ import __project_name__ as name setup( - name = "almapipy", + name = name, version = version, author = author, author_email = author_email, From 7111fbafc36334b6c87bd620215eccb3ccee69fe Mon Sep 17 00:00:00 2001 From: pac0san Date: Tue, 27 Nov 2018 22:33:51 +0100 Subject: [PATCH 38/47] CRUD getting on --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8b42f4..d16bcd7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Post, Put and Delete functions will be gradually added in future releases. | [courses](#access-courses) | X | | | | | [resource sharing partners](#access-resource-sharing-partners) | X | | | | | [task-lists](#access-task-lists) | X | | | | -| [users](#access-users) | X | In Progress | | In Progress | +| [users](#access-users) | X | Testing | Testing | Testing | | [electronic](#access-electronic) | X | | | | ## Use @@ -96,7 +96,8 @@ alma.courses.citations(course_id, reading_list_id) Alma provides a set of Web services for handling user information, enabling you to quickly and easily manipulate user details. These Web services can be used by external systems—such as student information systems (SIS)—to retrieve or update user data. ```python -# Create a user, providing an identifier and some necessary or even additional data (according to each Alma implementation) +# Create a user, providing an identifier and some necessary or even additional data +# (according to each Alma implementation) data = {'first_name': 'Tester #001', 'last_name': 'from Alma', 'account_type': {'value': 'EXTERNAL', 'desc': 'External'}, 'external_id': 'SIS', From a0d2f6b50c8a3e384ea96d9ca2878af2bf5a0743 Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 28 Nov 2018 13:05:59 +0100 Subject: [PATCH 39/47] Some cleaning --- almapipy/__init__.py | 2 +- almapipy/client.py | 54 -------------------- almapipy/users.py | 117 ++++++++----------------------------------- 3 files changed, 22 insertions(+), 151 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index 4866c84..e4bde00 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -24,7 +24,7 @@ __project_description__ = "Python requests wrapper for the Ex Libris Alma API" __project_url__ = "https://github.com/UCDavisLibrary/almapipy" __license__ = "MIT License" -__version__ = "0.1.2.dev2" +__version__ = "0.1.2.dev3" __status__ = "Development" diff --git a/almapipy/client.py b/almapipy/client.py index 37b6494..431bd02 100644 --- a/almapipy/client.py +++ b/almapipy/client.py @@ -75,14 +75,6 @@ def Post(self, url, data, args, headers, raw=False): message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) - """ - print("\nDebug: client.py Post #1") - print(url) - print(data_aux) - print(args_aux) - print(headers_aux) - """ - # Send request response = requests.post(url, data=data_aux, params=args_aux, headers=headers_aux) if raw: @@ -119,26 +111,9 @@ def Get(self, url, args, headers, raw=False): # Preserve Auth and add 'User-Agent' in headers headers_aux['User-Agent'] = self.cnxn_params['User-Agent'] - """ - print("\nDebug: client.py Get #1") - print(url) - print(args_aux) - print(headers_aux) - """ - # Send request. response = requests.get(url, params=args_aux, headers=headers_aux) - """ - print("\nDebug: client.py Get #2") - print(url) - print(args_aux) - print(headers_aux) - print(response) - print(raw) - print("") - """ - if raw: return response @@ -194,25 +169,9 @@ def Put(self, url, data, headers, raw=False): message = "Put content type must be either 'json' or 'xml'" raise utils.ArgError(message) - """ - print("\nDebug: client.py Put #1") - print(url) - print(data_aux) - print(headers_aux) - """ - # Send request response = requests.put(url, data=data_aux, headers=headers_aux) - """ - print("\nDebug: client.py Put #2") - print(url) - print(data_aux) - print(headers_aux) - print(response) - print("") - """ - if raw: return response @@ -255,22 +214,9 @@ def Delete(self, url, args, headers, raw=False): message = "Post content type must be either 'json' or 'xml'" raise utils.ArgError(message) - """ - print("\nDebug: client.py Delete #1") - print(url) - print(args) - print(headers_aux) - """ - # Send request response = requests.delete(url, params=args_aux, headers=headers_aux) - """ - print("\nDebug: client.py Delete #2") - print(response) - print("") - """ - if raw: return response diff --git a/almapipy/users.py b/almapipy/users.py index a1c8bfe..eb5802f 100644 --- a/almapipy/users.py +++ b/almapipy/users.py @@ -30,6 +30,11 @@ def __init__(self, cnxn_params={}): self.fees = SubClientUsersFees(self.cnxn_params) self.deposits = SubClientUsersDeposits(self.cnxn_params) + # Returned values by functions on success/failure (not much "creativity" + # for now) + self.SUCCESS = True + self.FAILURE = False + def create(self, identifier, id_type, user_data, raw=False): """Create a single user if it does not exist yet in Alma @@ -51,7 +56,7 @@ def create(self, identifier, id_type, user_data, raw=False): Returns: (?) The user (at Alma) if a new user is created. - "{'total_record_count': 0}" if the 'identifier' was already set. + "FAILURE" if the 'identifier' was already set. """ @@ -77,15 +82,6 @@ def create(self, identifier, id_type, user_data, raw=False): # Search for a user with this 'user_identifier' response = self.Get(url, args=args, headers=headers, raw=raw) - """ - print("\nDebug: users.py Create #1") - print(headers) - print(url) - print(args) - print(response) - print("") - """ - # TODO: ¿what happens when no response? Parse 'requests.models.Response' if response['total_record_count'] == 0: # No user exists with this 'identifier': Let's create it. @@ -107,25 +103,12 @@ def create(self, identifier, id_type, user_data, raw=False): args.pop('q', None) - """ - print("\nDebug: users.py Create #2") - print(headers) - print(url) - print(args) - print(data) - """ - # Send request response = self.Post(url, data=data, args=args, headers=headers, raw=raw) - """ - print(response) - print("") - """ - else: # User already exist in Alma. - response = {'total_record_count': 0} + response = self.FAILURE return response @@ -157,16 +140,11 @@ def read(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_ args = q_params.copy() + # Set 'API Key' as 'Authorization' at the Header headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} - """ - print("\nDebug: users.py Read #1") - print(query) - print(args) - print("") - """ - url = self.cnxn_params['api_uri_full'] + if user_id: url += ("/" + str(user_id)) else: @@ -188,15 +166,6 @@ def read(self, user_id=None, query={}, limit=10, offset=0, all_records=False, q_ if user_id: return response - """ - print("\nDebug: users.py Read #2") - print(url) - print(args) - print(headers) - print(response) - print("") - """ - # make multiple api calls until all records are retrieved if all_records: response = self.__read_all__(url=url, args=args, headers=headers, raw=raw, @@ -222,51 +191,30 @@ def update(self, primary_id, user_data, raw=False): raw (bool): If true, returns raw requests object. Returns: (?) - "{'total_record_count': 1}" if the user was updated successfully. - "{'total_record_count': 0}" if there were any trouble. + "SUCCESS" if the user was updated successfully. + "FAILURE" if there were any trouble. """ - + + # Set 'API Key' as 'Authorization' at the Header headers = {'Authorization': 'apikey {}'.format(self.cnxn_params['api_key'])} + url = self.cnxn_params['api_uri_full'] + "/" + str(primary_id) - # Search for a user with this 'primary_id' + # Search for a user with this 'primary_id' included in the URL response = self.Get(url, args={}, headers=headers, raw=raw) - """ - print("\nDebug: users.py Update #1") - print(headers) - print(url) - print(user_data) - print(response) - """ - # TODO: ¿what happens when no response? Parse 'requests.models.Response' if response['primary_id']: # Found an user with this 'primary_id': Let's update it. - """ - print("\nDebug: users.py Update #3") - print(user_data) - """ - # Send request self.Put(url, data=user_data, headers=headers, raw=raw) - response = {'total_record_count': 1} - - """ - print("\nDebug: users.py Update #4") - print(headers) - print(url) - print(user_data) - print(response) - print(raw) - print("") - """ + response = self.SUCCESS else: # (a single) User not found in Alma. - response = {'total_record_count': 0} + response = self.FAILURE return response @@ -283,8 +231,8 @@ def delete(self, identifier, id_type, raw=False): raw (bool): If true, returns raw requests object. Returns: (?) - "{'total_record_count': 1}" if the user has been successfully removed. - "{'total_record_count': 0}" if the 'identifier' was not present in Alma. + "SUCCESS" if the user has been successfully removed. + "FAILURE" if the 'identifier' was not present in Alma. """ @@ -307,43 +255,20 @@ def delete(self, identifier, id_type, raw=False): # Search for a user with this 'user_identifier' response = self.Get(url, args=args, headers=headers, raw=raw) - """ - print("\nDebug: users.py delete #1") - print(headers) - print(url) - print(args) - print(response) - """ - # TODO: ¿what happens when no response? Parse 'requests.models.Response' if response['total_record_count'] == 1: # A single user exists with this 'identifier': Let's remove it. args.clear() args['primary_id'] = query['primary_id'] url += (str('/' + args['primary_id'])) - - """ - print("\nDebug: users.py delete #2") - print(headers) - print(url) - print(args) - """ - # Send request # SEE: "TODO: Eval exception 204" at the end of Delete 'function' in 'client.py' # response = self.Delete(url, args=args, headers=headers, raw=raw) self.Delete(url, args=args, headers=headers, raw=raw) - response = {'total_record_count': 1} - - """ - print("\nDebug: users.py delete #3") - print(response) - print("") - """ - + response = self.SUCCESS else: # (a single) User not found in Alma. - response = {'total_record_count': 0} + response = self.FAILURE return response From cd65249f6b092dfc3d51ccf0d5586ce2cab75aaa Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 28 Nov 2018 13:11:53 +0100 Subject: [PATCH 40/47] Increasing the version to 1.0.1.dev0 --- almapipy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index e4bde00..b3e7a4d 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -24,7 +24,7 @@ __project_description__ = "Python requests wrapper for the Ex Libris Alma API" __project_url__ = "https://github.com/UCDavisLibrary/almapipy" __license__ = "MIT License" -__version__ = "0.1.2.dev3" +__version__ = "1.0.1.dev0" __status__ = "Development" From 92037625e2f6472c347b20408a6faf46525574d7 Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 13:04:17 +0100 Subject: [PATCH 41/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d16bcd7..ee403c4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Post, Put and Delete functions will be gradually added in future releases. | [courses](#access-courses) | X | | | | | [resource sharing partners](#access-resource-sharing-partners) | X | | | | | [task-lists](#access-task-lists) | X | | | | -| [users](#access-users) | X | Testing | Testing | Testing | +| [users](#access-users) | X | X | X | X | | [electronic](#access-electronic) | X | | | | ## Use From 857a64a3dee6004986d336d039cc69714f5b3fc0 Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 13:30:24 +0100 Subject: [PATCH 42/47] Update __init__.py --- almapipy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index b3e7a4d..e67dc40 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -22,7 +22,7 @@ __author_email__ = "spelkey@ucdavis.edu" __project_name__ = "almapipy" __project_description__ = "Python requests wrapper for the Ex Libris Alma API" -__project_url__ = "https://github.com/UCDavisLibrary/almapipy" +__project_url__ = "https://github.com/pac0san/almapipy" __license__ = "MIT License" __version__ = "1.0.1.dev0" __status__ = "Development" From d885d0cd51dd084bdb7a5ab2727aa85d96f7b8cb Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 13:38:51 +0100 Subject: [PATCH 43/47] Update __init__.py --- almapipy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/almapipy/__init__.py b/almapipy/__init__.py index e67dc40..0d465f5 100644 --- a/almapipy/__init__.py +++ b/almapipy/__init__.py @@ -18,8 +18,8 @@ from . import utils -__author__ = "Steve Pelkey" -__author_email__ = "spelkey@ucdavis.edu" +__author__ = "Steve Pelkey, Fco. Sanchez" +__author_email__ = "spelkey@ucdavis.edu, GitHub@minarnet.es" __project_name__ = "almapipy" __project_description__ = "Python requests wrapper for the Ex Libris Alma API" __project_url__ = "https://github.com/pac0san/almapipy" From c604e5c1e1363290fa715ca846f420cf5e3f06ef Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 13:41:22 +0100 Subject: [PATCH 44/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee403c4..65f6e02 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ almapipy is python requests wrapper for easily accessing the Ex Libris Alma API. It is designed to be lightweight, and imposes a structure that mimics the [Alma API architecture](https://developers.exlibrisgroup.com/alma/apis). ## Installation -```pip install almapipy``` +```pip install git+https://github.com/pac0san/almapipy``` ## Progress and Roadmap Get functionality has been developed around all the Alma APIs (listed below). From 2b0213ec5181aa7852429ed1d2816a2114e1f266 Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 13:46:27 +0100 Subject: [PATCH 45/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65f6e02..3a288c7 100644 --- a/README.md +++ b/README.md @@ -221,4 +221,4 @@ alma.task_lists.lending.get(library_id) ## Attribution and Contact -* **Author**: [Steve Pelkey](mailto:spelkey@ucdavis.edu) +* **Author**: [Steve Pelkey](mailto:spelkey@ucdavis.edu), [Fco. Sanchez](mailto:GitHub@minarnet.es) From db88abd5edec5e9a9491f60908e15492a4cab952 Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 13:52:24 +0100 Subject: [PATCH 46/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a288c7..e686bf7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ almapipy is python requests wrapper for easily accessing the Ex Libris Alma API. It is designed to be lightweight, and imposes a structure that mimics the [Alma API architecture](https://developers.exlibrisgroup.com/alma/apis). ## Installation -```pip install git+https://github.com/pac0san/almapipy``` +```pip install (--upgrade) git+https://github.com/pac0san/almapipy``` ## Progress and Roadmap Get functionality has been developed around all the Alma APIs (listed below). From 3f7b5a3c0f383a0c7f23b53a0dbaec27f727681a Mon Sep 17 00:00:00 2001 From: pac0san Date: Wed, 27 Mar 2019 14:43:47 +0100 Subject: [PATCH 47/47] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e686bf7..2581fc7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # almapipy: Python Wrapper for Alma API