From 0f996b86fe6e0329cb11202fc8e77c10d4417811 Mon Sep 17 00:00:00 2001 From: Terrell Russell Date: Thu, 12 Feb 2026 11:56:54 -0500 Subject: [PATCH 1/4] [#36] prepare packaging for initial release --- LICENSE | 29 + README.md | 13 +- irods_http_client/__init__.py | 2 +- irods_http_client/collection_operations.py | 740 ++----- irods_http_client/common.py | 11 + irods_http_client/data_object_operations.py | 1804 +++++++---------- irods_http_client/irodsHttpClient.py | 89 +- irods_http_client/query_operations.py | 359 ++-- irods_http_client/resource_operations.py | 578 ++---- irods_http_client/rule_operations.py | 171 +- irods_http_client/ticket_operations.py | 224 +-- irods_http_client/user_group_operations.py | 797 ++------ irods_http_client/zone_operations.py | 282 +-- pyproject.toml | 26 + test/config.py | 111 +- test/test_endpoint_operations.py | 1994 +++++++++++-------- 16 files changed, 2992 insertions(+), 4238 deletions(-) create mode 100644 LICENSE create mode 100644 irods_http_client/common.py create mode 100644 pyproject.toml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4a9c4bb --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024, The University of North Carolina at Chapel Hill +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 02b4475..fd1ea47 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,24 @@ This is a Python wrapper for the [iRODS HTTP API](https://github.com/irods/irods Documentation for the endpoint operations can be found [here](https://github.com/irods/irods_client_http_api/blob/main/API.md). -## Setup -**NOTICE:** This project is not yet available through pip. To use, clone the repository into the desired location. +## Install + +This wrapper is available via pip: + ``` -git clone https://github.com/irods/irods_client_http_python.git +pip install irods-http-client ``` + ## Usage To use the wrapper, follow the steps listed below. ```py -from irods_client import IrodsClient +from irods_http_client.irodsHttpClient import IrodsHttpClient # Create an instance of the wrapper with the base url of the iRODS server to # be accessed. , , and are placeholders, and need # to be replaced by appropriate values. -api = IrodsClient('http://:/irods-http-api/') +api = IrodsHttpClient('http://:/irods-http-api/') # Most endpoint operations require a user to be authenticated in order to # be executed. Authenticate with a username and password, and store the diff --git a/irods_http_client/__init__.py b/irods_http_client/__init__.py index 05e0e52..46a1a9e 100644 --- a/irods_http_client/__init__.py +++ b/irods_http_client/__init__.py @@ -1 +1 @@ -from .irodsHttpClient import IrodsHttpClient \ No newline at end of file +from .irodsHttpClient import IrodsHttpClient diff --git a/irods_http_client/collection_operations.py b/irods_http_client/collection_operations.py index 64fdaf2..f8e1425 100644 --- a/irods_http_client/collection_operations.py +++ b/irods_http_client/collection_operations.py @@ -1,18 +1,18 @@ -import requests +from . import common import json +import requests class Collections: - + def __init__(self, url_base: str): - """" - Initializes Collections with a base url. + """ + Initializes Collections with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - - def create(self, lpath: str, create_intermediates: int=0): + def create(self, lpath: str, create_intermediates: int = 0): """ Creates a new collection. @@ -24,61 +24,32 @@ def create(self, lpath: str, create_intermediates: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(create_intermediates, int)): - raise TypeError('create_intermediates must be an int 1 or 0') - if ((not create_intermediates == 0) and (not create_intermediates == 1)): - raise ValueError('create_intermediates must be an int 1 or 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(create_intermediates, int): + raise TypeError("create_intermediates must be an int 1 or 0") + if (not create_intermediates == 0) and (not create_intermediates == 1): + raise ValueError("create_intermediates must be an int 1 or 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'create', - 'lpath': lpath, - 'create-intermediates': create_intermediates + "op": "create", + "lpath": lpath, + "create-intermediates": create_intermediates, } - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code'] == 0 and rdict['created'] == False: - print('Failed to create collection: \'' + lpath + '\' already exists') - elif rdict['irods_response']['status_code']: - print('Failed to create collection \'' + lpath + '\': iRODS Status Code ' + str(rdict['irods_response']['status_code']) + ' - ' + str(rdict['irods_response']['status_message'])) - else: - print('Collection \'' + lpath + '\' created successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def remove(self, lpath: str, recurse: int=0, no_trash: int=0): + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) + + def remove(self, lpath: str, recurse: int = 0, no_trash: int = 0): """ Removes an existing collection. @@ -91,75 +62,37 @@ def remove(self, lpath: str, recurse: int=0, no_trash: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(recurse, int)): - raise TypeError('recurse must be an int 1 or 0') - if ((not recurse == 0) and (not recurse == 1)): - raise ValueError('recurse must be an int 1 or 0') - if (not isinstance(no_trash, int)): - raise TypeError('no_trash must be an int 1 or 0') - if ((not no_trash == 0) and (not no_trash == 1)): - raise ValueError('no_trash must be an int 1 or 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(recurse, int): + raise TypeError("recurse must be an int 1 or 0") + if (not recurse == 0) and (not recurse == 1): + raise ValueError("recurse must be an int 1 or 0") + if not isinstance(no_trash, int): + raise TypeError("no_trash must be an int 1 or 0") + if (not no_trash == 0) and (not no_trash == 1): + raise ValueError("no_trash must be an int 1 or 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'remove', - 'lpath': lpath, - 'recurse': recurse, - 'no-trash': no_trash + "op": "remove", + "lpath": lpath, + "recurse": recurse, + "no-trash": no_trash, } - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to remove collection \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Collection \'' + lpath + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): #made redundant by updated else, can remove when housekeeping - rdict = r.json() - - print('Failed to remove collection \'' + lpath + '\': iRODS Status Code ' + str(rdict['irods_response']['status_code'])) - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) - def stat(self, lpath: str, ticket: str=''): + def stat(self, lpath: str, ticket: str = ""): """ Gives information about a collection. @@ -171,56 +104,25 @@ def stat(self, lpath: str, ticket: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(ticket, str)): - raise TypeError('ticket must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(ticket, str): + raise TypeError("ticket must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } - params = { - 'op': 'stat', - 'lpath': lpath, - 'ticket': ticket - } + params = {"op": "stat", "lpath": lpath, "ticket": ticket} - r = requests.get(self.url_base + '/collections', params=params, headers=headers) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to retrieve information for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Information for \'' + lpath + '\' retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get(self.url_base + "/collections", params=params, headers=headers) + return common.process_response(r) - def list(self, lpath: str, recurse: int=0, ticket: str=''): + def list(self, lpath: str, recurse: int = 0, ticket: str = ""): """ Shows the contents of a collection @@ -233,61 +135,31 @@ def list(self, lpath: str, recurse: int=0, ticket: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(recurse, int)): - raise TypeError('recurse must be an int 1 or 0') - if ((not recurse == 0) and (not recurse == 1)): - raise ValueError('recurse must be an int 1 or 0') - if (not isinstance(ticket, str)): - raise TypeError('ticket must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(recurse, int): + raise TypeError("recurse must be an int 1 or 0") + if (not recurse == 0) and (not recurse == 1): + raise ValueError("recurse must be an int 1 or 0") + if not isinstance(ticket, str): + raise TypeError("ticket must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } - params = { - 'op': 'list', - 'lpath': lpath, - 'recurse': recurse, - 'ticket': ticket - } + params = {"op": "list", "lpath": lpath, "recurse": recurse, "ticket": ticket} - r = requests.get(self.url_base + '/collections', params=params, headers=headers) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to retrieve list for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('List for \'' + lpath + '\' retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get(self.url_base + "/collections", params=params, headers=headers) + return common.process_response(r) - def set_permission(self, lpath: str, entity_name: str, permission: str, admin: int=0): + def set_permission( + self, lpath: str, entity_name: str, permission: str, admin: int = 0 + ): """ Sets the permission of a user for a given collection. @@ -301,65 +173,40 @@ def set_permission(self, lpath: str, entity_name: str, permission: str, admin: i - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(entity_name, str)): - raise TypeError('entity_name must be a string') - if (not isinstance(permission, str)): - raise TypeError('permission must be a string (\'null\', \'read\', \'write\', or \'own\')') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(entity_name, str): + raise TypeError("entity_name must be a string") + if not isinstance(permission, str): + raise TypeError( + "permission must be a string ('null', 'read', 'write', or 'own')" + ) + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'set_permission', - 'lpath': lpath, - 'entity-name': entity_name, - 'permission': permission, - 'admin': admin + "op": "set_permission", + "lpath": lpath, + "entity-name": entity_name, + "permission": permission, + "admin": admin, } - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to set permission for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Permission for \'' + lpath + '\' set successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) - def set_inheritance(self, lpath: str, enable: int, admin: int=0): + def set_inheritance(self, lpath: str, enable: int, admin: int = 0): """ Sets the inheritance for a collection. @@ -372,70 +219,37 @@ def set_inheritance(self, lpath: str, enable: int, admin: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(enable, int)): - raise TypeError('enable must be an int 1 or 0') - if ((not enable == 0) and (not enable == 1)): - raise ValueError('enable must be an int 1 or 0') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(enable, int): + raise TypeError("enable must be an int 1 or 0") + if (not enable == 0) and (not enable == 1): + raise ValueError("enable must be an int 1 or 0") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'set_inheritance', - 'lpath': lpath, - 'enable': enable, - 'admin': admin + "op": "set_inheritance", + "lpath": lpath, + "enable": enable, + "admin": admin, } - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - operation = '' - if (enable == 1): - operation = 'enabled' - else: - operation = 'disabled' - - if rdict['irods_response']['status_code']: - print('Failed to set inheritance for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Inheritance for \'' + lpath + '\' ' + operation) - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) - def modify_permissions(self, lpath: str, operations: dict, admin: int=0): + def modify_permissions(self, lpath: str, operations: dict, admin: int = 0): """ Modifies permissions for multiple users or groups for a collection. @@ -448,64 +262,37 @@ def modify_permissions(self, lpath: str, operations: dict, admin: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(operations, list)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(operations[0], dict)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(operations, list): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(operations[0], dict): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'modify_permissions', - 'lpath': lpath, - 'operations': json.dumps(operations), - 'admin': admin + "op": "modify_permissions", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, } - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify permissions for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Permissions for \'' + lpath + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) - def modify_metadata(self, lpath: str, operations: dict, admin: int=0): + def modify_metadata(self, lpath: str, operations: dict, admin: int = 0): """ Modifies the metadata for a collection. @@ -518,62 +305,35 @@ def modify_metadata(self, lpath: str, operations: dict, admin: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(operations, list)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(operations[0], dict)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(operations, list): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(operations[0], dict): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'modify_metadata', - 'lpath': lpath, - 'operations': json.dumps(operations), - 'admin': admin + "op": "modify_metadata", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, } - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify metadata for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Metadata for \'' + lpath + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) def rename(self, old_lpath: str, new_lpath: str): """ @@ -582,62 +342,31 @@ def rename(self, old_lpath: str, new_lpath: str): Parameters - old_lpath: The current absolute logical path of the collection. - new_lpath: The absolute logical path of the destination for the collection. - + Returns - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(old_lpath, str)): - raise TypeError('old_lpath must be a string') - if (not isinstance(new_lpath, str)): - raise TypeError('new_lpath must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(old_lpath, str): + raise TypeError("old_lpath must be a string") + if not isinstance(new_lpath, str): + raise TypeError("new_lpath must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'rename', - 'old-lpath': old_lpath, - 'new-lpath': new_lpath - } + data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath} - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to rename \'' + old_lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('\'' + old_lpath + '\' renamed to \'' + new_lpath + '\'') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) - def touch(self, lpath, seconds_since_epoch=-1, reference=''): + def touch(self, lpath, seconds_since_epoch=-1, reference=""): """ Updates mtime for a collection @@ -650,60 +379,33 @@ def touch(self, lpath, seconds_since_epoch=-1, reference=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(seconds_since_epoch, int)): - raise TypeError('seconds_since_epoch must be an int') - if (not seconds_since_epoch >= -1): - raise ValueError('seconds_since_epoch must be greater than or equal to 0 or flag value -1') - if (not isinstance(reference, str)): - raise TypeError('reference must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(seconds_since_epoch, int): + raise TypeError("seconds_since_epoch must be an int") + if not seconds_since_epoch >= -1: + raise ValueError( + "seconds_since_epoch must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(reference, str): + raise TypeError("reference must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'touch', - 'lpath': lpath - } + data = {"op": "touch", "lpath": lpath} + + if seconds_since_epoch != -1: + data["seconds-since-epoch"] = seconds_since_epoch + + if reference != "": + data["reference"] = reference - if (seconds_since_epoch != -1): - data['seconds-since-epoch'] = seconds_since_epoch - - if (reference != ''): - data['reference'] = reference - - r = requests.post(self.url_base + '/collections', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to update mtime for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('mtime for \'' + lpath + '\' updated successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.post(self.url_base + "/collections", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/common.py b/irods_http_client/common.py new file mode 100644 index 0000000..2c2dce5 --- /dev/null +++ b/irods_http_client/common.py @@ -0,0 +1,11 @@ +import json + +def process_response(r): + if r.status_code / 100 == 2: + rdict = r.json() + return {"status_code": r.status_code, "data": rdict} + else: + rdict = None + if r.text != "": + rdict = r.json() + return {"status_code": r.status_code, "data": rdict} diff --git a/irods_http_client/data_object_operations.py b/irods_http_client/data_object_operations.py index be818fd..4ee40b9 100644 --- a/irods_http_client/data_object_operations.py +++ b/irods_http_client/data_object_operations.py @@ -1,17 +1,24 @@ +from . import common import requests -import json class DataObjects: def __init__(self, url_base: str): - """" - Initializes DataObjects with a base url. + """ + Initializes DataObjects with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - - def touch(self, lpath, no_create: int=0, replica_number: int=-1, leaf_resources: str='', seconds_since_epoch=-1, reference=''): + def touch( + self, + lpath, + no_create: int = 0, + replica_number: int = -1, + leaf_resources: str = "", + seconds_since_epoch=-1, + reference="", + ): """ Updates mtime for an existing data object or creates a new one @@ -27,82 +34,58 @@ def touch(self, lpath, no_create: int=0, replica_number: int=-1, leaf_resources: - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(no_create, int)): - raise TypeError('no_create must be an int 1 or 0') - if ((not no_create == 0) and (not no_create == 1)): - raise ValueError('no_create must be an int 1 or 0') - if (not isinstance(replica_number, int)): - raise TypeError('replica_number must be an int') - if (not replica_number >= -1): - raise ValueError('replica_number must be greater than or equal to 0 or flag value -1') - if (not isinstance(leaf_resources, str)): - raise TypeError('leaf_resources must be a string') - if (not isinstance(seconds_since_epoch, int)): - raise TypeError('seconds_since_epoch must be an int') - if (not seconds_since_epoch >= -1): - raise ValueError('seconds_since_epoch must be greater than or equal to 0 or flag value -1') - if (not isinstance(reference, str)): - raise TypeError('reference must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(no_create, int): + raise TypeError("no_create must be an int 1 or 0") + if (not no_create == 0) and (not no_create == 1): + raise ValueError("no_create must be an int 1 or 0") + if not isinstance(replica_number, int): + raise TypeError("replica_number must be an int") + if not replica_number >= -1: + raise ValueError( + "replica_number must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(leaf_resources, str): + raise TypeError("leaf_resources must be a string") + if not isinstance(seconds_since_epoch, int): + raise TypeError("seconds_since_epoch must be an int") + if not seconds_since_epoch >= -1: + raise ValueError( + "seconds_since_epoch must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(reference, str): + raise TypeError("reference must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'touch', - 'lpath': lpath, - 'no-create': no_create - } + data = {"op": "touch", "lpath": lpath, "no-create": no_create} - if (seconds_since_epoch != -1): - data['seconds-since-epoch'] = seconds_since_epoch - - if (replica_number != -1): - data['replica-number'] = replica_number - - if (leaf_resources != ''): - data['leaf-resources'] = leaf_resources - - if (reference != ''): - data['reference'] = reference - - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to touch data object \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Data object \'' + lpath + '\' touched successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + if seconds_since_epoch != -1: + data["seconds-since-epoch"] = seconds_since_epoch + + if replica_number != -1: + data["replica-number"] = replica_number + + if leaf_resources != "": + data["leaf-resources"] = leaf_resources + + if reference != "": + data["reference"] = reference + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - def remove(self, lpath: str, catalog_only: int=0, no_trash: int=0, admin: int=0): + def remove( + self, lpath: str, catalog_only: int = 0, no_trash: int = 0, admin: int = 0 + ): """ Removes an existing data object. @@ -116,78 +99,50 @@ def remove(self, lpath: str, catalog_only: int=0, no_trash: int=0, admin: int=0) - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(catalog_only, int)): - raise TypeError('catalog_only must be an int 1 or 0') - if ((not catalog_only == 0) and (not catalog_only == 1)): - raise ValueError('catalog_only must be an int 1 or 0') - if (not isinstance(no_trash, int)): - raise TypeError('no_trash must be an int 1 or 0') - if ((not no_trash == 0) and (not no_trash == 1)): - raise ValueError('no_trash must be an int 1 or 0') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(catalog_only, int): + raise TypeError("catalog_only must be an int 1 or 0") + if (not catalog_only == 0) and (not catalog_only == 1): + raise ValueError("catalog_only must be an int 1 or 0") + if not isinstance(no_trash, int): + raise TypeError("no_trash must be an int 1 or 0") + if (not no_trash == 0) and (not no_trash == 1): + raise ValueError("no_trash must be an int 1 or 0") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'remove', - 'lpath': lpath, - 'catalog-only': catalog_only, - 'no-trash': no_trash, - 'admin': admin + "op": "remove", + "lpath": lpath, + "catalog-only": catalog_only, + "no-trash": no_trash, + "admin": admin, } - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to remove data object \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Data object \'' + lpath + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - rdict = r.json() - print('Failed to remove data object \'' + lpath + '\': iRODS Status Code ' + str(rdict['irods_response']['status_code'])) - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def calculate_checksum(self, lpath: str, resource: str='', replica_number: int=-1, force: int=0, all: int=0, admin: int=0): + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def calculate_checksum( + self, + lpath: str, + resource: str = "", + replica_number: int = -1, + force: int = 0, + all: int = 0, + admin: int = 0, + ): """ Calculates the checksum for a data object. @@ -203,81 +158,63 @@ def calculate_checksum(self, lpath: str, resource: str='', replica_number: int=- - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise T('lpath must be a string') - if (not isinstance(resource, str)): - raise T('resource must be a string') - if (not isinstance(replica_number, int)): - raise T('replica_number must be an int') - if (not replica_number >= -1): - raise ValueError('replica number must be greater than or equal to 0 or flag value -1') - if (not isinstance(force, int)): - raise TypeError('force must be an int 1 or 0') - if ((not force == 0) and (not force == 1)): - raise ValueError('force must be an int 1 or 0') - if (not isinstance(all, int)): - raise TypeError('all must be an int 1 or 0') - if ((not all == 0) and (not all == 1)): - raise ValueError('all must be an int 1 or 0') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise T("lpath must be a string") + if not isinstance(resource, str): + raise T("resource must be a string") + if not isinstance(replica_number, int): + raise T("replica_number must be an int") + if not replica_number >= -1: + raise ValueError( + "replica number must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(force, int): + raise TypeError("force must be an int 1 or 0") + if (not force == 0) and (not force == 1): + raise ValueError("force must be an int 1 or 0") + if not isinstance(all, int): + raise TypeError("all must be an int 1 or 0") + if (not all == 0) and (not all == 1): + raise ValueError("all must be an int 1 or 0") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'calculate_checksum', - 'lpath': lpath, - 'force': force, - 'all': all, - 'admin': admin + "op": "calculate_checksum", + "lpath": lpath, + "force": force, + "all": all, + "admin": admin, } - if (resource != ''): - data['resource'] = resource - - if (replica_number != -1): - data['replica-number'] = replica_number - - - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to calculate checksum for data object \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Checksum for data object \'' + lpath + '\' calculated successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + if resource != "": + data["resource"] = resource + + if replica_number != -1: + data["replica-number"] = replica_number - def verify_checksum(self, lpath: str, resource: str='', replica_number: int=-1, compute_checksums: int=0, admin: int=0): + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def verify_checksum( + self, + lpath: str, + resource: str = "", + replica_number: int = -1, + compute_checksums: int = 0, + admin: int = 0, + ): """ Verifies the checksum for a data object. @@ -292,76 +229,51 @@ def verify_checksum(self, lpath: str, resource: str='', replica_number: int=-1, - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(resource, str)): - raise TypeError('resource must be a string') - if (not isinstance(replica_number, int)): - raise TypeError('replica_number must be an int') - if (not replica_number >= -1): - raise ValueError('replica_number must be greater than or equal to 0 or flag value -1') - if (not isinstance(compute_checksums, int)): - raise TypeError('compute_checksums must be an int 1 or 0') - if ((not compute_checksums == 0) and (not compute_checksums == 1)): - raise ValueError('force must be an int 1 or 0') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(resource, str): + raise TypeError("resource must be a string") + if not isinstance(replica_number, int): + raise TypeError("replica_number must be an int") + if not replica_number >= -1: + raise ValueError( + "replica_number must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(compute_checksums, int): + raise TypeError("compute_checksums must be an int 1 or 0") + if (not compute_checksums == 0) and (not compute_checksums == 1): + raise ValueError("force must be an int 1 or 0") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'calculate_checksum', - 'lpath': lpath, - 'compute-checksums': compute_checksums, - 'admin': admin + "op": "calculate_checksum", + "lpath": lpath, + "compute-checksums": compute_checksums, + "admin": admin, } - if (resource != ''): - data['resource'] = resource - - if (replica_number != -1): - data['replica-number'] = replica_number - - - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to verify checksum for data object \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Checksum for data object \'' + lpath + '\' verified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + if resource != "": + data["resource"] = resource + + if replica_number != -1: + data["replica-number"] = replica_number - def stat(self, lpath: str, ticket: str=''): + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def stat(self, lpath: str, ticket: str = ""): """ Gives information about a data object. @@ -373,52 +285,23 @@ def stat(self, lpath: str, ticket: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(ticket, str)): - raise TypeError('ticket must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(ticket, str): + raise TypeError("ticket must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } - params = { - 'op': 'stat', - 'lpath': lpath, - 'ticket': ticket - } + params = {"op": "stat", "lpath": lpath, "ticket": ticket} - r = requests.get(self.url_base + '/data-objects', params=params, headers=headers) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to retrieve information for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Information for \'' + lpath + '\' retrieved successfully') - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get(self.url_base + "/data-objects", params=params, headers=headers) + return common.process_response(r) def rename(self, old_lpath: str, new_lpath: str): """ @@ -432,56 +315,33 @@ def rename(self, old_lpath: str, new_lpath: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(old_lpath, str)): - raise TypeError('old_lpath must be a string') - if (not isinstance(new_lpath, str)): - raise TypeError('new_lpath must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(old_lpath, str): + raise TypeError("old_lpath must be a string") + if not isinstance(new_lpath, str): + raise TypeError("new_lpath must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'rename', - 'old-lpath': old_lpath, - 'new-lpath': new_lpath - } + data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath} - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to rename \'' + old_lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('\'' + old_lpath + '\' renamed to \'' + new_lpath + '\'') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - def copy(self, src_lpath: str, dst_lpath: str, src_resource: str='', dst_resource: str='', overwrite: int=0): + def copy( + self, + src_lpath: str, + dst_lpath: str, + src_resource: str = "", + dst_resource: str = "", + overwrite: int = 0, + ): """ Copies a data object. @@ -496,71 +356,47 @@ def copy(self, src_lpath: str, dst_lpath: str, src_resource: str='', dst_resourc - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(src_lpath, str)): - raise TypeError('src_lpath must be a string') - if (not isinstance(dst_lpath, str)): - raise TypeError('dst_lpath must be a string') - if (not isinstance(src_resource, str)): - raise TypeError('src_resource must be a string') - if (not isinstance(dst_resource, str)): - raise TypeError('dst_lpath must be a string') - if (not isinstance(overwrite, int)): - raise TypeError('overwrite must be an int 1 or 0') - if ((not overwrite == 0) and (not overwrite == 1)): - raise ValueError('overwrite must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(src_lpath, str): + raise TypeError("src_lpath must be a string") + if not isinstance(dst_lpath, str): + raise TypeError("dst_lpath must be a string") + if not isinstance(src_resource, str): + raise TypeError("src_resource must be a string") + if not isinstance(dst_resource, str): + raise TypeError("dst_lpath must be a string") + if not isinstance(overwrite, int): + raise TypeError("overwrite must be an int 1 or 0") + if (not overwrite == 0) and (not overwrite == 1): + raise ValueError("overwrite must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'copy', - 'src-lpath': src_lpath, - 'dst-lpath': dst_lpath, - 'overwrite': overwrite + "op": "copy", + "src-lpath": src_lpath, + "dst-lpath": dst_lpath, + "overwrite": overwrite, } - if (src_resource != ''): - data['src-resource'] = src_resource - - if (dst_resource != ''): - data['dst-resource'] = dst_resource - - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to copy \'' + src_lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('\'' + src_lpath + '\' copied to \'' + dst_lpath + '\'') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + if src_resource != "": + data["src-resource"] = src_resource - def replicate(self, lpath: str, src_resource: str='', dst_resource: str='', admin: int=0): + if dst_resource != "": + data["dst-resource"] = dst_resource + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def replicate( + self, lpath: str, src_resource: str = "", dst_resource: str = "", admin: int = 0 + ): """ Replicates a data object from one resource to another. @@ -574,70 +410,40 @@ def replicate(self, lpath: str, src_resource: str='', dst_resource: str='', admi - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(src_resource, str)): - raise TypeError('src_resource must be a string') - if (not isinstance(dst_resource, str)): - raise TypeError('dst_lpath must be a string') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - - headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', - } + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(src_resource, str): + raise TypeError("src_resource must be a string") + if not isinstance(dst_resource, str): + raise TypeError("dst_lpath must be a string") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") - data = { - 'op': 'replicate', - 'lpath': lpath, - 'admin': admin + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - if (src_resource != ''): - data['src-resource'] = src_resource + data = {"op": "replicate", "lpath": lpath, "admin": admin} - if (dst_resource != ''): - data['dst-resource'] = dst_resource + if src_resource != "": + data["src-resource"] = src_resource - print(data) + if dst_resource != "": + data["dst-resource"] = dst_resource - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to replicate \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('\'' + lpath + '\' replicated from \'' + src_resource + '\' to \'' + dst_resource + '\'') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - def trim(self, lpath: str, replica_number: int, catalog_only: int=0, admin: int=0): + def trim( + self, lpath: str, replica_number: int, catalog_only: int = 0, admin: int = 0 + ): """ Trims an existing replica or removes its catalog entry. @@ -651,66 +457,48 @@ def trim(self, lpath: str, replica_number: int, catalog_only: int=0, admin: int= - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise T('lpath must be a string') - if (not isinstance(replica_number, int)): - raise T('replica_number must be an int') - if (not isinstance(catalog_only, int)): - raise TypeError('catalog_only must be an int 1 or 0') - if ((not catalog_only == 0) and (not catalog_only == 1)): - raise ValueError('catalog_only must be an int 1 or 0') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise T("lpath must be a string") + if not isinstance(replica_number, int): + raise T("replica_number must be an int") + if not isinstance(catalog_only, int): + raise TypeError("catalog_only must be an int 1 or 0") + if (not catalog_only == 0) and (not catalog_only == 1): + raise ValueError("catalog_only must be an int 1 or 0") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'trim', - 'lpath': lpath, - 'replica-number': replica_number, - 'catalog-only': catalog_only, - 'admin': admin + "op": "trim", + "lpath": lpath, + "replica-number": replica_number, + "catalog-only": catalog_only, + "admin": admin, } - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to trim \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Sucessfully trimmed \'' + lpath + '\'') - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def register(self, lpath: str, ppath: str, resource: str, as_additional_replica: int=0, data_size: int=-1, checksum: str=''): + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def register( + self, + lpath: str, + ppath: str, + resource: str, + as_additional_replica: int = 0, + data_size: int = -1, + checksum: str = "", + ): """ Registers a data object/replica into the catalog. @@ -726,76 +514,52 @@ def register(self, lpath: str, ppath: str, resource: str, as_additional_replica: - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(ppath, str)): - raise TypeError('ppath must be a string') - if (not isinstance(resource, str)): - raise TypeError('resource must be a string') - if (not isinstance(as_additional_replica, int)): - raise TypeError('as_additional_replica must be an int 1 or 0') - if ((not as_additional_replica == 0) and (not as_additional_replica == 1)): - raise ValueError('as_additional_replica must be an int 1 or 0') - if (not isinstance(data_size, int)): - raise TypeError('data_size must be an int') - if (not data_size >= -1): - raise ValueError('data_size must be greater than or equal to 0 or flag value -1') - if (not isinstance(checksum, str)): - raise TypeError('checksum must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(ppath, str): + raise TypeError("ppath must be a string") + if not isinstance(resource, str): + raise TypeError("resource must be a string") + if not isinstance(as_additional_replica, int): + raise TypeError("as_additional_replica must be an int 1 or 0") + if (not as_additional_replica == 0) and (not as_additional_replica == 1): + raise ValueError("as_additional_replica must be an int 1 or 0") + if not isinstance(data_size, int): + raise TypeError("data_size must be an int") + if not data_size >= -1: + raise ValueError( + "data_size must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(checksum, str): + raise TypeError("checksum must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'register', - 'lpath': lpath, - 'ppath': ppath, - 'resource': resource, - 'as_additional_replica': as_additional_replica + "op": "register", + "lpath": lpath, + "ppath": ppath, + "resource": resource, + "as_additional_replica": as_additional_replica, } - if (data_size != -1): - data['data-size'] = data_size - - if (checksum != ''): - data['checksum'] = checksum + if data_size != -1: + data["data-size"] = data_size - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) + if checksum != "": + data["checksum"] = checksum - if (r.status_code / 100 == 2): - rdict = r.json() + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - if rdict['irods_response']['status_code']: - print('Failed to register \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Sucessfully registered \'' + lpath + '\'') - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def read(self, lpath: str, offset: int=0, count: int=-1, ticket: str=''): + def read(self, lpath: str, offset: int = 0, count: int = -1, ticket: str = ""): """ Reads bytes from a data object. @@ -809,58 +573,52 @@ def read(self, lpath: str, offset: int=0, count: int=-1, ticket: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(offset, int)): - raise TypeError('offset must be an int') - if (not isinstance(count, int)): - raise TypeError('count must be an int') - if (not count >= -1): - raise ValueError('count must be greater than or equal to 0 or flag value -1') - if (not isinstance(ticket, str)): - raise TypeError('ticket must be a string') - - headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', - } + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(offset, int): + raise TypeError("offset must be an int") + if not isinstance(count, int): + raise TypeError("count must be an int") + if not count >= -1: + raise ValueError( + "count must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(ticket, str): + raise TypeError("ticket must be a string") - params = { - 'op': 'read', - 'lpath': lpath, - 'offset': offset + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - if (count != -1): - params['count'] = count - - if (ticket != ''): - params['ticket'] = ticket - - r = requests.get(self.url_base + '/data-objects',params=params , headers=headers) - - if (r.status_code / 100 == 2): - print('Sucessfully read \'' + lpath + '\'') - return(r.text) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def write(self, bytes, lpath: str='', resource: str='', offset: int=0, truncate: int=1, append: int=0, parallel_write_handle: str='', stream_index: int=-1): + params = {"op": "read", "lpath": lpath, "offset": offset} + + if count != -1: + params["count"] = count + + if ticket != "": + params["ticket"] = ticket + + r = requests.get( + self.url_base + "/data-objects", params=params, headers=headers + ) + return common.process_response(r) + + def write( + self, + bytes, + lpath: str = "", + resource: str = "", + offset: int = 0, + truncate: int = 1, + append: int = 0, + parallel_write_handle: str = "", + stream_index: int = -1, + ): """ Writes bytes to a data object. @@ -878,87 +636,70 @@ def write(self, bytes, lpath: str='', resource: str='', offset: int=0, truncate: - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(resource, str)): - raise TypeError('resource must be a string') - if (not isinstance(offset, int)): - raise TypeError('offset must be an int') - if (not offset >= 0): - raise ValueError('offset must be greater than or equal to 0') - if (not isinstance(truncate, int)): - raise TypeError('truncate must be an int 1 or 0') - if ((not truncate == 0) and (not truncate == 1)): - raise ValueError('truncate must be an int 1 or 0') - if (not isinstance(append, int)): - raise TypeError('append must be an int 1 or 0') - if ((not append == 0) and (not append == 1)): - raise ValueError('append must be an int 1 or 0') - if (not isinstance(parallel_write_handle, str)): - raise TypeError('parallel_write_handle must be a string') - if (not isinstance(stream_index, int)): - raise TypeError('stream_index must be an int') - if (not stream_index >= -1): - raise ValueError('stream_index must be greater than or equal to 0 or flag value -1') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(resource, str): + raise TypeError("resource must be a string") + if not isinstance(offset, int): + raise TypeError("offset must be an int") + if not offset >= 0: + raise ValueError("offset must be greater than or equal to 0") + if not isinstance(truncate, int): + raise TypeError("truncate must be an int 1 or 0") + if (not truncate == 0) and (not truncate == 1): + raise ValueError("truncate must be an int 1 or 0") + if not isinstance(append, int): + raise TypeError("append must be an int 1 or 0") + if (not append == 0) and (not append == 1): + raise ValueError("append must be an int 1 or 0") + if not isinstance(parallel_write_handle, str): + raise TypeError("parallel_write_handle must be a string") + if not isinstance(stream_index, int): + raise TypeError("stream_index must be an int") + if not stream_index >= -1: + raise ValueError( + "stream_index must be greater than or equal to 0 or flag value -1" + ) + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'write', - 'offset': offset, - 'truncate': truncate, - 'append': append, - 'bytes': bytes + "op": "write", + "offset": offset, + "truncate": truncate, + "append": append, + "bytes": bytes, } - if (parallel_write_handle != ''): - data['parallel-write-handle'] = parallel_write_handle + if parallel_write_handle != "": + data["parallel-write-handle"] = parallel_write_handle else: - data['lpath'] = lpath - - if (resource != ''): - data['resource'] = resource + data["lpath"] = lpath - if (stream_index != -1): - data['stream-index'] = stream_index + if resource != "": + data["resource"] = resource - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) + if stream_index != -1: + data["stream-index"] = stream_index - if (r.status_code / 100 == 2): - rdict = r.json() + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - if rdict['irods_response']['status_code']: - print('Failed to write to \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Sucessfully wrote to \'' + lpath + '\'') - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def parallel_write_init(self, lpath: str, stream_count: int, truncate: int=1, append: int=0, ticket: str=''): + def parallel_write_init( + self, + lpath: str, + stream_count: int, + truncate: int = 1, + append: int = 0, + ticket: str = "", + ): """ Initializes server-side state for parallel writing. @@ -974,71 +715,47 @@ def parallel_write_init(self, lpath: str, stream_count: int, truncate: int=1, ap - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(stream_count, int)): - raise TypeError('stream_count must be an int') - if (not stream_count >= 0): - raise ValueError('stream_count must be greater than or equal to 0 or flag value -1') - if (not isinstance(truncate, int)): - raise TypeError('truncate must be an int 1 or 0') - if ((not truncate == 0) and (not truncate == 1)): - raise ValueError('truncate must be an int 1 or 0') - if (not isinstance(append, int)): - raise TypeError('append must be an int 1 or 0') - if ((not append == 0) and (not append == 1)): - raise ValueError('append must be an int 1 or 0') - if (not isinstance(ticket, str)): - raise TypeError('ticket must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(stream_count, int): + raise TypeError("stream_count must be an int") + if not stream_count >= 0: + raise ValueError( + "stream_count must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(truncate, int): + raise TypeError("truncate must be an int 1 or 0") + if (not truncate == 0) and (not truncate == 1): + raise ValueError("truncate must be an int 1 or 0") + if not isinstance(append, int): + raise TypeError("append must be an int 1 or 0") + if (not append == 0) and (not append == 1): + raise ValueError("append must be an int 1 or 0") + if not isinstance(ticket, str): + raise TypeError("ticket must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'parallel_write_init', - 'lpath': lpath, - 'stream-count': stream_count, - 'truncate': truncate, - 'append': append + "op": "parallel_write_init", + "lpath": lpath, + "stream-count": stream_count, + "truncate": truncate, + "append": append, } - if (ticket != ''): - data['ticket'] = ticket - - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) + if ticket != "": + data["ticket"] = ticket - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to open parallel write to \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Sucessfully opened parallel write to \'' + lpath + '\'') - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) def parallel_write_shutdown(self, parallel_write_handle: str): """ @@ -1051,53 +768,27 @@ def parallel_write_shutdown(self, parallel_write_handle: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(parallel_write_handle, str)): - raise TypeError('parallel_write_handle must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(parallel_write_handle, str): + raise TypeError("parallel_write_handle must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'parallel_write_shutdown', - 'parallel-write-handle': parallel_write_handle + "op": "parallel_write_shutdown", + "parallel-write-handle": parallel_write_handle, } - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - if rdict['irods_response']['status_code']: - print('Failed to close parallel write: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Sucessfully closed parallel write.') - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def modify_metadata(self, lpath: str, operations: list, admin: int=0): + def modify_metadata(self, lpath: str, operations: list, admin: int = 0): """ Modifies the metadata for a data object @@ -1110,64 +801,39 @@ def modify_metadata(self, lpath: str, operations: list, admin: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(operations, list)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(operations[0], dict)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(operations, list): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(operations[0], dict): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'modify_metadata', - 'lpath': lpath, - 'operations': json.dumps(operations), - 'admin': admin + "op": "modify_metadata", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, } - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify metadata for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Metadata for \'' + lpath + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) - def set_permission(self, lpath: str, entity_name: str, permission: str, admin: int=0): + def set_permission( + self, lpath: str, entity_name: str, permission: str, admin: int = 0 + ): """ Sets the permission of a user for a given data object @@ -1181,65 +847,40 @@ def set_permission(self, lpath: str, entity_name: str, permission: str, admin: i - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(entity_name, str)): - raise TypeError('entity_name must be a string') - if (not isinstance(permission, str)): - raise TypeError('permission must be a string (\'null\', \'read\', \'write\', or \'own\')') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(entity_name, str): + raise TypeError("entity_name must be a string") + if not isinstance(permission, str): + raise TypeError( + "permission must be a string ('null', 'read', 'write', or 'own')" + ) + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'set_permission', - 'lpath': lpath, - 'entity-name': entity_name, - 'permission': permission, - 'admin': admin + "op": "set_permission", + "lpath": lpath, + "entity-name": entity_name, + "permission": permission, + "admin": admin, } - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to set permission for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Permission for \'' + lpath + '\' set successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def modify_permissions(self, lpath: str, operations: list, admin: int=0): + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def modify_permissions(self, lpath: str, operations: list, admin: int = 0): """ Modifies permissions for multiple users or groups for a data object. @@ -1252,72 +893,60 @@ def modify_permissions(self, lpath: str, operations: list, admin: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(operations, list)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(operations[0], dict)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(operations, list): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(operations[0], dict): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'modify_permissions', - 'lpath': lpath, - 'operations': json.dumps(operations), - 'admin': admin + "op": "modify_permissions", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, } - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify permissions for \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Permissions for \'' + lpath + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def modify_replica(self, lpath: str, resource_hierarchy: str='', replica_number: int=-1, new_data_checksum: str='', - new_data_comments: str='', new_data_create_time: int=-1, new_data_expiry: int=-1, - new_data_mode: str='', new_data_modify_time: str='', new_data_path: str='', - new_data_replica_number: int=-1, new_data_replica_status: int=-1, new_data_resource_id: int=-1, - new_data_size: int=-1, new_data_status: str='', new_data_type_name: str='', new_data_version: int=-1): + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) + + def modify_replica( + self, + lpath: str, + resource_hierarchy: str = "", + replica_number: int = -1, + new_data_checksum: str = "", + new_data_comments: str = "", + new_data_create_time: int = -1, + new_data_expiry: int = -1, + new_data_mode: str = "", + new_data_modify_time: str = "", + new_data_path: str = "", + new_data_replica_number: int = -1, + new_data_replica_status: int = -1, + new_data_resource_id: int = -1, + new_data_size: int = -1, + new_data_status: str = "", + new_data_type_name: str = "", + new_data_version: int = -1, + ): """ Modifies properties of a single replica. - WARNING: + WARNING: This operation requires rodsadmin level privileges and should only be used when there isn't a safer option. Misuse can lead to catalog inconsistencies and unexpected behavior. @@ -1348,164 +977,151 @@ def modify_replica(self, lpath: str, resource_hierarchy: str='', replica_number: - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be a string') - if (not isinstance(resource_hierarchy, str)): - raise TypeError('resource_hierarchy must be a string') - if (not isinstance(replica_number, int)): - raise TypeError('replica_number must be an int') - if ((resource_hierarchy != '') and (replica_number != -1)): - raise ValueError('replica_hierarchy and replica_number are mutually exclusive') - if (not isinstance(new_data_checksum, str)): - raise TypeError('new_data_checksum must be a string') - if (not isinstance(new_data_comments, str)): - raise TypeError('new_data_comments must be a string') - if (not isinstance(new_data_create_time, int)): - raise TypeError('new_data_create_time must be an int') - if (not new_data_create_time >= -1): - raise ValueError('new_data_create_time must be greater than or equal to 0 or flag value -1') - if (not isinstance(new_data_expiry, int)): - raise TypeError('new_data_expiry must be an int') - if (not new_data_expiry >= -1): - raise ValueError('new_data_expiry must be greater than or equal to 0 or flag value -1') - if (not isinstance(new_data_mode, str)): - raise TypeError('new_data_mode must be a string') - if (not isinstance(new_data_modify_time, str)): - raise TypeError('new_data_modify_time must be a string') - if (not isinstance(new_data_path, str)): - raise TypeError('new_data_path must be a string') - if (not isinstance(new_data_replica_number, int)): - raise TypeError('new_data_replica_number must be an int') - if (not new_data_replica_number >= -1): - raise ValueError('new_data_replica_number must be greater than or equal to 0 or flag value -1') - if (not isinstance(new_data_replica_status, int)): - raise TypeError('new_data_replica_status must be an int') - if (not new_data_replica_status >= -1): - raise ValueError('new_data_replica_status must be greater than or equal to 0 or flag value -1') - if (not isinstance(new_data_resource_id, int)): - raise TypeError('new_data_resource_id must be an int') - if (not new_data_resource_id >= -1): - raise ValueError('new_data_resource_id must be greater than or equal to 0 or flag value -1') - if (not isinstance(new_data_size, int)): - raise TypeError('new_data_size must be an int') - if (not new_data_size >= -1): - raise ValueError('new_data_size must be greater than or equal to 0 or flag value -1') - if (not isinstance(new_data_status, str)): - raise TypeError('new_data_status must be a string') - if (not isinstance(new_data_type_name, str)): - raise TypeError('new_data_type_name must be a string') - if (not isinstance(new_data_version, int)): - raise TypeError('new_data_version must be an int') - if (not new_data_version >= -1): - raise ValueError('new_data_version must be greater than or equal to 0 or flag value -1') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be a string") + if not isinstance(resource_hierarchy, str): + raise TypeError("resource_hierarchy must be a string") + if not isinstance(replica_number, int): + raise TypeError("replica_number must be an int") + if (resource_hierarchy != "") and (replica_number != -1): + raise ValueError( + "replica_hierarchy and replica_number are mutually exclusive" + ) + if not isinstance(new_data_checksum, str): + raise TypeError("new_data_checksum must be a string") + if not isinstance(new_data_comments, str): + raise TypeError("new_data_comments must be a string") + if not isinstance(new_data_create_time, int): + raise TypeError("new_data_create_time must be an int") + if not new_data_create_time >= -1: + raise ValueError( + "new_data_create_time must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(new_data_expiry, int): + raise TypeError("new_data_expiry must be an int") + if not new_data_expiry >= -1: + raise ValueError( + "new_data_expiry must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(new_data_mode, str): + raise TypeError("new_data_mode must be a string") + if not isinstance(new_data_modify_time, str): + raise TypeError("new_data_modify_time must be a string") + if not isinstance(new_data_path, str): + raise TypeError("new_data_path must be a string") + if not isinstance(new_data_replica_number, int): + raise TypeError("new_data_replica_number must be an int") + if not new_data_replica_number >= -1: + raise ValueError( + "new_data_replica_number must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(new_data_replica_status, int): + raise TypeError("new_data_replica_status must be an int") + if not new_data_replica_status >= -1: + raise ValueError( + "new_data_replica_status must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(new_data_resource_id, int): + raise TypeError("new_data_resource_id must be an int") + if not new_data_resource_id >= -1: + raise ValueError( + "new_data_resource_id must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(new_data_size, int): + raise TypeError("new_data_size must be an int") + if not new_data_size >= -1: + raise ValueError( + "new_data_size must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(new_data_status, str): + raise TypeError("new_data_status must be a string") + if not isinstance(new_data_type_name, str): + raise TypeError("new_data_type_name must be a string") + if not isinstance(new_data_version, int): + raise TypeError("new_data_version must be an int") + if not new_data_version >= -1: + raise ValueError( + "new_data_version must be greater than or equal to 0 or flag value -1" + ) + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'modify_permissions', - 'lpath': lpath - } + data = {"op": "modify_permissions", "lpath": lpath} - if (resource_hierarchy != ''): - data['resource-hierarchy'] = resource_hierarchy - - if (replica_number != -1): - data['replica-numbeer'] = replica_number + if resource_hierarchy != "": + data["resource-hierarchy"] = resource_hierarchy + + if replica_number != -1: + data["replica-numbeer"] = replica_number # Boolean for checking if the user passed in any new_data parameters no_params = True - if (new_data_checksum != ''): - data['new-data-checksum'] = new_data_checksum - no_params = False - - if (new_data_comments != ''): - data['new-data-comments'] = new_data_comments + if new_data_checksum != "": + data["new-data-checksum"] = new_data_checksum no_params = False - - if (new_data_create_time != -1): - data['new-data-create-time'] = new_data_create_time - no_params = False - - if (new_data_expiry != -1): - data['new-data-expiry'] = new_data_expiry + + if new_data_comments != "": + data["new-data-comments"] = new_data_comments no_params = False - if (new_data_mode != ''): - data['new-data-mode'] = new_data_mode + if new_data_create_time != -1: + data["new-data-create-time"] = new_data_create_time no_params = False - if (new_data_modify_time != ''): - data['new-data-modify-time'] = new_data_modify_time + if new_data_expiry != -1: + data["new-data-expiry"] = new_data_expiry no_params = False - if (new_data_path != ''): - data['new-data-path'] = new_data_path + if new_data_mode != "": + data["new-data-mode"] = new_data_mode no_params = False - if (new_data_replica_number != -1): - data['new-data-replica-number'] = new_data_replica_number + if new_data_modify_time != "": + data["new-data-modify-time"] = new_data_modify_time no_params = False - if (new_data_replica_status != -1): - data['new-data-replica-status'] = new_data_replica_status + if new_data_path != "": + data["new-data-path"] = new_data_path no_params = False - if (new_data_resource_id != -1): - data['new-data-resource-id'] = new_data_resource_id + if new_data_replica_number != -1: + data["new-data-replica-number"] = new_data_replica_number no_params = False - if (new_data_size != -1): - data['new-data-size'] = new_data_size + if new_data_replica_status != -1: + data["new-data-replica-status"] = new_data_replica_status no_params = False - - if (new_data_status != ''): - data['new-data-status'] = new_data_status + + if new_data_resource_id != -1: + data["new-data-resource-id"] = new_data_resource_id no_params = False - - if (new_data_type_name != ''): - data['new-data-type-name'] = new_data_type_name + + if new_data_size != -1: + data["new-data-size"] = new_data_size no_params = False - - if (new_data_version != ''): - data['new-data-version'] = new_data_version + + if new_data_status != "": + data["new-data-status"] = new_data_status no_params = False - if (no_params): - raise RuntimeError('At least one new data parameter must be given.') + if new_data_type_name != "": + data["new-data-type-name"] = new_data_type_name + no_params = False - r = requests.post(self.url_base + '/data-objects', headers=headers, data=data) + if new_data_version != "": + data["new-data-version"] = new_data_version + no_params = False - if (r.status_code / 100 == 2): - rdict = r.json() + if no_params: + raise RuntimeError("At least one new data parameter must be given.") - if rdict['irods_response']['status_code']: - print('Failed to modify \'' + lpath + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('\'' + lpath + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/irodsHttpClient.py b/irods_http_client/irodsHttpClient.py index 35e841a..f7db8e2 100644 --- a/irods_http_client/irodsHttpClient.py +++ b/irods_http_client/irodsHttpClient.py @@ -6,11 +6,15 @@ from irods_http_client.ticket_operations import Tickets from irods_http_client.user_group_operations import UsersGroups from irods_http_client.zone_operations import Zones +import logging import requests +logger = logging.getLogger(__name__) + + class IrodsHttpClient: def __init__(self, url_base: str): - """ Gets the base url from the user to initialize a client instance. """ + """Gets the base url from the user to initialize a client instance.""" self.url_base = url_base self.token = None @@ -23,47 +27,47 @@ def __init__(self, url_base: str): self.users_groups = UsersGroups(url_base) self.zones = Zones(url_base) - - def authenticate(self, username: str='', password: str='', openid_token: str=''): + def authenticate( + self, username: str = "", password: str = "", openid_token: str = "" + ): """ Takes user credentials as parameters and attempts to authenticate and retrieve a token. Parameters - username: The username of the user to be authenticated. - password: The password of the user to be authenticated. - + Returns - User token generated by the server. """ - if (not isinstance(username, str)): - raise TypeError('username must be a string') - if (not isinstance(password, str)): - raise TypeError('password must be a string') - if (not isinstance(openid_token, str)): - raise TypeError('openid_token must be a string') - - if (openid_token != ''): #TODO: Add openid authentication - return('logged in with openid') - - r = requests.post(self.url_base + '/authenticate', auth=(username, password)) - - if (r.status_code / 100 == 2): - if (self.token == None): + if not isinstance(username, str): + raise TypeError("username must be a string") + if not isinstance(password, str): + raise TypeError("password must be a string") + if not isinstance(openid_token, str): + raise TypeError("openid_token must be a string") + + if openid_token != "": # TODO: Add openid authentication + return "logged in with openid" + + r = requests.post(self.url_base + "/authenticate", auth=(username, password)) + + if r.status_code / 100 == 2: + if self.token == None: self.setToken(r.text) - return(r.text) + return r.text else: - raise RuntimeError('Failed to authenticate: ' + str(r.status_code)) - + raise RuntimeError("Failed to authenticate: " + str(r.status_code)) def setToken(self, token: str): """ Sets the token to be used when making requests. Parameters - - token: The tokent to be set. + - token: The token to be set. """ - if (not isinstance(token, str)): - raise TypeError('token must be a string') + if not isinstance(token, str): + raise TypeError("token must be a string") self.token = token self.collections.token = token @@ -74,12 +78,10 @@ def setToken(self, token: str): self.tickets.token = token self.users_groups.token = token self.zones.token = token - def getToken(self): - """ Returns the authentication token currently in use """ - return(self.token) - + """Returns the authentication token currently in use""" + return self.token def info(self): """ @@ -89,35 +91,18 @@ def info(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - + headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } - r = requests.get(self.url_base + '/info', headers=headers) + r = requests.get(self.url_base + "/info", headers=headers) - if (r.status_code / 100 == 2): + if r.status_code / 100 == 2: rdict = r.json() - - print('Server information for retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + return {"status_code": r.status_code, "data": rdict} else: - irods_err = '' rdict = None - if (r.text != ''): + if r.text != "": rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + return {"status_code": r.status_code, "data": rdict} diff --git a/irods_http_client/query_operations.py b/irods_http_client/query_operations.py index a14f127..50987a3 100644 --- a/irods_http_client/query_operations.py +++ b/irods_http_client/query_operations.py @@ -1,22 +1,30 @@ +from . import common import requests -import json class Queries: def __init__(self, url_base: str): - """" - Initializes Queries with a base url. + """ + Initializes Queries with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - - def execute_genquery(self, query: str, offset: int=0, count: int=-1, case_sensitive: int=1, distinct: int=1, - parser: str='genquery1', sql_only: int=0, zone: str=''): + def execute_genquery( + self, + query: str, + offset: int = 0, + count: int = -1, + case_sensitive: int = 1, + distinct: int = 1, + parser: str = "genquery1", + sql_only: int = 0, + zone: str = "", + ): """ Excecutes a GenQuery string and returns the results. - + Parameters - query: The query being executed - offset (optional): Number of rows to skip. Defaults to 0. @@ -31,96 +39,78 @@ def execute_genquery(self, query: str, offset: int=0, count: int=-1, case_sensit - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(query, str)): - raise TypeError('query must be a string') - if ((not isinstance(offset, int))): - raise TypeError('offset must be an int') - if (not offset >= 0): - raise ValueError('offset must be greater than or equal to 0') - if ((not isinstance(count, int))): - raise TypeError('count must be an int') - if (not count >= -1): - raise ValueError('count must be greater than or equal to 0 or flag value -1') - if (not isinstance(case_sensitive, int)): - raise TypeError('case_sensitive must be an int 1 or 0') - if ((not case_sensitive == 0) and (not case_sensitive == 1)): - raise ValueError('case_sensitive must be an int 1 or 0') - if (not isinstance(distinct, int)): - raise TypeError('distinct must be an int 1 or 0') - if ((not distinct == 0) and (not distinct == 1)): - raise ValueError('distinct must be an int 1 or 0') - if (not isinstance(parser, str)): - raise TypeError('parser must be a string') - if ((not parser == 'genquery1') and (not parser == 'genquery2')): - raise ValueError('parser must be either \'genquery1\' or \'genquery2\'') - if (not isinstance(sql_only, int)): - raise TypeError('sql_only must be an int 1 or 0') - if ((not sql_only == 0) and (not sql_only == 1)): - raise ValueError('sql_only must be an int 1 or 0') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(query, str): + raise TypeError("query must be a string") + if not isinstance(offset, int): + raise TypeError("offset must be an int") + if not offset >= 0: + raise ValueError("offset must be greater than or equal to 0") + if not isinstance(count, int): + raise TypeError("count must be an int") + if not count >= -1: + raise ValueError( + "count must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(case_sensitive, int): + raise TypeError("case_sensitive must be an int 1 or 0") + if (not case_sensitive == 0) and (not case_sensitive == 1): + raise ValueError("case_sensitive must be an int 1 or 0") + if not isinstance(distinct, int): + raise TypeError("distinct must be an int 1 or 0") + if (not distinct == 0) and (not distinct == 1): + raise ValueError("distinct must be an int 1 or 0") + if not isinstance(parser, str): + raise TypeError("parser must be a string") + if (not parser == "genquery1") and (not parser == "genquery2"): + raise ValueError("parser must be either 'genquery1' or 'genquery2'") + if not isinstance(sql_only, int): + raise TypeError("sql_only must be an int 1 or 0") + if (not sql_only == 0) and (not sql_only == 1): + raise ValueError("sql_only must be an int 1 or 0") + if not isinstance(zone, str): + raise TypeError("zone must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } params = { - 'op': 'execute_genquery', - 'query': query, - 'offset': offset, - 'parser': parser + "op": "execute_genquery", + "query": query, + "offset": offset, + "parser": parser, } - if (count != -1): - params['count'] = count - - if (zone != ''): - params['zone'] = zone - - if (parser == 'genquery1'): - params['case-sensitive'] = case_sensitive - params['distinct'] = distinct - else: - params['sql-only'] = sql_only - - r = requests.get(self.url_base + '/query', headers=headers, params=params) + if count != -1: + params["count"] = count - if (r.status_code / 100 == 2): - rdict = r.json() + if zone != "": + params["zone"] = zone - if rdict['irods_response']['status_code']: - print('Failed to execute query: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Query executed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + if parser == "genquery1": + params["case-sensitive"] = case_sensitive + params["distinct"] = distinct else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - - def execute_specific_query(self, name: str, args: str='', args_delimiter: str=',', offset: int=0, count: int=-1): + params["sql-only"] = sql_only + + r = requests.get(self.url_base + "/query", headers=headers, params=params) + return common.process_response(r) + + def execute_specific_query( + self, + name: str, + args: str = "", + args_delimiter: str = ",", + offset: int = 0, + count: int = -1, + ): """ Excecutes a specific query and returns the results. - + Parameters - name: The name of the query to be executed - args (optional): The arguments to be passed into the query @@ -132,76 +122,51 @@ def execute_specific_query(self, name: str, args: str='', args_delimiter: str=', - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(args, str)): - raise TypeError('args must be a string') - if (not isinstance(args_delimiter, str)): - raise TypeError('args_delimiter must be a string') - if ((not isinstance(offset, int))): - raise TypeError('offset must be an int') - if (not offset >= 0): - raise ValueError('offset must be greater than or equal to 0') - if ((not isinstance(count, int))): - raise TypeError('count must be an int') - if (not count >= -1): - raise ValueError('count must be greater than or equal to 0 or flag value -1') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(args, str): + raise TypeError("args must be a string") + if not isinstance(args_delimiter, str): + raise TypeError("args_delimiter must be a string") + if not isinstance(offset, int): + raise TypeError("offset must be an int") + if not offset >= 0: + raise ValueError("offset must be greater than or equal to 0") + if not isinstance(count, int): + raise TypeError("count must be an int") + if not count >= -1: + raise ValueError( + "count must be greater than or equal to 0 or flag value -1" + ) headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } params = { - 'op': 'execute_specific_query', - 'name': name, - 'offset': offset, - 'args-delimiter': args_delimiter + "op": "execute_specific_query", + "name": name, + "offset": offset, + "args-delimiter": args_delimiter, } - if (count != -1): - params['count'] = count - - if (args != ''): - params['args'] = args + if count != -1: + params["count"] = count - r = requests.get(self.url_base + '/query', headers=headers, params=params) + if args != "": + params["args"] = args - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to execute query: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Query executed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get(self.url_base + "/query", headers=headers, params=params) + return common.process_response(r) def add_specific_query(self, name: str, sql: str): """ Adds a SpecificQuery to the iRODS zone. - + Parameters - name: The name of the query to be added. - sql: The SQL attached to the query. @@ -210,59 +175,28 @@ def add_specific_query(self, name: str, sql: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(sql, str)): - raise TypeError('sql must be a string') - - headers = { - 'Authorization': 'Bearer ' + self.token, - } + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(sql, str): + raise TypeError("sql must be a string") - data = { - 'op': 'add_specific_query', - 'name': name, - 'sql': sql + headers = { + "Authorization": "Bearer " + self.token, } - r = requests.post(self.url_base + '/query', headers=headers, data=data) + data = {"op": "add_specific_query", "name": name, "sql": sql} - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to add query: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Query added successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/query", headers=headers, data=data) + return common.process_response(r) def remove_specific_query(self, name): """ Removes a SpecificQuery from the iRODS zone. - + Parameters - name: The name of the SpecificQuery to be removed @@ -270,47 +204,18 @@ def remove_specific_query(self, name): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - - headers = { - 'Authorization': 'Bearer ' + self.token, - } + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") - data = { - 'op': 'remove_specific_query', - 'name': name + headers = { + "Authorization": "Bearer " + self.token, } - r = requests.post(self.url_base + '/query', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to remove query: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Query removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') + data = {"op": "remove_specific_query", "name": name} - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.post(self.url_base + "/query", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/resource_operations.py b/irods_http_client/resource_operations.py index d215d7a..a5f8ac0 100644 --- a/irods_http_client/resource_operations.py +++ b/irods_http_client/resource_operations.py @@ -1,21 +1,21 @@ -import requests +from . import common import json +import requests class Resources: def __init__(self, url_base: str): - """" - Initializes DataObjects with a base url. + """ + Initializes DataObjects with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - def create(self, name: str, type: str, host: str, vault_path: str, context: str): """ Creates a new resource. - + Parameters - name: The name of the resource to be created. - type: The type of the resource to be created. @@ -27,75 +27,44 @@ def create(self, name: str, type: str, host: str, vault_path: str, context: str) - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(type, str)): - raise TypeError('type must be a string') - if (not isinstance(host, str)): - raise TypeError('host must be a string') - if (not isinstance(vault_path, str)): - raise TypeError('vault_path must be a string') - if (not isinstance(context, str)): - raise TypeError('context must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(type, str): + raise TypeError("type must be a string") + if not isinstance(host, str): + raise TypeError("host must be a string") + if not isinstance(vault_path, str): + raise TypeError("vault_path must be a string") + if not isinstance(context, str): + raise TypeError("context must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'create', - 'name': name, - 'type': type - } + data = {"op": "create", "name": name, "type": type} - if (host != ''): - data['host'] = host - - if (vault_path != ''): - data['vault-path'] = vault_path - - if (context != ''): - data['context'] = context - - r = requests.post(self.url_base + '/resources', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to create resource \'' + name + '\': iRODS Status Code ' + str(rdict['irods_response']['status_code']) + ' - ' + str(rdict['irods_response']['status_message'])) - else: - print('Resource \'' + name + '\' created successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + if host != "": + data["host"] = host + + if vault_path != "": + data["vault-path"] = vault_path + + if context != "": + data["context"] = context + + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) def remove(self, name: str): """ Removes an existing resource. - + Parameters - name: The name of the resource to be removed. @@ -103,61 +72,27 @@ def remove(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove', - 'name': name - } + data = {"op": "remove", "name": name} - r = requests.post(self.url_base + '/resources', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to remove resource \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Resource \'' + name + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to remove resource \'' + name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) def modify(self, name: str, property: str, value: str): """ Modifies a property for a resource. - + Parameters - name: The name of the resource to be modified. - property: The property to be modified. @@ -167,72 +102,49 @@ def modify(self, name: str, property: str, value: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(property, str)): - raise TypeError('property must be a string') - if property not in ['name', 'type', 'host', 'vault_path', 'context', 'status', 'free_space', 'comments', 'information']: - raise ValueError('Invalid property. Valid properties:\n - name\n - type\n - host\n - ''vault_path\n - context' + - '\n - status\n - free_space\n - comments\n - information') - if (not isinstance(value, str)): - raise TypeError('value must be a string') - if ((property == 'status') and (value != 'up') and (value != 'down')): - raise ValueError('status must be either \'up\' or \'down\'') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(property, str): + raise TypeError("property must be a string") + if property not in [ + "name", + "type", + "host", + "vault_path", + "context", + "status", + "free_space", + "comments", + "information", + ]: + raise ValueError( + "Invalid property. Valid properties:\n - name\n - type\n - host\n - " + "vault_path\n - context" + + "\n - status\n - free_space\n - comments\n - information" + ) + if not isinstance(value, str): + raise TypeError("value must be a string") + if (property == "status") and (value != "up") and (value != "down"): + raise ValueError("status must be either 'up' or 'down'") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'modify', - 'name': name, - 'property': property, - 'value': value - } - - r = requests.post(self.url_base + '/resources', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify property for \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Property for \'' + name + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to modify property for \'' + name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + data = {"op": "modify", "name": name, "property": property, "value": value} + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) - def add_child(self, parent_name: str, child_name: str, context: str=''): + def add_child(self, parent_name: str, child_name: str, context: str = ""): """ Creates a parent-child relationship between two resources. - + Parameters - parent_name: The name of the parent resource. - child_name: The name of the child resource. @@ -242,69 +154,34 @@ def add_child(self, parent_name: str, child_name: str, context: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(parent_name, str)): - raise TypeError('parent_name must be a string') - if (not isinstance(child_name, str)): - raise TypeError('child_name must be a string') - if (not isinstance(context, str)): - raise TypeError('context must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(parent_name, str): + raise TypeError("parent_name must be a string") + if not isinstance(child_name, str): + raise TypeError("child_name must be a string") + if not isinstance(context, str): + raise TypeError("context must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', - } - - data = { - 'op': 'add_child', - 'parent-name': parent_name, - 'child-name': child_name + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - if (context != ''): - data['context'] = context + data = {"op": "add_child", "parent-name": parent_name, "child-name": child_name} - r = requests.post(self.url_base + '/resources', headers=headers, data=data) + if context != "": + data["context"] = context - if (r.status_code / 100 == 2): - rdict = r.json() + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) - if rdict['irods_response']['status_code']: - print('Failed to add \'' + child_name + '\' as a child of \'' + parent_name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Added \'' + child_name + '\' as a child of \'' + parent_name + '\' successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to add \'' + child_name + '\' as a child of \'' + parent_name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - def remove_child(self, parent_name: str, child_name: str): """ Removes a parent-child relationship between two resources. - + Parameters - parent_name: The name of the parent resource. - child_name: The name of the child resource. @@ -313,64 +190,33 @@ def remove_child(self, parent_name: str, child_name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(parent_name, str)): - raise TypeError('parent_name must be a string') - if (not isinstance(child_name, str)): - raise TypeError('child_name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(parent_name, str): + raise TypeError("parent_name must be a string") + if not isinstance(child_name, str): + raise TypeError("child_name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'remove_child', - 'parent-name': parent_name, - 'child-name': child_name + "op": "remove_child", + "parent-name": parent_name, + "child-name": child_name, } - r = requests.post(self.url_base + '/resources', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to remove \'' + child_name + '\' as a child of \'' + parent_name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Removed \'' + child_name + '\' as a child of \'' + parent_name + '\' successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to remove \'' + child_name + '\' as a child of \'' + parent_name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) def rebalance(self, name: str): """ Rebalances a resource hierarchy. - + Parameters - name: The name of the resource to be rebalanced. @@ -378,61 +224,27 @@ def rebalance(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - - headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', - } + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") - data = { - 'op': 'rebalance', - 'name': name + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - r = requests.post(self.url_base + '/resources', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to rebalance \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('\'' + name + '\' rebalanced successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to rebalance\'' + name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + data = {"op": "rebalance", "name": name} + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) def stat(self, name: str): """ Retrieves information for a resource. - + Parameters - name: The name of the resource to be accessed. @@ -440,61 +252,27 @@ def stat(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - params = { - 'op': 'stat', - 'name': name - } + params = {"op": "stat", "name": name} - r = requests.get(self.url_base + '/resources', headers=headers, params=params) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to retrieve information for \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Information for \'' + name + '\' retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to retrieve information for \'' + name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get(self.url_base + "/resources", headers=headers, params=params) + return common.process_response(r) - def modify_metadata(self, name: str, operations: dict, admin: int=0): + def modify_metadata(self, name: str, operations: dict, admin: int = 0): """ Modifies the metadata for a resource. - + Parameters - name: The absolute logical path of the resource to have its metadata modified. - operations: Dictionary containing the operations to carry out. Should contain the operation, attribute, value, and optionally units. @@ -504,58 +282,32 @@ def modify_metadata(self, name: str, operations: dict, admin: int=0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(operations, list)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(operations[0], dict)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(admin, int)): - raise TypeError('admin must be an int 1 or 0') - if ((not admin == 0) and (not admin == 1)): - raise ValueError('admin must be an int 1 or 0') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(operations, list): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(operations[0], dict): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(admin, int): + raise TypeError("admin must be an int 1 or 0") + if (not admin == 0) and (not admin == 1): + raise ValueError("admin must be an int 1 or 0") + headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'modify_metadata', - 'name': name, - 'operations': json.dumps(operations), - 'admin': admin + "op": "modify_metadata", + "name": name, + "operations": json.dumps(operations), + "admin": admin, } - r = requests.post(self.url_base + '/resources', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify metadata for \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Metadata for \'' + name + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.post(self.url_base + "/resources", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/rule_operations.py b/irods_http_client/rule_operations.py index fca88d6..878daa1 100644 --- a/irods_http_client/rule_operations.py +++ b/irods_http_client/rule_operations.py @@ -1,17 +1,16 @@ +from . import common import requests -import json class Rules: - + def __init__(self, url_base: str): - """" - Initializes Rules with a base url. + """ + Initializes Rules with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - def list_rule_engines(self): """ Lists available rule engine plugin instances. @@ -20,51 +19,20 @@ def list_rule_engines(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - - headers = { - 'Authorization': 'Bearer ' + self.token, - } - params = { - 'op': 'list_rule_engines' + headers = { + "Authorization": "Bearer " + self.token, } - r = requests.get(self.url_base + '/rules', params=params, headers=headers) - - rdict = r.json() - - if (r.status_code / 100 == 2): - if rdict['irods_response']['status_code']: - print('Failed to retrieve rule engines list: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Rule engine list retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + params = {"op": "list_rule_engines"} + r = requests.get(self.url_base + "/rules", params=params, headers=headers) + return common.process_response(r) - def execute(self, rule_text: str, rep_instance: str=''): + def execute(self, rule_text: str, rep_instance: str = ""): """ Executes rule code. - + Parameters - rule_text: The rule code to execute. - rep_instance (optional): The rule engine plugin to run the rule-text against. @@ -73,62 +41,32 @@ def execute(self, rule_text: str, rep_instance: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(rule_text, str)): - raise TypeError('name must be a string') - if (not isinstance(rep_instance, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(rule_text, str): + raise TypeError("name must be a string") + if not isinstance(rep_instance, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'execute', - 'rule-text': rule_text - } - - if (rep_instance != ''): - data['rep-instance'] = rep_instance - - r = requests.post(self.url_base + '/rules', headers=headers, data=data) + data = {"op": "execute", "rule-text": rule_text} - if (r.status_code / 100 == 2): - rdict = r.json() + if rep_instance != "": + data["rep-instance"] = rep_instance - if rdict['irods_response']['status_code']: - print('Failed to remove execute rule: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Rule executed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/rules", headers=headers, data=data) + return common.process_response(r) def remove_delay_rule(self, rule_id: int): """ Removes a delay rule from the catalog. - + Parameters - rule_id: The id of the delay rule to be removed. @@ -136,50 +74,21 @@ def remove_delay_rule(self, rule_id: int): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(rule_id, int)): - raise TypeError('rule_id must be an int') - if (not rule_id >= 0): - raise ValueError('rule_id must be greater than or equal to 0') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(rule_id, int): + raise TypeError("rule_id must be an int") + if not rule_id >= 0: + raise ValueError("rule_id must be greater than or equal to 0") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove_delay_rule', - 'rule-id': rule_id - } + data = {"op": "remove_delay_rule", "rule-id": rule_id} - r = requests.post(self.url_base + '/rules', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to remove delay rule: iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Delay rule removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.post(self.url_base + "/rules", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/ticket_operations.py b/irods_http_client/ticket_operations.py index 4d209f3..6ca7187 100644 --- a/irods_http_client/ticket_operations.py +++ b/irods_http_client/ticket_operations.py @@ -1,22 +1,31 @@ +from . import common import requests -import json class Tickets: def __init__(self, url_base: str): - """" - Initializes Tickets with a base url. + """ + Initializes Tickets with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - - def create(self, lpath: str, type: str='read', use_count: int=-1, write_data_object_count: int=-1, write_byte_count: int=-1, - seconds_until_expiration: int=-1, users: str='', groups: str='', hosts: str=''): + def create( + self, + lpath: str, + type: str = "read", + use_count: int = -1, + write_data_object_count: int = -1, + write_byte_count: int = -1, + seconds_until_expiration: int = -1, + users: str = "", + groups: str = "", + hosts: str = "", + ): """ Creates a new ticket for a collection or data object. - + Parameters - lpath: Absolute logical path to a data object or collection. - type (optional): Read or write. Defaults to read. @@ -32,98 +41,76 @@ def create(self, lpath: str, type: str='read', use_count: int=-1, write_data_obj - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(lpath, str)): - raise TypeError('lpath must be an string') - if (not isinstance(type, str)): - raise TypeError('type must be a string') - if type not in ['read', 'write']: - raise ValueError('type must be either read or write') - if (not isinstance(use_count, int)): - raise TypeError('use_count must be an int') - if (not use_count >= -1): - raise ValueError('use_count must be greater than or equal to 0 or flag value -1') - if (not isinstance(write_data_object_count, int)): - raise TypeError('write_data_object_count must be an int') - if (not write_data_object_count >= -1): - raise ValueError('write_data_object_count must be greater than or equal to 0 or flag value -1') - if (not isinstance(write_byte_count, int)): - raise TypeError('write_byte_count must be an int') - if (not write_byte_count >= -1): - raise ValueError('write_byte_count must be greater than or equal to 0 or flag value -1') - if (not isinstance(seconds_until_expiration, int)): - raise TypeError('seconds_until_expiration must be an int') - if (not seconds_until_expiration >= -1): - raise ValueError('seconds_until_expiration must be greater than or equal to 0 or flag value -1') - if (not isinstance(users, str)): - raise TypeError('users must be an string') - if (not isinstance(groups, str)): - raise TypeError('groups must be an string') - if (not isinstance(hosts, str)): - raise TypeError('hosts must be an string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(lpath, str): + raise TypeError("lpath must be an string") + if not isinstance(type, str): + raise TypeError("type must be a string") + if type not in ["read", "write"]: + raise ValueError("type must be either read or write") + if not isinstance(use_count, int): + raise TypeError("use_count must be an int") + if not use_count >= -1: + raise ValueError( + "use_count must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(write_data_object_count, int): + raise TypeError("write_data_object_count must be an int") + if not write_data_object_count >= -1: + raise ValueError( + "write_data_object_count must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(write_byte_count, int): + raise TypeError("write_byte_count must be an int") + if not write_byte_count >= -1: + raise ValueError( + "write_byte_count must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(seconds_until_expiration, int): + raise TypeError("seconds_until_expiration must be an int") + if not seconds_until_expiration >= -1: + raise ValueError( + "seconds_until_expiration must be greater than or equal to 0 or flag value -1" + ) + if not isinstance(users, str): + raise TypeError("users must be an string") + if not isinstance(groups, str): + raise TypeError("groups must be an string") + if not isinstance(hosts, str): + raise TypeError("hosts must be an string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', - } - - data = { - 'op': 'create', - 'lpath': lpath, - 'type': type + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - if (use_count != -1): - data['use-count'] = use_count - if (write_data_object_count != -1): - data['write-data-object-count'] = write_data_object_count - if (write_byte_count != -1): - data['write-byte-count'] = write_byte_count - if (seconds_until_expiration != -1): - data['seconds-until-expiration'] = seconds_until_expiration - if (users != ''): - data['users'] = users - if (groups != ''): - data['groups'] = groups - if (hosts != ''): - data['hosts'] = hosts - - print(data) - - r = requests.post(self.url_base + '/tickets', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - print('Ticket generated successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + data = {"op": "create", "lpath": lpath, "type": type} + + if use_count != -1: + data["use-count"] = use_count + if write_data_object_count != -1: + data["write-data-object-count"] = write_data_object_count + if write_byte_count != -1: + data["write-byte-count"] = write_byte_count + if seconds_until_expiration != -1: + data["seconds-until-expiration"] = seconds_until_expiration + if users != "": + data["users"] = users + if groups != "": + data["groups"] = groups + if hosts != "": + data["hosts"] = hosts + + r = requests.post(self.url_base + "/tickets", headers=headers, data=data) + return common.process_response(r) def remove(self, name: str): """ Removes an existing ticket. - + Parameters - name: The ticket to be removed. @@ -131,52 +118,19 @@ def remove(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded', + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove', - 'name': name - } - - r = requests.post(self.url_base + '/tickets', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to remove ticket \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Ticket \'' + name + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - elif (r.status_code / 100 == 4): - print('Failed to remove ticket \'' + name + '\'') - - return(r) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRods Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') + data = {"op": "remove", "name": name} - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.post(self.url_base + "/tickets", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/user_group_operations.py b/irods_http_client/user_group_operations.py index 73e06be..7be44ca 100644 --- a/irods_http_client/user_group_operations.py +++ b/irods_http_client/user_group_operations.py @@ -1,20 +1,20 @@ -import requests +from . import common import json +import requests class UsersGroups: def __init__(self, url_base: str): """ - Initializes UsersGroups with a base url. + Initializes UsersGroups with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - - def create_user(self, name: str, zone: str, user_type: str='rodsuser'): + def create_user(self, name: str, zone: str, user_type: str = "rodsuser"): """ Creates a new user. Requires rodsadmin or groupadmin privileges. - + Parameters - name: The name of the user to be created. - zone: The zone for the user to be created. @@ -24,64 +24,35 @@ def create_user(self, name: str, zone: str, user_type: str='rodsuser'): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') - if (not isinstance(user_type, str)): - raise TypeError('user_type must be a string') - if user_type and user_type not in ['rodsuser', 'groupadmin', 'rodsadmin']: - raise ValueError('user_type must be set to rodsuser, groupadmin, or rodsadmin.') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") + if not isinstance(user_type, str): + raise TypeError("user_type must be a string") + if user_type and user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: + raise ValueError( + "user_type must be set to rodsuser, groupadmin, or rodsadmin." + ) headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'create_user', - 'name': name, - 'zone': zone, - 'user-type': user_type - } - - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to create user \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('User \'' + name + '\' created successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + data = {"op": "create_user", "name": name, "zone": zone, "user-type": user_type} + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) def remove_user(self, name: str, zone: str): """ Removes a user. Requires rodsadmin privileges. - + Parameters - name: The name of the user to be removed. - zone: The zone for the user to be removed. @@ -90,59 +61,29 @@ def remove_user(self, name: str, zone: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove_user', - 'name': name, - 'zone': zone - } + data = {"op": "remove_user", "name": name, "zone": zone} - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to remove user \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('User \'' + name + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) - - def set_password(self, name: str, zone: str, new_password: str=''): + def set_password(self, name: str, zone: str, new_password: str = ""): """ Changes a users password. Requires rodsadmin privileges. - + Parameters - name: The name of the user to have their password changed. - zone: The zone for the user to have their password changed. @@ -152,62 +93,36 @@ def set_password(self, name: str, zone: str, new_password: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') - if (not isinstance(new_password, str)): - raise TypeError('new_password must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") + if not isinstance(new_password, str): + raise TypeError("new_password must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'set_password', - 'name': name, - 'zone': zone, - 'new-password': new_password + "op": "set_password", + "name": name, + "zone": zone, + "new-password": new_password, } - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to change password for user \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Password for user \'' + name + '\' changed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) def set_user_type(self, name: str, zone: str, user_type: str): """ Changes a users type. Requires rodsadmin privileges. - + Parameters - name: The name of the user to have their type updated. - zone: The zone for the user to have their type updated. @@ -217,65 +132,40 @@ def set_user_type(self, name: str, zone: str, user_type: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') - if (not isinstance(user_type, str)): - raise TypeError('user_type must be a string') - if user_type and user_type not in ['rodsuser', 'groupadmin', 'rodsadmin']: - raise ValueError('user_type must be set to rodsuser, groupadmin, or rodsadmin.') - + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") + if not isinstance(user_type, str): + raise TypeError("user_type must be a string") + if user_type and user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: + raise ValueError( + "user_type must be set to rodsuser, groupadmin, or rodsadmin." + ) headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'set_user_type', - 'name': name, - 'zone': zone, - 'new-user-type': user_type + "op": "set_user_type", + "name": name, + "zone": zone, + "new-user-type": user_type, } - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to change type for user \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Type for user \'' + name + '\' changed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) def create_group(self, name: str): """ Creates a new group. Requires rodsadmin or groupadmin privileges. - + Parameters - name: The name of the group to be created. @@ -283,115 +173,57 @@ def create_group(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'create_group', - 'name': name - } + data = {"op": "create_group", "name": name} - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to create group \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Group \'' + name + '\' created successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) def remove_group(self, name: str): """ Removes a group. Requires rodsadmin privileges. - + Parameters - name: The name of the group to be removed. - + Parameters Returns - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove_group', - 'name': name - } + data = {"op": "remove_group", "name": name} - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to remove group \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Group \'' + name + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) - def add_to_group(self, user: str, zone: str, group: str=''): + def add_to_group(self, user: str, zone: str, group: str = ""): """ Adds a user to a group. Requires rodsadmin or groupadmin privileges. - + Parameters - user: The user to be added to the group. - zone: The zone for the user to be added to the group. @@ -401,62 +233,31 @@ def add_to_group(self, user: str, zone: str, group: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(user, str)): - raise TypeError('user must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') - if (not isinstance(group, str)): - raise TypeError('group must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(user, str): + raise TypeError("user must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") + if not isinstance(group, str): + raise TypeError("group must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'add_to_group', - 'user': user, - 'zone': zone, - 'group': group - } - - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to add user \'' + user + '\' to group \'' + group + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('User \'' + user + '\' added to group \'' + group + '\' successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + data = {"op": "add_to_group", "user": user, "zone": zone, "group": group} + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) def remove_from_group(self, user: str, zone: str, group: str): """ Removes a user from a group. Requires rodsadmin or groupadmin privileges. - + Parameters - user: The user to be removed from the group. - zone: The zone for the user to be removed from the group. @@ -466,57 +267,26 @@ def remove_from_group(self, user: str, zone: str, group: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(user, str)): - raise TypeError('user must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') - if (not isinstance(group, str)): - raise TypeError('group must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(user, str): + raise TypeError("user must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") + if not isinstance(group, str): + raise TypeError("group must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove_from_group', - 'user': user, - 'zone': zone, - 'group': group - } + data = {"op": "remove_from_group", "user": user, "zone": zone, "group": group} - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to remove user \'' + user + '\' from group \'' + group + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('User \'' + user + '\' removed from group \'' + group + '\' successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) def users(self): """ @@ -526,47 +296,19 @@ def users(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) - headers = { - 'Authorization': 'Bearer ' + self.token - } + headers = {"Authorization": "Bearer " + self.token} - params = { - 'op': 'users' - } + params = {"op": "users"} - r = requests.get(self.url_base + '/users-groups', headers=headers, params=params) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to retrieve user list : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('User list retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get( + self.url_base + "/users-groups", headers=headers, params=params + ) + return common.process_response(r) def groups(self): """ @@ -576,52 +318,26 @@ def groups(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) headers = { - 'Authorization': 'Bearer ' + self.token, + "Authorization": "Bearer " + self.token, } - params = { - 'op': 'groups' - } + params = {"op": "groups"} - r = requests.get(self.url_base + '/users-groups', headers=headers, params=params) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to retrieve group list : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Group list retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + r = requests.get( + self.url_base + "/users-groups", headers=headers, params=params + ) + return common.process_response(r) - def is_member_of_group(self, group: str, user: str, zone: str): """ Returns whether a user is a member of a group or not. - + Parameters group: The group being checked. user: The user being checked. @@ -631,62 +347,38 @@ def is_member_of_group(self, group: str, user: str, zone: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(group, str)): - raise TypeError('group must be a string') - if (not isinstance(user, str)): - raise TypeError('user must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(group, str): + raise TypeError("group must be a string") + if not isinstance(user, str): + raise TypeError("user must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } params = { - 'op': 'is_member_of_group', - 'group': group, - 'user': user, - 'zone': zone + "op": "is_member_of_group", + "group": group, + "user": user, + "zone": zone, } - r = requests.get(self.url_base + '/users-groups', headers=headers, params=params) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to check membership in group \'' + group + '\' for user \'' + user + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Membership in group \'' + group + '\' for user \'' + user + '\' checked successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + r = requests.get( + self.url_base + "/users-groups", headers=headers, params=params + ) + return common.process_response(r) - def stat(self, name: str, zone: str=''): + def stat(self, name: str, zone: str = ""): """ Returns information about a user or group. - + Parameters - name: The name of the user or group to be accessed. - zone: The zone of the user to be accessed. Not required for groups. @@ -695,60 +387,31 @@ def stat(self, name: str, zone: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(zone, str)): - raise TypeError('zone must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(zone, str): + raise TypeError("zone must be a string") - headers = { - 'Authorization': 'Bearer ' + self.token - } + headers = {"Authorization": "Bearer " + self.token} - params = { - 'op': 'stat', - 'name': name - } + params = {"op": "stat", "name": name} - if (zone != ''): - params['zone'] = zone - - r = requests.get(self.url_base + '/users-groups', headers=headers, params=params) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to retrieve information for \'' + name + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Information for \'' + name + '\' retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - + if zone != "": + params["zone"] = zone + + r = requests.get( + self.url_base + "/users-groups", headers=headers, params=params + ) + return common.process_response(r) def modify_metadata(self, name: str, operations: list): """ Modifies the metadata for a user or group. Requires rodsadmin privileges. - + Parameters - name: The user or group to be modified. - operations: The operations to be carried out. @@ -757,53 +420,27 @@ def modify_metadata(self, name: str, operations: list): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(operations, list)): - raise TypeError('operations must be a list of dictionaries') - if (not isinstance(operations[0], dict)): - raise TypeError('operations must be a list of dictionaries') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(operations, list): + raise TypeError("operations must be a list of dictionaries") + if not isinstance(operations[0], dict): + raise TypeError("operations must be a list of dictionaries") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } data = { - 'op': 'modify_metadata', - 'name': name, - 'operations': json.dumps(operations) + "op": "modify_metadata", + "name": name, + "operations": json.dumps(operations), } - r = requests.post(self.url_base + '/users-groups', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - - if rdict['irods_response']['status_code']: - print('Failed to modify metadata for \'' + name + '\': iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Metadata for \'' + name + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) + return common.process_response(r) diff --git a/irods_http_client/zone_operations.py b/irods_http_client/zone_operations.py index 75a2b17..59534ca 100644 --- a/irods_http_client/zone_operations.py +++ b/irods_http_client/zone_operations.py @@ -1,17 +1,16 @@ +from . import common import requests -import json class Zones: def __init__(self, url_base: str): """ - Initializes Zones with a base url. + Initializes Zones with a base url. Token is set to None initially, and updated when setToken() is called in irodsClient. """ self.url_base = url_base self.token = None - - def add(self, name: str, connection_info: str='', comment: str=''): + def add(self, name: str, connection_info: str = "", comment: str = ""): """ Adds a remote zone to the local zone. Requires rodsadmin privileges. @@ -24,60 +23,31 @@ def add(self, name: str, connection_info: str='', comment: str=''): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(connection_info, str)): - raise TypeError('connection_info must be a string') - if (not isinstance(comment, str)): - raise TypeError('comment must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(connection_info, str): + raise TypeError("connection_info must be a string") + if not isinstance(comment, str): + raise TypeError("comment must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' - } - - data = { - 'op': 'add', - 'name': name + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - if (connection_info != ''): - data['connection-info'] = connection_info - if (comment != ''): - data['comment'] = comment - - r = requests.post(self.url_base + '/zones', headers=headers, data=data) + data = {"op": "add", "name": name} - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to add zone \'' + name + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Zone \'' + name + '\' added successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + if connection_info != "": + data["connection-info"] = connection_info + if comment != "": + data["comment"] = comment + r = requests.post(self.url_base + "/zones", headers=headers, data=data) + return common.process_response(r) def remove(self, name: str): """ @@ -90,51 +60,22 @@ def remove(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'remove', - 'name': name - } - - r = requests.post(self.url_base + '/zones', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to remove zone \'' + name + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Zone \'' + name + '\' removed successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + data = {"op": "remove", "name": name} + r = requests.post(self.url_base + "/zones", headers=headers, data=data) + return common.process_response(r) def modify(self, name: str, property: str, value: str): """ @@ -150,57 +91,26 @@ def modify(self, name: str, property: str, value: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') - if (not isinstance(property, str)): - raise TypeError('property must be a string') - if (not isinstance(value, str)): - raise TypeError('value must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") + if not isinstance(property, str): + raise TypeError("property must be a string") + if not isinstance(value, str): + raise TypeError("value must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - data = { - 'op': 'modify', - 'name': name, - 'property': property, - 'value': value - } - - r = requests.post(self.url_base + '/zones', headers=headers, data=data) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to modify zone \'' + name + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Zone \'' + name + '\' modified successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) + data = {"op": "modify", "name": name, "property": property, "value": value} + r = requests.post(self.url_base + "/zones", headers=headers, data=data) + return common.process_response(r) def report(self): """ @@ -210,49 +120,21 @@ def report(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - params = { - 'op': 'report' - } + params = {"op": "report"} - r = requests.get(self.url_base + '/zones', headers=headers, params=params) + r = requests.get(self.url_base + "/zones", headers=headers, params=params) + return common.process_response(r) - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to retrieve information for the iRODS zone : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Information for the iRODS zone retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - - def stat(self, name: str): """ Returns information about a named iRODS zone. Requires rodsadmin privileges. @@ -261,47 +143,19 @@ def stat(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if (self.token == None): - raise RuntimeError('No token set. Use setToken() to set the auth token to be used') - if (not isinstance(name, str)): - raise TypeError('name must be a string') + if self.token == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used" + ) + if not isinstance(name, str): + raise TypeError("name must be a string") headers = { - 'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/x-www-form-urlencoded' + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", } - params = { - 'op': 'stat', - 'name': name - } - - r = requests.get(self.url_base + '/zones', headers=headers, params=params) - - if (r.status_code / 100 == 2): - rdict = r.json() - if rdict['irods_response']['status_code']: - print('Failed to retrieve information for zone \'' + name + '\' : iRODS Status Code' + str(rdict['irods_response']['status_code'])) - else: - print('Information for zone \'' + name + '\' retrieved successfully') - - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) - else: - irods_err = '' - rdict = None - if (r.text != ''): - rdict = r.json() - irods_err = ': iRODS Status Code' + str(rdict['irods_response']) - print(f'Error <{r.status_code}>{irods_err}') + params = {"op": "stat", "name": name} - return( - { - 'status_code': r.status_code, - 'data': rdict - } - ) \ No newline at end of file + r = requests.get(self.url_base + "/zones", headers=headers, params=params) + return common.process_response(r) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..386bea3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "irods-http-client" +version = "0.1.0" +authors = [ + { name="iRODS Consortium", email="info@irods.org" }, +] +description = "A Python wrapper for the iRODS HTTP API" +readme = "README.md" +requires-python = ">=3.9" +license = "BSD-3-Clause" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] + +[project.urls] +Homepage = "https://github.com/irods/irods_client_http_python" +"Bug Tracker" = "https://github.com/irods/irods_client_http_python/issues" + +[build-system] +requires = [ + "jsonschema", + "requests", + "setuptools >= 40.9.0" +] +build-backend = "setuptools.build_meta" diff --git a/test/config.py b/test/config.py index 8f5ec53..6dc8c4a 100644 --- a/test/config.py +++ b/test/config.py @@ -1,83 +1,50 @@ import logging from jsonschema import validate - test_config = { - 'log_level': logging.INFO, - - - 'host': 'localhost', - 'port': 9001, - 'url_base': '/irods-http-api/0.3.0', - - - 'rodsadmin': { - 'username': 'rods', - 'password': 'rods' - }, - - - 'rodsuser': { - 'username': 'jeb', - 'password': 'ding' - }, - - - 'irods_zone': 'tempZone', - 'irods_server_hostname': 'localhost' + "log_level": logging.INFO, + "host": "localhost", + "port": 9001, + "url_base": "/irods-http-api/0.6.0", + "rodsadmin": {"username": "rods", "password": "rods"}, + "rodsuser": {"username": "jeb", "password": "ding"}, + "irods_zone": "tempZone", + "irods_server_hostname": "localhost", } schema = { - '$schema': 'http://json-schema.org/draft-07/schema#', - '$id': 'https://schemas.irods.org/irods-http-api/test/0.3.0/test-schema.json', - 'type': 'object', - 'properties': { - 'host': { - 'type': 'string' - }, - 'port': { - 'type': 'number' - }, - 'url_base': { - 'type': 'string' - }, - 'rodsadmin': { - '$ref': '#/definitions/login' - }, - 'rodsuser': { - '$ref': '#/definitions/login' - }, - 'irods_zone': { - 'type': 'string' - }, - 'irods_server_hostname': { - 'type': 'string' - } - }, - 'required': [ - 'host', - 'port', - 'url_base', - 'rodsadmin', - 'rodsuser', - 'irods_zone', - 'irods_server_hostname' - ], - 'definitions': { - 'login': { - 'type': 'object', - 'properties': { - 'username': { - 'type': 'string' - }, - 'password': { - 'type': 'string' - } - }, - 'required': [ 'username', 'password' ] - } - } + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.irods.org/irods-http-api/test/0.6.0/test-schema.json", + "type": "object", + "properties": { + "host": {"type": "string"}, + "port": {"type": "number"}, + "url_base": {"type": "string"}, + "rodsadmin": {"$ref": "#/definitions/login"}, + "rodsuser": {"$ref": "#/definitions/login"}, + "irods_zone": {"type": "string"}, + "irods_server_hostname": {"type": "string"}, + }, + "required": [ + "host", + "port", + "url_base", + "rodsadmin", + "rodsuser", + "irods_zone", + "irods_server_hostname", + ], + "definitions": { + "login": { + "type": "object", + "properties": { + "username": {"type": "string"}, + "password": {"type": "string"}, + }, + "required": ["username", "password"], + } + }, } diff --git a/test/test_endpoint_operations.py b/test/test_endpoint_operations.py index eba538f..d0d8894 100644 --- a/test/test_endpoint_operations.py +++ b/test/test_endpoint_operations.py @@ -8,7 +8,7 @@ def setup_class(cls, opts): - '''Initializes shared state needed by all test cases. + """Initializes shared state needed by all test cases. This function is designed to be called in setUpClass(). @@ -17,676 +17,1044 @@ def setup_class(cls, opts): Arguments: cls -- The class to attach state to. opts -- A dict containing options for controlling the behavior of the function. - ''' - + """ # Used as a signal for determining whether setUpClass() succeeded or not. # If this results in being True, no tests should be allowed to run. cls._class_init_error = False - cls._remove_rodsuser = False - # Initialize the class logger. cls.logger = logging.getLogger(cls.__name__) - - log_level = config.test_config.get('log_level', logging.INFO) + log_level = config.test_config.get("log_level", logging.INFO) cls.logger.setLevel(log_level) - ch = logging.StreamHandler() ch.setLevel(log_level) - ch.setFormatter(logging.Formatter(f'[%(asctime)s] [{cls.__name__}] [%(levelname)s] %(message)s')) - + ch.setFormatter( + logging.Formatter(f"[%(asctime)s] [{cls.__name__}] [%(levelname)s] %(message)s") + ) cls.logger.addHandler(ch) - # Initialize state. - - if config.test_config.get('host', None) == None: - cls.logger.debug('Missing configuration property: host') + if config.test_config.get("host", None) == None: + cls.logger.debug("Missing configuration property: host") cls._class_init_error = True return - - if config.test_config.get('port', None) == None: - cls.logger.debug('Missing configuration property: port') + if config.test_config.get("port", None) == None: + cls.logger.debug("Missing configuration property: port") cls._class_init_error = True return - - if config.test_config.get('url_base', None) == None: - cls.logger.debug('Missing configuration property: url_base') + if config.test_config.get("url_base", None) == None: + cls.logger.debug("Missing configuration property: url_base") cls._class_init_error = True return - cls.url_base = f"http://{config.test_config['host']}:{config.test_config['port']}{config.test_config['url_base']}" cls.url_endpoint = f'{cls.url_base}/{opts["endpoint_name"]}' cls.api = IrodsHttpClient(cls.url_base) - cls.zone_name = config.test_config['irods_zone'] - cls.host = config.test_config['irods_server_hostname'] - + cls.zone_name = config.test_config["irods_zone"] + cls.host = config.test_config["irods_server_hostname"] # create_rodsuser cannot be honored if init_rodsadmin is set to False. # Therefore, return immediately. - if not opts.get('init_rodsadmin', True): - cls.logger.debug('init_rodsadmin is False. Class setup complete.') + if not opts.get("init_rodsadmin", True): + cls.logger.debug("init_rodsadmin is False. Class setup complete.") return - # Authenticate as a rodsadmin and store the bearer token. - cls.rodsadmin_username = config.test_config['rodsadmin']['username'] + cls.rodsadmin_username = config.test_config["rodsadmin"]["username"] try: - cls.rodsadmin_bearer_token = cls.api.authenticate(cls.rodsadmin_username, config.test_config['rodsadmin']['password']) + cls.rodsadmin_bearer_token = cls.api.authenticate( + cls.rodsadmin_username, config.test_config["rodsadmin"]["password"] + ) except RuntimeError: cls._class_init_error = True - cls.logger.debug(f'Failed to authenticate as rodsadmin [{cls.rodsadmin_username}].') + cls.logger.debug( + f"Failed to authenticate as rodsadmin [{cls.rodsadmin_username}]." + ) return - + # Authenticate as a rodsuser and store the bearer token. - # Should be replaced once user operations are implemented - # Currently, the user specified in the config file must exist before running tests - cls.rodsuser_username = config.test_config['rodsuser']['username'] - print(cls.rodsuser_username) - print(config.test_config['rodsuser']['password']) + cls.rodsuser_username = config.test_config["rodsuser"]["username"] + try: - cls.rodsuser_bearer_token = cls.api.authenticate(cls.rodsuser_username, config.test_config['rodsuser']['password']) + cls.api.users_groups.create_user( + cls.rodsuser_username, cls.zone_name, "rodsuser" + ) + cls.api.users_groups.set_password( + cls.rodsuser_username, + cls.zone_name, + config.test_config["rodsuser"]["password"], + ) + cls.rodsuser_bearer_token = cls.api.authenticate( + cls.rodsuser_username, config.test_config["rodsuser"]["password"] + ) except RuntimeError: cls._class_init_error = True - cls.logger.debug(f'Failed to authenticate as rodsuser [{cls.rodsuser_username}].') + cls.logger.debug( + f"Failed to authenticate as rodsuser [{cls.rodsuser_username}]." + ) return - cls.logger.debug('Class setup complete.') + cls.logger.debug("Class setup complete.") def tear_down_class(cls): if cls._class_init_error: return - if not cls._remove_rodsuser: - return + cls.api.users_groups.remove_user(cls.rodsuser_username, cls.zone_name) # Tests for collections operations class collectionsTests(unittest.TestCase): @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'collections'}) + setup_class(cls, {"endpoint_name": "collections"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) - #tests the create operation + # tests the create operation def testCreate(self): - print(self.zone_name) self.api.setToken(self.rodsadmin_bearer_token) - #clean up test collections - self.api.collections.remove(f'/{self.zone_name}/home/new') - self.api.collections.remove(f'/{self.zone_name}/home/test/folder') - self.api.collections.remove(f'/{self.zone_name}/home/test') + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/new") + self.api.collections.remove(f"/{self.zone_name}/home/test/folder") + self.api.collections.remove(f"/{self.zone_name}/home/test") - #test param checking + # test param checking self.assertRaises(TypeError, self.api.collections.create, 0, 0) - self.assertRaises(TypeError, self.api.collections.create, f'/{self.zone_name}/home/{self.rodsadmin_username}', '0') - self.assertRaises(ValueError, self.api.collections.create, f'/{self.zone_name}/home/{self.rodsadmin_username}', 7) - - #test creating new collection - response = self.api.collections.create(f'/{self.zone_name}/home/new') - self.assertTrue(response['data']['created']) - self.assertEqual(response['data']['irods_response']['status_code'], 0) - - #test creating existing collection - response = self.api.collections.create(f'/{self.zone_name}/home/new') - self.assertFalse(response['data']['created']) - self.assertEqual(response['data']['irods_response']['status_code'], 0) - - #test invalid path - response = self.api.collections.create('{self.zone_name}/home/new') - self.assertEqual('{\'irods_response\': {\'status_code\': -358000, \'status_message\': \'path does not exist: OBJ_PATH_DOES_NOT_EXIST\'}}', str(response['data'])) - - #test create_intermediates - response = self.api.collections.create(f'/{self.zone_name}/home/test/folder', 0) - self.assertEqual('{\'irods_response\': {\'status_code\': -358000, \'status_message\': \'path does not exist: OBJ_PATH_DOES_NOT_EXIST\'}}', str(response['data'])) - response = self.api.collections.create(f'/{self.zone_name}/home/test/folder', 1) - self.assertEqual('{\'created\': True, \'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - - #tests the remove operation + self.assertRaises( + TypeError, + self.api.collections.create, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.create, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 7, + ) + + # test creating new collection + response = self.api.collections.create(f"/{self.zone_name}/home/new") + self.assertTrue(response["data"]["created"]) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test creating existing collection + response = self.api.collections.create(f"/{self.zone_name}/home/new") + self.assertFalse(response["data"]["created"]) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test invalid path + response = self.api.collections.create(f"{self.zone_name}/home/new") + self.assertEqual( + "{'irods_response': {'status_code': -358000, 'status_message': 'path does not exist: OBJ_PATH_DOES_NOT_EXIST'}}", + str(response["data"]), + ) + + # test create_intermediates + response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 0) + self.assertEqual( + "{'irods_response': {'status_code': -358000, 'status_message': 'path does not exist: OBJ_PATH_DOES_NOT_EXIST'}}", + str(response["data"]), + ) + response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 1) + self.assertEqual( + "{'created': True, 'irods_response': {'status_code': 0}}", + str(response["data"]), + ) + + # tests the remove operation def testRemove(self): self.api.setToken(self.rodsadmin_bearer_token) - #clean up test collections - self.api.collections.remove(f'/{self.zone_name}/home/new') - self.api.collections.remove(f'/{self.zone_name}/home/test/folder') - self.api.collections.remove(f'/{self.zone_name}/home/test') + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/new") + self.api.collections.remove(f"/{self.zone_name}/home/test/folder") + self.api.collections.remove(f"/{self.zone_name}/home/test") - #test param checking + # test param checking self.assertRaises(TypeError, self.api.collections.remove, 0, 0, 0) - self.assertRaises(TypeError, self.api.collections.remove, f'/{self.zone_name}/home/{self.rodsadmin_username}', '0', 0) - self.assertRaises(ValueError, self.api.collections.remove, f'/{self.zone_name}/home/{self.rodsadmin_username}', 5, 0) - self.assertRaises(TypeError, self.api.collections.remove, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0, '0') - self.assertRaises(ValueError, self.api.collections.remove, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0, 5) - - #test removing collection - response = self.api.collections.create(f'/{self.zone_name}/home/new') - self.assertEqual('{\'created\': True, \'irods_response\': {\'status_code\': 0}}', str(response['data'])) - response = self.api.collections.remove(f'/{self.zone_name}/home/new') - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - #test invalid paths - response = self.api.collections.stat(f'/{self.zone_name}/home/tensaitekinaaidorusama') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - response = self.api.collections.stat(f'/{self.zone_name}/home/aremonainainaikoremonainainai') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - response = self.api.collections.stat(f'/{self.zone_name}/home/binglebangledingledangle') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - response = self.api.collections.stat(f'{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - - #test recurse - response = self.api.collections.create(f'/{self.zone_name}/home/test/folder', 1) - self.assertEqual('{\'created\': True, \'irods_response\': {\'status_code\': 0}}', str(response['data'])) - response = self.api.collections.remove(f'/{self.zone_name}/home/test', 0) - self.assertEqual('{\'irods_response\': {\'status_code\': -79000, \'status_message\': \'cannot remove non-empty collection: SYS_COLLECTION_NOT_EMPTY\'}}', str(response['data'])) - response = self.api.collections.remove(f'/{self.zone_name}/home/test', 1) - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - - #tests the stat operation + self.assertRaises( + TypeError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + 0, + ) + self.assertRaises( + ValueError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + 5, + ) + + # test removing collection + response = self.api.collections.create(f"/{self.zone_name}/home/new") + self.assertEqual( + "{'created': True, 'irods_response': {'status_code': 0}}", + str(response["data"]), + ) + response = self.api.collections.remove(f"/{self.zone_name}/home/new") + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + # test invalid paths + response = self.api.collections.stat( + f"/{self.zone_name}/home/tensaitekinaaidorusama" + ) + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + response = self.api.collections.stat( + f"/{self.zone_name}/home/aremonainainaikoremonainainai" + ) + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + response = self.api.collections.stat( + f"/{self.zone_name}/home/binglebangledingledangle" + ) + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + response = self.api.collections.stat( + f"{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + + # test recurse + response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 1) + self.assertEqual( + "{'created': True, 'irods_response': {'status_code': 0}}", + str(response["data"]), + ) + response = self.api.collections.remove(f"/{self.zone_name}/home/test", 0) + self.assertEqual( + "{'irods_response': {'status_code': -79000, 'status_message': 'cannot remove non-empty collection: SYS_COLLECTION_NOT_EMPTY'}}", + str(response["data"]), + ) + response = self.api.collections.remove(f"/{self.zone_name}/home/test", 1) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # tests the stat operation def testStat(self): self.api.setToken(self.rodsadmin_bearer_token) - #clean up test collections - self.api.collections.remove(f'/{self.zone_name}/home/new') - - #test param checking - self.assertRaises(TypeError, self.api.collections.stat, 0, 'ticket') - self.assertRaises(TypeError, self.api.collections.stat, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0) - - #test invalid paths - response = self.api.collections.stat(f'/{self.zone_name}/home/new') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - response = self.api.collections.stat('{self.zone_name}/home/new') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - - #test valid path - response = self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertTrue(response['data']['permissions']) - - - #tests the list operation + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/new") + + # test param checking + self.assertRaises(TypeError, self.api.collections.stat, 0, "ticket") + self.assertRaises( + TypeError, + self.api.collections.stat, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + ) + + # test invalid paths + response = self.api.collections.stat(f"/{self.zone_name}/home/new") + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + response = self.api.collections.stat(f"{self.zone_name}/home/new") + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + + # test valid path + response = self.api.collections.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertTrue(response["data"]["permissions"]) + + # tests the list operation def testList(self): self.api.setToken(self.rodsadmin_bearer_token) - #clean up test collections - self.api.collections.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb') - self.api.collections.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/albania') - self.api.collections.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/bosnia') - self.api.collections.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia') - - #test param checking - self.assertRaises(TypeError, self.api.collections.list, 0, 'ticket') - self.assertRaises(TypeError, self.api.collections.list, f'/{self.zone_name}/home/{self.rodsadmin_username}', '0', 'ticket') - self.assertRaises(ValueError, self.api.collections.list, f'/{self.zone_name}/home/{self.rodsadmin_username}', 5, 'ticket') - self.assertRaises(TypeError, self.api.collections.list, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0, 0) - - #test empty collection - response = self.api.collections.list(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual('None', str(response['data']['entries'])) - - #test collection with one item - self.api.collections.create(f'/{self.zone_name}/home/{self.rodsadmin_username}/bosnia') - response = self.api.collections.list(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/bosnia', str(response['data']['entries'][0])) - - #test collection with multiple items - self.api.collections.create(f'/{self.zone_name}/home/{self.rodsadmin_username}/albania') - self.api.collections.create(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia') - response = self.api.collections.list(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/albania', str(response['data']['entries'][0])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/bosnia', str(response['data']['entries'][1])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia', str(response['data']['entries'][2])) - - #test without recursion - self.api.collections.create(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb') - response = self.api.collections.list(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/albania', str(response['data']['entries'][0])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/bosnia', str(response['data']['entries'][1])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia', str(response['data']['entries'][2])) - self.assertEqual(len(response['data']['entries']), 3) - - #test with recursion - response = self.api.collections.list(f'/{self.zone_name}/home/{self.rodsadmin_username}', 1) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/albania', str(response['data']['entries'][0])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/bosnia', str(response['data']['entries'][1])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia', str(response['data']['entries'][2])) - self.assertEqual(f'/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb', str(response['data']['entries'][3])) - - - #tests the set permission operation + # clean up test collections + self.api.collections.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb" + ) + self.api.collections.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania" + ) + self.api.collections.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia" + ) + self.api.collections.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia" + ) + + # test param checking + self.assertRaises(TypeError, self.api.collections.list, 0, "ticket") + self.assertRaises( + TypeError, + self.api.collections.list, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + "ticket", + ) + self.assertRaises( + ValueError, + self.api.collections.list, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + "ticket", + ) + self.assertRaises( + TypeError, + self.api.collections.list, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + 0, + ) + + # test empty collection + response = self.api.collections.list( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertEqual("None", str(response["data"]["entries"])) + + # test collection with one item + self.api.collections.create( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia" + ) + response = self.api.collections.list( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][0]), + ) + + # test collection with multiple items + self.api.collections.create( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania" + ) + self.api.collections.create( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia" + ) + response = self.api.collections.list( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", + str(response["data"]["entries"][0]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][1]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", + str(response["data"]["entries"][2]), + ) + + # test without recursion + self.api.collections.create( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb" + ) + response = self.api.collections.list( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", + str(response["data"]["entries"][0]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][1]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", + str(response["data"]["entries"][2]), + ) + self.assertEqual(len(response["data"]["entries"]), 3) + + # test with recursion + response = self.api.collections.list( + f"/{self.zone_name}/home/{self.rodsadmin_username}", 1 + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", + str(response["data"]["entries"][0]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][1]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", + str(response["data"]["entries"][2]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb", + str(response["data"]["entries"][3]), + ) + + # tests the set permission operation def testSetPermission(self): self.api.setToken(self.rodsadmin_bearer_token) - #test param checking - self.assertRaises(TypeError, self.api.collections.set_permission, 0, 'jeb', 'read', 0) - self.assertRaises(TypeError, self.api.collections.set_permission, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0, 'read', 0) - self.assertRaises(TypeError, self.api.collections.set_permission, f'/{self.zone_name}/home/{self.rodsadmin_username}', 'jeb', 0, 0) - self.assertRaises(TypeError, self.api.collections.set_permission, f'/{self.zone_name}/home/{self.rodsadmin_username}', 'jeb', 'read', '0') - self.assertRaises(ValueError, self.api.collections.set_permission, f'/{self.zone_name}/home/{self.rodsadmin_username}', 'jeb', 'read', 5) - - #create new collection - response = self.api.collections.create(f'/{self.zone_name}/home/setPerms') - self.assertEqual(response['data']['irods_response']['status_code'], 0) - - #test no permission + # test param checking + self.assertRaises( + TypeError, self.api.collections.set_permission, 0, "jeb", "read", 0 + ) + self.assertRaises( + TypeError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + "read", + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "jeb", + 0, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "jeb", + "read", + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "jeb", + "read", + 5, + ) + + # create new collection + response = self.api.collections.create(f"/{self.zone_name}/home/setPerms") + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test no permission self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f'/{self.zone_name}/home/setPerms') - self.assertEqual(response['data']['irods_response']['status_code'], -170000) + response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") + self.assertEqual(response["data"]["irods_response"]["status_code"], -170000) - #test set permission + # test set permission self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.set_permission(f'/{self.zone_name}/home/setPerms', self.rodsuser_username, 'read') - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - #test with permission + response = self.api.collections.set_permission( + f"/{self.zone_name}/home/setPerms", self.rodsuser_username, "read" + ) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # test with permission self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f'/{self.zone_name}/home/setPerms') - self.assertTrue(response['data']['permissions']) - - #test set permission null - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.set_permission(f'/{self.zone_name}/home/setPerms', self.rodsuser_username, 'null') - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) + response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") + self.assertTrue(response["data"]["permissions"]) - #test no permission + # test set permission null + self.api.setToken(self.rodsadmin_bearer_token) + response = self.api.collections.set_permission( + f"/{self.zone_name}/home/setPerms", self.rodsuser_username, "null" + ) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # test no permission self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f'/{self.zone_name}/home/setPerms') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) + response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) - #remove the collection + # remove the collection self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.remove(f'/{self.zone_name}/home/setPerms', 1, 1) - self.assertEqual(response['data']['irods_response']['status_code'], 0) - + response = self.api.collections.remove(f"/{self.zone_name}/home/setPerms", 1, 1) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - #tests the set inheritance operation + # tests the set inheritance operation def testSetInheritance(self): self.api.setToken(self.rodsadmin_bearer_token) - #test param checking + # test param checking self.assertRaises(TypeError, self.api.collections.set_inheritance, 0, 0, 0) - self.assertRaises(TypeError, self.api.collections.set_inheritance, f'/{self.zone_name}/home/{self.rodsadmin_username}', '0', 0) - self.assertRaises(ValueError, self.api.collections.set_inheritance, f'/{self.zone_name}/home/{self.rodsadmin_username}', 5, 0) - self.assertRaises(TypeError, self.api.collections.set_inheritance, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0, '0') - self.assertRaises(ValueError, self.api.collections.set_inheritance, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0, 5) - - #control - response = self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertFalse(response['data']['inheritance_enabled']) - - #test enabling inheritance - response = self.api.collections.set_inheritance(f'/{self.zone_name}/home/{self.rodsadmin_username}', 1) - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - #check if changed - response = self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertTrue(response['data']['inheritance_enabled']) - - #test disabling inheritance - response = self.api.collections.set_inheritance(f'/{self.zone_name}/home/{self.rodsadmin_username}', 0) - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - #check if changed - response = self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertFalse(response['data']['inheritance_enabled']) - - - #test the modify permissions operation + self.assertRaises( + TypeError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + 0, + ) + self.assertRaises( + ValueError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + 5, + ) + + # control + response = self.api.collections.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertFalse(response["data"]["inheritance_enabled"]) + + # test enabling inheritance + response = self.api.collections.set_inheritance( + f"/{self.zone_name}/home/{self.rodsadmin_username}", 1 + ) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # check if changed + response = self.api.collections.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertTrue(response["data"]["inheritance_enabled"]) + + # test disabling inheritance + response = self.api.collections.set_inheritance( + f"/{self.zone_name}/home/{self.rodsadmin_username}", 0 + ) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # check if changed + response = self.api.collections.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertFalse(response["data"]["inheritance_enabled"]) + + # test the modify permissions operation def testModifyPermissions(self): self.api.setToken(self.rodsadmin_bearer_token) - ops_permissions = [ - { - 'entity_name': self.rodsuser_username, - 'acl': 'read' - } - ] - - ops_permissions_null = [ - { - 'entity_name': self.rodsuser_username, - 'acl': 'null' - } - ] - - #test param checking - self.assertRaises(TypeError, self.api.collections.modify_permissions, 0, ops_permissions, 0) - self.assertRaises(TypeError, self.api.collections.modify_permissions, f'/{self.zone_name}/home/{self.rodsadmin_username}', 5, 0) - self.assertRaises(TypeError, self.api.collections.modify_permissions, f'/{self.zone_name}/home/{self.rodsadmin_username}', ops_permissions, '0') - self.assertRaises(ValueError, self.api.collections.modify_permissions, f'/{self.zone_name}/home/{self.rodsadmin_username}', ops_permissions, 5) - - #create new collection - response = self.api.collections.create(f'/{self.zone_name}/home/modPerms') - self.assertEqual(response['data']['irods_response']['status_code'], 0) - - #test no permissions + ops_permissions = [{"entity_name": self.rodsuser_username, "acl": "read"}] + + ops_permissions_null = [{"entity_name": self.rodsuser_username, "acl": "null"}] + + # test param checking + self.assertRaises( + TypeError, self.api.collections.modify_permissions, 0, ops_permissions, 0 + ) + self.assertRaises( + TypeError, + self.api.collections.modify_permissions, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.modify_permissions, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_permissions, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.modify_permissions, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_permissions, + 5, + ) + + # create new collection + response = self.api.collections.create(f"/{self.zone_name}/home/modPerms") + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test no permissions self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f'/{self.zone_name}/home/modPerms') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) + response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) - #test set permissions + # test set permissions self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.modify_permissions(f'/{self.zone_name}/home/modPerms', ops_permissions) - self.assertEqual(response['data']['irods_response']['status_code'], 0) + response = self.api.collections.modify_permissions( + f"/{self.zone_name}/home/modPerms", ops_permissions + ) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - #test with permissions + # test with permissions self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f'/{self.zone_name}/home/modPerms') - self.assertTrue(response['data']['permissions']) + response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") + self.assertTrue(response["data"]["permissions"]) - #test set permissions nuil + # test set permissions nuil self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.modify_permissions(f'/{self.zone_name}/home/modPerms', ops_permissions_null) - self.assertEqual(response['data']['irods_response']['status_code'], 0) + response = self.api.collections.modify_permissions( + f"/{self.zone_name}/home/modPerms", ops_permissions_null + ) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - #test without permissions + # test without permissions self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f'/{self.zone_name}/home/modPerms') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) + response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) - #remove the collection + # remove the collection self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.remove(f'/{self.zone_name}/home/modPerms', 1, 1) - self.assertEqual(response['data']['irods_response']['status_code'], 0) - + response = self.api.collections.remove(f"/{self.zone_name}/home/modPerms", 1, 1) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - #test the modify metadata operation + # test the modify metadata operation def testModifyMetadata(self): self.api.setToken(self.rodsadmin_bearer_token) - - ops_metadata = [ - { - 'operation': 'add', - 'attribute': 'eyeballs', - 'value': 'itchy' - } - ] + + ops_metadata = [{"operation": "add", "attribute": "eyeballs", "value": "itchy"}] ops_metadata_remove = [ - { - 'operation': 'remove', - 'attribute': 'eyeballs', - 'value': 'itchy' - } + {"operation": "remove", "attribute": "eyeballs", "value": "itchy"} ] - #test param checking - self.assertRaises(TypeError, self.api.collections.modify_metadata, 0, ops_metadata, 0) - self.assertRaises(TypeError, self.api.collections.modify_metadata, f'/{self.zone_name}/home/{self.rodsadmin_username}', 5, 0) - self.assertRaises(TypeError, self.api.collections.modify_metadata, f'/{self.zone_name}/home/{self.rodsadmin_username}', ops_metadata, '0') - self.assertRaises(ValueError, self.api.collections.modify_metadata, f'/{self.zone_name}/home/{self.rodsadmin_username}', ops_metadata, 5) - - #test adding and removing metadata - response = self.api.collections.modify_metadata(f'/{self.zone_name}/home/{self.rodsadmin_username}', ops_metadata) - self.assertEqual(response['data']['irods_response']['status_code'], 0) - response = self.api.collections.modify_metadata(f'/{self.zone_name}/home/{self.rodsadmin_username}', ops_metadata_remove) - self.assertEqual(response['data']['irods_response']['status_code'], 0) - - - #tests the rename operation + # test param checking + self.assertRaises( + TypeError, self.api.collections.modify_metadata, 0, ops_metadata, 0 + ) + self.assertRaises( + TypeError, + self.api.collections.modify_metadata, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.modify_metadata, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_metadata, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.modify_metadata, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_metadata, + 5, + ) + + # test adding and removing metadata + response = self.api.collections.modify_metadata( + f"/{self.zone_name}/home/{self.rodsadmin_username}", ops_metadata + ) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + response = self.api.collections.modify_metadata( + f"/{self.zone_name}/home/{self.rodsadmin_username}", ops_metadata_remove + ) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # tests the rename operation def testRename(self): self.api.setToken(self.rodsadmin_bearer_token) - #test param checking - self.assertRaises(TypeError, self.api.collections.rename, f'/{self.zone_name}/home/{self.rodsadmin_username}', 0) - self.assertRaises(TypeError, self.api.collections.rename, 0, f'/{self.zone_name}/home/pods') - - #test before move - response = self.api.collections.stat(f'/{self.zone_name}/home/pods') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - response = self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertTrue(response['data']['permissions']) - - #test renaming - response = self.api.collections.rename(f'/{self.zone_name}/home/{self.rodsadmin_username}', f'/{self.zone_name}/home/pods') - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - #test before move - response = self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual('{\'irods_response\': {\'status_code\': -170000}}', str(response['data'])) - response = self.api.collections.stat(f'/{self.zone_name}/home/pods') - self.assertTrue(response['data']['permissions']) - - #test renaming - response = self.api.collections.rename(f'/{self.zone_name}/home/pods', f'/{self.zone_name}/home/{self.rodsadmin_username}') - self.assertEqual('{\'irods_response\': {\'status_code\': 0}}', str(response['data'])) - - - #tests the touch operation + # test param checking + self.assertRaises( + TypeError, + self.api.collections.rename, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + ) + self.assertRaises( + TypeError, self.api.collections.rename, 0, f"/{self.zone_name}/home/pods" + ) + + # test before move + response = self.api.collections.stat(f"/{self.zone_name}/home/pods") + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + response = self.api.collections.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertTrue(response["data"]["permissions"]) + + # test renaming + response = self.api.collections.rename( + f"/{self.zone_name}/home/{self.rodsadmin_username}", + f"/{self.zone_name}/home/pods", + ) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # test before move + response = self.api.collections.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}" + ) + self.assertEqual( + "{'irods_response': {'status_code': -170000}}", str(response["data"]) + ) + response = self.api.collections.stat(f"/{self.zone_name}/home/pods") + self.assertTrue(response["data"]["permissions"]) + + # test renaming + response = self.api.collections.rename( + f"/{self.zone_name}/home/pods", + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ) + self.assertEqual( + "{'irods_response': {'status_code': 0}}", str(response["data"]) + ) + + # tests the touch operation def testTouch(self): self.api.setToken(self.rodsadmin_bearer_token) - + self.assertTrue(True) - + # Tests for data object operations class dataObjectsTests(unittest.TestCase): - + @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'data_objects'}) + setup_class(cls, {"endpoint_name": "data_objects"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) def testCommonOperations(self): self.api.setToken(self.rodsadmin_bearer_token) - print(self.rodsadmin_bearer_token) try: # Create a unixfilesystem resource. - r = self.api.resources.create('resource', 'unixfilesystem', self.host, '/tmp/resource', '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.create( + "resource", "unixfilesystem", self.host, "/tmp/resource", "" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) self.api.setToken(self.rodsuser_bearer_token) # Create a non-empty data object - print(self.api.collections.stat(f'/{self.zone_name}/home/{self.rodsuser_username}')) - r = self.api.data_objects.write('These are the bytes being written to the object', f'/{self.zone_name}/home/{self.rodsuser_username}/file.txt', 'resource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.write( + "These are the bytes being written to the object", + f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", + "resource", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Replicate the data object - r = self.api.data_objects.replicate(f'/{self.zone_name}/home/{self.rodsuser_username}/file.txt', dst_resource='resource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.replicate( + f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", + dst_resource="resource", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that there are two replicas # TODO: Implement once query operations are completed # Trim the first data object - r = self.api.data_objects.trim(f'/{self.zone_name}/home/{self.rodsuser_username}/file.txt', 0) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.trim( + f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", 0 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Rename the data object - r = self.api.data_objects.rename(f'/{self.zone_name}/home/{self.rodsuser_username}/file.txt', f'/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.rename( + f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", + f"/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Copy the data object - r = self.api.data_objects.copy(f'/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt', f'/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.copy( + f"/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt", + f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Set permission on the object - r = self.api.data_objects.set_permission(f'/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt', 'rods', 'read') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.set_permission( + f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt", + "rods", + "read", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Confirm that the permission has been set - r = self.api.data_objects.stat(f'/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertIn({ - 'name': 'rods', - 'zone': self.zone_name, - 'type': 'rodsadmin', - 'perm': 'read_object' - }, r['data']['permissions']) + r = self.api.data_objects.stat( + f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertIn( + { + "name": "rods", + "zone": self.zone_name, + "type": "rodsadmin", + "perm": "read_object", + }, + r["data"]["permissions"], + ) finally: # Remove the data objects - r = self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt', 0, 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt", 0, 1 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - r = self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt', 0, 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt", 0, 1 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) self.api.setToken(self.rodsadmin_bearer_token) # Remove the resource - r = self.api.resources.remove('resource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - + r = self.api.resources.remove("resource") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) def testChecksums(self): self.api.setToken(self.rodsadmin_bearer_token) # Create a unixfilesystem resource. - r = self.api.resources.create('newresource', 'unixfilesystem', self.host, '/tmp/newresource', '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.create( + "newresource", "unixfilesystem", self.host, "/tmp/newresource", "" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Create a non-empty data object - r = self.api.data_objects.write('These are the bytes being written to the object', f'/{self.zone_name}/home/{self.rodsadmin_username}/file.txt', 'newresource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.write( + "These are the bytes being written to the object", + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", + "newresource", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Replicate the data object - r = self.api.data_objects.replicate(f'/{self.zone_name}/home/{self.rodsadmin_username}/file.txt', dst_resource='newresource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.replicate( + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", + dst_resource="newresource", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that there are two replicas # TODO: Implement once query operations are completed try: # Calculate a checksum for the first replica - r = self.api.data_objects.calculate_checksum(f'/{self.zone_name}/home/{self.rodsadmin_username}/file.txt', replica_number=0) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.calculate_checksum( + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", + replica_number=0, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Verify checksum information across all replicas. - r = self.api.data_objects.verify_checksum(f'/{self.zone_name}/home/{self.rodsadmin_username}/file.txt') - print(r) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.verify_checksum( + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) finally: # Remove the data objects - r = self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/file.txt', 0, 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", 0, 1 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Remove the resource - r = self.api.resources.remove('newresource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - - + r = self.api.resources.remove("newresource") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + def testTouch(self): self.api.setToken(self.rodsadmin_bearer_token) # Test touching non existant data object with no_create - r = self.api.data_objects.touch(f'/{self.zone_name}/home/{self.rodsadmin_username}/new.txt', 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.touch( + f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 1 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the object has not been created - r = self.api.data_objects.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}/new.txt') - self.assertEqual(r['data']['irods_response']['status_code'], -171000) + r = self.api.data_objects.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], -171000) # Test touching non existant object without no_create - r = self.api.data_objects.touch(f'/{self.zone_name}/home/{self.rodsadmin_username}/new.txt', 0) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.touch( + f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 0 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the object has been created - r = self.api.data_objects.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}/new.txt') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Test touching existing object without no_create - r = self.api.data_objects.touch(f'/{self.zone_name}/home/{self.rodsadmin_username}/new.txt', 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.touch( + f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 1 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Remove the object - r = self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/new.txt') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) def testRegister(self): self.api.setToken(self.rodsadmin_bearer_token) # Create a non-empty local file. - content = 'data' - with open('/tmp/register-demo.txt', 'w') as f: - f.write(content) - + content = "data" + with open("/tmp/register-demo.txt", "w") as f: + f.write(content) + # Show the data object we want to create via registration does not exist. - r = self.api.data_objects.stat(f'/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt') - self.assertEqual(r['data']['irods_response']['status_code'], -171000) + r = self.api.data_objects.stat( + f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], -171000) try: # Create a unixfilesystem resource. - r = self.api.resources.create('register_resource', 'unixfilesystem', self.host, '/tmp/register_resource', '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.create( + "register_resource", + "unixfilesystem", + self.host, + "/tmp/register_resource", + "", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Register the local file into the catalog as a new data object. # We know we're registering a new data object because the "as-additional-replica" # parameter isn't set to 1. - r = self.api.data_objects.register(f'/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt', '/tmp/register-demo.txt', 'register_resource', data_size=len(content)) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.register( + f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt", + "/tmp/register-demo.txt", + "register_resource", + data_size=len(content), + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show a new data object exists with the expected replica information. # TODO: add when query operations are implemented finally: # Unregisterr the dataq object - r = self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt', 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt", 1 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Remove the resource - r = self.api.resources.remove('register_resource') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.remove("register_resource") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - def testParallelWrite(self): self.api.setToken(self.rodsadmin_bearer_token) - self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt', 0, 1) + self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", 0, 1 + ) # Open parallel write - r = self.api.data_objects.parallel_write_init(f'/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt', 3) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - handle = r['data']['parallel_write_handle'] - + r = self.api.data_objects.parallel_write_init( + f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", 3 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + handle = r["data"]["parallel_write_handle"] + try: # Write to the data object using the parallel write handle. futures = [] with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: - for e in enumerate(['A', 'B', 'C']): + for e in enumerate(["A", "B", "C"]): count = 10 - futures.append(executor.submit( - self.api.data_objects.write, bytes=e[1] * count, offset=e[0] * count, stream_index=e[0], parallel_write_handle=handle - )) + futures.append( + executor.submit( + self.api.data_objects.write, + bytes=e[1] * count, + offset=e[0] * count, + stream_index=e[0], + parallel_write_handle=handle, + ) + ) for f in concurrent.futures.as_completed(futures): r = f.result() - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) finally: # Close parallel write r = self.api.data_objects.parallel_write_shutdown(handle) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Remove the object - r = self.api.data_objects.remove(f'/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt', 0, 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - - + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", + 0, + 1, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Tests for resources operations @@ -694,117 +1062,129 @@ class resourcesTests(unittest.TestCase): @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'resources'}) + setup_class(cls, {"endpoint_name": "resources"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) def testCommonOperations(self): self.api.setToken(self.rodsadmin_bearer_token) - #TEMPORARY pre-test cleanup - #test is currently not passing, so cleanup occusrs at the beginning to allow it to be run more than once in a row - self.api.resources.remove_child('test_repl', 'test_ufs0') - self.api.resources.remove_child('test_repl', 'test_ufs1') - self.api.resources.remove('test_ufs0') - self.api.resources.remove('test_ufs1') - self.api.resources.remove('test_repl') + # TEMPORARY pre-test cleanup + # test is currently not passing, so cleanup occusrs at the beginning to allow it to be run more than once in a row + self.api.resources.remove_child("test_repl", "test_ufs0") + self.api.resources.remove_child("test_repl", "test_ufs1") + self.api.resources.remove("test_ufs0") + self.api.resources.remove("test_ufs1") + self.api.resources.remove("test_repl") - resc_repl = 'test_repl' - resc_ufs0 = 'test_ufs0' - resc_ufs1 = 'test_ufs1' + resc_repl = "test_repl" + resc_ufs0 = "test_ufs0" + resc_ufs1 = "test_ufs1" # Create three resources (replication w/ two unixfilesystem resources). - r = self.api.resources.create(resc_repl, 'replication', '', '', '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - + r = self.api.resources.create(resc_repl, "replication", "", "", "") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + # Show the replication resource was created. r = self.api.resources.stat(resc_repl) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['exists'], True) - self.assertIn('id', r['data']['info']) - self.assertEqual(r['data']['info']['name'], resc_repl) - self.assertEqual(r['data']['info']['type'], 'replication') - self.assertEqual(r['data']['info']['zone'], 'tempZone') - self.assertEqual(r['data']['info']['host'], 'EMPTY_RESC_HOST') - self.assertEqual(r['data']['info']['vault_path'], 'EMPTY_RESC_PATH') - self.assertIn('status', r['data']['info']) - self.assertIn('context', r['data']['info']) - self.assertIn('comments', r['data']['info']) - self.assertIn('information', r['data']['info']) - self.assertIn('free_space', r['data']['info']) - self.assertIn('free_space_last_modified', r['data']['info']) - self.assertEqual(r['data']['info']['parent_id'], '') - self.assertIn('created', r['data']['info']) - self.assertIn('last_modified', r['data']['info']) - self.assertIn('last_modified_millis', r['data']['info']) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], True) + self.assertIn("id", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["name"], resc_repl) + self.assertEqual(r["data"]["info"]["type"], "replication") + self.assertEqual(r["data"]["info"]["zone"], "tempZone") + self.assertEqual(r["data"]["info"]["host"], "EMPTY_RESC_HOST") + self.assertEqual(r["data"]["info"]["vault_path"], "EMPTY_RESC_PATH") + self.assertIn("status", r["data"]["info"]) + self.assertIn("context", r["data"]["info"]) + self.assertIn("comments", r["data"]["info"]) + self.assertIn("information", r["data"]["info"]) + self.assertIn("free_space", r["data"]["info"]) + self.assertIn("free_space_last_modified", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["parent_id"], "") + self.assertIn("created", r["data"]["info"]) + self.assertIn("last_modified", r["data"]["info"]) + self.assertIn("last_modified_millis", r["data"]["info"]) # Capture the replication resource's id. # This resource is going to be the parent of the unixfilesystem resources. # This value is needed to verify the relationship. - resc_repl_id = r['data']['info']['id'] + resc_repl_id = r["data"]["info"]["id"] for resc_name in [resc_ufs0, resc_ufs1]: - with self.subTest(f'Create and attach resource [{resc_name}] to [{resc_repl}]'): - vault_path = f'/tmp/{resc_name}_vault' + with self.subTest( + f"Create and attach resource [{resc_name}] to [{resc_repl}]" + ): + vault_path = f"/tmp/{resc_name}_vault" # Create a unixfilesystem resource. - r = self.api.resources.create(resc_name, 'unixfilesystem', self.host, vault_path, '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.create( + resc_name, "unixfilesystem", self.host, vault_path, "" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Add the unixfilesystem resource as a child of the replication resource. r = self.api.resources.add_child(resc_repl, resc_name) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the resource was created and configured successfully. r = self.api.resources.stat(resc_name) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['exists'], True) - self.assertIn('id', r['data']['info']) - self.assertEqual(r['data']['info']['name'], resc_name) - self.assertEqual(r['data']['info']['type'], 'unixfilesystem') - self.assertEqual(r['data']['info']['zone'], self.zone_name) - self.assertEqual(r['data']['info']['host'], self.host) - self.assertEqual(r['data']['info']['vault_path'], vault_path) - self.assertIn('status', r['data']['info']) - self.assertIn('context', r['data']['info']) - self.assertIn('comments', r['data']['info']) - self.assertIn('information', r['data']['info']) - self.assertIn('free_space', r['data']['info']) - self.assertIn('free_space_last_modified', r['data']['info']) - self.assertEqual(r['data']['info']['parent_id'], resc_repl_id) - self.assertIn('created', r['data']['info']) - self.assertIn('last_modified', r['data']['info']) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], True) + self.assertIn("id", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["name"], resc_name) + self.assertEqual(r["data"]["info"]["type"], "unixfilesystem") + self.assertEqual(r["data"]["info"]["zone"], self.zone_name) + self.assertEqual(r["data"]["info"]["host"], self.host) + self.assertEqual(r["data"]["info"]["vault_path"], vault_path) + self.assertIn("status", r["data"]["info"]) + self.assertIn("context", r["data"]["info"]) + self.assertIn("comments", r["data"]["info"]) + self.assertIn("information", r["data"]["info"]) + self.assertIn("free_space", r["data"]["info"]) + self.assertIn("free_space_last_modified", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["parent_id"], resc_repl_id) + self.assertIn("created", r["data"]["info"]) + self.assertIn("last_modified", r["data"]["info"]) # Create a data object targeting the replication resource. - data_object = f'/{self.zone_name}/home/{self.rodsadmin_username}/resource_obj' - r = self.api.data_objects.write('These are the bytes to be written', data_object, resc_repl, 0) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + data_object = f"/{self.zone_name}/home/{self.rodsadmin_username}/resource_obj" + r = self.api.data_objects.write( + "These are the bytes to be written", data_object, resc_repl, 0 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show there are two replicas under the replication resource hierarchy. - r = self.api.queries.execute_genquery(f"select DATA_NAME, RESC_NAME where DATA_NAME = '{os.path.basename(data_object)}'") - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(len(r['data']['rows']), 2) + r = self.api.queries.execute_genquery( + f"select DATA_NAME, RESC_NAME where DATA_NAME = '{os.path.basename(data_object)}'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 2) - resc_tuple = (r['data']['rows'][0][1], r['data']['rows'][1][1]) + resc_tuple = (r["data"]["rows"][0][1], r["data"]["rows"][1][1]) self.assertIn(resc_tuple, [(resc_ufs0, resc_ufs1), (resc_ufs1, resc_ufs0)]) # Trim a replica. r = self.api.data_objects.trim(data_object, 0) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show there is only one replica under the replication resource hierarchy. - r = self.api.queries.execute_genquery(f"select DATA_NAME, RESC_NAME where DATA_NAME = '{os.path.basename(data_object)}'") - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(len(r['data']['rows']), 1) + r = self.api.queries.execute_genquery( + f"select DATA_NAME, RESC_NAME where DATA_NAME = '{os.path.basename(data_object)}'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 1) # Launch rebalance r = self.api.resources.rebalance(resc_repl) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Give the rebalance operation time to complete! time.sleep(3) @@ -815,120 +1195,116 @@ def testCommonOperations(self): # Remove the data object. r = self.api.data_objects.remove(data_object, 0, 1) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - # Remove resources. + # Remove resources. for resc_name in [resc_ufs0, resc_ufs1]: - with self.subTest(f'Detach and remove resource [{resc_name}] from [{resc_repl}]'): + with self.subTest( + f"Detach and remove resource [{resc_name}] from [{resc_repl}]" + ): # Detach ufs resource from the replication resource. r = self.api.resources.remove_child(resc_repl, resc_name) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Remove ufs resource. r = self.api.resources.remove(resc_name) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the resource no longer exists. r = self.api.resources.stat(resc_name) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['exists'], False) - + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], False) + # Remove replication resource. r = self.api.resources.remove(resc_repl) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the resource no longer exists. r = self.api.resources.stat(resc_repl) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['exists'], False) - + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], False) def testModifyMetadata(self): self.api.setToken(self.rodsadmin_bearer_token) # Create a unixfilesystem resource. - r = self.api.resources.create('metadata_demo', 'unixfilesystem', self.host, '/tmp/metadata_demo_vault', '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - + r = self.api.resources.create( + "metadata_demo", "unixfilesystem", self.host, "/tmp/metadata_demo_vault", "" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + operations = [ - { - 'operation': 'add', - 'attribute': 'a1', - 'value': 'v1', - 'units': 'u1' - } + {"operation": "add", "attribute": "a1", "value": "v1", "units": "u1"} ] # Add the metadata to the resource - r = self.api.resources.modify_metadata('metadata_demo', operations) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.modify_metadata("metadata_demo", operations) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the metadata is on the resource - r = self.api.queries.execute_genquery("select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'") - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['rows'][0][0], 'metadata_demo') + r = self.api.queries.execute_genquery( + "select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["rows"][0][0], "metadata_demo") # Remove the metadata from the resource. operations = [ - { - 'operation': 'remove', - 'attribute': 'a1', - 'value': 'v1', - 'units': 'u1' - } + {"operation": "remove", "attribute": "a1", "value": "v1", "units": "u1"} ] - r = self.api.resources.modify_metadata('metadata_demo', operations) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.resources.modify_metadata("metadata_demo", operations) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the metadata is no longer on the resource - r = self.api.queries.execute_genquery("select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'") - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(len(r['data']['rows']), 0) + r = self.api.queries.execute_genquery( + "select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 0) # Remove the resource - r = self.api.resources.remove('metadata_demo') - + r = self.api.resources.remove("metadata_demo") def testModifyProperties(self): self.api.setToken(self.rodsadmin_bearer_token) - resource = 'properties_demo' + resource = "properties_demo" # Create a new resource. - r = self.api.resources.create(resource, 'replication', '', '', '') - self.assertEqual(r['data']['irods_response']['status_code'], 0) - + r = self.api.resources.create(resource, "replication", "", "", "") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + try: # The list of updates to apply in sequence. property_map = [ - ('name', 'test_modifying_resource_properties_renamed'), - ('type', 'passthru'), - ('host', 'example.org'), - ('vault_path', '/tmp/test_modifying_resource_properties_vault'), - ('status', 'down'), - ('status', 'up'), - ('comments', 'test_modifying_resource_properties_comments'), - ('information', 'test_modifying_resource_properties_information'), - ('free_space', 'test_modifying_resource_properties_free_space'), - ('context', 'test_modifying_resource_properties_context') + ("name", "test_modifying_resource_properties_renamed"), + ("type", "passthru"), + ("host", "example.org"), + ("vault_path", "/tmp/test_modifying_resource_properties_vault"), + ("status", "down"), + ("status", "up"), + ("comments", "test_modifying_resource_properties_comments"), + ("information", "test_modifying_resource_properties_information"), + ("free_space", "test_modifying_resource_properties_free_space"), + ("context", "test_modifying_resource_properties_context"), ] # Apply each update to the resource and verify that each one results # in the expected results. for p, v in property_map: - with self.subTest(f'Setting property [{p}] to value [{v}]'): + with self.subTest(f"Setting property [{p}] to value [{v}]"): # Change a property of the resource. r = self.api.resources.modify(resource, p, v) # Make sure to update the "resource" variable following a successful rename. - if 'name' == p: + if "name" == p: resource = v # Show the property was modified. r = self.api.resources.stat(resource) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['info'][p], v) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["info"][p], v) finally: # Remove the resource r = self.api.resources.remove(resource) @@ -939,59 +1315,62 @@ class rulesTests(unittest.TestCase): @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'rules'}) + setup_class(cls, {"endpoint_name": "rules"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) def testList(self): # Try listing rule engine plugins r = self.api.rules.list_rule_engines() - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertGreater(len(r['data']['rule_engine_plugin_instances']), 0) - + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertGreater(len(r["data"]["rule_engine_plugin_instances"]), 0) def testExecuteRule(self): - test_msg = 'This was run by the iRODS HTTP API test suite!' + test_msg = "This was run by the iRODS HTTP API test suite!" # Execute rule text against the iRODS rule language. - r = self.api.rules.execute(f'writeLine("stdout", "{test_msg}")', 'irods_rule_engine_plugin-irods_rule_language-instance') - - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(r['data']['stderr'], None) + r = self.api.rules.execute( + f'writeLine("stdout", "{test_msg}")', + "irods_rule_engine_plugin-irods_rule_language-instance", + ) + + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["stderr"], None) # The REP always appends a newline character to the result. While we could trim the result, # it is better to append a newline character to the expected result to guarantee things align. - self.assertEqual(r['data']['stdout'], test_msg + '\n') - + self.assertEqual(r["data"]["stdout"], test_msg + "\n") def testRemoveDelayRule(self): - rep_instance = 'irods_rule_engine_plugin-irods_rule_language-instance' + rep_instance = "irods_rule_engine_plugin-irods_rule_language-instance" # Schedule a delay rule to execute in the distant future. - r = self.api.rules.execute(f'delay("{rep_instance}1h") {{ writeLine("serverLog", "iRODS HTTP API"); }}', rep_instance) + r = self.api.rules.execute( + f'delay("{rep_instance}1h") {{ writeLine("serverLog", "iRODS HTTP API"); }}', + rep_instance, + ) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Find the delay rule we just created. # This query assumes the test suite is running on a system where no other delay # rules are being created. - r = self.api.queries.execute_genquery('select max(RULE_EXEC_ID)') + r = self.api.queries.execute_genquery("select max(RULE_EXEC_ID)") - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(len(r['data']['rows']), 1) - - print(r) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 1) # Remove the delay rule. - r = self.api.rules.remove_delay_rule(int(r['data']['rows'][0][0])) - print(r) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + r = self.api.rules.remove_delay_rule(int(r["data"]["rows"][0][0])) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Tests for tickets operations @@ -999,109 +1378,125 @@ class ticketsTests(unittest.TestCase): @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'tickets'}) + setup_class(cls, {"endpoint_name": "tickets"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') - + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) + def testCreateAndRemove(self): self.api.setToken(self.rodsuser_bearer_token) # Create a write ticket. - ticket_type = 'write' - ticket_path = f'/{self.zone_name}/home/{self.rodsuser_username}' + ticket_type = "write" + ticket_path = f"/{self.zone_name}/home/{self.rodsuser_username}" ticket_use_count = 2000 - ticket_groups = 'public' + ticket_groups = "public" ticket_hosts = self.host - r = self.api.tickets.create(ticket_path, ticket_type, use_count=ticket_use_count, seconds_until_expiration=3600, users='rods,jeb', groups=ticket_groups, hosts=ticket_hosts) - print(r) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - ticket_string = r['data']['ticket'] + r = self.api.tickets.create( + ticket_path, + ticket_type, + use_count=ticket_use_count, + seconds_until_expiration=3600, + users="rods,jeb", + groups=ticket_groups, + hosts=ticket_hosts, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + ticket_string = r["data"]["ticket"] self.assertGreater(len(ticket_string), 0) # Show the ticket exists and has the properties we defined during creation. # We can use GenQuery for this, but it does seem better to provide a convenience operation # for this. - r = self.api.queries.execute_genquery('select TICKET_STRING, TICKET_TYPE, TICKET_COLL_NAME, TICKET_USES_LIMIT, TICKET_ALLOWED_USER_NAME, TICKET_ALLOWED_GROUP_NAME, TICKET_ALLOWED_HOST') - - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertIn(ticket_string, r['data']['rows'][0]) - self.assertEqual(r['data']['rows'][0][1], ticket_type) - self.assertEqual(r['data']['rows'][0][2], ticket_path) - self.assertEqual(r['data']['rows'][0][3], str(ticket_use_count)) - self.assertIn(r['data']['rows'][0][4], ['rods', 'jeb']) - self.assertEqual(r['data']['rows'][0][5], ticket_groups) - self.assertGreater(len(r['data']['rows'][0][6]), 0) + r = self.api.queries.execute_genquery( + "select TICKET_STRING, TICKET_TYPE, TICKET_COLL_NAME, TICKET_USES_LIMIT, TICKET_ALLOWED_USER_NAME, TICKET_ALLOWED_GROUP_NAME, TICKET_ALLOWED_HOST" + ) + + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertIn(ticket_string, r["data"]["rows"][0]) + self.assertEqual(r["data"]["rows"][0][1], ticket_type) + self.assertEqual(r["data"]["rows"][0][2], ticket_path) + self.assertEqual(r["data"]["rows"][0][3], str(ticket_use_count)) + self.assertIn(r["data"]["rows"][0][4], ["rods", "jeb"]) + self.assertEqual(r["data"]["rows"][0][5], ticket_groups) + self.assertGreater(len(r["data"]["rows"][0][6]), 0) # Remove the ticket. r = self.api.tickets.remove(ticket_string) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show the ticket no longer exists. - r = self.api.queries.execute_genquery('select TICKET_STRING') + r = self.api.queries.execute_genquery("select TICKET_STRING") - self.assertEqual(r['data']['irods_response']['status_code'], 0) - self.assertEqual(len(r['data']['rows']), 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 0) # Tests for user operations class userTests(unittest.TestCase): @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'users-groups'}) + setup_class(cls, {"endpoint_name": "users-groups"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') - + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) + def test_create_stat_and_remove_rodsuser(self): self.api.setToken(self.rodsadmin_bearer_token) - new_username = 'test_user_rodsuser' - user_type = 'rodsuser' + new_username = "test_user_rodsuser" + user_type = "rodsuser" # Create a new user. r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) + # Stat the user. r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - stat_info = r['data'] - self.assertEqual(stat_info['irods_response']['status_code'], 0) - self.assertEqual(stat_info['exists'], True) - self.assertIn('id', stat_info) - self.assertEqual(stat_info['local_unique_name'], f'{new_username}#{self.zone_name}') - self.assertEqual(stat_info['type'], user_type) + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual( + stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" + ) + self.assertEqual(stat_info["type"], user_type) # Remove the user. r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) def test_set_password(self): self.api.setToken(self.rodsadmin_bearer_token) - new_username = 'test_user_rodsuser' - user_type = 'rodsuser' + new_username = "test_user_rodsuser" + user_type = "rodsuser" # Create a new user. r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - new_password = 'new_password' + new_password = "new_password" # Set a new password - r = self.api.users_groups.set_password(new_username, self.zone_name, new_password) - self.assertEqual(r['status_code'], 200) + r = self.api.users_groups.set_password( + new_username, self.zone_name, new_password + ) + self.assertEqual(r["status_code"], 200) # Try to get a token for the user token = self.api.authenticate(new_username, new_password) @@ -1109,331 +1504,340 @@ def test_set_password(self): # Remove the user. r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) def test_create_stat_and_remove_rodsadmin(self): self.api.setToken(self.rodsadmin_bearer_token) - new_username = 'test_user_rodsadmin' - user_type = 'rodsadmin' - headers = {'Authorization': 'Bearer ' + self.rodsadmin_bearer_token} + new_username = "test_user_rodsadmin" + user_type = "rodsadmin" + headers = {"Authorization": "Bearer " + self.rodsadmin_bearer_token} # Create a new user. r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) + # Stat the user. r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - stat_info = r['data'] - self.assertEqual(stat_info['irods_response']['status_code'], 0) - self.assertEqual(stat_info['exists'], True) - self.assertIn('id', stat_info) - self.assertEqual(stat_info['local_unique_name'], f'{new_username}#{self.zone_name}') - self.assertEqual(stat_info['type'], user_type) + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual( + stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" + ) + self.assertEqual(stat_info["type"], user_type) # Remove the user. r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) def test_create_stat_and_remove_groupadmin(self): self.api.setToken(self.rodsadmin_bearer_token) - new_username = 'test_user_groupadmin' - user_type = 'groupadmin' + new_username = "test_user_groupadmin" + user_type = "groupadmin" # Create a new user. r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) + # Stat the user. r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - stat_info = r['data'] - self.assertEqual(stat_info['irods_response']['status_code'], 0) - self.assertEqual(stat_info['exists'], True) - self.assertIn('id', stat_info) - self.assertEqual(stat_info['local_unique_name'], f'{new_username}#{self.zone_name}') - self.assertEqual(stat_info['type'], user_type) + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual( + stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" + ) + self.assertEqual(stat_info["type"], user_type) # Remove the user. r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) - + self.assertEqual(r["status_code"], 200) def test_add_remove_user_to_and_from_group(self): self.api.setToken(self.rodsadmin_bearer_token) # Create a new group. - new_group = 'test_group' + new_group = "test_group" r = self.api.users_groups.create_group(new_group) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Stat the group. r = self.api.users_groups.stat(new_group) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - stat_info = r['data'] - self.assertEqual(stat_info['irods_response']['status_code'], 0) - self.assertEqual(stat_info['exists'], True) - self.assertIn('id', stat_info) - self.assertEqual(stat_info['type'], 'rodsgroup') + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual(stat_info["type"], "rodsgroup") # Create a new user. - new_username = 'test_user_rodsuser' - user_type = 'rodsuser' + new_username = "test_user_rodsuser" + user_type = "rodsuser" r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) # Add user to group. r = self.api.users_groups.add_to_group(new_username, self.zone_name, new_group) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the user is a member of the group. - r = self.api.users_groups.is_member_of_group(new_group, new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertEqual(result['is_member'], True) + r = self.api.users_groups.is_member_of_group( + new_group, new_username, self.zone_name + ) + self.assertEqual(r["status_code"], 200) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["is_member"], True) # Remove user from group. - data = {'op': 'remove_from_group', 'group': new_group, 'user': new_username, 'zone': self.zone_name} - r = self.api.users_groups.remove_from_group(new_username, self.zone_name, new_group) - - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + data = { + "op": "remove_from_group", + "group": new_group, + "user": new_username, + "zone": self.zone_name, + } + r = self.api.users_groups.remove_from_group( + new_username, self.zone_name, new_group + ) + + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Remove the user. r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) # Remove group. r = self.api.users_groups.remove_group(new_group) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that the group no longer exists. - params = {'op': 'stat', 'name': new_group} + params = {"op": "stat", "name": new_group} r = self.api.users_groups.stat(new_group) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - - stat_info = r['data'] - self.assertEqual(stat_info['irods_response']['status_code'], 0) - self.assertEqual(stat_info['exists'], False) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], False) def test_only_a_rodsadmin_can_change_the_type_of_a_user(self): self.api.setToken(self.rodsadmin_bearer_token) # Create a new user. - new_username = 'test_user_rodsuser' - user_type = 'rodsuser' + new_username = "test_user_rodsuser" + user_type = "rodsuser" r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that a rodsadmin can change the type of the new user. - new_user_type = 'groupadmin' - r = self.api.users_groups.set_user_type(new_username, self.zone_name, new_user_type) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + new_user_type = "groupadmin" + r = self.api.users_groups.set_user_type( + new_username, self.zone_name, new_user_type + ) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show that a non-admin cannot change the type of the new user. self.api.setToken(self.rodsuser_bearer_token) - r = self.api.users_groups.set_user_type(new_user_type, self.zone_name, new_user_type) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], -13000) + r = self.api.users_groups.set_user_type( + new_user_type, self.zone_name, new_user_type + ) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], -13000) # Show that the user type matches the type set by the rodsadmin. - params = {'op': 'stat', 'name': new_username, 'zone': self.zone_name} + params = {"op": "stat", "name": new_username, "zone": self.zone_name} r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - stat_info = r['data'] - self.assertEqual(stat_info['irods_response']['status_code'], 0) - self.assertEqual(stat_info['exists'], True) - self.assertEqual(stat_info['local_unique_name'], f'{new_username}#{self.zone_name}') - self.assertEqual(stat_info['type'], new_user_type) + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertEqual( + stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" + ) + self.assertEqual(stat_info["type"], new_user_type) # Remove the user. self.api.setToken(self.rodsadmin_bearer_token) r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - - + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + def test_listing_all_users_in_zone(self): self.api.setToken(self.rodsuser_bearer_token) r = self.api.users_groups.users() - self.assertEqual(r['status_code'], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertIn({'name': self.rodsadmin_username, 'zone': self.zone_name}, result['users']) - self.assertIn({'name': self.rodsuser_username, 'zone': self.zone_name}, result['users']) - + self.assertEqual(r["status_code"], 200) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertIn( + {"name": self.rodsadmin_username, "zone": self.zone_name}, result["users"] + ) + self.assertIn( + {"name": self.rodsuser_username, "zone": self.zone_name}, result["users"] + ) def test_listing_all_groups_in_zone(self): self.api.setToken(self.rodsadmin_bearer_token) # Create a new group. - new_group = 'test_group' + new_group = "test_group" r = self.api.users_groups.create_group(new_group) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) self.api.setToken(self.rodsuser_bearer_token) # Get all groups. r = self.api.users_groups.groups() - self.assertEqual(r['status_code'], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertIn('public', result['groups']) - self.assertIn(new_group, result['groups']) + self.assertEqual(r["status_code"], 200) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertIn("public", result["groups"]) + self.assertIn(new_group, result["groups"]) self.api.setToken(self.rodsadmin_bearer_token) # Remove the new group. r = self.api.users_groups.remove_group(new_group) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) - + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) def test_modifying_metadata_atomically(self): self.api.setToken(self.rodsadmin_bearer_token) username = self.rodsuser_username # Add metadata to the user. - ops = [ - { - 'operation': 'add', - 'attribute': 'a1', - 'value': 'v1', - 'units': 'u1' - } - ] + ops = [{"operation": "add", "attribute": "a1", "value": "v1", "units": "u1"}] r = self.api.users_groups.modify_metadata(username, ops) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show the metadata exists on the user. - r = self.api.queries.execute_genquery("select USER_NAME where META_USER_ATTR_NAME = 'a1' and META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'") - self.assertEqual(r['status_code'], 200) + r = self.api.queries.execute_genquery( + "select USER_NAME where META_USER_ATTR_NAME = 'a1' and META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["status_code"], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertEqual(result['rows'][0][0], username) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["rows"][0][0], username) # Remove the metadata from the user. - ops = [ - { - 'operation': 'remove', - 'attribute': 'a1', - 'value': 'v1', - 'units': 'u1' - } - ] + ops = [{"operation": "remove", "attribute": "a1", "value": "v1", "units": "u1"}] r = self.api.users_groups.modify_metadata(username, ops) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Show the metadata no longer exists on the user. - r = self.api.queries.execute_genquery("select USER_NAME where META_USER_ATTR_NAME = 'a1' and META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'") - self.assertEqual(r['status_code'], 200) + r = self.api.queries.execute_genquery( + "select USER_NAME where META_USER_ATTR_NAME = 'a1' and META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["status_code"], 200) + + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(len(result["rows"]), 0) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertEqual(len(result['rows']), 0) - # Tests for zone operations class zoneTests(unittest.TestCase): @classmethod def setUpClass(cls): - setup_class(cls, {'endpoint_name': 'zones'}) + setup_class(cls, {"endpoint_name": "zones"}) @classmethod def tearDownClass(cls): tear_down_class(cls) def setUp(self): - self.assertFalse(self._class_init_error, 'Class initialization failed. Cannot continue.') - + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) def test_report_operation(self): self.api.setToken(self.rodsadmin_bearer_token) r = self.api.zones.report() - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) - zone_report = result['zone_report'] - self.assertIn('schema_version', zone_report) - self.assertIn('zones', zone_report) - self.assertGreaterEqual(len(zone_report['zones']), 1) + zone_report = result["zone_report"] + self.assertIn("zones", zone_report) + self.assertGreaterEqual(len(zone_report["zones"]), 1) + self.assertIn( + "schema_version", zone_report["zones"][0]["servers"][0]["server_config"] + ) - def test_adding_removing_and_modifying_zones(self): self.api.setToken(self.rodsadmin_bearer_token) # Add a remote zone to the local zone. # The new zone will not have any connection information or anything else. - zone_name = 'other_zone' + zone_name = "other_zone" r = self.api.zones.add(zone_name) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) try: # Show the new zone exists by executing the stat operation on it. r = self.api.zones.stat(zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertEqual(result['exists'], True) - self.assertEqual(result['info']['name'], zone_name) - self.assertEqual(result['info']['type'], 'remote') - self.assertEqual(result['info']['connection_info'], '') - self.assertEqual(result['info']['comment'], '') + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["exists"], True) + self.assertEqual(result["info"]["name"], zone_name) + self.assertEqual(result["info"]["type"], "remote") + self.assertEqual(result["info"]["connection_info"], "") + self.assertEqual(result["info"]["comment"], "") # The properties to update. property_map = [ - ('name', 'other_zone_renamed'), - ('connection_info', 'example.org:1247'), - ('comment', 'updated comment') + ("name", "other_zone_renamed"), + ("connection_info", "example.org:1247"), + ("comment", "updated comment"), ] # Change the properties of the new zone. for p, v in property_map: - with self.subTest(f'Setting property [{p}] to value [{v}]'): + with self.subTest(f"Setting property [{p}] to value [{v}]"): r = self.api.zones.modify(zone_name, p, v) - self.assertEqual(r['status_code'], 200) - self.assertEqual(r['data']['irods_response']['status_code'], 0) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Capture the new name of the zone following its renaming. - if 'name' == p: + if "name" == p: zone_name = v - + # Show the new zone was modified successfully. r = self.api.zones.stat(zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) - result = r['data'] - self.assertEqual(result['irods_response']['status_code'], 0) - self.assertEqual(result['exists'], True) - self.assertEqual(result['info'][p], v) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["exists"], True) + self.assertEqual(result["info"][p], v) finally: # Remove the remote zone. r = self.api.zones.remove(zone_name) - self.assertEqual(r['status_code'], 200) + self.assertEqual(r["status_code"], 200) + -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() From 6fbe44f434cd2bd011b8c0991e5cbba501d03fd2 Mon Sep 17 00:00:00 2001 From: Terrell Russell Date: Fri, 13 Feb 2026 13:14:03 -0500 Subject: [PATCH 2/4] [#27][#38] rename class and update module names --- README.md | 4 ++-- irods_http_client/__init__.py | 2 +- .../{irodsHttpClient.py => irods_http_client.py} | 2 +- test/test_endpoint_operations.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename irods_http_client/{irodsHttpClient.py => irods_http_client.py} (99%) diff --git a/README.md b/README.md index fd1ea47..3efad86 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ pip install irods-http-client To use the wrapper, follow the steps listed below. ```py -from irods_http_client.irodsHttpClient import IrodsHttpClient +from irods_http_client import IRODSHTTPClient # Create an instance of the wrapper with the base url of the iRODS server to # be accessed. , , and are placeholders, and need # to be replaced by appropriate values. -api = IrodsHttpClient('http://:/irods-http-api/') +api = IRODSHTTPClient('http://:/irods-http-api/') # Most endpoint operations require a user to be authenticated in order to # be executed. Authenticate with a username and password, and store the diff --git a/irods_http_client/__init__.py b/irods_http_client/__init__.py index 46a1a9e..6d429ef 100644 --- a/irods_http_client/__init__.py +++ b/irods_http_client/__init__.py @@ -1 +1 @@ -from .irodsHttpClient import IrodsHttpClient +from .irods_http_client import IRODSHTTPClient diff --git a/irods_http_client/irodsHttpClient.py b/irods_http_client/irods_http_client.py similarity index 99% rename from irods_http_client/irodsHttpClient.py rename to irods_http_client/irods_http_client.py index f7db8e2..3b2b133 100644 --- a/irods_http_client/irodsHttpClient.py +++ b/irods_http_client/irods_http_client.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) -class IrodsHttpClient: +class IRODSHTTPClient: def __init__(self, url_base: str): """Gets the base url from the user to initialize a client instance.""" self.url_base = url_base diff --git a/test/test_endpoint_operations.py b/test/test_endpoint_operations.py index d0d8894..41fa7c1 100644 --- a/test/test_endpoint_operations.py +++ b/test/test_endpoint_operations.py @@ -1,6 +1,6 @@ import config import unittest -from irods_http_client.irodsHttpClient import IrodsHttpClient +from irods_http_client import IRODSHTTPClient import concurrent.futures import os import time @@ -57,7 +57,7 @@ def setup_class(cls, opts): cls.url_base = f"http://{config.test_config['host']}:{config.test_config['port']}{config.test_config['url_base']}" cls.url_endpoint = f'{cls.url_base}/{opts["endpoint_name"]}' - cls.api = IrodsHttpClient(cls.url_base) + cls.api = IRODSHTTPClient(cls.url_base) cls.zone_name = config.test_config["irods_zone"] cls.host = config.test_config["irods_server_hostname"] From 8fb4186c7dafd4afafd2ef70990660c936f6a406 Mon Sep 17 00:00:00 2001 From: Terrell Russell Date: Fri, 13 Feb 2026 14:21:42 -0500 Subject: [PATCH 3/4] [#47] refactor common functions to increase coverage --- irods_http_client/collection_operations.py | 166 ++----- irods_http_client/common.py | 36 +- irods_http_client/data_object_operations.py | 491 +++++--------------- irods_http_client/irods_http_client.py | 11 +- irods_http_client/query_operations.py | 92 +--- irods_http_client/resource_operations.py | 102 ++-- irods_http_client/rule_operations.py | 22 +- irods_http_client/ticket_operations.py | 56 +-- irods_http_client/user_group_operations.py | 150 ++---- irods_http_client/zone_operations.py | 49 +- test/test_endpoint_operations.py | 93 +++- 11 files changed, 412 insertions(+), 856 deletions(-) diff --git a/irods_http_client/collection_operations.py b/irods_http_client/collection_operations.py index f8e1425..9be426e 100644 --- a/irods_http_client/collection_operations.py +++ b/irods_http_client/collection_operations.py @@ -24,16 +24,9 @@ def create(self, lpath: str, create_intermediates: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(create_intermediates, int): - raise TypeError("create_intermediates must be an int 1 or 0") - if (not create_intermediates == 0) and (not create_intermediates == 1): - raise ValueError("create_intermediates must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(create_intermediates) headers = { "Authorization": "Bearer " + self.token, @@ -62,20 +55,10 @@ def remove(self, lpath: str, recurse: int = 0, no_trash: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(recurse, int): - raise TypeError("recurse must be an int 1 or 0") - if (not recurse == 0) and (not recurse == 1): - raise ValueError("recurse must be an int 1 or 0") - if not isinstance(no_trash, int): - raise TypeError("no_trash must be an int 1 or 0") - if (not no_trash == 0) and (not no_trash == 1): - raise ValueError("no_trash must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(recurse) + common.validate_0_or_1(no_trash) headers = { "Authorization": "Bearer " + self.token, @@ -104,14 +87,9 @@ def stat(self, lpath: str, ticket: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(ticket, str): - raise TypeError("ticket must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(ticket, str) headers = { "Authorization": "Bearer " + self.token, @@ -135,18 +113,10 @@ def list(self, lpath: str, recurse: int = 0, ticket: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(recurse, int): - raise TypeError("recurse must be an int 1 or 0") - if (not recurse == 0) and (not recurse == 1): - raise ValueError("recurse must be an int 1 or 0") - if not isinstance(ticket, str): - raise TypeError("ticket must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(recurse) + common.validate_instance(ticket, str) headers = { "Authorization": "Bearer " + self.token, @@ -173,22 +143,15 @@ def set_permission( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(entity_name, str): - raise TypeError("entity_name must be a string") - if not isinstance(permission, str): - raise TypeError( - "permission must be a string ('null', 'read', 'write', or 'own')" + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(entity_name, str) + common.validate_instance(permission, str) + if permission not in ["null", "read", "write", "own"]: + raise ValueError( + "permission must be either 'null', 'read', 'write', or 'own'" ) - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -219,20 +182,10 @@ def set_inheritance(self, lpath: str, enable: int, admin: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(enable, int): - raise TypeError("enable must be an int 1 or 0") - if (not enable == 0) and (not enable == 1): - raise ValueError("enable must be an int 1 or 0") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(enable) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -262,20 +215,11 @@ def modify_permissions(self, lpath: str, operations: dict, admin: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(operations, list): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(operations[0], dict): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -305,20 +249,11 @@ def modify_metadata(self, lpath: str, operations: dict, admin: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(operations, list): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(operations[0], dict): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -347,14 +282,9 @@ def rename(self, old_lpath: str, new_lpath: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(old_lpath, str): - raise TypeError("old_lpath must be a string") - if not isinstance(new_lpath, str): - raise TypeError("new_lpath must be a string") + common.check_token(self.token) + common.validate_instance(old_lpath, str) + common.validate_instance(new_lpath, str) headers = { "Authorization": "Bearer " + self.token, @@ -379,20 +309,10 @@ def touch(self, lpath, seconds_since_epoch=-1, reference=""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(seconds_since_epoch, int): - raise TypeError("seconds_since_epoch must be an int") - if not seconds_since_epoch >= -1: - raise ValueError( - "seconds_since_epoch must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(reference, str): - raise TypeError("reference must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_gte_minus1(seconds_since_epoch) + common.validate_instance(reference, str) headers = { "Authorization": "Bearer " + self.token, diff --git a/irods_http_client/common.py b/irods_http_client/common.py index 2c2dce5..38969b7 100644 --- a/irods_http_client/common.py +++ b/irods_http_client/common.py @@ -1,11 +1,31 @@ import json + def process_response(r): - if r.status_code / 100 == 2: - rdict = r.json() - return {"status_code": r.status_code, "data": rdict} - else: - rdict = None - if r.text != "": - rdict = r.json() - return {"status_code": r.status_code, "data": rdict} + rdict = r.json() if r.text != "" else None + return {"status_code": r.status_code, "data": rdict} + +def check_token(t): + if t == None: + raise RuntimeError( + "No token set. Use setToken() to set the auth token to be used." + ) + +def validate_instance(x, expected_type): + if not isinstance(x, expected_type): + raise TypeError + +def validate_0_or_1(x): + validate_instance(x, int) + if x not in [0, 1]: + raise ValueError(f"{x} must be 0 or 1") + +def validate_gte_zero(x): + validate_instance(x, int) + if not x >= 0: + raise ValueError(f"{x} must be >= 0") + +def validate_gte_minus1(x): + validate_instance(x, int) + if not x >= -1: + raise ValueError(f"{x} must be >= 0, or flag value of -1") diff --git a/irods_http_client/data_object_operations.py b/irods_http_client/data_object_operations.py index 4ee40b9..2461812 100644 --- a/irods_http_client/data_object_operations.py +++ b/irods_http_client/data_object_operations.py @@ -34,32 +34,13 @@ def touch( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(no_create, int): - raise TypeError("no_create must be an int 1 or 0") - if (not no_create == 0) and (not no_create == 1): - raise ValueError("no_create must be an int 1 or 0") - if not isinstance(replica_number, int): - raise TypeError("replica_number must be an int") - if not replica_number >= -1: - raise ValueError( - "replica_number must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(leaf_resources, str): - raise TypeError("leaf_resources must be a string") - if not isinstance(seconds_since_epoch, int): - raise TypeError("seconds_since_epoch must be an int") - if not seconds_since_epoch >= -1: - raise ValueError( - "seconds_since_epoch must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(reference, str): - raise TypeError("reference must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(no_create) + common.validate_gte_minus1(replica_number) + common.validate_instance(leaf_resources, str) + common.validate_gte_minus1(seconds_since_epoch) + common.validate_instance(reference, str) headers = { "Authorization": "Bearer " + self.token, @@ -99,24 +80,11 @@ def remove( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(catalog_only, int): - raise TypeError("catalog_only must be an int 1 or 0") - if (not catalog_only == 0) and (not catalog_only == 1): - raise ValueError("catalog_only must be an int 1 or 0") - if not isinstance(no_trash, int): - raise TypeError("no_trash must be an int 1 or 0") - if (not no_trash == 0) and (not no_trash == 1): - raise ValueError("no_trash must be an int 1 or 0") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(catalog_only) + common.validate_0_or_1(no_trash) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -158,32 +126,13 @@ def calculate_checksum( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise T("lpath must be a string") - if not isinstance(resource, str): - raise T("resource must be a string") - if not isinstance(replica_number, int): - raise T("replica_number must be an int") - if not replica_number >= -1: - raise ValueError( - "replica number must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(force, int): - raise TypeError("force must be an int 1 or 0") - if (not force == 0) and (not force == 1): - raise ValueError("force must be an int 1 or 0") - if not isinstance(all, int): - raise TypeError("all must be an int 1 or 0") - if (not all == 0) and (not all == 1): - raise ValueError("all must be an int 1 or 0") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(resource, str) + common.validate_gte_minus1(replica_number) + common.validate_0_or_1(force) + common.validate_0_or_1(all) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -229,28 +178,12 @@ def verify_checksum( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(resource, str): - raise TypeError("resource must be a string") - if not isinstance(replica_number, int): - raise TypeError("replica_number must be an int") - if not replica_number >= -1: - raise ValueError( - "replica_number must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(compute_checksums, int): - raise TypeError("compute_checksums must be an int 1 or 0") - if (not compute_checksums == 0) and (not compute_checksums == 1): - raise ValueError("force must be an int 1 or 0") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(resource, str) + common.validate_gte_minus1(replica_number) + common.validate_0_or_1(compute_checksums) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -285,14 +218,9 @@ def stat(self, lpath: str, ticket: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(ticket, str): - raise TypeError("ticket must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(ticket, str) headers = { "Authorization": "Bearer " + self.token, @@ -315,14 +243,9 @@ def rename(self, old_lpath: str, new_lpath: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(old_lpath, str): - raise TypeError("old_lpath must be a string") - if not isinstance(new_lpath, str): - raise TypeError("new_lpath must be a string") + common.check_token(self.token) + common.validate_instance(old_lpath, str) + common.validate_instance(new_lpath, str) headers = { "Authorization": "Bearer " + self.token, @@ -348,30 +271,20 @@ def copy( Parameters - src_lpath: The absolute logical path of the source data object. - dst_lpath: The absolute logical path of the destination. - - src_resource: The absolute logical path of the source resource. - - dst_resource: The absolute logical path of the destination resource. + - src_resource: The name of the source resource. + - dst_resource: The name of the destination resource. - overwrite: set to 1 to overwrite an existing objject, otherwise set to 0. Defaults to 0. Returns - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(src_lpath, str): - raise TypeError("src_lpath must be a string") - if not isinstance(dst_lpath, str): - raise TypeError("dst_lpath must be a string") - if not isinstance(src_resource, str): - raise TypeError("src_resource must be a string") - if not isinstance(dst_resource, str): - raise TypeError("dst_lpath must be a string") - if not isinstance(overwrite, int): - raise TypeError("overwrite must be an int 1 or 0") - if (not overwrite == 0) and (not overwrite == 1): - raise ValueError("overwrite must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(src_lpath, str) + common.validate_instance(dst_lpath, str) + common.validate_instance(src_resource, str) + common.validate_instance(dst_resource, str) + common.validate_0_or_1(overwrite) headers = { "Authorization": "Bearer " + self.token, @@ -401,29 +314,20 @@ def replicate( Replicates a data object from one resource to another. Parameters - - lpath: The absolute logical path of the data object to be replicated. - - src_resource: The absolute logical path of the source resource. - - dst_resource: The absolute logical path of the destination resource. + - lpath: The absolute logical path of the data object to be replicated. + - src_resource: The name of the source resource. + - dst_resource: The name of the destination resource. - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. Returns - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(src_resource, str): - raise TypeError("src_resource must be a string") - if not isinstance(dst_resource, str): - raise TypeError("dst_lpath must be a string") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(src_resource, str) + common.validate_instance(dst_resource, str) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -457,22 +361,11 @@ def trim( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise T("lpath must be a string") - if not isinstance(replica_number, int): - raise T("replica_number must be an int") - if not isinstance(catalog_only, int): - raise TypeError("catalog_only must be an int 1 or 0") - if (not catalog_only == 0) and (not catalog_only == 1): - raise ValueError("catalog_only must be an int 1 or 0") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(replica_number, int) + common.validate_0_or_1(catalog_only) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -503,8 +396,8 @@ def register( Registers a data object/replica into the catalog. Parameters - - lpath: The absolute logical path of the data object to be registered. - - ppath: The absolute physical path of the data object to be registered. + - lpath: The absolute logical path of the data object to be registered. + - ppath: The absolute physical path of the data object to be registered. - resource: The resource that will own the replica. - as_additional_replica (optional): Set to 1 to register as a replica of an existing object, otherwise set to 0. Defaults to 0. - data_size (optional): The size of the replica in bytes. @@ -514,28 +407,13 @@ def register( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(ppath, str): - raise TypeError("ppath must be a string") - if not isinstance(resource, str): - raise TypeError("resource must be a string") - if not isinstance(as_additional_replica, int): - raise TypeError("as_additional_replica must be an int 1 or 0") - if (not as_additional_replica == 0) and (not as_additional_replica == 1): - raise ValueError("as_additional_replica must be an int 1 or 0") - if not isinstance(data_size, int): - raise TypeError("data_size must be an int") - if not data_size >= -1: - raise ValueError( - "data_size must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(checksum, str): - raise TypeError("checksum must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(ppath, str) + common.validate_instance(resource, str) + common.validate_0_or_1(as_additional_replica) + common.validate_gte_minus1(data_size) + common.validate_instance(checksum, str) headers = { "Authorization": "Bearer " + self.token, @@ -573,22 +451,11 @@ def read(self, lpath: str, offset: int = 0, count: int = -1, ticket: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(offset, int): - raise TypeError("offset must be an int") - if not isinstance(count, int): - raise TypeError("count must be an int") - if not count >= -1: - raise ValueError( - "count must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(ticket, str): - raise TypeError("ticket must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(offset, int) + common.validate_gte_minus1(count) + common.validate_instance(ticket, str) headers = { "Authorization": "Bearer " + self.token, @@ -623,8 +490,8 @@ def write( Writes bytes to a data object. Parameters - - lpath: The absolute logical path of the data object to be written to. - bytes: The bytes to be written. + - lpath: The absolute logical path of the data object to be written to. - resource (optional): The root resource to write to. - offset (optional): The number of bytes to skip. Defaults to 0. - truncate (optional): Set to 1 to truncate the data object before writing, otherwise set to 0. Defaults to 1. @@ -636,34 +503,17 @@ def write( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(resource, str): - raise TypeError("resource must be a string") - if not isinstance(offset, int): - raise TypeError("offset must be an int") - if not offset >= 0: - raise ValueError("offset must be greater than or equal to 0") - if not isinstance(truncate, int): - raise TypeError("truncate must be an int 1 or 0") - if (not truncate == 0) and (not truncate == 1): - raise ValueError("truncate must be an int 1 or 0") - if not isinstance(append, int): - raise TypeError("append must be an int 1 or 0") - if (not append == 0) and (not append == 1): - raise ValueError("append must be an int 1 or 0") - if not isinstance(parallel_write_handle, str): - raise TypeError("parallel_write_handle must be a string") - if not isinstance(stream_index, int): - raise TypeError("stream_index must be an int") - if not stream_index >= -1: - raise ValueError( - "stream_index must be greater than or equal to 0 or flag value -1" - ) + common.check_token(self.token) +# common.validate_instance(bytes, int) + if not len(bytes) >= 0: + raise ValueError("bytes must be greater than or equal to 0") + common.validate_instance(lpath, str) + common.validate_instance(resource, str) + common.validate_gte_zero(offset) + common.validate_0_or_1(truncate) + common.validate_0_or_1(append) + common.validate_instance(parallel_write_handle, str) + common.validate_gte_minus1(stream_index) headers = { "Authorization": "Bearer " + self.token, @@ -715,28 +565,12 @@ def parallel_write_init( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(stream_count, int): - raise TypeError("stream_count must be an int") - if not stream_count >= 0: - raise ValueError( - "stream_count must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(truncate, int): - raise TypeError("truncate must be an int 1 or 0") - if (not truncate == 0) and (not truncate == 1): - raise ValueError("truncate must be an int 1 or 0") - if not isinstance(append, int): - raise TypeError("append must be an int 1 or 0") - if (not append == 0) and (not append == 1): - raise ValueError("append must be an int 1 or 0") - if not isinstance(ticket, str): - raise TypeError("ticket must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_gte_zero(stream_count) + common.validate_0_or_1(truncate) + common.validate_0_or_1(append) + common.validate_instance(ticket, str) headers = { "Authorization": "Bearer " + self.token, @@ -768,12 +602,8 @@ def parallel_write_shutdown(self, parallel_write_handle: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(parallel_write_handle, str): - raise TypeError("parallel_write_handle must be a string") + common.check_token(self.token) + common.validate_instance(parallel_write_handle, str) headers = { "Authorization": "Bearer " + self.token, @@ -801,20 +631,11 @@ def modify_metadata(self, lpath: str, operations: list, admin: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(operations, list): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(operations[0], dict): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -847,22 +668,15 @@ def set_permission( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(entity_name, str): - raise TypeError("entity_name must be a string") - if not isinstance(permission, str): - raise TypeError( - "permission must be a string ('null', 'read', 'write', or 'own')" + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(entity_name, str) + common.validate_instance(permission, str) + if permission not in ["null", "read", "write", "own"]: + raise ValueError( + "permission must be either 'null', 'read', 'write', or 'own'" ) - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -893,20 +707,11 @@ def modify_permissions(self, lpath: str, operations: list, admin: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(operations, list): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(operations[0], dict): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, @@ -977,89 +782,41 @@ def modify_replica( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be a string") - if not isinstance(resource_hierarchy, str): - raise TypeError("resource_hierarchy must be a string") - if not isinstance(replica_number, int): - raise TypeError("replica_number must be an int") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(resource_hierarchy, str) + common.validate_instance(replica_number, int) if (resource_hierarchy != "") and (replica_number != -1): raise ValueError( "replica_hierarchy and replica_number are mutually exclusive" ) - if not isinstance(new_data_checksum, str): - raise TypeError("new_data_checksum must be a string") - if not isinstance(new_data_comments, str): - raise TypeError("new_data_comments must be a string") - if not isinstance(new_data_create_time, int): - raise TypeError("new_data_create_time must be an int") - if not new_data_create_time >= -1: - raise ValueError( - "new_data_create_time must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(new_data_expiry, int): - raise TypeError("new_data_expiry must be an int") - if not new_data_expiry >= -1: - raise ValueError( - "new_data_expiry must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(new_data_mode, str): - raise TypeError("new_data_mode must be a string") - if not isinstance(new_data_modify_time, str): - raise TypeError("new_data_modify_time must be a string") - if not isinstance(new_data_path, str): - raise TypeError("new_data_path must be a string") - if not isinstance(new_data_replica_number, int): - raise TypeError("new_data_replica_number must be an int") - if not new_data_replica_number >= -1: - raise ValueError( - "new_data_replica_number must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(new_data_replica_status, int): - raise TypeError("new_data_replica_status must be an int") - if not new_data_replica_status >= -1: - raise ValueError( - "new_data_replica_status must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(new_data_resource_id, int): - raise TypeError("new_data_resource_id must be an int") - if not new_data_resource_id >= -1: - raise ValueError( - "new_data_resource_id must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(new_data_size, int): - raise TypeError("new_data_size must be an int") - if not new_data_size >= -1: - raise ValueError( - "new_data_size must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(new_data_status, str): - raise TypeError("new_data_status must be a string") - if not isinstance(new_data_type_name, str): - raise TypeError("new_data_type_name must be a string") - if not isinstance(new_data_version, int): - raise TypeError("new_data_version must be an int") - if not new_data_version >= -1: - raise ValueError( - "new_data_version must be greater than or equal to 0 or flag value -1" - ) + common.validate_instance(new_data_checksum, str) + common.validate_instance(new_data_comments, str) + common.validate_gte_minus1(new_data_create_time) + common.validate_gte_minus1(new_data_expiry) + common.validate_instance(new_data_mode, str) + common.validate_instance(new_data_modify_time, str) + common.validate_instance(new_data_path, str) + common.validate_gte_minus1(new_data_replica_number) + common.validate_gte_minus1(new_data_replica_status) + common.validate_gte_minus1(new_data_resource_id) + common.validate_gte_minus1(new_data_size) + common.validate_instance(new_data_status, str) + common.validate_instance(new_data_name, str) + common.validate_gte_minus1(new_data_version) headers = { "Authorization": "Bearer " + self.token, "Content-Type": "application/x-www-form-urlencoded", } - data = {"op": "modify_permissions", "lpath": lpath} + data = {"op": "modify_replica", "lpath": lpath} if resource_hierarchy != "": data["resource-hierarchy"] = resource_hierarchy if replica_number != -1: - data["replica-numbeer"] = replica_number + data["replica-number"] = replica_number # Boolean for checking if the user passed in any new_data parameters no_params = True diff --git a/irods_http_client/irods_http_client.py b/irods_http_client/irods_http_client.py index 3b2b133..12b9923 100644 --- a/irods_http_client/irods_http_client.py +++ b/irods_http_client/irods_http_client.py @@ -6,6 +6,7 @@ from irods_http_client.ticket_operations import Tickets from irods_http_client.user_group_operations import UsersGroups from irods_http_client.zone_operations import Zones +from irods_http_client import common import logging import requests @@ -97,12 +98,4 @@ def info(self): } r = requests.get(self.url_base + "/info", headers=headers) - - if r.status_code / 100 == 2: - rdict = r.json() - return {"status_code": r.status_code, "data": rdict} - else: - rdict = None - if r.text != "": - rdict = r.json() - return {"status_code": r.status_code, "data": rdict} + return common.process_response(r) diff --git a/irods_http_client/query_operations.py b/irods_http_client/query_operations.py index 50987a3..df4bee5 100644 --- a/irods_http_client/query_operations.py +++ b/irods_http_client/query_operations.py @@ -23,7 +23,7 @@ def execute_genquery( zone: str = "", ): """ - Excecutes a GenQuery string and returns the results. + Executes a GenQuery string and returns the results. Parameters - query: The query being executed @@ -39,40 +39,17 @@ def execute_genquery( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(query, str): - raise TypeError("query must be a string") - if not isinstance(offset, int): - raise TypeError("offset must be an int") - if not offset >= 0: - raise ValueError("offset must be greater than or equal to 0") - if not isinstance(count, int): - raise TypeError("count must be an int") - if not count >= -1: - raise ValueError( - "count must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(case_sensitive, int): - raise TypeError("case_sensitive must be an int 1 or 0") - if (not case_sensitive == 0) and (not case_sensitive == 1): - raise ValueError("case_sensitive must be an int 1 or 0") - if not isinstance(distinct, int): - raise TypeError("distinct must be an int 1 or 0") - if (not distinct == 0) and (not distinct == 1): - raise ValueError("distinct must be an int 1 or 0") - if not isinstance(parser, str): - raise TypeError("parser must be a string") - if (not parser == "genquery1") and (not parser == "genquery2"): + common.check_token(self.token) + common.validate_instance(query, str) + common.validate_gte_zero(offset) + common.validate_gte_minus1(count) + common.validate_0_or_1(case_sensitive) + common.validate_0_or_1(distinct) + common.validate_instance(parser, str) + if parser not in ["genquery1", "genquery2"]: raise ValueError("parser must be either 'genquery1' or 'genquery2'") - if not isinstance(sql_only, int): - raise TypeError("sql_only must be an int 1 or 0") - if (not sql_only == 0) and (not sql_only == 1): - raise ValueError("sql_only must be an int 1 or 0") - if not isinstance(zone, str): - raise TypeError("zone must be a string") + common.validate_0_or_1(sql_only) + common.validate_instance(zone, str) headers = { "Authorization": "Bearer " + self.token, @@ -109,7 +86,7 @@ def execute_specific_query( count: int = -1, ): """ - Excecutes a specific query and returns the results. + Executes a specific query and returns the results. Parameters - name: The name of the query to be executed @@ -122,26 +99,12 @@ def execute_specific_query( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(args, str): - raise TypeError("args must be a string") - if not isinstance(args_delimiter, str): - raise TypeError("args_delimiter must be a string") - if not isinstance(offset, int): - raise TypeError("offset must be an int") - if not offset >= 0: - raise ValueError("offset must be greater than or equal to 0") - if not isinstance(count, int): - raise TypeError("count must be an int") - if not count >= -1: - raise ValueError( - "count must be greater than or equal to 0 or flag value -1" - ) + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(args, str) + common.validate_instance(args_delimiter, str) + common.validate_gte_zero(offset) + common.validate_gte_minus1(count) headers = { "Authorization": "Bearer " + self.token, @@ -175,14 +138,9 @@ def add_specific_query(self, name: str, sql: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(sql, str): - raise TypeError("sql must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(sql, str) headers = { "Authorization": "Bearer " + self.token, @@ -204,12 +162,8 @@ def remove_specific_query(self, name): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, diff --git a/irods_http_client/resource_operations.py b/irods_http_client/resource_operations.py index a5f8ac0..7f4bf7a 100644 --- a/irods_http_client/resource_operations.py +++ b/irods_http_client/resource_operations.py @@ -27,20 +27,12 @@ def create(self, name: str, type: str, host: str, vault_path: str, context: str) - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(type, str): - raise TypeError("type must be a string") - if not isinstance(host, str): - raise TypeError("host must be a string") - if not isinstance(vault_path, str): - raise TypeError("vault_path must be a string") - if not isinstance(context, str): - raise TypeError("context must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(type, str) + common.validate_instance(host, str) + common.validate_instance(vault_path, str) + common.validate_instance(context, str) headers = { "Authorization": "Bearer " + self.token, @@ -72,12 +64,8 @@ def remove(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, @@ -102,14 +90,9 @@ def modify(self, name: str, property: str, value: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(property, str): - raise TypeError("property must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(property, str) if property not in [ "name", "type", @@ -126,8 +109,7 @@ def modify(self, name: str, property: str, value: str): "vault_path\n - context" + "\n - status\n - free_space\n - comments\n - information" ) - if not isinstance(value, str): - raise TypeError("value must be a string") + common.validate_instance(value, str) if (property == "status") and (value != "up") and (value != "down"): raise ValueError("status must be either 'up' or 'down'") @@ -154,16 +136,10 @@ def add_child(self, parent_name: str, child_name: str, context: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(parent_name, str): - raise TypeError("parent_name must be a string") - if not isinstance(child_name, str): - raise TypeError("child_name must be a string") - if not isinstance(context, str): - raise TypeError("context must be a string") + common.check_token(self.token) + common.validate_instance(parent_name, str) + common.validate_instance(child_name, str) + common.validate_instance(context, str) headers = { "Authorization": "Bearer " + self.token, @@ -190,14 +166,9 @@ def remove_child(self, parent_name: str, child_name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(parent_name, str): - raise TypeError("parent_name must be a string") - if not isinstance(child_name, str): - raise TypeError("child_name must be a string") + common.check_token(self.token) + common.validate_instance(parent_name, str) + common.validate_instance(child_name, str) headers = { "Authorization": "Bearer " + self.token, @@ -224,12 +195,8 @@ def rebalance(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, @@ -252,12 +219,8 @@ def stat(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, @@ -282,20 +245,11 @@ def modify_metadata(self, name: str, operations: dict, admin: int = 0): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(operations, list): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(operations[0], dict): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(admin, int): - raise TypeError("admin must be an int 1 or 0") - if (not admin == 0) and (not admin == 1): - raise ValueError("admin must be an int 1 or 0") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) headers = { "Authorization": "Bearer " + self.token, diff --git a/irods_http_client/rule_operations.py b/irods_http_client/rule_operations.py index 878daa1..4964ed5 100644 --- a/irods_http_client/rule_operations.py +++ b/irods_http_client/rule_operations.py @@ -19,6 +19,7 @@ def list_rule_engines(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ + common.check_token(self.token) headers = { "Authorization": "Bearer " + self.token, @@ -41,14 +42,9 @@ def execute(self, rule_text: str, rep_instance: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(rule_text, str): - raise TypeError("name must be a string") - if not isinstance(rep_instance, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(rule_text, str) + common.validate_instance(rep_instance, str) headers = { "Authorization": "Bearer " + self.token, @@ -74,14 +70,8 @@ def remove_delay_rule(self, rule_id: int): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(rule_id, int): - raise TypeError("rule_id must be an int") - if not rule_id >= 0: - raise ValueError("rule_id must be greater than or equal to 0") + common.check_token(self.token) + common.validate_gte_zero(rule_id) headers = { "Authorization": "Bearer " + self.token, diff --git a/irods_http_client/ticket_operations.py b/irods_http_client/ticket_operations.py index 6ca7187..70d2cca 100644 --- a/irods_http_client/ticket_operations.py +++ b/irods_http_client/ticket_operations.py @@ -41,46 +41,18 @@ def create( - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(lpath, str): - raise TypeError("lpath must be an string") - if not isinstance(type, str): - raise TypeError("type must be a string") + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(type, str) if type not in ["read", "write"]: raise ValueError("type must be either read or write") - if not isinstance(use_count, int): - raise TypeError("use_count must be an int") - if not use_count >= -1: - raise ValueError( - "use_count must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(write_data_object_count, int): - raise TypeError("write_data_object_count must be an int") - if not write_data_object_count >= -1: - raise ValueError( - "write_data_object_count must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(write_byte_count, int): - raise TypeError("write_byte_count must be an int") - if not write_byte_count >= -1: - raise ValueError( - "write_byte_count must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(seconds_until_expiration, int): - raise TypeError("seconds_until_expiration must be an int") - if not seconds_until_expiration >= -1: - raise ValueError( - "seconds_until_expiration must be greater than or equal to 0 or flag value -1" - ) - if not isinstance(users, str): - raise TypeError("users must be an string") - if not isinstance(groups, str): - raise TypeError("groups must be an string") - if not isinstance(hosts, str): - raise TypeError("hosts must be an string") + common.validate_gte_minus1(use_count) + common.validate_gte_minus1(write_data_object_count) + common.validate_gte_minus1(write_byte_count) + common.validate_gte_minus1(seconds_until_expiration) + common.validate_instance(users, str) + common.validate_instance(groups, str) + common.validate_instance(hosts, str) headers = { "Authorization": "Bearer " + self.token, @@ -118,12 +90,8 @@ def remove(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, diff --git a/irods_http_client/user_group_operations.py b/irods_http_client/user_group_operations.py index 7be44ca..f279628 100644 --- a/irods_http_client/user_group_operations.py +++ b/irods_http_client/user_group_operations.py @@ -24,17 +24,11 @@ def create_user(self, name: str, zone: str, user_type: str = "rodsuser"): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") - if not isinstance(user_type, str): - raise TypeError("user_type must be a string") - if user_type and user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + common.validate_instance(user_type, str) + if user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: raise ValueError( "user_type must be set to rodsuser, groupadmin, or rodsadmin." ) @@ -61,14 +55,9 @@ def remove_user(self, name: str, zone: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) headers = { "Authorization": "Bearer " + self.token, @@ -93,16 +82,10 @@ def set_password(self, name: str, zone: str, new_password: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") - if not isinstance(new_password, str): - raise TypeError("new_password must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + common.validate_instance(new_password, str) headers = { "Authorization": "Bearer " + self.token, @@ -132,17 +115,11 @@ def set_user_type(self, name: str, zone: str, user_type: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") - if not isinstance(user_type, str): - raise TypeError("user_type must be a string") - if user_type and user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + common.validate_instance(user_type, str) + if user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: raise ValueError( "user_type must be set to rodsuser, groupadmin, or rodsadmin." ) @@ -173,12 +150,8 @@ def create_group(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, @@ -203,12 +176,8 @@ def remove_group(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, @@ -233,16 +202,10 @@ def add_to_group(self, user: str, zone: str, group: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(user, str): - raise TypeError("user must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") - if not isinstance(group, str): - raise TypeError("group must be a string") + common.check_token(self.token) + common.validate_instance(user, str) + common.validate_instance(zone, str) + common.validate_instance(group, str) headers = { "Authorization": "Bearer " + self.token, @@ -267,16 +230,10 @@ def remove_from_group(self, user: str, zone: str, group: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(user, str): - raise TypeError("user must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") - if not isinstance(group, str): - raise TypeError("group must be a string") + common.check_token(self.token) + common.validate_instance(user, str) + common.validate_instance(zone, str) + common.validate_instance(group, str) headers = { "Authorization": "Bearer " + self.token, @@ -296,10 +253,7 @@ def users(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) + common.check_token(self.token) headers = {"Authorization": "Bearer " + self.token} @@ -318,10 +272,7 @@ def groups(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) + common.check_token(self.token) headers = { "Authorization": "Bearer " + self.token, @@ -347,16 +298,10 @@ def is_member_of_group(self, group: str, user: str, zone: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(group, str): - raise TypeError("group must be a string") - if not isinstance(user, str): - raise TypeError("user must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") + common.check_token(self.token) + common.validate_instance(group, str) + common.validate_instance(user, str) + common.validate_instance(zone, str) headers = { "Authorization": "Bearer " + self.token, @@ -387,14 +332,9 @@ def stat(self, name: str, zone: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(zone, str): - raise TypeError("zone must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) headers = {"Authorization": "Bearer " + self.token} @@ -420,16 +360,10 @@ def modify_metadata(self, name: str, operations: list): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(operations, list): - raise TypeError("operations must be a list of dictionaries") - if not isinstance(operations[0], dict): - raise TypeError("operations must be a list of dictionaries") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) headers = { "Authorization": "Bearer " + self.token, diff --git a/irods_http_client/zone_operations.py b/irods_http_client/zone_operations.py index 59534ca..c349d34 100644 --- a/irods_http_client/zone_operations.py +++ b/irods_http_client/zone_operations.py @@ -23,16 +23,10 @@ def add(self, name: str, connection_info: str = "", comment: str = ""): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(connection_info, str): - raise TypeError("connection_info must be a string") - if not isinstance(comment, str): - raise TypeError("comment must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(connection_info, str) + common.validate_instance(comment, str) headers = { "Authorization": "Bearer " + self.token, @@ -60,12 +54,8 @@ def remove(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, @@ -91,16 +81,10 @@ def modify(self, name: str, property: str, value: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") - if not isinstance(property, str): - raise TypeError("property must be a string") - if not isinstance(value, str): - raise TypeError("value must be a string") + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(property, str) + common.validate_instance(value, str) headers = { "Authorization": "Bearer " + self.token, @@ -120,10 +104,7 @@ def report(self): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) + common.check_token(self.token) headers = { "Authorization": "Bearer " + self.token, @@ -143,12 +124,8 @@ def stat(self, name: str): - A dict containing the HTTP status code and iRODS response. - The iRODS response is only valid if no error occurred during HTTP communication. """ - if self.token == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used" - ) - if not isinstance(name, str): - raise TypeError("name must be a string") + common.check_token(self.token) + common.validate_instance(name, str) headers = { "Authorization": "Bearer " + self.token, diff --git a/test/test_endpoint_operations.py b/test/test_endpoint_operations.py index 41fa7c1..9116eb4 100644 --- a/test/test_endpoint_operations.py +++ b/test/test_endpoint_operations.py @@ -113,6 +113,29 @@ def tear_down_class(cls): cls.api.users_groups.remove_user(cls.rodsuser_username, cls.zone_name) +# Tests for library +class libraryTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + setup_class(cls, {"endpoint_name": "collections"}) + + @classmethod + def tearDownClass(cls): + tear_down_class(cls) + + def setUp(self): + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) + + # tests the info operation + def testInfo(self): + self.api.info() + + # tests the getToken operation + def testGetToken(self): + self.api.getToken() # Tests for collections operations class collectionsTests(unittest.TestCase): @@ -763,8 +786,10 @@ def testRename(self): # tests the touch operation def testTouch(self): self.api.setToken(self.rodsadmin_bearer_token) - - self.assertTrue(True) + r = self.api.collections.touch( + f"/{self.zone_name}/home/{self.rodsadmin_username}", + reference=f"/{self.zone_name}/home" + ) # Tests for data object operations @@ -802,6 +827,23 @@ def testCommonOperations(self): ) self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + # Add metadata to the data object + r = self.api.data_objects.modify_metadata( + f1, + operations=[{'operation': 'add', 'attribute': 'a', 'value': 'v','units': 'u'}] + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Modify the replica + self.api.setToken(self.rodsadmin_bearer_token) + r = self.api.data_objects.modify_replica( + f1, + replica_number=0, + new_data_comments="awesome" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.api.setToken(self.rodsuser_bearer_token) + # Replicate the data object r = self.api.data_objects.replicate( f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", @@ -855,6 +897,13 @@ def testCommonOperations(self): r["data"]["permissions"], ) + # Modify permission on the object + r = self.api.data_objects.modify_permissions( + f3, + operations=[{'entity_name': 'rods','acl': 'write'}] + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + finally: # Remove the data objects r = self.api.data_objects.remove( @@ -1372,6 +1421,46 @@ def testRemoveDelayRule(self): r = self.api.rules.remove_delay_rule(int(r["data"]["rows"][0][0])) self.assertEqual(r["data"]["irods_response"]["status_code"], 0) +# Tests for query operations +class queryTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + setup_class(cls, {"endpoint_name": "query"}) + + @classmethod + def tearDownClass(cls): + tear_down_class(cls) + + def setUp(self): + self.assertFalse( + self._class_init_error, "Class initialization failed. Cannot continue." + ) + + def testCreateExecuteRemoveSpecificQuery(self): + + try: + + # As rodsadmin, create a specific query + self.api.setToken(self.rodsadmin_bearer_token) + + name = "get_users_count" + sql = "select count(*) from r_user_main" + r = self.api.queries.add_specific_query(name=name, sql=sql) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Switch to rodsuser and execute it + self.api.setToken(self.rodsuser_bearer_token) + r = self.api.queries.execute_specific_query(name=name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["rows"][0][0], "3") + + finally: + + # Switch to rodsadmin and remove it + self.api.setToken(self.rodsadmin_bearer_token) + r = self.api.queries.remove_specific_query(name=name) + # Tests for tickets operations class ticketsTests(unittest.TestCase): From 6d2bd112f88c4b2d834e2a2efc7a5761c45992c3 Mon Sep 17 00:00:00 2001 From: Terrell Russell Date: Fri, 13 Feb 2026 18:57:13 -0500 Subject: [PATCH 4/4] [#46] add initial ruff linter --- .github/workflows/linter-ruff.yml | 11 + irods_http_client/__init__.py | 4 +- irods_http_client/collection_operations.py | 663 ++-- irods_http_client/common.py | 98 +- irods_http_client/data_object_operations.py | 1772 ++++----- irods_http_client/irods_http_client.py | 185 +- irods_http_client/query_operations.py | 356 +- irods_http_client/resource_operations.py | 539 +-- irods_http_client/rule_operations.py | 166 +- irods_http_client/ticket_operations.py | 209 +- irods_http_client/user_group_operations.py | 752 ++-- irods_http_client/zone_operations.py | 277 +- pyproject.toml | 481 ++- test/__init__.py | 1 + test/config.py | 81 +- test/test_endpoint_operations.py | 3659 +++++++++---------- 16 files changed, 4897 insertions(+), 4357 deletions(-) create mode 100644 .github/workflows/linter-ruff.yml create mode 100644 test/__init__.py diff --git a/.github/workflows/linter-ruff.yml b/.github/workflows/linter-ruff.yml new file mode 100644 index 0000000..d07e0f5 --- /dev/null +++ b/.github/workflows/linter-ruff.yml @@ -0,0 +1,11 @@ +name: linter-ruff + +on: pull_request + +defaults: + run: + shell: bash + +jobs: + ruff-lint: + uses: irods/irods_reusable_github_workflows/.github/workflows/linter-irods-ruff.yml@main diff --git a/irods_http_client/__init__.py b/irods_http_client/__init__.py index 6d429ef..e69d90d 100644 --- a/irods_http_client/__init__.py +++ b/irods_http_client/__init__.py @@ -1 +1,3 @@ -from .irods_http_client import IRODSHTTPClient +"""iRODS HTTP client library for Python.""" + +from .irods_http_client import IRODSHTTPClient as IRODSHTTPClient diff --git a/irods_http_client/collection_operations.py b/irods_http_client/collection_operations.py index 9be426e..7073bee 100644 --- a/irods_http_client/collection_operations.py +++ b/irods_http_client/collection_operations.py @@ -1,331 +1,340 @@ -from . import common +"""Collection operations for iRODS HTTP API.""" + import json + import requests -class Collections: +from . import common + - def __init__(self, url_base: str): - """ - Initializes Collections with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def create(self, lpath: str, create_intermediates: int = 0): - """ - Creates a new collection. - - Parameters - - lpath: The absolute logical path of the collection to be created. - - create_intermediates (optional): Set to 1 to create intermediates, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_0_or_1(create_intermediates) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "create", - "lpath": lpath, - "create-intermediates": create_intermediates, - } - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def remove(self, lpath: str, recurse: int = 0, no_trash: int = 0): - """ - Removes an existing collection. - - Parameters - - lpath: The absolute logical path of the collection to be removed. - - recurse (optional): Set to 1 to remove contents of the collection, otherwise set to 0. Defaults to 0. - - no_trash (optional): Set to 1 to move the collection to trash, 0 to permanently remove. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_0_or_1(recurse) - common.validate_0_or_1(no_trash) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "remove", - "lpath": lpath, - "recurse": recurse, - "no-trash": no_trash, - } - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def stat(self, lpath: str, ticket: str = ""): - """ - Gives information about a collection. - - Parameters - - lpath: The absolute logical path of the collection being accessed. - - ticket (optional): Ticket to be enabled before the operation. Defaults to an empty string. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(ticket, str) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = {"op": "stat", "lpath": lpath, "ticket": ticket} - - r = requests.get(self.url_base + "/collections", params=params, headers=headers) - return common.process_response(r) - - def list(self, lpath: str, recurse: int = 0, ticket: str = ""): - """ - Shows the contents of a collection - - Parameters - - lpath: The absolute logical path of the collection to have its contents listed. - - recurse (optional): Set to 1 to list the contents of objects in the collection, otherwise set to 0. Defaults to 0. - - ticket (optional): Ticket to be enabled before the operation. Defaults to an empty string. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_0_or_1(recurse) - common.validate_instance(ticket, str) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = {"op": "list", "lpath": lpath, "recurse": recurse, "ticket": ticket} - - r = requests.get(self.url_base + "/collections", params=params, headers=headers) - return common.process_response(r) - - def set_permission( - self, lpath: str, entity_name: str, permission: str, admin: int = 0 - ): - """ - Sets the permission of a user for a given collection. - - Parameters - - lpath: The absolute logical path of the collection to have a permission set. - - entity_name: The name of the user or group having its permission set. - - permission: The permission level being set. Either 'null', 'read', 'write', or 'own'. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(entity_name, str) - common.validate_instance(permission, str) - if permission not in ["null", "read", "write", "own"]: - raise ValueError( - "permission must be either 'null', 'read', 'write', or 'own'" - ) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "set_permission", - "lpath": lpath, - "entity-name": entity_name, - "permission": permission, - "admin": admin, - } - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def set_inheritance(self, lpath: str, enable: int, admin: int = 0): - """ - Sets the inheritance for a collection. - - Parameters - - lpath: The absolute logical path of the collection to have its inheritance set. - - enable: Set to 1 to enable inheritance, or 0 to disable. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_0_or_1(enable) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "set_inheritance", - "lpath": lpath, - "enable": enable, - "admin": admin, - } - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def modify_permissions(self, lpath: str, operations: dict, admin: int = 0): - """ - Modifies permissions for multiple users or groups for a collection. - - Parameters - - lpath: The absolute logical path of the collection to have its permissions modified. - - operations: Dictionary containing the operations to carry out. Should contain names and permissions for all operations. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(operations, list) - common.validate_instance(operations[0], dict) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "modify_permissions", - "lpath": lpath, - "operations": json.dumps(operations), - "admin": admin, - } - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def modify_metadata(self, lpath: str, operations: dict, admin: int = 0): - """ - Modifies the metadata for a collection. - - Parameters - - lpath: The absolute logical path of the collection to have its metadata modified. - - operations: Dictionary containing the operations to carry out. Should contain the operation, attribute, value, and optionally units. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(operations, list) - common.validate_instance(operations[0], dict) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "modify_metadata", - "lpath": lpath, - "operations": json.dumps(operations), - "admin": admin, - } - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def rename(self, old_lpath: str, new_lpath: str): - """ - Renames or moves a collection - - Parameters - - old_lpath: The current absolute logical path of the collection. - - new_lpath: The absolute logical path of the destination for the collection. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(old_lpath, str) - common.validate_instance(new_lpath, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath} - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) - - def touch(self, lpath, seconds_since_epoch=-1, reference=""): - """ - Updates mtime for a collection - - Parameters - - lpath: The absolute logical path of the collection being touched. - - seconds_since_epoch (optional): The value to set mtime to, defaults to -1 as a flag. - - reference (optional): The absolute logical path of the collection to use as a reference for mtime. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_gte_minus1(seconds_since_epoch) - common.validate_instance(reference, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "touch", "lpath": lpath} - - if seconds_since_epoch != -1: - data["seconds-since-epoch"] = seconds_since_epoch - - if reference != "": - data["reference"] = reference - - r = requests.post(self.url_base + "/collections", headers=headers, data=data) - return common.process_response(r) +class Collections: + """Perform collection operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize Collections with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def create(self, lpath: str, create_intermediates: int = 0): + """ + Create a new collection. + + Args: + lpath: The absolute logical path of the collection to be created. + create_intermediates: Set to 1 to create intermediates, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(create_intermediates) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "create", + "lpath": lpath, + "create-intermediates": create_intermediates, + } + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove(self, lpath: str, recurse: int = 0, no_trash: int = 0): + """ + Remove an existing collection. + + Args: + lpath: The absolute logical path of the collection to be removed. + recurse: Set to 1 to remove contents of the collection, otherwise set to 0. Defaults to 0. + no_trash: Set to 1 to move the collection to trash, 0 to permanently remove. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(recurse) + common.validate_0_or_1(no_trash) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "remove", + "lpath": lpath, + "recurse": recurse, + "no-trash": no_trash, + } + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def stat(self, lpath: str, ticket: str = ""): + """ + Give information about a collection. + + Args: + lpath: The absolute logical path of the collection being accessed. + ticket: Ticket to be enabled before the operation. Defaults to an empty string. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(ticket, str) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = {"op": "stat", "lpath": lpath, "ticket": ticket} + + r = requests.get(self.url_base + "/collections", params=params, headers=headers, timeout=30) + return common.process_response(r) + + def list(self, lpath: str, recurse: int = 0, ticket: str = ""): + """ + Show the contents of a collection. + + Args: + lpath: The absolute logical path of the collection to have its contents listed. + recurse: Set to 1 to list the contents of objects in the collection, + otherwise set to 0. Defaults to 0. + ticket: Ticket to be enabled before the operation. Defaults to an empty string. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(recurse) + common.validate_instance(ticket, str) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = {"op": "list", "lpath": lpath, "recurse": recurse, "ticket": ticket} + + r = requests.get(self.url_base + "/collections", params=params, headers=headers, timeout=30) + return common.process_response(r) + + def set_permission(self, lpath: str, entity_name: str, permission: str, admin: int = 0): + """ + Set the permission of a user for a given collection. + + Args: + lpath: The absolute logical path of the collection to have a permission set. + entity_name: The name of the user or group having its permission set. + permission: The permission level being set. Either 'null', 'read', 'write', or 'own'. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If permission is not 'null', 'read', 'write', or 'own'. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(entity_name, str) + common.validate_instance(permission, str) + if permission not in ["null", "read", "write", "own"]: + raise ValueError("permission must be either 'null', 'read', 'write', or 'own'") + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "set_permission", + "lpath": lpath, + "entity-name": entity_name, + "permission": permission, + "admin": admin, + } + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def set_inheritance(self, lpath: str, enable: int, admin: int = 0): + """ + Set the inheritance for a collection. + + Args: + lpath: The absolute logical path of the collection to have its inheritance set. + enable: Set to 1 to enable inheritance, or 0 to disable. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(enable) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "set_inheritance", + "lpath": lpath, + "enable": enable, + "admin": admin, + } + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify_permissions(self, lpath: str, operations: dict, admin: int = 0): + """ + Modify permissions for multiple users or groups for a collection. + + Args: + lpath: The absolute logical path of the collection to have its permissions modified. + operations: Dictionary containing the operations to carry out. Should contain names + and permissions for all operations. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "modify_permissions", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, + } + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify_metadata(self, lpath: str, operations: dict, admin: int = 0): + """ + Modify the metadata for a collection. + + Args: + lpath: The absolute logical path of the collection to have its metadata modified. + operations: Dictionary containing the operations to carry out. Should contain the + operation, attribute, value, and optionally units. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "modify_metadata", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, + } + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def rename(self, old_lpath: str, new_lpath: str): + """ + Rename or move a collection. + + Args: + old_lpath: The current absolute logical path of the collection. + new_lpath: The absolute logical path of the destination for the collection. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(old_lpath, str) + common.validate_instance(new_lpath, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath} + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def touch(self, lpath, seconds_since_epoch=-1, reference=""): + """ + Update mtime for a collection. + + Args: + lpath: The absolute logical path of the collection being touched. + seconds_since_epoch: The value to set mtime to, defaults to -1 as a flag. + reference: The absolute logical path of the collection to use as a reference for mtime. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_gte_minus1(seconds_since_epoch) + common.validate_instance(reference, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "touch", "lpath": lpath} + + if seconds_since_epoch != -1: + data["seconds-since-epoch"] = seconds_since_epoch + + if reference != "": + data["reference"] = reference + + r = requests.post(self.url_base + "/collections", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/common.py b/irods_http_client/common.py index 38969b7..e24210a 100644 --- a/irods_http_client/common.py +++ b/irods_http_client/common.py @@ -1,31 +1,93 @@ -import json +"""Common utility functions for iRODS HTTP client operations.""" def process_response(r): - rdict = r.json() if r.text != "" else None - return {"status_code": r.status_code, "data": rdict} + """ + Process an HTTP response and return standardized response dict. + + Args: + r: The HTTP response object. + + Returns: + A dict with 'status_code' and 'data' keys containing the HTTP status code + and parsed JSON response body (or None if response is empty). + """ + rdict = r.json() if r.text != "" else None + return {"status_code": r.status_code, "data": rdict} + def check_token(t): - if t == None: - raise RuntimeError( - "No token set. Use setToken() to set the auth token to be used." - ) + """ + Verify that an authentication token is set. + + Args: + t: The authentication token to check. + + Raises: + RuntimeError: If the token is None. + """ + if t is None: + raise RuntimeError("No token set. Use setToken() to set the auth token to be used.") + def validate_instance(x, expected_type): - if not isinstance(x, expected_type): - raise TypeError + """ + Validate that a value is an instance of the expected type. + + Args: + x: The value to validate. + expected_type: The expected type. + + Raises: + TypeError: If x is not an instance of expected_type. + """ + if not isinstance(x, expected_type): + raise TypeError + def validate_0_or_1(x): - validate_instance(x, int) - if x not in [0, 1]: - raise ValueError(f"{x} must be 0 or 1") + """ + Validate that a value is either 0 or 1. + + Args: + x: The value to validate (must be an int). + + Raises: + TypeError: If x is not an integer. + ValueError: If x is not 0 or 1. + """ + validate_instance(x, int) + if x not in [0, 1]: + raise ValueError(f"{x} must be 0 or 1") + def validate_gte_zero(x): - validate_instance(x, int) - if not x >= 0: - raise ValueError(f"{x} must be >= 0") + """ + Validate that a value is greater than or equal to zero. + + Args: + x: The value to validate (must be an int). + + Raises: + TypeError: If x is not an integer. + ValueError: If x is less than 0. + """ + validate_instance(x, int) + if not x >= 0: + raise ValueError(f"{x} must be >= 0") + def validate_gte_minus1(x): - validate_instance(x, int) - if not x >= -1: - raise ValueError(f"{x} must be >= 0, or flag value of -1") + """ + Validate that a value is greater than or equal to -1. + + Args: + x: The value to validate (must be an int). + + Raises: + TypeError: If x is not an integer. + ValueError: If x is less than -1. + """ + validate_instance(x, int) + if not x >= -1: + raise ValueError(f"{x} must be >= 0, or flag value of -1") diff --git a/irods_http_client/data_object_operations.py b/irods_http_client/data_object_operations.py index 2461812..4d99b97 100644 --- a/irods_http_client/data_object_operations.py +++ b/irods_http_client/data_object_operations.py @@ -1,884 +1,894 @@ -from . import common +"""Data object operations for iRODS HTTP API.""" + +import json + import requests +from . import common + + class DataObjects: - def __init__(self, url_base: str): - """ - Initializes DataObjects with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def touch( - self, - lpath, - no_create: int = 0, - replica_number: int = -1, - leaf_resources: str = "", - seconds_since_epoch=-1, - reference="", - ): - """ - Updates mtime for an existing data object or creates a new one - - Parameters - - lpath: The absolute logical path of the data object being touched. - - no_create (optional): Set to 1 to prevent creating a new object, otherwise set to 0. - - replica_number (optional): The replica number of the target replica. - - leaf_resources (optional): The resource holding an existing replica. If one does not exist, creates one. - - seconds_since_epoch (optional): The value to set mtime to, defaults to -1 as a flag. - - reference (optional): The absolute logical path of the data object to use as a reference for mtime. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_0_or_1(no_create) - common.validate_gte_minus1(replica_number) - common.validate_instance(leaf_resources, str) - common.validate_gte_minus1(seconds_since_epoch) - common.validate_instance(reference, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "touch", "lpath": lpath, "no-create": no_create} - - if seconds_since_epoch != -1: - data["seconds-since-epoch"] = seconds_since_epoch - - if replica_number != -1: - data["replica-number"] = replica_number - - if leaf_resources != "": - data["leaf-resources"] = leaf_resources - - if reference != "": - data["reference"] = reference - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def remove( - self, lpath: str, catalog_only: int = 0, no_trash: int = 0, admin: int = 0 - ): - """ - Removes an existing data object. - - Parameters - - lpath: The absolute logical path of the data object to be removed. - - catalog_only (optional): Set to 1 to remove only the catalog entry, otherwise set to 0. Defaults to 0. - - no_trash (optional): Set to 1 to move the data object to trash, 0 to permanently remove. Defaults to 0. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_0_or_1(catalog_only) - common.validate_0_or_1(no_trash) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "remove", - "lpath": lpath, - "catalog-only": catalog_only, - "no-trash": no_trash, - "admin": admin, - } - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def calculate_checksum( - self, - lpath: str, - resource: str = "", - replica_number: int = -1, - force: int = 0, - all: int = 0, - admin: int = 0, - ): - """ - Calculates the checksum for a data object. - - Parameters - - lpath: The absolute logical path of the data object to have its checksum calculated. - - resource (optional): The resource holding the existing replica. - - replica_number (optional): The replica number of the target replica. - - force (optional): Set to 1 to replace the existing checksum, otherwise set to 0. Defaults to 0. - - all (optional): Set to 1 to calculate the checksum for all replicas, otherwise set to 0. Defaults to 0. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(resource, str) - common.validate_gte_minus1(replica_number) - common.validate_0_or_1(force) - common.validate_0_or_1(all) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "calculate_checksum", - "lpath": lpath, - "force": force, - "all": all, - "admin": admin, - } - - if resource != "": - data["resource"] = resource - - if replica_number != -1: - data["replica-number"] = replica_number - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def verify_checksum( - self, - lpath: str, - resource: str = "", - replica_number: int = -1, - compute_checksums: int = 0, - admin: int = 0, - ): - """ - Verifies the checksum for a data object. - - Parameters - - lpath: The absolute logical path of the data object to have its checksum verified. - - resource (optional): The resource holding the existing replica. - - replica_number (optional): The replica number of the target replica. - - compute_checksums (optional): Set to 1 to skip checksum calculation, otherwise set to 0. Defaults to 0. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(resource, str) - common.validate_gte_minus1(replica_number) - common.validate_0_or_1(compute_checksums) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "calculate_checksum", - "lpath": lpath, - "compute-checksums": compute_checksums, - "admin": admin, - } - - if resource != "": - data["resource"] = resource - - if replica_number != -1: - data["replica-number"] = replica_number - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def stat(self, lpath: str, ticket: str = ""): - """ - Gives information about a data object. - - Parameters - - lpath: The absolute logical path of the data object being accessed. - - ticket (optional): Ticket to be enabled before the operation. Defaults to an empty string. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(ticket, str) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = {"op": "stat", "lpath": lpath, "ticket": ticket} - - r = requests.get(self.url_base + "/data-objects", params=params, headers=headers) - return common.process_response(r) - - def rename(self, old_lpath: str, new_lpath: str): - """ - Renames or moves a data object. - - Parameters - - old_lpath: The current absolute logical path of the data object. - - new_lpath: The absolute logical path of the destination for the data object. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(old_lpath, str) - common.validate_instance(new_lpath, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath} - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def copy( - self, - src_lpath: str, - dst_lpath: str, - src_resource: str = "", - dst_resource: str = "", - overwrite: int = 0, - ): - """ - Copies a data object. - - Parameters - - src_lpath: The absolute logical path of the source data object. - - dst_lpath: The absolute logical path of the destination. - - src_resource: The name of the source resource. - - dst_resource: The name of the destination resource. - - overwrite: set to 1 to overwrite an existing objject, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(src_lpath, str) - common.validate_instance(dst_lpath, str) - common.validate_instance(src_resource, str) - common.validate_instance(dst_resource, str) - common.validate_0_or_1(overwrite) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "copy", - "src-lpath": src_lpath, - "dst-lpath": dst_lpath, - "overwrite": overwrite, - } - - if src_resource != "": - data["src-resource"] = src_resource - - if dst_resource != "": - data["dst-resource"] = dst_resource - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def replicate( - self, lpath: str, src_resource: str = "", dst_resource: str = "", admin: int = 0 - ): - """ - Replicates a data object from one resource to another. - - Parameters - - lpath: The absolute logical path of the data object to be replicated. - - src_resource: The name of the source resource. - - dst_resource: The name of the destination resource. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(src_resource, str) - common.validate_instance(dst_resource, str) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "replicate", "lpath": lpath, "admin": admin} - - if src_resource != "": - data["src-resource"] = src_resource - - if dst_resource != "": - data["dst-resource"] = dst_resource - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def trim( - self, lpath: str, replica_number: int, catalog_only: int = 0, admin: int = 0 - ): - """ - Trims an existing replica or removes its catalog entry. - - Parameters - - lpath: The absolute logical path of the data object to be trimmed. - - replica_number: The replica number of the target replica. - - catalog_only (optional): Set to 1 to remove only the catalog entry, otherwise set to 0. Defaults to 0. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(replica_number, int) - common.validate_0_or_1(catalog_only) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "trim", - "lpath": lpath, - "replica-number": replica_number, - "catalog-only": catalog_only, - "admin": admin, - } - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def register( - self, - lpath: str, - ppath: str, - resource: str, - as_additional_replica: int = 0, - data_size: int = -1, - checksum: str = "", - ): - """ - Registers a data object/replica into the catalog. - - Parameters - - lpath: The absolute logical path of the data object to be registered. - - ppath: The absolute physical path of the data object to be registered. - - resource: The resource that will own the replica. - - as_additional_replica (optional): Set to 1 to register as a replica of an existing object, otherwise set to 0. Defaults to 0. - - data_size (optional): The size of the replica in bytes. - - checksum (optional): The checksum to associate with the replica. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(ppath, str) - common.validate_instance(resource, str) - common.validate_0_or_1(as_additional_replica) - common.validate_gte_minus1(data_size) - common.validate_instance(checksum, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "register", - "lpath": lpath, - "ppath": ppath, - "resource": resource, - "as_additional_replica": as_additional_replica, - } - - if data_size != -1: - data["data-size"] = data_size - - if checksum != "": - data["checksum"] = checksum - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def read(self, lpath: str, offset: int = 0, count: int = -1, ticket: str = ""): - """ - Reads bytes from a data object. - - Parameters - - lpath: The absolute logical path of the data object to be read from. - - offset (optional): The number of bytes to skip. Defaults to 0. - - count (optional): The number of bytes to read. - - ticket (optional): Ticket to be enabled before the operation. Defaults to an empty string. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(offset, int) - common.validate_gte_minus1(count) - common.validate_instance(ticket, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - params = {"op": "read", "lpath": lpath, "offset": offset} - - if count != -1: - params["count"] = count - - if ticket != "": - params["ticket"] = ticket - - r = requests.get( - self.url_base + "/data-objects", params=params, headers=headers - ) - return common.process_response(r) - - def write( - self, - bytes, - lpath: str = "", - resource: str = "", - offset: int = 0, - truncate: int = 1, - append: int = 0, - parallel_write_handle: str = "", - stream_index: int = -1, - ): - """ - Writes bytes to a data object. - - Parameters - - bytes: The bytes to be written. - - lpath: The absolute logical path of the data object to be written to. - - resource (optional): The root resource to write to. - - offset (optional): The number of bytes to skip. Defaults to 0. - - truncate (optional): Set to 1 to truncate the data object before writing, otherwise set to 0. Defaults to 1. - - append (optional): Set to 1 to append bytes to the data objectm otherwise set to 0. Defaults to 0. - - parallel_write_handle (optional): The handle to be used when writing in parallel. - - stream_index (optional): The stream to use when writing in parallel. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) -# common.validate_instance(bytes, int) - if not len(bytes) >= 0: - raise ValueError("bytes must be greater than or equal to 0") - common.validate_instance(lpath, str) - common.validate_instance(resource, str) - common.validate_gte_zero(offset) - common.validate_0_or_1(truncate) - common.validate_0_or_1(append) - common.validate_instance(parallel_write_handle, str) - common.validate_gte_minus1(stream_index) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "write", - "offset": offset, - "truncate": truncate, - "append": append, - "bytes": bytes, - } - - if parallel_write_handle != "": - data["parallel-write-handle"] = parallel_write_handle - else: - data["lpath"] = lpath - - if resource != "": - data["resource"] = resource - - if stream_index != -1: - data["stream-index"] = stream_index - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def parallel_write_init( - self, - lpath: str, - stream_count: int, - truncate: int = 1, - append: int = 0, - ticket: str = "", - ): - """ - Initializes server-side state for parallel writing. - - Parameters - - lpath: The absolute logical path of the data object to be initialized for parallel write. - - stream_count: THe number of streams to open. - - offset (optional): The number of bytes to skip. Defaults to 0. - - truncate (optional): Set to 1 to truncate the data object before writing, otherwise set to 0. Defaults to 1. - - append (optional): Set to 1 to append bytes to the data objectm otherwise set to 0. Defaults to 0. - - ticket (optional): Ticket to be enabled before the operation. Defaults to an empty string. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_gte_zero(stream_count) - common.validate_0_or_1(truncate) - common.validate_0_or_1(append) - common.validate_instance(ticket, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "parallel_write_init", - "lpath": lpath, - "stream-count": stream_count, - "truncate": truncate, - "append": append, - } - - if ticket != "": - data["ticket"] = ticket - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def parallel_write_shutdown(self, parallel_write_handle: str): - """ - Shuts down the parallel write state in the server. - - Parameters - - parallel_write_handle: Handle obtained from parallel_write_init - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(parallel_write_handle, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "parallel_write_shutdown", - "parallel-write-handle": parallel_write_handle, - } - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def modify_metadata(self, lpath: str, operations: list, admin: int = 0): - """ - Modifies the metadata for a data object - - Parameters - - lpath: The absolute logical path of the data object to have its inheritance set. - - operations: Dictionary containing the operations to carry out. Should contain the operation, attribute, value, and optionally units. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(operations, list) - common.validate_instance(operations[0], dict) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "modify_metadata", - "lpath": lpath, - "operations": json.dumps(operations), - "admin": admin, - } - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def set_permission( - self, lpath: str, entity_name: str, permission: str, admin: int = 0 - ): - """ - Sets the permission of a user for a given data object - - Parameters - - lpath: The absolute logical path of the data object to have a permission set. - - entity_name: The name of the user or group having its permission set. - - permission: The permission level being set. Either 'null', 'read', 'write', or 'own'. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(entity_name, str) - common.validate_instance(permission, str) - if permission not in ["null", "read", "write", "own"]: - raise ValueError( - "permission must be either 'null', 'read', 'write', or 'own'" - ) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "set_permission", - "lpath": lpath, - "entity-name": entity_name, - "permission": permission, - "admin": admin, - } - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def modify_permissions(self, lpath: str, operations: list, admin: int = 0): - """ - Modifies permissions for multiple users or groups for a data object. - - Parameters - - lpath: The absolute logical path of the data object to have its permissions modified. - - operations: Dictionary containing the operations to carry out. Should contain names and permissions for all operations. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(operations, list) - common.validate_instance(operations[0], dict) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "modify_permissions", - "lpath": lpath, - "operations": json.dumps(operations), - "admin": admin, - } - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) - - def modify_replica( - self, - lpath: str, - resource_hierarchy: str = "", - replica_number: int = -1, - new_data_checksum: str = "", - new_data_comments: str = "", - new_data_create_time: int = -1, - new_data_expiry: int = -1, - new_data_mode: str = "", - new_data_modify_time: str = "", - new_data_path: str = "", - new_data_replica_number: int = -1, - new_data_replica_status: int = -1, - new_data_resource_id: int = -1, - new_data_size: int = -1, - new_data_status: str = "", - new_data_type_name: str = "", - new_data_version: int = -1, - ): - """ - Modifies properties of a single replica. - - WARNING: - This operation requires rodsadmin level privileges and should only be used when there isn't a safer option. - Misuse can lead to catalog inconsistencies and unexpected behavior. - - Parameters - - lpath: The absolute logical path of the data object to have a replica modified. - - resource_hierarchy: The hierarchy containing the resource to be modified. Mutually exclusive with replica_number. - - replica_number: The number of the replica to be modified. mutually exclusive with resource_hierarchy. - - Note: - At least one of the following optional parameters must be passed in - - - new_data_checksum (optional): The new checksum to be set. - - new_data_comments (optional): The new comments to be set. - - new_data_create_time (optional): The new create time to be set. - - new_data_expiry (optional): The new expiry to be set. - - new_data_mode (optional): The new mode to be set. - - new_data_modify_time (optional): The new modify time to be set. - - new_data_path (optional): The new path to be set. - - new_data_replica_number (optional): The new replica number to be set. - - new_data_replica_status (optional): The new replica status to be set. - - new_data_resource_id (optional): The new resource id to be set - - new_data_size (optional): The new size to be set. - - new_data_status (optional): The new data status to be set. - - new_data_type_name (optional): The new type name to be set. - - new_data_version (optional): The new version to be set. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(resource_hierarchy, str) - common.validate_instance(replica_number, int) - if (resource_hierarchy != "") and (replica_number != -1): - raise ValueError( - "replica_hierarchy and replica_number are mutually exclusive" - ) - common.validate_instance(new_data_checksum, str) - common.validate_instance(new_data_comments, str) - common.validate_gte_minus1(new_data_create_time) - common.validate_gte_minus1(new_data_expiry) - common.validate_instance(new_data_mode, str) - common.validate_instance(new_data_modify_time, str) - common.validate_instance(new_data_path, str) - common.validate_gte_minus1(new_data_replica_number) - common.validate_gte_minus1(new_data_replica_status) - common.validate_gte_minus1(new_data_resource_id) - common.validate_gte_minus1(new_data_size) - common.validate_instance(new_data_status, str) - common.validate_instance(new_data_name, str) - common.validate_gte_minus1(new_data_version) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "modify_replica", "lpath": lpath} - - if resource_hierarchy != "": - data["resource-hierarchy"] = resource_hierarchy - - if replica_number != -1: - data["replica-number"] = replica_number - - # Boolean for checking if the user passed in any new_data parameters - no_params = True - - if new_data_checksum != "": - data["new-data-checksum"] = new_data_checksum - no_params = False - - if new_data_comments != "": - data["new-data-comments"] = new_data_comments - no_params = False - - if new_data_create_time != -1: - data["new-data-create-time"] = new_data_create_time - no_params = False - - if new_data_expiry != -1: - data["new-data-expiry"] = new_data_expiry - no_params = False - - if new_data_mode != "": - data["new-data-mode"] = new_data_mode - no_params = False - - if new_data_modify_time != "": - data["new-data-modify-time"] = new_data_modify_time - no_params = False - - if new_data_path != "": - data["new-data-path"] = new_data_path - no_params = False - - if new_data_replica_number != -1: - data["new-data-replica-number"] = new_data_replica_number - no_params = False - - if new_data_replica_status != -1: - data["new-data-replica-status"] = new_data_replica_status - no_params = False - - if new_data_resource_id != -1: - data["new-data-resource-id"] = new_data_resource_id - no_params = False - - if new_data_size != -1: - data["new-data-size"] = new_data_size - no_params = False - - if new_data_status != "": - data["new-data-status"] = new_data_status - no_params = False - - if new_data_type_name != "": - data["new-data-type-name"] = new_data_type_name - no_params = False - - if new_data_version != "": - data["new-data-version"] = new_data_version - no_params = False - - if no_params: - raise RuntimeError("At least one new data parameter must be given.") - - r = requests.post(self.url_base + "/data-objects", headers=headers, data=data) - return common.process_response(r) + """Perform data object operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize DataObjects with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def touch( + self, + lpath, + no_create: int = 0, + replica_number: int = -1, + leaf_resources: str = "", + seconds_since_epoch=-1, + reference="", + ): + """ + Update mtime for an existing data object or create a new one. + + Args: + lpath: The absolute logical path of the data object being touched. + no_create: Set to 1 to prevent creating a new object, otherwise set to 0. + replica_number: The replica number of the target replica. + leaf_resources: The resource holding an existing replica. If one does not exist, creates one. + seconds_since_epoch: The value to set mtime to, defaults to -1 as a flag. + reference: The absolute logical path of the data object to use as a reference for mtime. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(no_create) + common.validate_gte_minus1(replica_number) + common.validate_instance(leaf_resources, str) + common.validate_gte_minus1(seconds_since_epoch) + common.validate_instance(reference, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "touch", "lpath": lpath, "no-create": no_create} + + if seconds_since_epoch != -1: + data["seconds-since-epoch"] = seconds_since_epoch + + if replica_number != -1: + data["replica-number"] = replica_number + + if leaf_resources != "": + data["leaf-resources"] = leaf_resources + + if reference != "": + data["reference"] = reference + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove(self, lpath: str, catalog_only: int = 0, no_trash: int = 0, admin: int = 0): + """ + Remove an existing data object. + + Args: + lpath: The absolute logical path of the data object to be removed. + catalog_only: Set to 1 to remove only the catalog entry, otherwise set to 0. Defaults to 0. + no_trash: Set to 1 to move the data object to trash, 0 to permanently remove. Defaults to 0. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_0_or_1(catalog_only) + common.validate_0_or_1(no_trash) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "remove", + "lpath": lpath, + "catalog-only": catalog_only, + "no-trash": no_trash, + "admin": admin, + } + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def calculate_checksum( + self, + lpath: str, + resource: str = "", + replica_number: int = -1, + force: int = 0, + all_: int = 0, + admin: int = 0, + ): + """ + Calculate the checksum for a data object. + + Args: + lpath: The absolute logical path of the data object to have its checksum calculated. + resource: The resource holding the existing replica. + replica_number: The replica number of the target replica. + force: Set to 1 to replace the existing checksum, otherwise set to 0. Defaults to 0. + all_: Set to 1 to calculate the checksum for all replicas, otherwise set to 0. Defaults to 0. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(resource, str) + common.validate_gte_minus1(replica_number) + common.validate_0_or_1(force) + common.validate_0_or_1(all_) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "calculate_checksum", + "lpath": lpath, + "force": force, + "all": all_, + "admin": admin, + } + + if resource != "": + data["resource"] = resource + + if replica_number != -1: + data["replica-number"] = replica_number + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def verify_checksum( + self, + lpath: str, + resource: str = "", + replica_number: int = -1, + compute_checksums: int = 0, + admin: int = 0, + ): + """ + Verify the checksum for a data object. + + Args: + lpath: The absolute logical path of the data object to have its checksum verified. + resource: The resource holding the existing replica. + replica_number: The replica number of the target replica. + compute_checksums: Set to 1 to skip checksum calculation, otherwise set to 0. Defaults to 0. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(resource, str) + common.validate_gte_minus1(replica_number) + common.validate_0_or_1(compute_checksums) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "calculate_checksum", + "lpath": lpath, + "compute-checksums": compute_checksums, + "admin": admin, + } + + if resource != "": + data["resource"] = resource + + if replica_number != -1: + data["replica-number"] = replica_number + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def stat(self, lpath: str, ticket: str = ""): + """ + Give information about a data object. + + Args: + lpath: The absolute logical path of the data object being accessed. + ticket: Ticket to be enabled before the operation. Defaults to an empty string. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(ticket, str) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = {"op": "stat", "lpath": lpath, "ticket": ticket} + + r = requests.get(self.url_base + "/data-objects", params=params, headers=headers, timeout=30) + return common.process_response(r) + + def rename(self, old_lpath: str, new_lpath: str): + """ + Rename or move a data object. + + Args: + old_lpath: The current absolute logical path of the data object. + new_lpath: The absolute logical path of the destination for the data object. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(old_lpath, str) + common.validate_instance(new_lpath, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath} + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def copy( + self, + src_lpath: str, + dst_lpath: str, + src_resource: str = "", + dst_resource: str = "", + overwrite: int = 0, + ): + """ + Copy a data object. + + Args: + src_lpath: The absolute logical path of the source data object. + dst_lpath: The absolute logical path of the destination. + src_resource: The name of the source resource. + dst_resource: The name of the destination resource. + overwrite: Set to 1 to overwrite an existing objject, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(src_lpath, str) + common.validate_instance(dst_lpath, str) + common.validate_instance(src_resource, str) + common.validate_instance(dst_resource, str) + common.validate_0_or_1(overwrite) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "copy", + "src-lpath": src_lpath, + "dst-lpath": dst_lpath, + "overwrite": overwrite, + } + + if src_resource != "": + data["src-resource"] = src_resource + + if dst_resource != "": + data["dst-resource"] = dst_resource + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def replicate(self, lpath: str, src_resource: str = "", dst_resource: str = "", admin: int = 0): + """ + Replicates a data object from one resource to another. + + Args: + lpath: The absolute logical path of the data object to be replicated. + src_resource: The name of the source resource. + dst_resource: The name of the destination resource. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(src_resource, str) + common.validate_instance(dst_resource, str) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "replicate", "lpath": lpath, "admin": admin} + + if src_resource != "": + data["src-resource"] = src_resource + + if dst_resource != "": + data["dst-resource"] = dst_resource + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def trim(self, lpath: str, replica_number: int, catalog_only: int = 0, admin: int = 0): + """ + Trims an existing replica or removes its catalog entry. + + Args: + lpath: The absolute logical path of the data object to be trimmed. + replica_number: The replica number of the target replica. + catalog_only: Set to 1 to remove only the catalog entry, otherwise set to 0. Defaults to 0. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(replica_number, int) + common.validate_0_or_1(catalog_only) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "trim", + "lpath": lpath, + "replica-number": replica_number, + "catalog-only": catalog_only, + "admin": admin, + } + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def register( + self, + lpath: str, + ppath: str, + resource: str, + as_additional_replica: int = 0, + data_size: int = -1, + checksum: str = "", + ): + """ + Register a data object/replica into the catalog. + + Args: + lpath: The absolute logical path of the data object to be registered. + ppath: The absolute physical path of the data object to be registered. + resource: The resource that will own the replica. + as_additional_replica: Set to 1 to register as a replica of an existing + object, otherwise set to 0. Defaults to 0. + data_size: The size of the replica in bytes. + checksum: The checksum to associate with the replica. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(ppath, str) + common.validate_instance(resource, str) + common.validate_0_or_1(as_additional_replica) + common.validate_gte_minus1(data_size) + common.validate_instance(checksum, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "register", + "lpath": lpath, + "ppath": ppath, + "resource": resource, + "as_additional_replica": as_additional_replica, + } + + if data_size != -1: + data["data-size"] = data_size + + if checksum != "": + data["checksum"] = checksum + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def read(self, lpath: str, offset: int = 0, count: int = -1, ticket: str = ""): + """ + Read bytes from a data object. + + Args: + lpath: The absolute logical path of the data object to be read from. + offset: The number of bytes to skip. Defaults to 0. + count: The number of bytes to read. + ticket: Ticket to be enabled before the operation. Defaults to an empty string. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(offset, int) + common.validate_gte_minus1(count) + common.validate_instance(ticket, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + params = {"op": "read", "lpath": lpath, "offset": offset} + + if count != -1: + params["count"] = count + + if ticket != "": + params["ticket"] = ticket + + r = requests.get(self.url_base + "/data-objects", params=params, headers=headers, timeout=30) + # TODO(#45): confirm this is the format we want to return + # - this is the only payload that is different from common.process_response() + return {'status_code': r.status_code, 'data': {'irods_response': {'status_code': 0, 'bytes': r.content}}} + + def write( + self, + bytes_, + lpath: str = "", + resource: str = "", + offset: int = 0, + truncate: int = 1, + append: int = 0, + parallel_write_handle: str = "", + stream_index: int = -1, + ): + """ + Write bytes to a data object. + + Args: + bytes_: The bytes to be written. + lpath: The absolute logical path of the data object to be written to. + resource: The root resource to write to. + offset: The number of bytes to skip. Defaults to 0. + truncate: Set to 1 to truncate the data object before writing, otherwise set to 0. Defaults to 1. + append: Set to 1 to append bytes to the data objectm otherwise set to 0. Defaults to 0. + parallel_write_handle: The handle to be used when writing in parallel. + stream_index: The stream to use when writing in parallel. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If bytes length is less than 0. + """ + common.check_token(self.token) + # also need to validate that bytes_ is a proper type + if not len(bytes_) >= 0: + raise ValueError("bytes must be greater than or equal to 0") + common.validate_instance(lpath, str) + common.validate_instance(resource, str) + common.validate_gte_zero(offset) + common.validate_0_or_1(truncate) + common.validate_0_or_1(append) + common.validate_instance(parallel_write_handle, str) + common.validate_gte_minus1(stream_index) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "write", + "offset": offset, + "truncate": truncate, + "append": append, + "bytes": bytes_, + } + + if parallel_write_handle != "": + data["parallel-write-handle"] = parallel_write_handle + else: + data["lpath"] = lpath + + if resource != "": + data["resource"] = resource + + if stream_index != -1: + data["stream-index"] = stream_index + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def parallel_write_init( + self, + lpath: str, + stream_count: int, + truncate: int = 1, + append: int = 0, + ticket: str = "", + ): + """ + Initialize server-side state for parallel writing. + + Args: + lpath: The absolute logical path of the data object to be initialized for parallel write. + stream_count: The number of streams to open. + truncate: Set to 1 to truncate the data object before writing, otherwise set to 0. Defaults to 1. + append: Set to 1 to append bytes to the data objectm otherwise set to 0. Defaults to 0. + ticket: Ticket to be enabled before the operation. Defaults to an empty string. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_gte_zero(stream_count) + common.validate_0_or_1(truncate) + common.validate_0_or_1(append) + common.validate_instance(ticket, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "parallel_write_init", + "lpath": lpath, + "stream-count": stream_count, + "truncate": truncate, + "append": append, + } + + if ticket != "": + data["ticket"] = ticket + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def parallel_write_shutdown(self, parallel_write_handle: str): + """ + Shuts down the parallel write state in the server. + + Args: + parallel_write_handle: Handle obtained from parallel_write_init. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(parallel_write_handle, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "parallel_write_shutdown", + "parallel-write-handle": parallel_write_handle, + } + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify_metadata(self, lpath: str, operations: list, admin: int = 0): + """ + Modify the metadata for a data object. + + Args: + lpath: The absolute logical path of the data object to have its inheritance set. + operations: Dictionary containing the operations to carry out. Should contain the + operation, attribute, value, and optionally units. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "modify_metadata", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, + } + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def set_permission(self, lpath: str, entity_name: str, permission: str, admin: int = 0): + """ + Set the permission of a user for a given data object. + + Args: + lpath: The absolute logical path of the data object to have a permission set. + entity_name: The name of the user or group having its permission set. + permission: The permission level being set. Either 'null', 'read', 'write', or 'own'. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If permission is not 'null', 'read', 'write', or 'own'. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(entity_name, str) + common.validate_instance(permission, str) + if permission not in ["null", "read", "write", "own"]: + raise ValueError("permission must be either 'null', 'read', 'write', or 'own'") + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "set_permission", + "lpath": lpath, + "entity-name": entity_name, + "permission": permission, + "admin": admin, + } + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify_permissions(self, lpath: str, operations: list, admin: int = 0): + """ + Modify permissions for multiple users or groups for a data object. + + Args: + lpath: The absolute logical path of the data object to have its permissions modified. + operations: Dictionary containing the operations to carry out. Should contain names + and permissions for all operations. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "modify_permissions", + "lpath": lpath, + "operations": json.dumps(operations), + "admin": admin, + } + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify_replica( + self, + lpath: str, + resource_hierarchy: str = "", + replica_number: int = -1, + new_data_checksum: str = "", + new_data_comments: str = "", + new_data_create_time: int = -1, + new_data_expiry: int = -1, + new_data_mode: str = "", + new_data_modify_time: str = "", + new_data_path: str = "", + new_data_replica_number: int = -1, + new_data_replica_status: int = -1, + new_data_resource_id: int = -1, + new_data_size: int = -1, + new_data_status: str = "", + new_data_type_name: str = "", + new_data_version: int = -1, + ): + """ + Modify properties of a single replica. + + Warning: + This operation requires rodsadmin level privileges and should only be used when there isn't a safer option. + Misuse can lead to catalog inconsistencies and unexpected behavior. + + Args: + lpath: The absolute logical path of the data object to have a replica modified. + resource_hierarchy: The hierarchy containing the resource to be modified. + Mutually exclusive with replica_number. + replica_number: The number of the replica to be modified. mutually exclusive with + resource_hierarchy. + new_data_checksum: The new checksum to be set. + new_data_comments: The new comments to be set. + new_data_create_time: The new create time to be set. + new_data_expiry: The new expiry to be set. + new_data_mode: The new mode to be set. + new_data_modify_time: The new modify time to be set. + new_data_path: The new path to be set. + new_data_replica_number: The new replica number to be set. + new_data_replica_status: The new replica status to be set. + new_data_resource_id: The new resource id to be set. + new_data_size: The new size to be set. + new_data_status: The new data status to be set. + new_data_type_name: The new type name to be set. + new_data_version: The new version to be set. + + Note: + At least one of the new_data parameters must be passed in. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If both resource_hierarchy and replica_number are provided. + RuntimeError: If no new_data parameters are provided. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(resource_hierarchy, str) + common.validate_instance(replica_number, int) + if (resource_hierarchy != "") and (replica_number != -1): + raise ValueError("replica_hierarchy and replica_number are mutually exclusive") + common.validate_instance(new_data_checksum, str) + common.validate_instance(new_data_comments, str) + common.validate_gte_minus1(new_data_create_time) + common.validate_gte_minus1(new_data_expiry) + common.validate_instance(new_data_mode, str) + common.validate_instance(new_data_modify_time, str) + common.validate_instance(new_data_path, str) + common.validate_gte_minus1(new_data_replica_number) + common.validate_gte_minus1(new_data_replica_status) + common.validate_gte_minus1(new_data_resource_id) + common.validate_gte_minus1(new_data_size) + common.validate_instance(new_data_status, str) + common.validate_instance(new_data_type_name, str) + common.validate_gte_minus1(new_data_version) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "modify_replica", "lpath": lpath} + + if resource_hierarchy != "": + data["resource-hierarchy"] = resource_hierarchy + + if replica_number != -1: + data["replica-number"] = replica_number + + # Boolean for checking if the user passed in any new_data parameters + no_params = True + + if new_data_checksum != "": + data["new-data-checksum"] = new_data_checksum + no_params = False + + if new_data_comments != "": + data["new-data-comments"] = new_data_comments + no_params = False + + if new_data_create_time != -1: + data["new-data-create-time"] = new_data_create_time + no_params = False + + if new_data_expiry != -1: + data["new-data-expiry"] = new_data_expiry + no_params = False + + if new_data_mode != "": + data["new-data-mode"] = new_data_mode + no_params = False + + if new_data_modify_time != "": + data["new-data-modify-time"] = new_data_modify_time + no_params = False + + if new_data_path != "": + data["new-data-path"] = new_data_path + no_params = False + + if new_data_replica_number != -1: + data["new-data-replica-number"] = new_data_replica_number + no_params = False + + if new_data_replica_status != -1: + data["new-data-replica-status"] = new_data_replica_status + no_params = False + + if new_data_resource_id != -1: + data["new-data-resource-id"] = new_data_resource_id + no_params = False + + if new_data_size != -1: + data["new-data-size"] = new_data_size + no_params = False + + if new_data_status != "": + data["new-data-status"] = new_data_status + no_params = False + + if new_data_type_name != "": + data["new-data-type-name"] = new_data_type_name + no_params = False + + if new_data_version != "": + data["new-data-version"] = new_data_version + no_params = False + + if no_params: + raise RuntimeError("At least one new data parameter must be given.") + + r = requests.post(self.url_base + "/data-objects", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/irods_http_client.py b/irods_http_client/irods_http_client.py index 12b9923..10bda02 100644 --- a/irods_http_client/irods_http_client.py +++ b/irods_http_client/irods_http_client.py @@ -1,3 +1,8 @@ +"""Main client module for iRODS HTTP API interactions.""" + +import requests + +from irods_http_client import common from irods_http_client.collection_operations import Collections from irods_http_client.data_object_operations import DataObjects from irods_http_client.query_operations import Queries @@ -6,96 +11,96 @@ from irods_http_client.ticket_operations import Tickets from irods_http_client.user_group_operations import UsersGroups from irods_http_client.zone_operations import Zones -from irods_http_client import common -import logging -import requests - -logger = logging.getLogger(__name__) class IRODSHTTPClient: - def __init__(self, url_base: str): - """Gets the base url from the user to initialize a client instance.""" - self.url_base = url_base - self.token = None - - self.collections = Collections(url_base) - self.data_objects = DataObjects(url_base) - self.queries = Queries(url_base) - self.resources = Resources(url_base) - self.rules = Rules(url_base) - self.tickets = Tickets(url_base) - self.users_groups = UsersGroups(url_base) - self.zones = Zones(url_base) - - def authenticate( - self, username: str = "", password: str = "", openid_token: str = "" - ): - """ - Takes user credentials as parameters and attempts to authenticate and retrieve a token. - - Parameters - - username: The username of the user to be authenticated. - - password: The password of the user to be authenticated. - - Returns - - User token generated by the server. - """ - if not isinstance(username, str): - raise TypeError("username must be a string") - if not isinstance(password, str): - raise TypeError("password must be a string") - if not isinstance(openid_token, str): - raise TypeError("openid_token must be a string") - - if openid_token != "": # TODO: Add openid authentication - return "logged in with openid" - - r = requests.post(self.url_base + "/authenticate", auth=(username, password)) - - if r.status_code / 100 == 2: - if self.token == None: - self.setToken(r.text) - return r.text - else: - raise RuntimeError("Failed to authenticate: " + str(r.status_code)) - - def setToken(self, token: str): - """ - Sets the token to be used when making requests. - - Parameters - - token: The token to be set. - """ - if not isinstance(token, str): - raise TypeError("token must be a string") - self.token = token - - self.collections.token = token - self.data_objects.token = token - self.queries.token = token - self.resources.token = token - self.rules.token = token - self.tickets.token = token - self.users_groups.token = token - self.zones.token = token - - def getToken(self): - """Returns the authentication token currently in use""" - return self.token - - def info(self): - """ - Gives general information about the iRODS server. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - - headers = { - "Authorization": "Bearer " + self.token, - } - - r = requests.get(self.url_base + "/info", headers=headers) - return common.process_response(r) + """HTTP client for interacting with iRODS via REST API.""" + + def __init__(self, url_base: str): + """Get the base url from the user to initialize a client instance.""" + self.url_base = url_base + self.token = None + + self.collections = Collections(url_base) + self.data_objects = DataObjects(url_base) + self.queries = Queries(url_base) + self.resources = Resources(url_base) + self.rules = Rules(url_base) + self.tickets = Tickets(url_base) + self.users_groups = UsersGroups(url_base) + self.zones = Zones(url_base) + + def authenticate(self, username: str = "", password: str = ""): + """ + Take user credentials as parameters and attempts to authenticate and retrieve a token. + + Args: + username: The username of the user to be authenticated. + password: The password of the user to be authenticated. + + Returns: + User token generated by the server. + + Raises: + TypeError: If any parameter is not a string. + RuntimeError: If authentication fails. + """ + if not isinstance(username, str): + raise TypeError("username must be a string") + if not isinstance(password, str): + raise TypeError("password must be a string") + + r = requests.post(self.url_base + "/authenticate", auth=(username, password), timeout=30) + + if r.status_code / 100 == 2: # noqa: PLR2004 + if self.token is None: + self.set_token(r.text) + return r.text + raise RuntimeError("Failed to authenticate: " + str(r.status_code)) + + def set_token(self, token: str): + """ + Set the token to be used when making requests. + + Args: + token: The token to be set. + + Raises: + TypeError: If token is not a string. + """ + if not isinstance(token, str): + raise TypeError("token must be a string") + self.token = token + + self.collections.token = token + self.data_objects.token = token + self.queries.token = token + self.resources.token = token + self.rules.token = token + self.tickets.token = token + self.users_groups.token = token + self.zones.token = token + + def get_token(self): + """ + Return the authentication token currently in use. + + Returns: + The authentication token currently in use. + """ + return self.token + + def info(self): + """ + Give general information about the iRODS server. + + Returns + - A dict containing the HTTP status code and iRODS response. + - The iRODS response is only valid if no error occurred during HTTP communication. + """ + headers = { + "Authorization": "Bearer " + self.token, + } + + r = requests.get(self.url_base + "/info", headers=headers, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/query_operations.py b/irods_http_client/query_operations.py index df4bee5..058f37d 100644 --- a/irods_http_client/query_operations.py +++ b/irods_http_client/query_operations.py @@ -1,175 +1,187 @@ -from . import common +"""Query operations for iRODS HTTP API.""" + import requests -class Queries: +from . import common + - def __init__(self, url_base: str): - """ - Initializes Queries with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def execute_genquery( - self, - query: str, - offset: int = 0, - count: int = -1, - case_sensitive: int = 1, - distinct: int = 1, - parser: str = "genquery1", - sql_only: int = 0, - zone: str = "", - ): - """ - Executes a GenQuery string and returns the results. - - Parameters - - query: The query being executed - - offset (optional): Number of rows to skip. Defaults to 0. - - count (optional): Number of rows to return. Default set by administrator. - - case_sensitive (optional): Set to 1 to execute a case sensitive query, otherwise set to 0. Defaults to 1. Only supported by GenQuery1. - - distinct (optional): Set to 1 to collapse duplicate rows, otherwise set to 0. Defaults to 1. Only supported by GenQuery 1 - - parser (optional): User either genquery1 or genquery2. Defaults to genquery1. - - sql_only (optional): Set to 1 to execute an SQL only query, otherwise set to 0. Defaults to 0. Only supported by GenQuery2. - - zone (optional): The zone name. Defaults ot the local zone. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(query, str) - common.validate_gte_zero(offset) - common.validate_gte_minus1(count) - common.validate_0_or_1(case_sensitive) - common.validate_0_or_1(distinct) - common.validate_instance(parser, str) - if parser not in ["genquery1", "genquery2"]: - raise ValueError("parser must be either 'genquery1' or 'genquery2'") - common.validate_0_or_1(sql_only) - common.validate_instance(zone, str) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = { - "op": "execute_genquery", - "query": query, - "offset": offset, - "parser": parser, - } - - if count != -1: - params["count"] = count - - if zone != "": - params["zone"] = zone - - if parser == "genquery1": - params["case-sensitive"] = case_sensitive - params["distinct"] = distinct - else: - params["sql-only"] = sql_only - - r = requests.get(self.url_base + "/query", headers=headers, params=params) - return common.process_response(r) - - def execute_specific_query( - self, - name: str, - args: str = "", - args_delimiter: str = ",", - offset: int = 0, - count: int = -1, - ): - """ - Executes a specific query and returns the results. - - Parameters - - name: The name of the query to be executed - - args (optional): The arguments to be passed into the query - - args_delimiter (optional): The delimiter to be used to parse the args. Defaults to ','. - - offset (optional): Number of rows to skip. Defaults to 0. - - count (optional): Number of rows to return. Default set by administrator. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(args, str) - common.validate_instance(args_delimiter, str) - common.validate_gte_zero(offset) - common.validate_gte_minus1(count) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = { - "op": "execute_specific_query", - "name": name, - "offset": offset, - "args-delimiter": args_delimiter, - } - - if count != -1: - params["count"] = count - - if args != "": - params["args"] = args - - r = requests.get(self.url_base + "/query", headers=headers, params=params) - return common.process_response(r) - - def add_specific_query(self, name: str, sql: str): - """ - Adds a SpecificQuery to the iRODS zone. - - Parameters - - name: The name of the query to be added. - - sql: The SQL attached to the query. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(sql, str) - - headers = { - "Authorization": "Bearer " + self.token, - } - - data = {"op": "add_specific_query", "name": name, "sql": sql} - - r = requests.post(self.url_base + "/query", headers=headers, data=data) - return common.process_response(r) - - def remove_specific_query(self, name): - """ - Removes a SpecificQuery from the iRODS zone. - - Parameters - - name: The name of the SpecificQuery to be removed - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - } - - data = {"op": "remove_specific_query", "name": name} - - r = requests.post(self.url_base + "/query", headers=headers, data=data) - return common.process_response(r) +class Queries: + """Perform query operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize Queries with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def execute_genquery( + self, + query: str, + offset: int = 0, + count: int = -1, + case_sensitive: int = 1, + distinct: int = 1, + parser: str = "genquery1", + sql_only: int = 0, + zone: str = "", + ): + """ + Execute a GenQuery string and returns the results. + + Args: + query: The query being executed. + offset: Number of rows to skip. Defaults to 0. + count: Number of rows to return. Default set by administrator. + case_sensitive: Set to 1 to execute a case sensitive query, otherwise + set to 0. Defaults to 1. Only supported by GenQuery1. + distinct: Set to 1 to collapse duplicate rows, otherwise set to 0. + Defaults to 1. Only supported by GenQuery 1. + parser: User either genquery1 or genquery2. Defaults to genquery1. + sql_only: Set to 1 to execute an SQL only query, otherwise set to 0. + Defaults to 0. Only supported by GenQuery2. + zone: The zone name. Defaults to the local zone. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If parser is not 'genquery1' or 'genquery2'. + """ + common.check_token(self.token) + common.validate_instance(query, str) + common.validate_gte_zero(offset) + common.validate_gte_minus1(count) + common.validate_0_or_1(case_sensitive) + common.validate_0_or_1(distinct) + common.validate_instance(parser, str) + if parser not in ["genquery1", "genquery2"]: + raise ValueError("parser must be either 'genquery1' or 'genquery2'") + common.validate_0_or_1(sql_only) + common.validate_instance(zone, str) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = { + "op": "execute_genquery", + "query": query, + "offset": offset, + "parser": parser, + } + + if count != -1: + params["count"] = count + + if zone != "": + params["zone"] = zone + + if parser == "genquery1": + params["case-sensitive"] = case_sensitive + params["distinct"] = distinct + else: + params["sql-only"] = sql_only + + r = requests.get(self.url_base + "/query", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def execute_specific_query( + self, + name: str, + args: str = "", + args_delimiter: str = ",", + offset: int = 0, + count: int = -1, + ): + """ + Execute a specific query and returns the results. + + Args: + name: The name of the query to be executed. + args: The arguments to be passed into the query. + args_delimiter: The delimiter to be used to parse the args. Defaults to ','. + offset: Number of rows to skip. Defaults to 0. + count: Number of rows to return. Default set by administrator. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(args, str) + common.validate_instance(args_delimiter, str) + common.validate_gte_zero(offset) + common.validate_gte_minus1(count) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = { + "op": "execute_specific_query", + "name": name, + "offset": offset, + "args-delimiter": args_delimiter, + } + + if count != -1: + params["count"] = count + + if args != "": + params["args"] = args + + r = requests.get(self.url_base + "/query", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def add_specific_query(self, name: str, sql: str): + """ + Add a SpecificQuery to the iRODS zone. + + Args: + name: The name of the query to be added. + sql: The SQL attached to the query. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(sql, str) + + headers = { + "Authorization": "Bearer " + self.token, + } + + data = {"op": "add_specific_query", "name": name, "sql": sql} + + r = requests.post(self.url_base + "/query", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove_specific_query(self, name): + """ + Remove a SpecificQuery from the iRODS zone. + + Args: + name: The name of the SpecificQuery to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + } + + data = {"op": "remove_specific_query", "name": name} + + r = requests.post(self.url_base + "/query", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/resource_operations.py b/irods_http_client/resource_operations.py index 7f4bf7a..9243371 100644 --- a/irods_http_client/resource_operations.py +++ b/irods_http_client/resource_operations.py @@ -1,267 +1,280 @@ -from . import common +"""Resource operations for iRODS HTTP API.""" + import json + import requests -class Resources: +from . import common + - def __init__(self, url_base: str): - """ - Initializes DataObjects with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def create(self, name: str, type: str, host: str, vault_path: str, context: str): - """ - Creates a new resource. - - Parameters - - name: The name of the resource to be created. - - type: The type of the resource to be created. - - host: The host of the resource to be created. May or may not be required depending on the resource type. - - vault_path: Path to the storage vault for the resource. May or may not be required depending on the resource type. - - context: May or may not be required depending on the resource type. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(type, str) - common.validate_instance(host, str) - common.validate_instance(vault_path, str) - common.validate_instance(context, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "create", "name": name, "type": type} - - if host != "": - data["host"] = host - - if vault_path != "": - data["vault-path"] = vault_path - - if context != "": - data["context"] = context - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) - - def remove(self, name: str): - """ - Removes an existing resource. - - Parameters - - name: The name of the resource to be removed. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove", "name": name} - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) - - def modify(self, name: str, property: str, value: str): - """ - Modifies a property for a resource. - - Parameters - - name: The name of the resource to be modified. - - property: The property to be modified. - - value: The new value to be set. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(property, str) - if property not in [ - "name", - "type", - "host", - "vault_path", - "context", - "status", - "free_space", - "comments", - "information", - ]: - raise ValueError( - "Invalid property. Valid properties:\n - name\n - type\n - host\n - " - "vault_path\n - context" - + "\n - status\n - free_space\n - comments\n - information" - ) - common.validate_instance(value, str) - if (property == "status") and (value != "up") and (value != "down"): - raise ValueError("status must be either 'up' or 'down'") - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "modify", "name": name, "property": property, "value": value} - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) - - def add_child(self, parent_name: str, child_name: str, context: str = ""): - """ - Creates a parent-child relationship between two resources. - - Parameters - - parent_name: The name of the parent resource. - - child_name: The name of the child resource. - - context (optional): Additional information for the zone. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(parent_name, str) - common.validate_instance(child_name, str) - common.validate_instance(context, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "add_child", "parent-name": parent_name, "child-name": child_name} - - if context != "": - data["context"] = context - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) - - def remove_child(self, parent_name: str, child_name: str): - """ - Removes a parent-child relationship between two resources. - - Parameters - - parent_name: The name of the parent resource. - - child_name: The name of the child resource. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(parent_name, str) - common.validate_instance(child_name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "remove_child", - "parent-name": parent_name, - "child-name": child_name, - } - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) - - def rebalance(self, name: str): - """ - Rebalances a resource hierarchy. - - Parameters - - name: The name of the resource to be rebalanced. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "rebalance", "name": name} - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) - - def stat(self, name: str): - """ - Retrieves information for a resource. - - Parameters - - name: The name of the resource to be accessed. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - params = {"op": "stat", "name": name} - - r = requests.get(self.url_base + "/resources", headers=headers, params=params) - return common.process_response(r) - - def modify_metadata(self, name: str, operations: dict, admin: int = 0): - """ - Modifies the metadata for a resource. - - Parameters - - name: The absolute logical path of the resource to have its metadata modified. - - operations: Dictionary containing the operations to carry out. Should contain the operation, attribute, value, and optionally units. - - admin (optional): Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(operations, list) - common.validate_instance(operations[0], dict) - common.validate_0_or_1(admin) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "modify_metadata", - "name": name, - "operations": json.dumps(operations), - "admin": admin, - } - - r = requests.post(self.url_base + "/resources", headers=headers, data=data) - return common.process_response(r) +class Resources: + """Perform resource operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize DataObjects with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def create(self, name: str, type_: str, host: str, vault_path: str, context: str): + """ + Create a new resource. + + Args: + name: The name of the resource to be created. + type_: The type of the resource to be created. + host: The host of the resource to be created. May or may not be required depending + on the resource type. + vault_path: Path to the storage vault for the resource. May or may not be required + depending on the resource type. + context: May or may not be required depending on the resource type. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(type_, str) + common.validate_instance(host, str) + common.validate_instance(vault_path, str) + common.validate_instance(context, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "create", "name": name, "type": type_} + + if host != "": + data["host"] = host + + if vault_path != "": + data["vault-path"] = vault_path + + if context != "": + data["context"] = context + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove(self, name: str): + """ + Remove an existing resource. + + Args: + name: The name of the resource to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove", "name": name} + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify(self, name: str, property_: str, value: str): + """ + Modify a property for a resource. + + Args: + name: The name of the resource to be modified. + property_: The property to be modified. + value: The new value to be set. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If property_ is not a valid resource property. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(property_, str) + if property_ not in [ + "name", + "type", + "host", + "vault_path", + "context", + "status", + "free_space", + "comments", + "information", + ]: + raise ValueError( + "Invalid property. Valid properties:\n - name\n - type\n - host\n - " + "vault_path\n - context" + "\n - status\n - free_space\n - comments\n - information" + ) + common.validate_instance(value, str) + if (property_ == "status") and (value not in ["up", "down"]): + raise ValueError("status must be either 'up' or 'down'") + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "modify", "name": name, "property": property_, "value": value} + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def add_child(self, parent_name: str, child_name: str, context: str = ""): + """ + Create a parent-child relationship between two resources. + + Args: + parent_name: The name of the parent resource. + child_name: The name of the child resource. + context: Additional information for the zone. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(parent_name, str) + common.validate_instance(child_name, str) + common.validate_instance(context, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "add_child", "parent-name": parent_name, "child-name": child_name} + + if context != "": + data["context"] = context + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove_child(self, parent_name: str, child_name: str): + """ + Remove a parent-child relationship between two resources. + + Args: + parent_name: The name of the parent resource. + child_name: The name of the child resource. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(parent_name, str) + common.validate_instance(child_name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "remove_child", + "parent-name": parent_name, + "child-name": child_name, + } + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def rebalance(self, name: str): + """ + Rebalance a resource hierarchy. + + Args: + name: The name of the resource to be rebalanced. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "rebalance", "name": name} + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def stat(self, name: str): + """ + Retrieve information for a resource. + + Args: + name: The name of the resource to be accessed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + params = {"op": "stat", "name": name} + + r = requests.get(self.url_base + "/resources", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def modify_metadata(self, name: str, operations: dict, admin: int = 0): + """ + Modify the metadata for a resource. + + Args: + name: The absolute logical path of the resource to have its metadata modified. + operations: Dictionary containing the operations to carry out. Should contain the + operation, attribute, value, and optionally units. + admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + common.validate_0_or_1(admin) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "modify_metadata", + "name": name, + "operations": json.dumps(operations), + "admin": admin, + } + + r = requests.post(self.url_base + "/resources", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/rule_operations.py b/irods_http_client/rule_operations.py index 4964ed5..9f53e33 100644 --- a/irods_http_client/rule_operations.py +++ b/irods_http_client/rule_operations.py @@ -1,84 +1,90 @@ -from . import common +"""Rule operations for iRODS HTTP API.""" + import requests +from . import common + + class Rules: + """Perform rule operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize Rules with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def list_rule_engines(self): + """ + List available rule engine plugin instances. + + Returns + - A dict containing the HTTP status code and iRODS response. + - The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = {"op": "list_rule_engines"} + + r = requests.get(self.url_base + "/rules", params=params, headers=headers, timeout=30) + return common.process_response(r) + + def execute(self, rule_text: str, rep_instance: str = ""): + """ + Execute rule code. + + Args: + rule_text: The rule code to execute. + rep_instance: The rule engine plugin to run the rule-text against. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(rule_text, str) + common.validate_instance(rep_instance, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "execute", "rule-text": rule_text} + + if rep_instance != "": + data["rep-instance"] = rep_instance + + r = requests.post(self.url_base + "/rules", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove_delay_rule(self, rule_id: int): + """ + Remove a delay rule from the catalog. + + Args: + rule_id: The id of the delay rule to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_gte_zero(rule_id) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove_delay_rule", "rule-id": rule_id} - def __init__(self, url_base: str): - """ - Initializes Rules with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def list_rule_engines(self): - """ - Lists available rule engine plugin instances. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = {"op": "list_rule_engines"} - - r = requests.get(self.url_base + "/rules", params=params, headers=headers) - return common.process_response(r) - - def execute(self, rule_text: str, rep_instance: str = ""): - """ - Executes rule code. - - Parameters - - rule_text: The rule code to execute. - - rep_instance (optional): The rule engine plugin to run the rule-text against. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(rule_text, str) - common.validate_instance(rep_instance, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "execute", "rule-text": rule_text} - - if rep_instance != "": - data["rep-instance"] = rep_instance - - r = requests.post(self.url_base + "/rules", headers=headers, data=data) - return common.process_response(r) - - def remove_delay_rule(self, rule_id: int): - """ - Removes a delay rule from the catalog. - - Parameters - - rule_id: The id of the delay rule to be removed. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_gte_zero(rule_id) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove_delay_rule", "rule-id": rule_id} - - r = requests.post(self.url_base + "/rules", headers=headers, data=data) - return common.process_response(r) + r = requests.post(self.url_base + "/rules", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/ticket_operations.py b/irods_http_client/ticket_operations.py index 70d2cca..16799af 100644 --- a/irods_http_client/ticket_operations.py +++ b/irods_http_client/ticket_operations.py @@ -1,104 +1,113 @@ -from . import common +"""Ticket operations for iRODS HTTP API.""" + import requests +from . import common + + class Tickets: + """Perform ticket operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize Tickets with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def create( + self, + lpath: str, + type_: str = "read", + use_count: int = -1, + write_data_object_count: int = -1, + write_byte_count: int = -1, + seconds_until_expiration: int = -1, + users: str = "", + groups: str = "", + hosts: str = "", + ): + """ + Create a new ticket for a collection or data object. + + Args: + lpath: Absolute logical path to a data object or collection. + type_: Read or write. Defaults to read. + use_count: Number of times the ticket can be used. + write_data_object_count: Max number of writes that can be performed. + write_byte_count: Max number of bytes that can be written. + seconds_until_expiration: Number of seconds before the ticket expires. + users: Comma-delimited list of users allowed to use the ticket. + groups: Comma-delimited list of groups allowed to use the ticket. + hosts: Comma-delimited list of hosts allowed to use the ticket. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If type_ is not 'read' or 'write'. + """ + common.check_token(self.token) + common.validate_instance(lpath, str) + common.validate_instance(type_, str) + if type_ not in ["read", "write"]: + raise ValueError("type must be either read or write") + common.validate_gte_minus1(use_count) + common.validate_gte_minus1(write_data_object_count) + common.validate_gte_minus1(write_byte_count) + common.validate_gte_minus1(seconds_until_expiration) + common.validate_instance(users, str) + common.validate_instance(groups, str) + common.validate_instance(hosts, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "create", "lpath": lpath, "type": type_} + + if use_count != -1: + data["use-count"] = use_count + if write_data_object_count != -1: + data["write-data-object-count"] = write_data_object_count + if write_byte_count != -1: + data["write-byte-count"] = write_byte_count + if seconds_until_expiration != -1: + data["seconds-until-expiration"] = seconds_until_expiration + if users != "": + data["users"] = users + if groups != "": + data["groups"] = groups + if hosts != "": + data["hosts"] = hosts + + r = requests.post(self.url_base + "/tickets", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove(self, name: str): + """ + Remove an existing ticket. + + Args: + name: The ticket to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove", "name": name} - def __init__(self, url_base: str): - """ - Initializes Tickets with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def create( - self, - lpath: str, - type: str = "read", - use_count: int = -1, - write_data_object_count: int = -1, - write_byte_count: int = -1, - seconds_until_expiration: int = -1, - users: str = "", - groups: str = "", - hosts: str = "", - ): - """ - Creates a new ticket for a collection or data object. - - Parameters - - lpath: Absolute logical path to a data object or collection. - - type (optional): Read or write. Defaults to read. - - use_count (optional): Number of times the ticket can be used. - - write_data_object_count (optional): Max number of writes that can be performed. - - write_byte_count (optional): Max number of bytes that can be written. - - seconds_until_expiration (optional): Number of seconds before the ticket expires. - - users (optional): Comma-delimited list of users allowed to use the ticket. - - groups (optional): Comma-delimited list of groups allowed to use the ticket. - - hosts (optional): Comma-delimited list of hosts allowed to use the ticket. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(lpath, str) - common.validate_instance(type, str) - if type not in ["read", "write"]: - raise ValueError("type must be either read or write") - common.validate_gte_minus1(use_count) - common.validate_gte_minus1(write_data_object_count) - common.validate_gte_minus1(write_byte_count) - common.validate_gte_minus1(seconds_until_expiration) - common.validate_instance(users, str) - common.validate_instance(groups, str) - common.validate_instance(hosts, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "create", "lpath": lpath, "type": type} - - if use_count != -1: - data["use-count"] = use_count - if write_data_object_count != -1: - data["write-data-object-count"] = write_data_object_count - if write_byte_count != -1: - data["write-byte-count"] = write_byte_count - if seconds_until_expiration != -1: - data["seconds-until-expiration"] = seconds_until_expiration - if users != "": - data["users"] = users - if groups != "": - data["groups"] = groups - if hosts != "": - data["hosts"] = hosts - - r = requests.post(self.url_base + "/tickets", headers=headers, data=data) - return common.process_response(r) - - def remove(self, name: str): - """ - Removes an existing ticket. - - Parameters - - name: The ticket to be removed. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove", "name": name} - - r = requests.post(self.url_base + "/tickets", headers=headers, data=data) - return common.process_response(r) + r = requests.post(self.url_base + "/tickets", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/user_group_operations.py b/irods_http_client/user_group_operations.py index f279628..f5812cf 100644 --- a/irods_http_client/user_group_operations.py +++ b/irods_http_client/user_group_operations.py @@ -1,380 +1,380 @@ -from . import common +"""User and group operations for iRODS HTTP API.""" + import json + import requests +from . import common + + class UsersGroups: - def __init__(self, url_base: str): - """ - Initializes UsersGroups with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def create_user(self, name: str, zone: str, user_type: str = "rodsuser"): - """ - Creates a new user. Requires rodsadmin or groupadmin privileges. - - Parameters - - name: The name of the user to be created. - - zone: The zone for the user to be created. - - user_type (optional): Can be rodsuser, groupadmin, or rodsadmin. Defaults to rodsuser. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(zone, str) - common.validate_instance(user_type, str) - if user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: - raise ValueError( - "user_type must be set to rodsuser, groupadmin, or rodsadmin." - ) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "create_user", "name": name, "zone": zone, "user-type": user_type} - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def remove_user(self, name: str, zone: str): - """ - Removes a user. Requires rodsadmin privileges. - - Parameters - - name: The name of the user to be removed. - - zone: The zone for the user to be removed. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(zone, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove_user", "name": name, "zone": zone} - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def set_password(self, name: str, zone: str, new_password: str = ""): - """ - Changes a users password. Requires rodsadmin privileges. - - Parameters - - name: The name of the user to have their password changed. - - zone: The zone for the user to have their password changed. - - new_password: The new password to set for the user. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(zone, str) - common.validate_instance(new_password, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "set_password", - "name": name, - "zone": zone, - "new-password": new_password, - } - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def set_user_type(self, name: str, zone: str, user_type: str): - """ - Changes a users type. Requires rodsadmin privileges. - - Parameters - - name: The name of the user to have their type updated. - - zone: The zone for the user to have their type updated. - - user_type: Can be rodsuser, groupadmin, or rodsadmin. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(zone, str) - common.validate_instance(user_type, str) - if user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: - raise ValueError( - "user_type must be set to rodsuser, groupadmin, or rodsadmin." - ) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "set_user_type", - "name": name, - "zone": zone, - "new-user-type": user_type, - } - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def create_group(self, name: str): - """ - Creates a new group. Requires rodsadmin or groupadmin privileges. - - Parameters - - name: The name of the group to be created. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "create_group", "name": name} - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def remove_group(self, name: str): - """ - Removes a group. Requires rodsadmin privileges. - - Parameters - - name: The name of the group to be removed. - - Parameters - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove_group", "name": name} - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def add_to_group(self, user: str, zone: str, group: str = ""): - """ - Adds a user to a group. Requires rodsadmin or groupadmin privileges. - - Parameters - - user: The user to be added to the group. - - zone: The zone for the user to be added to the group. - - group: The group for the user to be added to. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(user, str) - common.validate_instance(zone, str) - common.validate_instance(group, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "add_to_group", "user": user, "zone": zone, "group": group} - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def remove_from_group(self, user: str, zone: str, group: str): - """ - Removes a user from a group. Requires rodsadmin or groupadmin privileges. - - Parameters - - user: The user to be removed from the group. - - zone: The zone for the user to be removed from the group. - - group: The group for the user to be removed from. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(user, str) - common.validate_instance(zone, str) - common.validate_instance(group, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove_from_group", "user": user, "zone": zone, "group": group} - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) - - def users(self): - """ - Lists all users in the zone. Requires rodsadmin privileges. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - - headers = {"Authorization": "Bearer " + self.token} - - params = {"op": "users"} - - r = requests.get( - self.url_base + "/users-groups", headers=headers, params=params - ) - return common.process_response(r) - - def groups(self): - """ - Lists all groups in the zone. Requires rodsadmin privileges. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - - headers = { - "Authorization": "Bearer " + self.token, - } - - params = {"op": "groups"} - - r = requests.get( - self.url_base + "/users-groups", headers=headers, params=params - ) - return common.process_response(r) - - def is_member_of_group(self, group: str, user: str, zone: str): - """ - Returns whether a user is a member of a group or not. - - Parameters - group: The group being checked. - user: The user being checked. - zone: The zone for the user being checked. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(group, str) - common.validate_instance(user, str) - common.validate_instance(zone, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - params = { - "op": "is_member_of_group", - "group": group, - "user": user, - "zone": zone, - } - - r = requests.get( - self.url_base + "/users-groups", headers=headers, params=params - ) - return common.process_response(r) - - def stat(self, name: str, zone: str = ""): - """ - Returns information about a user or group. - - Parameters - - name: The name of the user or group to be accessed. - - zone: The zone of the user to be accessed. Not required for groups. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(zone, str) - - headers = {"Authorization": "Bearer " + self.token} - - params = {"op": "stat", "name": name} - - if zone != "": - params["zone"] = zone - - r = requests.get( - self.url_base + "/users-groups", headers=headers, params=params - ) - return common.process_response(r) - - def modify_metadata(self, name: str, operations: list): - """ - Modifies the metadata for a user or group. Requires rodsadmin privileges. - - Parameters - - name: The user or group to be modified. - - operations: The operations to be carried out. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(operations, list) - common.validate_instance(operations[0], dict) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = { - "op": "modify_metadata", - "name": name, - "operations": json.dumps(operations), - } - - r = requests.post(self.url_base + "/users-groups", headers=headers, data=data) - return common.process_response(r) + """Perform user and group operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize UsersGroups with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def create_user(self, name: str, zone: str, user_type: str = "rodsuser"): + """ + Create a new user. Requires rodsadmin or groupadmin privileges. + + Args: + name: The name of the user to be created. + zone: The zone for the user to be created. + user_type: Can be rodsuser, groupadmin, or rodsadmin. Defaults to rodsuser. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If user_type is not 'rodsuser', 'groupadmin', or 'rodsadmin'. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + common.validate_instance(user_type, str) + if user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: + raise ValueError("user_type must be set to rodsuser, groupadmin, or rodsadmin.") + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "create_user", "name": name, "zone": zone, "user-type": user_type} + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove_user(self, name: str, zone: str): + """ + Remove a user. Requires rodsadmin privileges. + + Args: + name: The name of the user to be removed. + zone: The zone for the user to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove_user", "name": name, "zone": zone} + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def set_password(self, name: str, zone: str, new_password: str = ""): + """ + Change a users password. Requires rodsadmin privileges. + + Args: + name: The name of the user to have their password changed. + zone: The zone for the user to have their password changed. + new_password: The new password to set for the user. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + common.validate_instance(new_password, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "set_password", + "name": name, + "zone": zone, + "new-password": new_password, + } + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def set_user_type(self, name: str, zone: str, user_type: str): + """ + Change a users type. Requires rodsadmin privileges. + + Args: + name: The name of the user to have their type updated. + zone: The zone for the user to have their type updated. + user_type: Can be rodsuser, groupadmin, or rodsadmin. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + + Raises: + ValueError: If user_type is not 'rodsuser', 'groupadmin', or 'rodsadmin'. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + common.validate_instance(user_type, str) + if user_type not in ["rodsuser", "groupadmin", "rodsadmin"]: + raise ValueError("user_type must be set to rodsuser, groupadmin, or rodsadmin.") + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "set_user_type", + "name": name, + "zone": zone, + "new-user-type": user_type, + } + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def create_group(self, name: str): + """ + Create a new group. Requires rodsadmin or groupadmin privileges. + + Args: + name: The name of the group to be created. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "create_group", "name": name} + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove_group(self, name: str): + """ + Remove a group. Requires rodsadmin privileges. + + Args: + name: The name of the group to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove_group", "name": name} + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def add_to_group(self, user: str, zone: str, group: str = ""): + """ + Add a user to a group. Requires rodsadmin or groupadmin privileges. + + Args: + user: The user to be added to the group. + zone: The zone for the user to be added to the group. + group: The group for the user to be added to. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(user, str) + common.validate_instance(zone, str) + common.validate_instance(group, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "add_to_group", "user": user, "zone": zone, "group": group} + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove_from_group(self, user: str, zone: str, group: str): + """ + Remove a user from a group. Requires rodsadmin or groupadmin privileges. + + Args: + user: The user to be removed from the group. + zone: The zone for the user to be removed from the group. + group: The group for the user to be removed from. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(user, str) + common.validate_instance(zone, str) + common.validate_instance(group, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove_from_group", "user": user, "zone": zone, "group": group} + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def users(self): + """ + List all users in the zone. Requires rodsadmin privileges. + + Returns + - A dict containing the HTTP status code and iRODS response. + - The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + + headers = {"Authorization": "Bearer " + self.token} + + params = {"op": "users"} + + r = requests.get(self.url_base + "/users-groups", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def groups(self): + """ + List all groups in the zone. Requires rodsadmin privileges. + + Returns + - A dict containing the HTTP status code and iRODS response. + - The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + + headers = { + "Authorization": "Bearer " + self.token, + } + + params = {"op": "groups"} + + r = requests.get(self.url_base + "/users-groups", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def is_member_of_group(self, group: str, user: str, zone: str): + """ + Return whether a user is a member of a group or not. + + Args: + group: The group being checked. + user: The user being checked. + zone: The zone for the user being checked. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(group, str) + common.validate_instance(user, str) + common.validate_instance(zone, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + params = { + "op": "is_member_of_group", + "group": group, + "user": user, + "zone": zone, + } + + r = requests.get(self.url_base + "/users-groups", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def stat(self, name: str, zone: str = ""): + """ + Return information about a user or group. + + Args: + name: The name of the user or group to be accessed. + zone: The zone of the user to be accessed. Not required for groups. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(zone, str) + + headers = {"Authorization": "Bearer " + self.token} + + params = {"op": "stat", "name": name} + + if zone != "": + params["zone"] = zone + + r = requests.get(self.url_base + "/users-groups", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def modify_metadata(self, name: str, operations: list): + """ + Modify the metadata for a user or group. Requires rodsadmin privileges. + + Args: + name: The user or group to be modified. + operations: The operations to be carried out. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(operations, list) + common.validate_instance(operations[0], dict) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = { + "op": "modify_metadata", + "name": name, + "operations": json.dumps(operations), + } + + r = requests.post(self.url_base + "/users-groups", headers=headers, data=data, timeout=30) + return common.process_response(r) diff --git a/irods_http_client/zone_operations.py b/irods_http_client/zone_operations.py index c349d34..087e385 100644 --- a/irods_http_client/zone_operations.py +++ b/irods_http_client/zone_operations.py @@ -1,138 +1,145 @@ -from . import common +"""Zone operations for iRODS HTTP API.""" + import requests +from . import common + + class Zones: - def __init__(self, url_base: str): - """ - Initializes Zones with a base url. - Token is set to None initially, and updated when setToken() is called in irodsClient. - """ - self.url_base = url_base - self.token = None - - def add(self, name: str, connection_info: str = "", comment: str = ""): - """ - Adds a remote zone to the local zone. Requires rodsadmin privileges. - - Parameters - - name: The name of the zone to be added. - - connection_info (optional): The host and port to connect to. If included, must be in the format : - - comment (optional): The comment to attach to the zone. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(connection_info, str) - common.validate_instance(comment, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "add", "name": name} - - if connection_info != "": - data["connection-info"] = connection_info - if comment != "": - data["comment"] = comment - - r = requests.post(self.url_base + "/zones", headers=headers, data=data) - return common.process_response(r) - - def remove(self, name: str): - """ - Removes a remote zone from the local zone. Requires rodsadmin privileges. - - Parameters - - name: The zone to be removed - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "remove", "name": name} - - r = requests.post(self.url_base + "/zones", headers=headers, data=data) - return common.process_response(r) - - def modify(self, name: str, property: str, value: str): - """ - Modifies properties of a remote zone. Requires rodsadmin privileges. - - Parameters - - name: The name of the zone to be modified. - - property: The property to be modified. Can be set to 'name', 'connection_info', or 'comment'. - The value for 'connection_info' must be in the format :. - - value: The new value to be set. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - common.validate_instance(property, str) - common.validate_instance(value, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - data = {"op": "modify", "name": name, "property": property, "value": value} - - r = requests.post(self.url_base + "/zones", headers=headers, data=data) - return common.process_response(r) - - def report(self): - """ - Returns information about the iRODS zone. Requires rodsadmin privileges. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - params = {"op": "report"} - - r = requests.get(self.url_base + "/zones", headers=headers, params=params) - return common.process_response(r) - - def stat(self, name: str): - """ - Returns information about a named iRODS zone. Requires rodsadmin privileges. - - Returns - - A dict containing the HTTP status code and iRODS response. - - The iRODS response is only valid if no error occurred during HTTP communication. - """ - common.check_token(self.token) - common.validate_instance(name, str) - - headers = { - "Authorization": "Bearer " + self.token, - "Content-Type": "application/x-www-form-urlencoded", - } - - params = {"op": "stat", "name": name} - - r = requests.get(self.url_base + "/zones", headers=headers, params=params) - return common.process_response(r) + """Perform zone operations via iRODS HTTP API.""" + + def __init__(self, url_base: str): + """ + Initialize Zones with a base url. + + Token is set to None initially, and updated when setToken() is called in irodsClient. + """ + self.url_base = url_base + self.token = None + + def add(self, name: str, connection_info: str = "", comment: str = ""): + """ + Add a remote zone to the local zone. Requires rodsadmin privileges. + + Args: + name: The name of the zone to be added. + connection_info: The host and port to connect to. If included, must be in the format :. + comment: The comment to attach to the zone. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(connection_info, str) + common.validate_instance(comment, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "add", "name": name} + + if connection_info != "": + data["connection-info"] = connection_info + if comment != "": + data["comment"] = comment + + r = requests.post(self.url_base + "/zones", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def remove(self, name: str): + """ + Remove a remote zone from the local zone. Requires rodsadmin privileges. + + Args: + name: The zone to be removed. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "remove", "name": name} + + r = requests.post(self.url_base + "/zones", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def modify(self, name: str, property_: str, value: str): + """ + Modify properties of a remote zone. Requires rodsadmin privileges. + + Args: + name: The name of the zone to be modified. + property_: The property to be modified. Can be set to 'name', 'connection_info', or 'comment'. + The value for 'connection_info' must be in the format :. + value: The new value to be set. + + Returns: + A dict containing the HTTP status code and iRODS response. + The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + common.validate_instance(property_, str) + common.validate_instance(value, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + data = {"op": "modify", "name": name, "property": property_, "value": value} + + r = requests.post(self.url_base + "/zones", headers=headers, data=data, timeout=30) + return common.process_response(r) + + def report(self): + """ + Return information about the iRODS zone. Requires rodsadmin privileges. + + Returns + - A dict containing the HTTP status code and iRODS response. + - The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + params = {"op": "report"} + + r = requests.get(self.url_base + "/zones", headers=headers, params=params, timeout=30) + return common.process_response(r) + + def stat(self, name: str): + """ + Return information about a named iRODS zone. Requires rodsadmin privileges. + + Returns + - A dict containing the HTTP status code and iRODS response. + - The iRODS response is only valid if no error occurred during HTTP communication. + """ + common.check_token(self.token) + common.validate_instance(name, str) + + headers = { + "Authorization": "Bearer " + self.token, + "Content-Type": "application/x-www-form-urlencoded", + } + + params = {"op": "stat", "name": name} + + r = requests.get(self.url_base + "/zones", headers=headers, params=params, timeout=30) + return common.process_response(r) diff --git a/pyproject.toml b/pyproject.toml index 386bea3..5bc6f5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,8 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/irods/irods_client_http_python" -"Bug Tracker" = "https://github.com/irods/irods_client_http_python/issues" +Repository = "https://github.com/irods/irods_client_http_python" +Issues = "https://github.com/irods/irods_client_http_python/issues" [build-system] requires = [ @@ -24,3 +24,480 @@ requires = [ "setuptools >= 40.9.0" ] build-backend = "setuptools.build_meta" + +[tool.ruff] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Ensure this matches tool.ruff.lint.pycodestyle.max-doc-length +line-length = 120 +indent-width = 4 + +[tool.ruff.format] +quote-style = "preserve" +indent-style = "tab" +line-ending = "auto" + +# Enable reformatting of code snippets in docstrings. +docstring-code-format = true + +# Respect magic trailing commas. +skip-magic-trailing-comma = false + +[tool.ruff.lint] +# Enable preview rules, but require them to be explicitly enabled +preview = true +explicit-preview-rules = true +# Enable rules +select = [ + # eradicate + "ERA", + # flake8-2020 + "YTT", + # flake8-annotations, uncomment if we ever add type annotations + #"ANN", + # flake8-executable + "EXE", + # flake8-async + "ASYNC", + # flake8-blind-except + "BLE", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-datetimez + "DTZ", + # flake8-executable + "EXE", + # flake8-future-annotations + "FA", + # flake8-implicit-str-concat + "ISC", + # flake8-import-conventions + "ICN", + # flake8-logging + "LOG", + # flake8-logging-format + "G", + # flake8-no-pep420 + "INP", + # flake8-pie + "PIE", + # flake8-pyi + "PYI", + # flake8-raise + "RSE", + # flake8-return + "RET", + # flake8-self + "SLF", + # flake8-simplify + "SIM", + # flake8-slots + "SLOT", + # flake8-tidy-imports + "TID", + # flake8-todos + "TD", + # flake8-type-checking + "TC", + # flake8-unused-arguments + "ARG", + # flake8-use-pathlib + "PTH", + # flynt + "FLY", + # isort + "I", + # pep8-naming + "N", + # Perflint + "PERF", + # pycodestyle + "E", "W", + # pydoclint + "DOC", + # pydocstyle, flake8-docstrings + "D", + # Pyflakes + "F", + # pygrep-hooks + "PGH", + # Pylint + "PL", + # pyupgrade + "UP", + # refurb + "FURB", + # Ruff-specific rules + "RUF", + # tryceratops + "TRY", + + ## flake8-bandit + ## We want a subset and it's easier to handle some here than in ignore + # S1??: general tests + "S1", + # S2??: misconfiguration + "S2", + # S301: suspicious pickle usage + "S301", + # S306: usage of insecure and deprecated mktemp + "S306", + # S307: usage of insecure eval + "S307", + # S311: usage of bad rand + "S311", + # S313-S319: usage of insecure XML parsing + "S313", "S314", "S315", "S316", "S317", "S318", "S319", + # S5??: cryptography + "S5", + # shell usage + "S602", "S604", "S605", + # S612: + ## flake8-commas + ## We only want one rule from this one + # COM818: trailing comman on bare tuple prohibited + "COM818", + ## flake8-quotes + ## We don't want most rules from this one, as quotes are mostly handled by the formatter + # Q004: Unnecessary escape on inner quote character + "Q004", + ## pydocstyle + ## Certain rules are disabled by tool.ruff.lint.pydocstyle.convention, but we still want some of them + # D213: Multi-line docstring summary should start at the second line + "D213", + # D214: Overindented section + "D214", + # D215: Overindented section underline + "D215", + # D404: First word of the docstring should not be "This" + "D404", + # D405: Section name should be properly capitalized + "D405", + # D406: Missing newline after section name + "D406", + # D409: Mismatched section underline length + "D409", + # D416: Missing colon after section name + "D416", + # D417: Missing parameter documentation + "D417", + + ### preview rules + ## pydoclint + # DOC102: undocumented parameter missing from signature + "DOC102", + # DOC201: undocumented return value + "DOC201", + # DOC202: docstring has returns section, but function/method does not return anything + "DOC202", + # DOC402: undocumented yield value + "DOC402", + # DOC403: docstring has yields section, but function/method does not yield anything + "DOC403", + # DOC501: undocumented raised exception + "DOC501", +] +# Disable rules +ignore = [ + # Not compatible with indent-style: tab + "D206", + + # false positives galore + "ERA001", + + ## flake8-datetimez + # DTZ003: datetime.datetime.utcnow() used + "DTZ003", + + ## flake8-tidy-imports + # TID252: Prefer absolute imports over relative imports + # This can introduce difficulties + "TID252", + + ## flake8-todos + # TD002: Missing author in TODO + "TD002", + + ## pycodestyle + # E402: Module level import not at top of file + # We often need to do some checking before importing stuff + "E402", + # Indentation/alignment/spacing stuff + # E128: continuation line under-indented for visual indent + # Don't hate me cuz I'm pretty. + # Uncomment if ruff ever supports this + #"E128", + # Most indentation handled by formatter + "E101", # indentation contains mixed spaces and tabs + "E111", # wrong indentation multiple + "E114", # wrong indentation multiple (for comments) + "E117", # over-indentation + "W191", # indentation includes tabs + + ## pydoclint + # DOC502: docstring specifies potential exception that is not actually raised in the fucntion/method body + # Does not account for exceptions from other sources + "DOC502", + + ## pylint + # PLC0415: import should be at top-level + # plenty of reasons to put it in a block + "PLC0415", + # PLC1901: comparison to empty string + # Does not account for types + "PLC1901", + # PLE0116: continue in finally block + # Supported since Python 3.8 + "PLE0116", + # Design complexity stuff + # These judgements should always be made by humans + #"PLC0302", # too many lines in module (uncomment if ruff ever supports this) + #"PLR0901", # too many class ancestors (uncomment if ruff ever supports this) + #"PLR0902", # too many instance attributes (uncomment if ruff ever supports this) + #"PLR0903", # too few public methods (uncomment if ruff ever supports this) + "PLR0904", # too many public methods + "PLR0911", # too many return statements + "PLR0912", # too many branches + "PLR0913", # too many arguments + "PLR0914", # too many locals + "PLR0915", # too many statements + "PLR0916", # too many boolean expressions in a single statement + "PLR0917", # too many positional arguments + "PLR1702", # too many nested blocks + + ## tryceratops + # TRY003: Avoid specifying long messages outside the exception class + # Way, way too many false positives + "TRY003", +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Don't whine about codes in noqa that ruff doesn't (yet) support +external = [ + # openstack's hacking flake8 plugin + "V", + # flake8-multiline-containers + "JS", + # flake8-typing-imports + "TYP" +] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# Allow suggesting `from __future__ import annotations` +future-annotations = true + +# TODO and TODO-like tags to recognize +task-tags = [ + 'FIXME', + 'TODO', + 'HACK', + 'KLUDGE', + 'BUG', + 'OPTIMIZE', + 'XXX', + 'JANK', + 'AWFUL', + 'BAD', + 'CRIME', + 'CRIMES', +] + +# Recognize `typing_extensions` imports +typing-extensions = true +# Speciy additional typing extension modules +typing-modules = [] + +[tool.ruff.lint.per-file-ignores] +"setup.py" = [ + # pydocstyle, flake8-docstrings + # Ignore missing module docstring in setup.py + "D100", +] +"irods/test/*_test.py" = [ + # flake8-bandit + # S106: Possible hardcoded password assigned to argument + # Ignore passwords in tests + "S106", + # pydocstyle, flake8-docstrings + # Ignore missing docstrings in tests + "D1", +] + +[tool.ruff.lint.flake8-annotations] +# Suppress ANN401 for dynamically typed *args and **kwargs arguments. +allow-star-arg-any = true +# Allow omission of __init__ return type hint if at least one argument is annotated +#mypy-init-return = true +# Suppress violations for dummy argument variables +#suppress-dummy-args = true + +[tool.ruff.lint.flake8-bugbear] +# function calls to allow as default arguments +extend-immutable-calls = [] + +[tool.ruff.lint.flake8-builtins] +#allowed-modules = [] +#ignorelist = [] + +[lint.flake8-implicit-str-concat] +# Allow multiline implicit string concatenation +allow-multiline = true + +[tool.ruff.lint.flake8-import-conventions] +# Disallow using the `from ... import ...` syntax for individual modules +banned-from = [] + +[tool.ruff.lint.flake8-import-conventions.extend-aliases] +# To enforce an aliasing convention, follow this pattern: +#"module1" = "alias1" +#"module2" = "alias2" + +[tool.ruff.lint.flake8-import-conventions.banned-aliases] +# To disallow specific aliases, follow this pattern: +#"module1" = ["alias1", "alias2"] +#"module2" = ["alias3", "alias4"] + +[tool.ruff.lint.flake8-self] +# names to ignore when flagging private member access +extend-ignore-names = ["test_*"] + +[tool.ruff.lint.flake8-tidy-imports] +# Disallow specific module-level imports (allow only in functions and blocks) +banned-module-level-imports = [] + +[tool.ruff.lint.flake8-tidy-imports.banned-api] +# Disallow specific modules from being imported at all +"optparse".msg = "Use argparse instead of optparse" +"getopt".msg = "Use argparse instead of getopt" +"distutils".msg = "distutils has been removed from the stdlib" +"imp".msg = "imp has been removed from the stdlib" +"pycrypto".msg = "pycrypto is no longer maintained; use pyca/cryptography instead" + +[tool.ruff.lint.flake8-type-checking] +# Exempt specific modules from needing to be moved into type-checking blocks +exempt-modules = ["typing"] +# Exempt classes that list any of the enumerated classes as a base class from needing to be moved +# into type-checking blocks +runtime-evaluated-base-classes = [] +# Exempt classes and functions decorated with any of the enumerated decorators from being moved +# into type-checking blocks +runtime-evaluated-decorators = [] +# Quote type annotations if doing so would allow an import to be moved into a type-checking block +#quote-annotations = true + +[tool.ruff.lint.flake8-unused-arguments] +# Do not ignore unused *args/**kwargs +ignore-variadic-names = false + +[tool.ruff.lint.isort] +# Order imports by type +order-by-type = true +# ordering of relative imports +relative-imports-order = "closest-to-furthest" +# force specific imports to the top of their section +force-to-top = [] +# Combine aliased imports +combine-as-imports = true +# Combine aliased imports as multi-line compount imports +force-wrap-aliases = true +# modules to separate into auxiliary block(s), in the order specified +forced-separate = [] +# modules to consider as part of the stdlib +extra-standard-library = [] +# modules to consider as being third-party +known-third-party = [ + # distro_distil is first-party, but is an iRODS-independent package + "distro_distil", +] +# modules to consider as first-party +known-first-party = [] +# modules to consider as being a local folder +known-local-folder = [] +# ensure certain imports are in all files +required-imports = [] +# single-line rule exceptions +single-line-exclusions = [] +# Do not fold mult-line imports if there is a trailing comma +split-on-trailing-comma = true +# tokesn to always recognize as CONSTANTs +constants = [ + #"GSI_AUTH_SCHEME", + #"PAM_AUTH_SCHEME", + #"DEFAULT_CONFIG_PATH" +] +# tokens to always recognise as variables +variables = [] + +[tool.ruff.lint.pep8-naming] +# treat methos with these decorators as class methods +classmethod-decorators = [] +# treat methos with these decorators as static methods +staticmethod-decorators = [] +# names to ignore when considering naming violations +extend-ignore-names = [] + +[tool.ruff.lint.pycodestyle] +# Ensure this matches toll.ruff.line-length +#max-doc-length = 120 + +[tool.ruff.lint.pydocstyle] +# Adhere to PEP257 +convention = "pep257" +# Ignore docstrings for functions/methods with these fully-qualified decorators +ignore-decorators = ["typing.overload"] +# Do not ignore undocumented *args/**kwargs +ignore-var-parameters = false +# Treat methods with these decorators as properties +property-decorators = [] + +[tool.ruff.lint.pyflakes] +# functions/classes to consider generic +extend-generics = [] + +[tool.ruff.lint.pylint] +# dunder method names to allow +allow-dunder-method-names = [] +# types to ignore when flagging magic values +allow-magic-value-types = [ + "str", + "bytes", + #"complex", + #"float", + #"int", +] diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..d53f927 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ +"""iRODS HTTP client test module.""" diff --git a/test/config.py b/test/config.py index 6dc8c4a..9178b39 100644 --- a/test/config.py +++ b/test/config.py @@ -1,50 +1,53 @@ +"""Test configuration and settings for iRODS HTTP client tests.""" + import logging + from jsonschema import validate test_config = { - "log_level": logging.INFO, - "host": "localhost", - "port": 9001, - "url_base": "/irods-http-api/0.6.0", - "rodsadmin": {"username": "rods", "password": "rods"}, - "rodsuser": {"username": "jeb", "password": "ding"}, - "irods_zone": "tempZone", - "irods_server_hostname": "localhost", + "log_level": logging.INFO, + "host": "localhost", + "port": 9001, + "url_base": "/irods-http-api/0.6.0", + "rodsadmin": {"username": "rods", "password": "rods"}, + "rodsuser": {"username": "jeb", "password": "ding"}, + "irods_zone": "tempZone", + "irods_server_hostname": "localhost", } schema = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schemas.irods.org/irods-http-api/test/0.6.0/test-schema.json", - "type": "object", - "properties": { - "host": {"type": "string"}, - "port": {"type": "number"}, - "url_base": {"type": "string"}, - "rodsadmin": {"$ref": "#/definitions/login"}, - "rodsuser": {"$ref": "#/definitions/login"}, - "irods_zone": {"type": "string"}, - "irods_server_hostname": {"type": "string"}, - }, - "required": [ - "host", - "port", - "url_base", - "rodsadmin", - "rodsuser", - "irods_zone", - "irods_server_hostname", - ], - "definitions": { - "login": { - "type": "object", - "properties": { - "username": {"type": "string"}, - "password": {"type": "string"}, - }, - "required": ["username", "password"], - } - }, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schemas.irods.org/irods-http-api/test/0.6.0/test-schema.json", + "type": "object", + "properties": { + "host": {"type": "string"}, + "port": {"type": "number"}, + "url_base": {"type": "string"}, + "rodsadmin": {"$ref": "#/definitions/login"}, + "rodsuser": {"$ref": "#/definitions/login"}, + "irods_zone": {"type": "string"}, + "irods_server_hostname": {"type": "string"}, + }, + "required": [ + "host", + "port", + "url_base", + "rodsadmin", + "rodsuser", + "irods_zone", + "irods_server_hostname", + ], + "definitions": { + "login": { + "type": "object", + "properties": { + "username": {"type": "string"}, + "password": {"type": "string"}, + }, + "required": ["username", "password"], + } + }, } diff --git a/test/test_endpoint_operations.py b/test/test_endpoint_operations.py index 9116eb4..377579b 100644 --- a/test/test_endpoint_operations.py +++ b/test/test_endpoint_operations.py @@ -1,1932 +1,1845 @@ -import config -import unittest -from irods_http_client import IRODSHTTPClient -import concurrent.futures -import os -import time -import logging - - -def setup_class(cls, opts): - """Initializes shared state needed by all test cases. - - - This function is designed to be called in setUpClass(). - - - Arguments: - cls -- The class to attach state to. - opts -- A dict containing options for controlling the behavior of the function. - """ - - # Used as a signal for determining whether setUpClass() succeeded or not. - # If this results in being True, no tests should be allowed to run. - cls._class_init_error = False - - # Initialize the class logger. - cls.logger = logging.getLogger(cls.__name__) - - log_level = config.test_config.get("log_level", logging.INFO) - cls.logger.setLevel(log_level) - - ch = logging.StreamHandler() - ch.setLevel(log_level) - ch.setFormatter( - logging.Formatter(f"[%(asctime)s] [{cls.__name__}] [%(levelname)s] %(message)s") - ) - - cls.logger.addHandler(ch) - - # Initialize state. - - if config.test_config.get("host", None) == None: - cls.logger.debug("Missing configuration property: host") - cls._class_init_error = True - return - - if config.test_config.get("port", None) == None: - cls.logger.debug("Missing configuration property: port") - cls._class_init_error = True - return - - if config.test_config.get("url_base", None) == None: - cls.logger.debug("Missing configuration property: url_base") - cls._class_init_error = True - return +""" +Integration tests for iRODS HTTP API endpoint operations. - cls.url_base = f"http://{config.test_config['host']}:{config.test_config['port']}{config.test_config['url_base']}" - cls.url_endpoint = f'{cls.url_base}/{opts["endpoint_name"]}' +Tests cover all major operation categories: collections, data objects, +resources, rules, queries, tickets, users/groups, and zones. +""" - cls.api = IRODSHTTPClient(cls.url_base) +import concurrent.futures +import logging +import pathlib +import time +import unittest - cls.zone_name = config.test_config["irods_zone"] - cls.host = config.test_config["irods_server_hostname"] +import config - # create_rodsuser cannot be honored if init_rodsadmin is set to False. - # Therefore, return immediately. - if not opts.get("init_rodsadmin", True): - cls.logger.debug("init_rodsadmin is False. Class setup complete.") - return +from irods_http_client import IRODSHTTPClient - # Authenticate as a rodsadmin and store the bearer token. - cls.rodsadmin_username = config.test_config["rodsadmin"]["username"] - try: - cls.rodsadmin_bearer_token = cls.api.authenticate( - cls.rodsadmin_username, config.test_config["rodsadmin"]["password"] - ) - except RuntimeError: - cls._class_init_error = True - cls.logger.debug( - f"Failed to authenticate as rodsadmin [{cls.rodsadmin_username}]." - ) - return +def setup_class(cls, opts): + """ + Initialize shared state needed by all test cases. + + This function is designed to be called in setUpClass(). + + Args: + cls: The class to attach state to. + opts: A dict containing options for controlling the behavior of the function. + """ + # Used as a signal for determining whether setUpClass() succeeded or not. + # If this results in being True, no tests should be allowed to run. + cls._class_init_error = False + + # Initialize the class logger. + cls.logger = logging.getLogger(cls.__name__) + + log_level = config.test_config.get("log_level", logging.INFO) + cls.logger.setLevel(log_level) + + ch = logging.StreamHandler() + ch.setLevel(log_level) + ch.setFormatter(logging.Formatter(f"[%(asctime)s] [{cls.__name__}] [%(levelname)s] %(message)s")) + + cls.logger.addHandler(ch) + + # Initialize state. + + if config.test_config.get("host", None) is None: + cls.logger.debug("Missing configuration property: host") + cls._class_init_error = True + return + + if config.test_config.get("port", None) is None: + cls.logger.debug("Missing configuration property: port") + cls._class_init_error = True + return + + if config.test_config.get("url_base", None) is None: + cls.logger.debug("Missing configuration property: url_base") + cls._class_init_error = True + return + + cls.url_base = f"http://{config.test_config['host']}:{config.test_config['port']}{config.test_config['url_base']}" + cls.url_endpoint = f'{cls.url_base}/{opts["endpoint_name"]}' + + cls.api = IRODSHTTPClient(cls.url_base) + + cls.zone_name = config.test_config["irods_zone"] + cls.host = config.test_config["irods_server_hostname"] + + # create_rodsuser cannot be honored if init_rodsadmin is set to False. + # Therefore, return immediately. + if not opts.get("init_rodsadmin", True): + cls.logger.debug("init_rodsadmin is False. Class setup complete.") + return + + # Authenticate as a rodsadmin and store the bearer token. + cls.rodsadmin_username = config.test_config["rodsadmin"]["username"] + + try: + cls.rodsadmin_bearer_token = cls.api.authenticate( + cls.rodsadmin_username, config.test_config["rodsadmin"]["password"] + ) + except RuntimeError: + cls._class_init_error = True + cls.logger.debug("Failed to authenticate as rodsadmin [%].", cls.rodsadmin_username) + return + + # Authenticate as a rodsuser and store the bearer token. + cls.rodsuser_username = config.test_config["rodsuser"]["username"] + + try: + cls.api.users_groups.create_user(cls.rodsuser_username, cls.zone_name, "rodsuser") + cls.api.users_groups.set_password( + cls.rodsuser_username, + cls.zone_name, + config.test_config["rodsuser"]["password"], + ) + cls.rodsuser_bearer_token = cls.api.authenticate( + cls.rodsuser_username, config.test_config["rodsuser"]["password"] + ) + except RuntimeError: + cls._class_init_error = True + cls.logger.debug("Failed to authenticate as rodsuser [%].", cls.rodsuser_username) + return + + cls.logger.debug("Class setup complete.") - # Authenticate as a rodsuser and store the bearer token. - cls.rodsuser_username = config.test_config["rodsuser"]["username"] - try: - cls.api.users_groups.create_user( - cls.rodsuser_username, cls.zone_name, "rodsuser" - ) - cls.api.users_groups.set_password( - cls.rodsuser_username, - cls.zone_name, - config.test_config["rodsuser"]["password"], - ) - cls.rodsuser_bearer_token = cls.api.authenticate( - cls.rodsuser_username, config.test_config["rodsuser"]["password"] - ) - except RuntimeError: - cls._class_init_error = True - cls.logger.debug( - f"Failed to authenticate as rodsuser [{cls.rodsuser_username}]." - ) - return +def tear_down_class(cls): + """ + Clean up shared state after test class execution. - cls.logger.debug("Class setup complete.") + Removes the rodsuser created during setup. + Args: + cls: The class to clean up state from. + """ + if cls._class_init_error: + return -def tear_down_class(cls): - if cls._class_init_error: - return + cls.api.users_groups.remove_user(cls.rodsuser_username, cls.zone_name) - cls.api.users_groups.remove_user(cls.rodsuser_username, cls.zone_name) # Tests for library -class libraryTests(unittest.TestCase): +class LibraryTests(unittest.TestCase): + """Test library-level operations (info, get_token).""" - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "collections"}) + @classmethod + def setUpClass(cls): + """Set up class-level resources for library tests.""" + setup_class(cls, {"endpoint_name": "collections"}) - @classmethod - def tearDownClass(cls): - tear_down_class(cls) + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") - # tests the info operation - def testInfo(self): - self.api.info() + # tests the info operation + def test_info(self): + """Test the info operation to retrieve server information.""" + self.api.info() + + # tests the getToken operation + def test_get_token(self): + """Test the get_token operation to retrieve the current authentication token.""" + self.api.get_token() - # tests the getToken operation - def testGetToken(self): - self.api.getToken() # Tests for collections operations -class collectionsTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "collections"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - # tests the create operation - def testCreate(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # clean up test collections - self.api.collections.remove(f"/{self.zone_name}/home/new") - self.api.collections.remove(f"/{self.zone_name}/home/test/folder") - self.api.collections.remove(f"/{self.zone_name}/home/test") - - # test param checking - self.assertRaises(TypeError, self.api.collections.create, 0, 0) - self.assertRaises( - TypeError, - self.api.collections.create, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "0", - ) - self.assertRaises( - ValueError, - self.api.collections.create, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 7, - ) - - # test creating new collection - response = self.api.collections.create(f"/{self.zone_name}/home/new") - self.assertTrue(response["data"]["created"]) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test creating existing collection - response = self.api.collections.create(f"/{self.zone_name}/home/new") - self.assertFalse(response["data"]["created"]) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test invalid path - response = self.api.collections.create(f"{self.zone_name}/home/new") - self.assertEqual( - "{'irods_response': {'status_code': -358000, 'status_message': 'path does not exist: OBJ_PATH_DOES_NOT_EXIST'}}", - str(response["data"]), - ) - - # test create_intermediates - response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 0) - self.assertEqual( - "{'irods_response': {'status_code': -358000, 'status_message': 'path does not exist: OBJ_PATH_DOES_NOT_EXIST'}}", - str(response["data"]), - ) - response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 1) - self.assertEqual( - "{'created': True, 'irods_response': {'status_code': 0}}", - str(response["data"]), - ) - - # tests the remove operation - def testRemove(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # clean up test collections - self.api.collections.remove(f"/{self.zone_name}/home/new") - self.api.collections.remove(f"/{self.zone_name}/home/test/folder") - self.api.collections.remove(f"/{self.zone_name}/home/test") - - # test param checking - self.assertRaises(TypeError, self.api.collections.remove, 0, 0, 0) - self.assertRaises( - TypeError, - self.api.collections.remove, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "0", - 0, - ) - self.assertRaises( - ValueError, - self.api.collections.remove, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 5, - 0, - ) - self.assertRaises( - TypeError, - self.api.collections.remove, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - "0", - ) - self.assertRaises( - ValueError, - self.api.collections.remove, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - 5, - ) - - # test removing collection - response = self.api.collections.create(f"/{self.zone_name}/home/new") - self.assertEqual( - "{'created': True, 'irods_response': {'status_code': 0}}", - str(response["data"]), - ) - response = self.api.collections.remove(f"/{self.zone_name}/home/new") - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - # test invalid paths - response = self.api.collections.stat( - f"/{self.zone_name}/home/tensaitekinaaidorusama" - ) - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - response = self.api.collections.stat( - f"/{self.zone_name}/home/aremonainainaikoremonainainai" - ) - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - response = self.api.collections.stat( - f"/{self.zone_name}/home/binglebangledingledangle" - ) - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - response = self.api.collections.stat( - f"{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - - # test recurse - response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 1) - self.assertEqual( - "{'created': True, 'irods_response': {'status_code': 0}}", - str(response["data"]), - ) - response = self.api.collections.remove(f"/{self.zone_name}/home/test", 0) - self.assertEqual( - "{'irods_response': {'status_code': -79000, 'status_message': 'cannot remove non-empty collection: SYS_COLLECTION_NOT_EMPTY'}}", - str(response["data"]), - ) - response = self.api.collections.remove(f"/{self.zone_name}/home/test", 1) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # tests the stat operation - def testStat(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # clean up test collections - self.api.collections.remove(f"/{self.zone_name}/home/new") - - # test param checking - self.assertRaises(TypeError, self.api.collections.stat, 0, "ticket") - self.assertRaises( - TypeError, - self.api.collections.stat, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - ) - - # test invalid paths - response = self.api.collections.stat(f"/{self.zone_name}/home/new") - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - response = self.api.collections.stat(f"{self.zone_name}/home/new") - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - - # test valid path - response = self.api.collections.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertTrue(response["data"]["permissions"]) - - # tests the list operation - def testList(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # clean up test collections - self.api.collections.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb" - ) - self.api.collections.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/albania" - ) - self.api.collections.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia" - ) - self.api.collections.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia" - ) - - # test param checking - self.assertRaises(TypeError, self.api.collections.list, 0, "ticket") - self.assertRaises( - TypeError, - self.api.collections.list, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "0", - "ticket", - ) - self.assertRaises( - ValueError, - self.api.collections.list, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 5, - "ticket", - ) - self.assertRaises( - TypeError, - self.api.collections.list, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - 0, - ) - - # test empty collection - response = self.api.collections.list( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertEqual("None", str(response["data"]["entries"])) - - # test collection with one item - self.api.collections.create( - f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia" - ) - response = self.api.collections.list( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", - str(response["data"]["entries"][0]), - ) - - # test collection with multiple items - self.api.collections.create( - f"/{self.zone_name}/home/{self.rodsadmin_username}/albania" - ) - self.api.collections.create( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia" - ) - response = self.api.collections.list( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", - str(response["data"]["entries"][0]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", - str(response["data"]["entries"][1]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", - str(response["data"]["entries"][2]), - ) - - # test without recursion - self.api.collections.create( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb" - ) - response = self.api.collections.list( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", - str(response["data"]["entries"][0]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", - str(response["data"]["entries"][1]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", - str(response["data"]["entries"][2]), - ) - self.assertEqual(len(response["data"]["entries"]), 3) - - # test with recursion - response = self.api.collections.list( - f"/{self.zone_name}/home/{self.rodsadmin_username}", 1 - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", - str(response["data"]["entries"][0]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", - str(response["data"]["entries"][1]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", - str(response["data"]["entries"][2]), - ) - self.assertEqual( - f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb", - str(response["data"]["entries"][3]), - ) - - # tests the set permission operation - def testSetPermission(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # test param checking - self.assertRaises( - TypeError, self.api.collections.set_permission, 0, "jeb", "read", 0 - ) - self.assertRaises( - TypeError, - self.api.collections.set_permission, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - "read", - 0, - ) - self.assertRaises( - TypeError, - self.api.collections.set_permission, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "jeb", - 0, - 0, - ) - self.assertRaises( - TypeError, - self.api.collections.set_permission, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "jeb", - "read", - "0", - ) - self.assertRaises( - ValueError, - self.api.collections.set_permission, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "jeb", - "read", - 5, - ) - - # create new collection - response = self.api.collections.create(f"/{self.zone_name}/home/setPerms") - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test no permission - self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") - self.assertEqual(response["data"]["irods_response"]["status_code"], -170000) - - # test set permission - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.set_permission( - f"/{self.zone_name}/home/setPerms", self.rodsuser_username, "read" - ) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # test with permission - self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") - self.assertTrue(response["data"]["permissions"]) - - # test set permission null - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.set_permission( - f"/{self.zone_name}/home/setPerms", self.rodsuser_username, "null" - ) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # test no permission - self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - - # remove the collection - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.remove(f"/{self.zone_name}/home/setPerms", 1, 1) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # tests the set inheritance operation - def testSetInheritance(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # test param checking - self.assertRaises(TypeError, self.api.collections.set_inheritance, 0, 0, 0) - self.assertRaises( - TypeError, - self.api.collections.set_inheritance, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - "0", - 0, - ) - self.assertRaises( - ValueError, - self.api.collections.set_inheritance, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 5, - 0, - ) - self.assertRaises( - TypeError, - self.api.collections.set_inheritance, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - "0", - ) - self.assertRaises( - ValueError, - self.api.collections.set_inheritance, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - 5, - ) - - # control - response = self.api.collections.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertFalse(response["data"]["inheritance_enabled"]) - - # test enabling inheritance - response = self.api.collections.set_inheritance( - f"/{self.zone_name}/home/{self.rodsadmin_username}", 1 - ) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # check if changed - response = self.api.collections.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertTrue(response["data"]["inheritance_enabled"]) - - # test disabling inheritance - response = self.api.collections.set_inheritance( - f"/{self.zone_name}/home/{self.rodsadmin_username}", 0 - ) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # check if changed - response = self.api.collections.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertFalse(response["data"]["inheritance_enabled"]) - - # test the modify permissions operation - def testModifyPermissions(self): - self.api.setToken(self.rodsadmin_bearer_token) - - ops_permissions = [{"entity_name": self.rodsuser_username, "acl": "read"}] - - ops_permissions_null = [{"entity_name": self.rodsuser_username, "acl": "null"}] - - # test param checking - self.assertRaises( - TypeError, self.api.collections.modify_permissions, 0, ops_permissions, 0 - ) - self.assertRaises( - TypeError, - self.api.collections.modify_permissions, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 5, - 0, - ) - self.assertRaises( - TypeError, - self.api.collections.modify_permissions, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - ops_permissions, - "0", - ) - self.assertRaises( - ValueError, - self.api.collections.modify_permissions, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - ops_permissions, - 5, - ) - - # create new collection - response = self.api.collections.create(f"/{self.zone_name}/home/modPerms") - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test no permissions - self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - - # test set permissions - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.modify_permissions( - f"/{self.zone_name}/home/modPerms", ops_permissions - ) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test with permissions - self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") - self.assertTrue(response["data"]["permissions"]) - - # test set permissions nuil - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.modify_permissions( - f"/{self.zone_name}/home/modPerms", ops_permissions_null - ) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test without permissions - self.api.setToken(self.rodsuser_bearer_token) - response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - - # remove the collection - self.api.setToken(self.rodsadmin_bearer_token) - response = self.api.collections.remove(f"/{self.zone_name}/home/modPerms", 1, 1) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # test the modify metadata operation - def testModifyMetadata(self): - self.api.setToken(self.rodsadmin_bearer_token) - - ops_metadata = [{"operation": "add", "attribute": "eyeballs", "value": "itchy"}] - - ops_metadata_remove = [ - {"operation": "remove", "attribute": "eyeballs", "value": "itchy"} - ] - - # test param checking - self.assertRaises( - TypeError, self.api.collections.modify_metadata, 0, ops_metadata, 0 - ) - self.assertRaises( - TypeError, - self.api.collections.modify_metadata, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 5, - 0, - ) - self.assertRaises( - TypeError, - self.api.collections.modify_metadata, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - ops_metadata, - "0", - ) - self.assertRaises( - ValueError, - self.api.collections.modify_metadata, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - ops_metadata, - 5, - ) - - # test adding and removing metadata - response = self.api.collections.modify_metadata( - f"/{self.zone_name}/home/{self.rodsadmin_username}", ops_metadata - ) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - response = self.api.collections.modify_metadata( - f"/{self.zone_name}/home/{self.rodsadmin_username}", ops_metadata_remove - ) - self.assertEqual(response["data"]["irods_response"]["status_code"], 0) - - # tests the rename operation - def testRename(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # test param checking - self.assertRaises( - TypeError, - self.api.collections.rename, - f"/{self.zone_name}/home/{self.rodsadmin_username}", - 0, - ) - self.assertRaises( - TypeError, self.api.collections.rename, 0, f"/{self.zone_name}/home/pods" - ) - - # test before move - response = self.api.collections.stat(f"/{self.zone_name}/home/pods") - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - response = self.api.collections.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertTrue(response["data"]["permissions"]) - - # test renaming - response = self.api.collections.rename( - f"/{self.zone_name}/home/{self.rodsadmin_username}", - f"/{self.zone_name}/home/pods", - ) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # test before move - response = self.api.collections.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}" - ) - self.assertEqual( - "{'irods_response': {'status_code': -170000}}", str(response["data"]) - ) - response = self.api.collections.stat(f"/{self.zone_name}/home/pods") - self.assertTrue(response["data"]["permissions"]) - - # test renaming - response = self.api.collections.rename( - f"/{self.zone_name}/home/pods", - f"/{self.zone_name}/home/{self.rodsadmin_username}", - ) - self.assertEqual( - "{'irods_response': {'status_code': 0}}", str(response["data"]) - ) - - # tests the touch operation - def testTouch(self): - self.api.setToken(self.rodsadmin_bearer_token) - r = self.api.collections.touch( - f"/{self.zone_name}/home/{self.rodsadmin_username}", - reference=f"/{self.zone_name}/home" - ) +class CollectionsTests(unittest.TestCase): + """Test iRODS collection operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for collection tests.""" + setup_class(cls, {"endpoint_name": "collections"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + # tests the create operation + def test_create(self): + """Test collection creation operations and parameter validation.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/new") + self.api.collections.remove(f"/{self.zone_name}/home/test/folder") + self.api.collections.remove(f"/{self.zone_name}/home/test") + + # test param checking + self.assertRaises(TypeError, self.api.collections.create, 0, 0) + self.assertRaises( + TypeError, + self.api.collections.create, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.create, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 7, + ) + + # test creating new collection + response = self.api.collections.create(f"/{self.zone_name}/home/new") + self.assertTrue(response["data"]["created"]) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test creating existing collection + response = self.api.collections.create(f"/{self.zone_name}/home/new") + self.assertFalse(response["data"]["created"]) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test invalid path + response = self.api.collections.create(f"{self.zone_name}/home/new") + self.assertEqual( + "{'irods_response': {'status_code': -358000, " + "'status_message': 'path does not exist: OBJ_PATH_DOES_NOT_EXIST'}}", + str(response["data"]), + ) + + # test create_intermediates + response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 0) + self.assertEqual( + "{'irods_response': {'status_code': -358000, " + "'status_message': 'path does not exist: OBJ_PATH_DOES_NOT_EXIST'}}", + str(response["data"]), + ) + response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 1) + self.assertEqual( + "{'created': True, 'irods_response': {'status_code': 0}}", + str(response["data"]), + ) + + # tests the remove operation + def test_remove(self): + """Test collection removal operations and parameter validation.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/new") + self.api.collections.remove(f"/{self.zone_name}/home/test/folder") + self.api.collections.remove(f"/{self.zone_name}/home/test") + + # test param checking + self.assertRaises(TypeError, self.api.collections.remove, 0, 0, 0) + self.assertRaises( + TypeError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + 0, + ) + self.assertRaises( + ValueError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.remove, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + 5, + ) + + # test removing collection + response = self.api.collections.create(f"/{self.zone_name}/home/new") + self.assertEqual( + "{'created': True, 'irods_response': {'status_code': 0}}", + str(response["data"]), + ) + response = self.api.collections.remove(f"/{self.zone_name}/home/new") + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + # test invalid paths + response = self.api.collections.stat(f"/{self.zone_name}/home/tensaitekinaaidorusama") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + response = self.api.collections.stat(f"/{self.zone_name}/home/aremonainainaikoremonainainai") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + response = self.api.collections.stat(f"/{self.zone_name}/home/binglebangledingledangle") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + response = self.api.collections.stat(f"{self.zone_name}/home/{self.rodsadmin_username}") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + + # test recurse + response = self.api.collections.create(f"/{self.zone_name}/home/test/folder", 1) + self.assertEqual( + "{'created': True, 'irods_response': {'status_code': 0}}", + str(response["data"]), + ) + response = self.api.collections.remove(f"/{self.zone_name}/home/test", 0) + self.assertEqual( + "{'irods_response': {'status_code': -79000, " + "'status_message': 'cannot remove non-empty collection: " + "SYS_COLLECTION_NOT_EMPTY'}}", + str(response["data"]), + ) + response = self.api.collections.remove(f"/{self.zone_name}/home/test", 1) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # tests the stat operation + def test_stat(self): + """Test collection stat operation to retrieve metadata.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/new") + + # test param checking + self.assertRaises(TypeError, self.api.collections.stat, 0, "ticket") + self.assertRaises( + TypeError, + self.api.collections.stat, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + ) + + # test invalid paths + response = self.api.collections.stat(f"/{self.zone_name}/home/new") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + response = self.api.collections.stat(f"{self.zone_name}/home/new") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + + # test valid path + response = self.api.collections.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertTrue(response["data"]["permissions"]) + + # tests the list operation + def test_list(self): + """Test collection list operation to enumerate contents.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # clean up test collections + self.api.collections.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb") + self.api.collections.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/albania") + self.api.collections.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia") + self.api.collections.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia") + + # test param checking + self.assertRaises(TypeError, self.api.collections.list, 0, "ticket") + self.assertRaises( + TypeError, + self.api.collections.list, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + "ticket", + ) + self.assertRaises( + ValueError, + self.api.collections.list, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + "ticket", + ) + self.assertRaises( + TypeError, + self.api.collections.list, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + 0, + ) + + # test empty collection + response = self.api.collections.list(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertEqual("None", str(response["data"]["entries"])) + + # test collection with one item + self.api.collections.create(f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia") + response = self.api.collections.list(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][0]), + ) + + # test collection with multiple items + self.api.collections.create(f"/{self.zone_name}/home/{self.rodsadmin_username}/albania") + self.api.collections.create(f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia") + response = self.api.collections.list(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", + str(response["data"]["entries"][0]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][1]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", + str(response["data"]["entries"][2]), + ) + + # test without recursion + self.api.collections.create(f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb") + response = self.api.collections.list(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", + str(response["data"]["entries"][0]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][1]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", + str(response["data"]["entries"][2]), + ) + self.assertEqual(len(response["data"]["entries"]), 3) + + # test with recursion + response = self.api.collections.list(f"/{self.zone_name}/home/{self.rodsadmin_username}", 1) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/albania", + str(response["data"]["entries"][0]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/bosnia", + str(response["data"]["entries"][1]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia", + str(response["data"]["entries"][2]), + ) + self.assertEqual( + f"/{self.zone_name}/home/{self.rodsadmin_username}/croatia/zagreb", + str(response["data"]["entries"][3]), + ) + + # tests the set permission operation + def test_set_permission(self): + """Test setting permissions on collections.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # test param checking + self.assertRaises(TypeError, self.api.collections.set_permission, 0, "jeb", "read", 0) + self.assertRaises( + TypeError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + "read", + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "jeb", + 0, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "jeb", + "read", + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.set_permission, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "jeb", + "read", + 5, + ) + + # create new collection + response = self.api.collections.create(f"/{self.zone_name}/home/setPerms") + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test no permission + self.api.set_token(self.rodsuser_bearer_token) + response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") + self.assertEqual(response["data"]["irods_response"]["status_code"], -170000) + + # test set permission + self.api.set_token(self.rodsadmin_bearer_token) + response = self.api.collections.set_permission( + f"/{self.zone_name}/home/setPerms", self.rodsuser_username, "read" + ) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # test with permission + self.api.set_token(self.rodsuser_bearer_token) + response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") + self.assertTrue(response["data"]["permissions"]) + + # test set permission null + self.api.set_token(self.rodsadmin_bearer_token) + response = self.api.collections.set_permission( + f"/{self.zone_name}/home/setPerms", self.rodsuser_username, "null" + ) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # test no permission + self.api.set_token(self.rodsuser_bearer_token) + response = self.api.collections.stat(f"/{self.zone_name}/home/setPerms") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + + # remove the collection + self.api.set_token(self.rodsadmin_bearer_token) + response = self.api.collections.remove(f"/{self.zone_name}/home/setPerms", 1, 1) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # tests the set inheritance operation + def test_set_inheritance(self): + """Test setting inheritance for collection permissions.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # test param checking + self.assertRaises(TypeError, self.api.collections.set_inheritance, 0, 0, 0) + self.assertRaises( + TypeError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + "0", + 0, + ) + self.assertRaises( + ValueError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.set_inheritance, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + 5, + ) + + # control + response = self.api.collections.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertFalse(response["data"]["inheritance_enabled"]) + + # test enabling inheritance + response = self.api.collections.set_inheritance(f"/{self.zone_name}/home/{self.rodsadmin_username}", 1) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # check if changed + response = self.api.collections.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertTrue(response["data"]["inheritance_enabled"]) + + # test disabling inheritance + response = self.api.collections.set_inheritance(f"/{self.zone_name}/home/{self.rodsadmin_username}", 0) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # check if changed + response = self.api.collections.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertFalse(response["data"]["inheritance_enabled"]) + + # test the modify permissions operation + def test_modify_permissions(self): + """Test modifying permissions on collections.""" + self.api.set_token(self.rodsadmin_bearer_token) + + ops_permissions = [{"entity_name": self.rodsuser_username, "acl": "read"}] + + ops_permissions_null = [{"entity_name": self.rodsuser_username, "acl": "null"}] + + # test param checking + self.assertRaises(TypeError, self.api.collections.modify_permissions, 0, ops_permissions, 0) + self.assertRaises( + TypeError, + self.api.collections.modify_permissions, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.modify_permissions, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_permissions, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.modify_permissions, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_permissions, + 5, + ) + + # create new collection + response = self.api.collections.create(f"/{self.zone_name}/home/modPerms") + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test no permissions + self.api.set_token(self.rodsuser_bearer_token) + response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + + # test set permissions + self.api.set_token(self.rodsadmin_bearer_token) + response = self.api.collections.modify_permissions(f"/{self.zone_name}/home/modPerms", ops_permissions) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test with permissions + self.api.set_token(self.rodsuser_bearer_token) + response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") + self.assertTrue(response["data"]["permissions"]) + + # test set permissions nuil + self.api.set_token(self.rodsadmin_bearer_token) + response = self.api.collections.modify_permissions(f"/{self.zone_name}/home/modPerms", ops_permissions_null) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test without permissions + self.api.set_token(self.rodsuser_bearer_token) + response = self.api.collections.stat(f"/{self.zone_name}/home/modPerms") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + + # remove the collection + self.api.set_token(self.rodsadmin_bearer_token) + response = self.api.collections.remove(f"/{self.zone_name}/home/modPerms", 1, 1) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # test the modify metadata operation + def test_modify_metadata(self): + """Test modifying metadata on collections.""" + self.api.set_token(self.rodsadmin_bearer_token) + + ops_metadata = [{"operation": "add", "attribute": "eyeballs", "value": "itchy"}] + + ops_metadata_remove = [{"operation": "remove", "attribute": "eyeballs", "value": "itchy"}] + + # test param checking + self.assertRaises(TypeError, self.api.collections.modify_metadata, 0, ops_metadata, 0) + self.assertRaises( + TypeError, + self.api.collections.modify_metadata, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 5, + 0, + ) + self.assertRaises( + TypeError, + self.api.collections.modify_metadata, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_metadata, + "0", + ) + self.assertRaises( + ValueError, + self.api.collections.modify_metadata, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ops_metadata, + 5, + ) + + # test adding and removing metadata + response = self.api.collections.modify_metadata( + f"/{self.zone_name}/home/{self.rodsadmin_username}", ops_metadata + ) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + response = self.api.collections.modify_metadata( + f"/{self.zone_name}/home/{self.rodsadmin_username}", ops_metadata_remove + ) + self.assertEqual(response["data"]["irods_response"]["status_code"], 0) + + # tests the rename operation + def test_rename(self): + """Test renaming collections.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # test param checking + self.assertRaises( + TypeError, + self.api.collections.rename, + f"/{self.zone_name}/home/{self.rodsadmin_username}", + 0, + ) + self.assertRaises(TypeError, self.api.collections.rename, 0, f"/{self.zone_name}/home/pods") + + # test before move + response = self.api.collections.stat(f"/{self.zone_name}/home/pods") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + response = self.api.collections.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertTrue(response["data"]["permissions"]) + + # test renaming + response = self.api.collections.rename( + f"/{self.zone_name}/home/{self.rodsadmin_username}", + f"/{self.zone_name}/home/pods", + ) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # test before move + response = self.api.collections.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}") + self.assertEqual("{'irods_response': {'status_code': -170000}}", str(response["data"])) + response = self.api.collections.stat(f"/{self.zone_name}/home/pods") + self.assertTrue(response["data"]["permissions"]) + + # test renaming + response = self.api.collections.rename( + f"/{self.zone_name}/home/pods", + f"/{self.zone_name}/home/{self.rodsadmin_username}", + ) + self.assertEqual("{'irods_response': {'status_code': 0}}", str(response["data"])) + + # tests the touch operation + def test_touch(self): + """Test touch operation to update collection timestamps.""" + self.api.set_token(self.rodsadmin_bearer_token) + self.api.collections.touch( + f"/{self.zone_name}/home/{self.rodsadmin_username}", reference=f"/{self.zone_name}/home" + ) # Tests for data object operations -class dataObjectsTests(unittest.TestCase): - - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "data_objects"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - def testCommonOperations(self): - self.api.setToken(self.rodsadmin_bearer_token) - - try: - # Create a unixfilesystem resource. - r = self.api.resources.create( - "resource", "unixfilesystem", self.host, "/tmp/resource", "" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - self.api.setToken(self.rodsuser_bearer_token) - # Create a non-empty data object - r = self.api.data_objects.write( - "These are the bytes being written to the object", - f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", - "resource", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Add metadata to the data object - r = self.api.data_objects.modify_metadata( - f1, - operations=[{'operation': 'add', 'attribute': 'a', 'value': 'v','units': 'u'}] - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Modify the replica - self.api.setToken(self.rodsadmin_bearer_token) - r = self.api.data_objects.modify_replica( - f1, - replica_number=0, - new_data_comments="awesome" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.api.setToken(self.rodsuser_bearer_token) - - # Replicate the data object - r = self.api.data_objects.replicate( - f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", - dst_resource="resource", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that there are two replicas - # TODO: Implement once query operations are completed - - # Trim the first data object - r = self.api.data_objects.trim( - f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", 0 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Rename the data object - r = self.api.data_objects.rename( - f"/{self.zone_name}/home/{self.rodsuser_username}/file.txt", - f"/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Copy the data object - r = self.api.data_objects.copy( - f"/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt", - f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Set permission on the object - r = self.api.data_objects.set_permission( - f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt", - "rods", - "read", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Confirm that the permission has been set - r = self.api.data_objects.stat( - f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertIn( - { - "name": "rods", - "zone": self.zone_name, - "type": "rodsadmin", - "perm": "read_object", - }, - r["data"]["permissions"], - ) - - # Modify permission on the object - r = self.api.data_objects.modify_permissions( - f3, - operations=[{'entity_name': 'rods','acl': 'write'}] - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - finally: - # Remove the data objects - r = self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsuser_username}/anotherfile.txt", 0, 1 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - r = self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsuser_username}/newfile.txt", 0, 1 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - self.api.setToken(self.rodsadmin_bearer_token) - - # Remove the resource - r = self.api.resources.remove("resource") - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - def testChecksums(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Create a unixfilesystem resource. - r = self.api.resources.create( - "newresource", "unixfilesystem", self.host, "/tmp/newresource", "" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Create a non-empty data object - r = self.api.data_objects.write( - "These are the bytes being written to the object", - f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", - "newresource", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Replicate the data object - r = self.api.data_objects.replicate( - f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", - dst_resource="newresource", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that there are two replicas - # TODO: Implement once query operations are completed - - try: - # Calculate a checksum for the first replica - r = self.api.data_objects.calculate_checksum( - f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", - replica_number=0, - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Verify checksum information across all replicas. - r = self.api.data_objects.verify_checksum( - f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - finally: - # Remove the data objects - r = self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", 0, 1 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove the resource - r = self.api.resources.remove("newresource") - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - def testTouch(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Test touching non existant data object with no_create - r = self.api.data_objects.touch( - f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 1 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the object has not been created - r = self.api.data_objects.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], -171000) - - # Test touching non existant object without no_create - r = self.api.data_objects.touch( - f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 0 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the object has been created - r = self.api.data_objects.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Test touching existing object without no_create - r = self.api.data_objects.touch( - f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 1 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove the object - r = self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - def testRegister(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Create a non-empty local file. - content = "data" - with open("/tmp/register-demo.txt", "w") as f: - f.write(content) - - # Show the data object we want to create via registration does not exist. - r = self.api.data_objects.stat( - f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], -171000) - - try: - # Create a unixfilesystem resource. - r = self.api.resources.create( - "register_resource", - "unixfilesystem", - self.host, - "/tmp/register_resource", - "", - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Register the local file into the catalog as a new data object. - # We know we're registering a new data object because the "as-additional-replica" - # parameter isn't set to 1. - r = self.api.data_objects.register( - f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt", - "/tmp/register-demo.txt", - "register_resource", - data_size=len(content), - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show a new data object exists with the expected replica information. - # TODO: add when query operations are implemented - finally: - # Unregisterr the dataq object - r = self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt", 1 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove the resource - r = self.api.resources.remove("register_resource") - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - def testParallelWrite(self): - self.api.setToken(self.rodsadmin_bearer_token) - self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", 0, 1 - ) - - # Open parallel write - r = self.api.data_objects.parallel_write_init( - f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", 3 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - handle = r["data"]["parallel_write_handle"] - - try: - # Write to the data object using the parallel write handle. - futures = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: - for e in enumerate(["A", "B", "C"]): - count = 10 - futures.append( - executor.submit( - self.api.data_objects.write, - bytes=e[1] * count, - offset=e[0] * count, - stream_index=e[0], - parallel_write_handle=handle, - ) - ) - for f in concurrent.futures.as_completed(futures): - r = f.result() - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - finally: - # Close parallel write - r = self.api.data_objects.parallel_write_shutdown(handle) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove the object - r = self.api.data_objects.remove( - f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", - 0, - 1, - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) +class DataObjectsTests(unittest.TestCase): + """Test iRODS data object operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for data object tests.""" + setup_class(cls, {"endpoint_name": "data_objects"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + def test_common_operations(self): + """Test common data object operations (write, read, replicate, etc.).""" + self.api.set_token(self.rodsadmin_bearer_token) + + f1 = f"/{self.zone_name}/home/{self.rodsuser_username}/f1.txt" + f2 = f"/{self.zone_name}/home/{self.rodsuser_username}/f2.txt" + f3 = f"/{self.zone_name}/home/{self.rodsuser_username}/f3.txt" + resc = "resource" + + try: + # Create a unixfilesystem resource + r = self.api.resources.create( + resc, + "unixfilesystem", + self.host, + "/tmp/resource", # noqa: S108 + "", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + self.api.set_token(self.rodsuser_bearer_token) + # Create a non-empty data object + r = self.api.data_objects.write("These are the bytes being written to the object", f1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Read the data object + r = self.api.data_objects.read(f1, offset=6) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertIn('being written', r["data"]['irods_response']['bytes'].decode('utf-8')) + + # Add metadata to the data object + r = self.api.data_objects.modify_metadata( + f1, operations=[{'operation': 'add', 'attribute': 'a', 'value': 'v', 'units': 'u'}] + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Modify the replica + self.api.set_token(self.rodsadmin_bearer_token) + r = self.api.data_objects.modify_replica(f1, replica_number=0, new_data_comments="awesome") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.api.set_token(self.rodsuser_bearer_token) + + # Replicate the data object + r = self.api.data_objects.replicate( + f1, + dst_resource=resc, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that there are two replicas + r = self.api.queries.execute_genquery( + f"select DATA_NAME, DATA_REPL_NUM where DATA_NAME = '{f1.rsplit('/', maxsplit=1)[-1]}'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 2) + + # Trim the first data object + r = self.api.data_objects.trim(f1, 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Rename the data object + r = self.api.data_objects.rename(f1, f2) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Copy the data object + r = self.api.data_objects.copy(f2, f3) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Set permission on the object + r = self.api.data_objects.set_permission( + f3, + "rods", + "read", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Confirm that the permission has been set + r = self.api.data_objects.stat(f3) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertIn( + { + "name": "rods", + "zone": self.zone_name, + "type": "rodsadmin", + "perm": "read_object", + }, + r["data"]["permissions"], + ) + + # Modify permission on the object + r = self.api.data_objects.modify_permissions(f3, operations=[{'entity_name': 'rods', 'acl': 'write'}]) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + finally: + # Remove the data objects + r = self.api.data_objects.remove(f1, 0, 1) + + r = self.api.data_objects.remove(f2, 0, 1) + + r = self.api.data_objects.remove(f3, 0, 1) + + # Remove the resource + self.api.set_token(self.rodsadmin_bearer_token) + r = self.api.resources.remove(resc) + + def test_checksums(self): + """Test checksum calculation and verification for data objects.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Create a unixfilesystem resource. + r = self.api.resources.create( + "newresource", + "unixfilesystem", + self.host, + "/tmp/newresource", # noqa: S108 + "", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Create a non-empty data object + r = self.api.data_objects.write( + "These are the bytes being written to the object", + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Replicate the data object + r = self.api.data_objects.replicate( + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", + dst_resource="newresource", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that there are two replicas + r = self.api.queries.execute_genquery("select DATA_NAME, DATA_REPL_NUM where DATA_NAME = 'file.txt'") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 2) + + try: + # Calculate a checksum for the first replica + r = self.api.data_objects.calculate_checksum( + f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", + replica_number=0, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Verify checksum information across all replicas. + r = self.api.data_objects.verify_checksum(f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + finally: + # Remove the data objects + r = self.api.data_objects.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/file.txt", 0, 1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove the resource + r = self.api.resources.remove("newresource") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + def test_touch(self): + """Test touch operation on data objects.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Test touching non existant data object with no_create + r = self.api.data_objects.touch(f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the object has not been created + r = self.api.data_objects.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt") + self.assertEqual(r["data"]["irods_response"]["status_code"], -171000) + + # Test touching non existant object without no_create + r = self.api.data_objects.touch(f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the object has been created + r = self.api.data_objects.stat(f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Test touching existing object without no_create + r = self.api.data_objects.touch(f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt", 1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove the object + r = self.api.data_objects.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/new.txt") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + def test_register(self): + """Test registering existing files as iRODS data objects.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Create a non-empty local file. + filename = f"/{self.zone_name}/home/{self.rodsadmin_username}/register-demo.txt" + content = "data" + + with pathlib.Path("/tmp/register-demo.txt").open("w") as f: # noqa: S108 + f.write(content) + + # Show the data object we want to create via registration does not exist. + r = self.api.data_objects.stat(filename) + self.assertEqual(r["data"]["irods_response"]["status_code"], -171000) + + try: + # Create a unixfilesystem resource. + r = self.api.resources.create( + "register_resource", + "unixfilesystem", + self.host, + "/tmp/register_resource", # noqa: S108 + "", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Register the local file into the catalog as a new data object. + # We know we're registering a new data object because the "as-additional-replica" + # parameter isn't set to 1. + r = self.api.data_objects.register( + filename, + "/tmp/register-demo.txt", # noqa: S108 + "register_resource", + data_size=len(content), + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show a new data object exists with the expected replica information. + r = self.api.queries.execute_genquery( + "select DATA_NAME, DATA_PATH, RESC_NAME where DATA_NAME = 'register-demo.txt'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 1) + self.assertEqual(r["data"]["rows"][0][1], "/tmp/register-demo.txt") # noqa: S108 + self.assertEqual(r["data"]["rows"][0][2], "register_resource") + + finally: + # Unregister the data object + r = self.api.data_objects.remove(filename, 1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove the resource + r = self.api.resources.remove("register_resource") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + def test_parallel_write(self): + """Test parallel writing to data objects.""" + self.api.set_token(self.rodsadmin_bearer_token) + self.api.data_objects.remove(f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", 0, 1) + + # Open parallel write + r = self.api.data_objects.parallel_write_init( + f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", 3 + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + handle = r["data"]["parallel_write_handle"] + + try: + # Write to the data object using the parallel write handle. + futures = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + for e in enumerate(["A", "B", "C"]): + count = 10 + futures.append( + executor.submit( + self.api.data_objects.write, + bytes_=e[1] * count, + offset=e[0] * count, + stream_index=e[0], + parallel_write_handle=handle, + ) + ) + for f in concurrent.futures.as_completed(futures): + r = f.result() + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + finally: + # Close parallel write + r = self.api.data_objects.parallel_write_shutdown(handle) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove the object + r = self.api.data_objects.remove( + f"/{self.zone_name}/home/{self.rodsadmin_username}/parallel-write.txt", + 0, + 1, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) # Tests for resources operations -class resourcesTests(unittest.TestCase): - - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "resources"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - def testCommonOperations(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # TEMPORARY pre-test cleanup - # test is currently not passing, so cleanup occusrs at the beginning to allow it to be run more than once in a row - self.api.resources.remove_child("test_repl", "test_ufs0") - self.api.resources.remove_child("test_repl", "test_ufs1") - self.api.resources.remove("test_ufs0") - self.api.resources.remove("test_ufs1") - self.api.resources.remove("test_repl") - - resc_repl = "test_repl" - resc_ufs0 = "test_ufs0" - resc_ufs1 = "test_ufs1" - - # Create three resources (replication w/ two unixfilesystem resources). - r = self.api.resources.create(resc_repl, "replication", "", "", "") - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show the replication resource was created. - r = self.api.resources.stat(resc_repl) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["exists"], True) - self.assertIn("id", r["data"]["info"]) - self.assertEqual(r["data"]["info"]["name"], resc_repl) - self.assertEqual(r["data"]["info"]["type"], "replication") - self.assertEqual(r["data"]["info"]["zone"], "tempZone") - self.assertEqual(r["data"]["info"]["host"], "EMPTY_RESC_HOST") - self.assertEqual(r["data"]["info"]["vault_path"], "EMPTY_RESC_PATH") - self.assertIn("status", r["data"]["info"]) - self.assertIn("context", r["data"]["info"]) - self.assertIn("comments", r["data"]["info"]) - self.assertIn("information", r["data"]["info"]) - self.assertIn("free_space", r["data"]["info"]) - self.assertIn("free_space_last_modified", r["data"]["info"]) - self.assertEqual(r["data"]["info"]["parent_id"], "") - self.assertIn("created", r["data"]["info"]) - self.assertIn("last_modified", r["data"]["info"]) - self.assertIn("last_modified_millis", r["data"]["info"]) - - # Capture the replication resource's id. - # This resource is going to be the parent of the unixfilesystem resources. - # This value is needed to verify the relationship. - resc_repl_id = r["data"]["info"]["id"] - - for resc_name in [resc_ufs0, resc_ufs1]: - with self.subTest( - f"Create and attach resource [{resc_name}] to [{resc_repl}]" - ): - vault_path = f"/tmp/{resc_name}_vault" - - # Create a unixfilesystem resource. - r = self.api.resources.create( - resc_name, "unixfilesystem", self.host, vault_path, "" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Add the unixfilesystem resource as a child of the replication resource. - r = self.api.resources.add_child(resc_repl, resc_name) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the resource was created and configured successfully. - r = self.api.resources.stat(resc_name) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["exists"], True) - self.assertIn("id", r["data"]["info"]) - self.assertEqual(r["data"]["info"]["name"], resc_name) - self.assertEqual(r["data"]["info"]["type"], "unixfilesystem") - self.assertEqual(r["data"]["info"]["zone"], self.zone_name) - self.assertEqual(r["data"]["info"]["host"], self.host) - self.assertEqual(r["data"]["info"]["vault_path"], vault_path) - self.assertIn("status", r["data"]["info"]) - self.assertIn("context", r["data"]["info"]) - self.assertIn("comments", r["data"]["info"]) - self.assertIn("information", r["data"]["info"]) - self.assertIn("free_space", r["data"]["info"]) - self.assertIn("free_space_last_modified", r["data"]["info"]) - self.assertEqual(r["data"]["info"]["parent_id"], resc_repl_id) - self.assertIn("created", r["data"]["info"]) - self.assertIn("last_modified", r["data"]["info"]) - - # Create a data object targeting the replication resource. - data_object = f"/{self.zone_name}/home/{self.rodsadmin_username}/resource_obj" - r = self.api.data_objects.write( - "These are the bytes to be written", data_object, resc_repl, 0 - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show there are two replicas under the replication resource hierarchy. - r = self.api.queries.execute_genquery( - f"select DATA_NAME, RESC_NAME where DATA_NAME = '{os.path.basename(data_object)}'" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(len(r["data"]["rows"]), 2) - - resc_tuple = (r["data"]["rows"][0][1], r["data"]["rows"][1][1]) - self.assertIn(resc_tuple, [(resc_ufs0, resc_ufs1), (resc_ufs1, resc_ufs0)]) - - # Trim a replica. - r = self.api.data_objects.trim(data_object, 0) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show there is only one replica under the replication resource hierarchy. - r = self.api.queries.execute_genquery( - f"select DATA_NAME, RESC_NAME where DATA_NAME = '{os.path.basename(data_object)}'" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(len(r["data"]["rows"]), 1) - - # Launch rebalance - r = self.api.resources.rebalance(resc_repl) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Give the rebalance operation time to complete! - time.sleep(3) - - # - # Clean-up - # - - # Remove the data object. - r = self.api.data_objects.remove(data_object, 0, 1) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove resources. - for resc_name in [resc_ufs0, resc_ufs1]: - with self.subTest( - f"Detach and remove resource [{resc_name}] from [{resc_repl}]" - ): - # Detach ufs resource from the replication resource. - r = self.api.resources.remove_child(resc_repl, resc_name) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove ufs resource. - r = self.api.resources.remove(resc_name) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the resource no longer exists. - r = self.api.resources.stat(resc_name) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["exists"], False) - - # Remove replication resource. - r = self.api.resources.remove(resc_repl) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the resource no longer exists. - r = self.api.resources.stat(resc_repl) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["exists"], False) - - def testModifyMetadata(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Create a unixfilesystem resource. - r = self.api.resources.create( - "metadata_demo", "unixfilesystem", self.host, "/tmp/metadata_demo_vault", "" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - operations = [ - {"operation": "add", "attribute": "a1", "value": "v1", "units": "u1"} - ] - - # Add the metadata to the resource - r = self.api.resources.modify_metadata("metadata_demo", operations) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the metadata is on the resource - r = self.api.queries.execute_genquery( - "select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["rows"][0][0], "metadata_demo") - - # Remove the metadata from the resource. - operations = [ - {"operation": "remove", "attribute": "a1", "value": "v1", "units": "u1"} - ] - - r = self.api.resources.modify_metadata("metadata_demo", operations) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the metadata is no longer on the resource - r = self.api.queries.execute_genquery( - "select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'" - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(len(r["data"]["rows"]), 0) - - # Remove the resource - r = self.api.resources.remove("metadata_demo") - - def testModifyProperties(self): - self.api.setToken(self.rodsadmin_bearer_token) - - resource = "properties_demo" - - # Create a new resource. - r = self.api.resources.create(resource, "replication", "", "", "") - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - try: - # The list of updates to apply in sequence. - property_map = [ - ("name", "test_modifying_resource_properties_renamed"), - ("type", "passthru"), - ("host", "example.org"), - ("vault_path", "/tmp/test_modifying_resource_properties_vault"), - ("status", "down"), - ("status", "up"), - ("comments", "test_modifying_resource_properties_comments"), - ("information", "test_modifying_resource_properties_information"), - ("free_space", "test_modifying_resource_properties_free_space"), - ("context", "test_modifying_resource_properties_context"), - ] - - # Apply each update to the resource and verify that each one results - # in the expected results. - for p, v in property_map: - with self.subTest(f"Setting property [{p}] to value [{v}]"): - # Change a property of the resource. - r = self.api.resources.modify(resource, p, v) - - # Make sure to update the "resource" variable following a successful rename. - if "name" == p: - resource = v - - # Show the property was modified. - r = self.api.resources.stat(resource) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["info"][p], v) - finally: - # Remove the resource - r = self.api.resources.remove(resource) +class ResourcesTests(unittest.TestCase): + """Test iRODS resource operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for resource tests.""" + setup_class(cls, {"endpoint_name": "resources"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + def test_common_operations(self): + """Test common resource operations (create, list, stat, etc.).""" + self.api.set_token(self.rodsadmin_bearer_token) + + # TEMPORARY pre-test cleanup + # test is currently not passing, so cleanup occurs at the beginning to allow it + # to be run more than once in a row + self.api.resources.remove_child("test_repl", "test_ufs0") + self.api.resources.remove_child("test_repl", "test_ufs1") + self.api.resources.remove("test_ufs0") + self.api.resources.remove("test_ufs1") + self.api.resources.remove("test_repl") + + resc_repl = "test_repl" + resc_ufs0 = "test_ufs0" + resc_ufs1 = "test_ufs1" + + # Create three resources (replication w/ two unixfilesystem resources). + r = self.api.resources.create(resc_repl, "replication", "", "", "") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show the replication resource was created. + r = self.api.resources.stat(resc_repl) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], True) + self.assertIn("id", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["name"], resc_repl) + self.assertEqual(r["data"]["info"]["type"], "replication") + self.assertEqual(r["data"]["info"]["zone"], "tempZone") + self.assertEqual(r["data"]["info"]["host"], "EMPTY_RESC_HOST") + self.assertEqual(r["data"]["info"]["vault_path"], "EMPTY_RESC_PATH") + self.assertIn("status", r["data"]["info"]) + self.assertIn("context", r["data"]["info"]) + self.assertIn("comments", r["data"]["info"]) + self.assertIn("information", r["data"]["info"]) + self.assertIn("free_space", r["data"]["info"]) + self.assertIn("free_space_last_modified", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["parent_id"], "") + self.assertIn("created", r["data"]["info"]) + self.assertIn("last_modified", r["data"]["info"]) + self.assertIn("last_modified_millis", r["data"]["info"]) + + # Capture the replication resource's id. + # This resource is going to be the parent of the unixfilesystem resources. + # This value is needed to verify the relationship. + resc_repl_id = r["data"]["info"]["id"] + + for resc_name in [resc_ufs0, resc_ufs1]: + with self.subTest(f"Create and attach resource [{resc_name}] to [{resc_repl}]"): + vault_path = f"/tmp/{resc_name}_vault" # noqa: S108 + + # Create a unixfilesystem resource. + r = self.api.resources.create(resc_name, "unixfilesystem", self.host, vault_path, "") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Add the unixfilesystem resource as a child of the replication resource. + r = self.api.resources.add_child(resc_repl, resc_name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the resource was created and configured successfully. + r = self.api.resources.stat(resc_name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], True) + self.assertIn("id", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["name"], resc_name) + self.assertEqual(r["data"]["info"]["type"], "unixfilesystem") + self.assertEqual(r["data"]["info"]["zone"], self.zone_name) + self.assertEqual(r["data"]["info"]["host"], self.host) + self.assertEqual(r["data"]["info"]["vault_path"], vault_path) + self.assertIn("status", r["data"]["info"]) + self.assertIn("context", r["data"]["info"]) + self.assertIn("comments", r["data"]["info"]) + self.assertIn("information", r["data"]["info"]) + self.assertIn("free_space", r["data"]["info"]) + self.assertIn("free_space_last_modified", r["data"]["info"]) + self.assertEqual(r["data"]["info"]["parent_id"], resc_repl_id) + self.assertIn("created", r["data"]["info"]) + self.assertIn("last_modified", r["data"]["info"]) + + # Create a data object targeting the replication resource. + data_object = f"/{self.zone_name}/home/{self.rodsadmin_username}/resource_obj" + r = self.api.data_objects.write("These are the bytes to be written", data_object, resc_repl, 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show there are two replicas under the replication resource hierarchy. + r = self.api.queries.execute_genquery( + f"select DATA_NAME, RESC_NAME where DATA_NAME = '{pathlib.Path(data_object).name}'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 2) + + resc_tuple = (r["data"]["rows"][0][1], r["data"]["rows"][1][1]) + self.assertIn(resc_tuple, [(resc_ufs0, resc_ufs1), (resc_ufs1, resc_ufs0)]) + + # Trim a replica. + r = self.api.data_objects.trim(data_object, 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show there is only one replica under the replication resource hierarchy. + r = self.api.queries.execute_genquery( + f"select DATA_NAME, RESC_NAME where DATA_NAME = '{pathlib.Path(data_object).name}'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 1) + + # Launch rebalance + r = self.api.resources.rebalance(resc_repl) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Give the rebalance operation time to complete! + time.sleep(3) + + # + # Clean-up + # + + # Remove the data object. + r = self.api.data_objects.remove(data_object, 0, 1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove resources. + for resc_name in [resc_ufs0, resc_ufs1]: + with self.subTest(f"Detach and remove resource [{resc_name}] from [{resc_repl}]"): + # Detach ufs resource from the replication resource. + r = self.api.resources.remove_child(resc_repl, resc_name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove ufs resource. + r = self.api.resources.remove(resc_name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the resource no longer exists. + r = self.api.resources.stat(resc_name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], False) + + # Remove replication resource. + r = self.api.resources.remove(resc_repl) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the resource no longer exists. + r = self.api.resources.stat(resc_repl) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["exists"], False) + + def test_modify_metadata(self): + """Test modifying metadata on resources.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Create a unixfilesystem resource. + r = self.api.resources.create( + "metadata_demo", + "unixfilesystem", + self.host, + "/tmp/metadata_demo_vault", # noqa: S108 + "", + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + operations = [{"operation": "add", "attribute": "a1", "value": "v1", "units": "u1"}] + + # Add the metadata to the resource + r = self.api.resources.modify_metadata("metadata_demo", operations) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the metadata is on the resource + r = self.api.queries.execute_genquery( + "select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and " + "META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["rows"][0][0], "metadata_demo") + + # Remove the metadata from the resource. + operations = [{"operation": "remove", "attribute": "a1", "value": "v1", "units": "u1"}] + + r = self.api.resources.modify_metadata("metadata_demo", operations) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the metadata is no longer on the resource + r = self.api.queries.execute_genquery( + "select RESC_NAME where META_RESC_ATTR_NAME = 'a1' and " + "META_RESC_ATTR_VALUE = 'v1' and META_RESC_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 0) + + # Remove the resource + r = self.api.resources.remove("metadata_demo") + + def test_modify_properties(self): + """Test modifying resource properties.""" + self.api.set_token(self.rodsadmin_bearer_token) + + resource = "properties_demo" + + # Create a new resource. + r = self.api.resources.create(resource, "replication", "", "", "") + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + try: + # The list of updates to apply in sequence. + property_map = [ + ("name", "test_modifying_resource_properties_renamed"), + ("type", "passthru"), + ("host", "example.org"), + ("vault_path", "/tmp/test_modifying_resource_properties_vault"), # noqa: S108 + ("status", "down"), + ("status", "up"), + ("comments", "test_modifying_resource_properties_comments"), + ("information", "test_modifying_resource_properties_information"), + ("free_space", "test_modifying_resource_properties_free_space"), + ("context", "test_modifying_resource_properties_context"), + ] + + # Apply each update to the resource and verify that each one results + # in the expected results. + for p, v in property_map: + with self.subTest(f"Setting property [{p}] to value [{v}]"): + # Change a property of the resource. + r = self.api.resources.modify(resource, p, v) + + # Make sure to update the "resource" variable following a successful rename. + if p == "name": + resource = v + + # Show the property was modified. + r = self.api.resources.stat(resource) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["info"][p], v) + finally: + # Remove the resource + r = self.api.resources.remove(resource) # Tests for rule operations -class rulesTests(unittest.TestCase): +class RulesTests(unittest.TestCase): + """Test iRODS rule operations.""" - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "rules"}) + @classmethod + def setUpClass(cls): + """Set up class-level resources for rule tests.""" + setup_class(cls, {"endpoint_name": "rules"}) - @classmethod - def tearDownClass(cls): - tear_down_class(cls) + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") - def testList(self): - # Try listing rule engine plugins - r = self.api.rules.list_rule_engines() + def test_list(self): + """Test listing rule engine plugins.""" + # Try listing rule engine plugins + r = self.api.rules.list_rule_engines() - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertGreater(len(r["data"]["rule_engine_plugin_instances"]), 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertGreater(len(r["data"]["rule_engine_plugin_instances"]), 0) - def testExecuteRule(self): - test_msg = "This was run by the iRODS HTTP API test suite!" + def test_execute_rule(self): + """Test executing iRODS rules.""" + test_msg = "This was run by the iRODS HTTP API test suite!" - # Execute rule text against the iRODS rule language. - r = self.api.rules.execute( - f'writeLine("stdout", "{test_msg}")', - "irods_rule_engine_plugin-irods_rule_language-instance", - ) + # Execute rule text against the iRODS rule language. + r = self.api.rules.execute( + f'writeLine("stdout", "{test_msg}")', + "irods_rule_engine_plugin-irods_rule_language-instance", + ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["stderr"], None) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["stderr"], None) - # The REP always appends a newline character to the result. While we could trim the result, - # it is better to append a newline character to the expected result to guarantee things align. - self.assertEqual(r["data"]["stdout"], test_msg + "\n") + # The REP always appends a newline character to the result. While we could trim the result, + # it is better to append a newline character to the expected result to guarantee things align. + self.assertEqual(r["data"]["stdout"], test_msg + "\n") - def testRemoveDelayRule(self): - rep_instance = "irods_rule_engine_plugin-irods_rule_language-instance" + def test_remove_delay_rule(self): + """Test removing delayed execution rules.""" + rep_instance = "irods_rule_engine_plugin-irods_rule_language-instance" - # Schedule a delay rule to execute in the distant future. - r = self.api.rules.execute( - f'delay("{rep_instance}1h") {{ writeLine("serverLog", "iRODS HTTP API"); }}', - rep_instance, - ) + # Schedule a delay rule to execute in the distant future. + r = self.api.rules.execute( + f'delay("{rep_instance}1h") ' + f'{{ writeLine("serverLog", "iRODS HTTP API"); }}', + rep_instance, + ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - # Find the delay rule we just created. - # This query assumes the test suite is running on a system where no other delay - # rules are being created. - r = self.api.queries.execute_genquery("select max(RULE_EXEC_ID)") + # Find the delay rule we just created. + # This query assumes the test suite is running on a system where no other delay + # rules are being created. + r = self.api.queries.execute_genquery("select max(RULE_EXEC_ID)") - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(len(r["data"]["rows"]), 1) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 1) - # Remove the delay rule. - r = self.api.rules.remove_delay_rule(int(r["data"]["rows"][0][0])) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + # Remove the delay rule. + r = self.api.rules.remove_delay_rule(int(r["data"]["rows"][0][0])) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) -# Tests for query operations -class queryTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "query"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - def testCreateExecuteRemoveSpecificQuery(self): - - try: - - # As rodsadmin, create a specific query - self.api.setToken(self.rodsadmin_bearer_token) - - name = "get_users_count" - sql = "select count(*) from r_user_main" - r = self.api.queries.add_specific_query(name=name, sql=sql) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Switch to rodsuser and execute it - self.api.setToken(self.rodsuser_bearer_token) - r = self.api.queries.execute_specific_query(name=name) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(r["data"]["rows"][0][0], "3") - - finally: - - # Switch to rodsadmin and remove it - self.api.setToken(self.rodsadmin_bearer_token) - r = self.api.queries.remove_specific_query(name=name) +# Tests for query operations +class QueryTests(unittest.TestCase): + """Test iRODS query operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for query tests.""" + setup_class(cls, {"endpoint_name": "query"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + def test_create_execute_remove_specific_query(self): + """Test creating, executing, and removing specific queries.""" + try: + # As rodsadmin, create a specific query + self.api.set_token(self.rodsadmin_bearer_token) + + name = "get_users_count" + sql = "select count(*) from r_user_main" + r = self.api.queries.add_specific_query(name=name, sql=sql) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Switch to rodsuser and execute it + self.api.set_token(self.rodsuser_bearer_token) + r = self.api.queries.execute_specific_query(name=name) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(r["data"]["rows"][0][0], "3") + + finally: + # Switch to rodsadmin and remove it + self.api.set_token(self.rodsadmin_bearer_token) + r = self.api.queries.remove_specific_query(name=name) # Tests for tickets operations -class ticketsTests(unittest.TestCase): - - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "tickets"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - def testCreateAndRemove(self): - self.api.setToken(self.rodsuser_bearer_token) - - # Create a write ticket. - ticket_type = "write" - ticket_path = f"/{self.zone_name}/home/{self.rodsuser_username}" - ticket_use_count = 2000 - ticket_groups = "public" - ticket_hosts = self.host - r = self.api.tickets.create( - ticket_path, - ticket_type, - use_count=ticket_use_count, - seconds_until_expiration=3600, - users="rods,jeb", - groups=ticket_groups, - hosts=ticket_hosts, - ) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - ticket_string = r["data"]["ticket"] - self.assertGreater(len(ticket_string), 0) - - # Show the ticket exists and has the properties we defined during creation. - # We can use GenQuery for this, but it does seem better to provide a convenience operation - # for this. - r = self.api.queries.execute_genquery( - "select TICKET_STRING, TICKET_TYPE, TICKET_COLL_NAME, TICKET_USES_LIMIT, TICKET_ALLOWED_USER_NAME, TICKET_ALLOWED_GROUP_NAME, TICKET_ALLOWED_HOST" - ) - - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertIn(ticket_string, r["data"]["rows"][0]) - self.assertEqual(r["data"]["rows"][0][1], ticket_type) - self.assertEqual(r["data"]["rows"][0][2], ticket_path) - self.assertEqual(r["data"]["rows"][0][3], str(ticket_use_count)) - self.assertIn(r["data"]["rows"][0][4], ["rods", "jeb"]) - self.assertEqual(r["data"]["rows"][0][5], ticket_groups) - self.assertGreater(len(r["data"]["rows"][0][6]), 0) - - # Remove the ticket. - r = self.api.tickets.remove(ticket_string) - - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show the ticket no longer exists. - r = self.api.queries.execute_genquery("select TICKET_STRING") - - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - self.assertEqual(len(r["data"]["rows"]), 0) +class TicketsTests(unittest.TestCase): + """Test iRODS ticket operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for ticket tests.""" + setup_class(cls, {"endpoint_name": "tickets"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + def test_create_and_remove(self): + """Test creating and removing tickets.""" + self.api.set_token(self.rodsuser_bearer_token) + + # Create a write ticket. + ticket_type = "write" + ticket_path = f"/{self.zone_name}/home/{self.rodsuser_username}" + ticket_use_count = 2000 + ticket_groups = "public" + ticket_hosts = self.host + r = self.api.tickets.create( + ticket_path, + ticket_type, + use_count=ticket_use_count, + seconds_until_expiration=3600, + users="rods,jeb", + groups=ticket_groups, + hosts=ticket_hosts, + ) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + ticket_string = r["data"]["ticket"] + self.assertGreater(len(ticket_string), 0) + + # Show the ticket exists and has the properties we defined during creation. + # We can use GenQuery for this, but it does seem better to provide a convenience + # operation for this. + r = self.api.queries.execute_genquery( + "select TICKET_STRING, TICKET_TYPE, TICKET_COLL_NAME, TICKET_USES_LIMIT, " + "TICKET_ALLOWED_USER_NAME, TICKET_ALLOWED_GROUP_NAME, TICKET_ALLOWED_HOST" + ) + + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertIn(ticket_string, r["data"]["rows"][0]) + self.assertEqual(r["data"]["rows"][0][1], ticket_type) + self.assertEqual(r["data"]["rows"][0][2], ticket_path) + self.assertEqual(r["data"]["rows"][0][3], str(ticket_use_count)) + self.assertIn(r["data"]["rows"][0][4], ["rods", "jeb"]) + self.assertEqual(r["data"]["rows"][0][5], ticket_groups) + self.assertGreater(len(r["data"]["rows"][0][6]), 0) + + # Remove the ticket. + r = self.api.tickets.remove(ticket_string) + + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show the ticket no longer exists. + r = self.api.queries.execute_genquery("select TICKET_STRING") + + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + self.assertEqual(len(r["data"]["rows"]), 0) # Tests for user operations -class userTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "users-groups"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - def test_create_stat_and_remove_rodsuser(self): - self.api.setToken(self.rodsadmin_bearer_token) - - new_username = "test_user_rodsuser" - user_type = "rodsuser" - - # Create a new user. - r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r["status_code"], 200) - - # Stat the user. - r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - stat_info = r["data"] - self.assertEqual(stat_info["irods_response"]["status_code"], 0) - self.assertEqual(stat_info["exists"], True) - self.assertIn("id", stat_info) - self.assertEqual( - stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" - ) - self.assertEqual(stat_info["type"], user_type) - - # Remove the user. - r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - def test_set_password(self): - self.api.setToken(self.rodsadmin_bearer_token) - - new_username = "test_user_rodsuser" - user_type = "rodsuser" - - # Create a new user. - r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r["status_code"], 200) - - new_password = "new_password" - # Set a new password - r = self.api.users_groups.set_password( - new_username, self.zone_name, new_password - ) - self.assertEqual(r["status_code"], 200) - - # Try to get a token for the user - token = self.api.authenticate(new_username, new_password) - self.assertIsInstance(token, str) - - # Remove the user. - r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - def test_create_stat_and_remove_rodsadmin(self): - self.api.setToken(self.rodsadmin_bearer_token) - - new_username = "test_user_rodsadmin" - user_type = "rodsadmin" - headers = {"Authorization": "Bearer " + self.rodsadmin_bearer_token} - - # Create a new user. - r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r["status_code"], 200) - - # Stat the user. - r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - stat_info = r["data"] - self.assertEqual(stat_info["irods_response"]["status_code"], 0) - self.assertEqual(stat_info["exists"], True) - self.assertIn("id", stat_info) - self.assertEqual( - stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" - ) - self.assertEqual(stat_info["type"], user_type) - - # Remove the user. - r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - def test_create_stat_and_remove_groupadmin(self): - self.api.setToken(self.rodsadmin_bearer_token) - - new_username = "test_user_groupadmin" - user_type = "groupadmin" - - # Create a new user. - r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r["status_code"], 200) - - # Stat the user. - r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - stat_info = r["data"] - self.assertEqual(stat_info["irods_response"]["status_code"], 0) - self.assertEqual(stat_info["exists"], True) - self.assertIn("id", stat_info) - self.assertEqual( - stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" - ) - self.assertEqual(stat_info["type"], user_type) - - # Remove the user. - r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - def test_add_remove_user_to_and_from_group(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Create a new group. - new_group = "test_group" - r = self.api.users_groups.create_group(new_group) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Stat the group. - r = self.api.users_groups.stat(new_group) - self.assertEqual(r["status_code"], 200) - - stat_info = r["data"] - self.assertEqual(stat_info["irods_response"]["status_code"], 0) - self.assertEqual(stat_info["exists"], True) - self.assertIn("id", stat_info) - self.assertEqual(stat_info["type"], "rodsgroup") - - # Create a new user. - new_username = "test_user_rodsuser" - user_type = "rodsuser" - r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r["status_code"], 200) - - # Add user to group. - r = self.api.users_groups.add_to_group(new_username, self.zone_name, new_group) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the user is a member of the group. - r = self.api.users_groups.is_member_of_group( - new_group, new_username, self.zone_name - ) - self.assertEqual(r["status_code"], 200) - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertEqual(result["is_member"], True) - - # Remove user from group. - data = { - "op": "remove_from_group", - "group": new_group, - "user": new_username, - "zone": self.zone_name, - } - r = self.api.users_groups.remove_from_group( - new_username, self.zone_name, new_group - ) - - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Remove the user. - r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - # Remove group. - r = self.api.users_groups.remove_group(new_group) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that the group no longer exists. - params = {"op": "stat", "name": new_group} - r = self.api.users_groups.stat(new_group) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - stat_info = r["data"] - self.assertEqual(stat_info["irods_response"]["status_code"], 0) - self.assertEqual(stat_info["exists"], False) - - def test_only_a_rodsadmin_can_change_the_type_of_a_user(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Create a new user. - new_username = "test_user_rodsuser" - user_type = "rodsuser" - r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that a rodsadmin can change the type of the new user. - new_user_type = "groupadmin" - r = self.api.users_groups.set_user_type( - new_username, self.zone_name, new_user_type - ) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show that a non-admin cannot change the type of the new user. - self.api.setToken(self.rodsuser_bearer_token) - r = self.api.users_groups.set_user_type( - new_user_type, self.zone_name, new_user_type - ) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], -13000) - - # Show that the user type matches the type set by the rodsadmin. - params = {"op": "stat", "name": new_username, "zone": self.zone_name} - r = self.api.users_groups.stat(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - - stat_info = r["data"] - self.assertEqual(stat_info["irods_response"]["status_code"], 0) - self.assertEqual(stat_info["exists"], True) - self.assertEqual( - stat_info["local_unique_name"], f"{new_username}#{self.zone_name}" - ) - self.assertEqual(stat_info["type"], new_user_type) - - # Remove the user. - self.api.setToken(self.rodsadmin_bearer_token) - r = self.api.users_groups.remove_user(new_username, self.zone_name) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - def test_listing_all_users_in_zone(self): - self.api.setToken(self.rodsuser_bearer_token) - - r = self.api.users_groups.users() - self.assertEqual(r["status_code"], 200) - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertIn( - {"name": self.rodsadmin_username, "zone": self.zone_name}, result["users"] - ) - self.assertIn( - {"name": self.rodsuser_username, "zone": self.zone_name}, result["users"] - ) - - def test_listing_all_groups_in_zone(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Create a new group. - new_group = "test_group" - r = self.api.users_groups.create_group(new_group) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - self.api.setToken(self.rodsuser_bearer_token) - # Get all groups. - r = self.api.users_groups.groups() - self.assertEqual(r["status_code"], 200) - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertIn("public", result["groups"]) - self.assertIn(new_group, result["groups"]) - - self.api.setToken(self.rodsadmin_bearer_token) - # Remove the new group. - r = self.api.users_groups.remove_group(new_group) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - def test_modifying_metadata_atomically(self): - self.api.setToken(self.rodsadmin_bearer_token) - username = self.rodsuser_username - - # Add metadata to the user. - ops = [{"operation": "add", "attribute": "a1", "value": "v1", "units": "u1"}] - r = self.api.users_groups.modify_metadata(username, ops) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show the metadata exists on the user. - r = self.api.queries.execute_genquery( - "select USER_NAME where META_USER_ATTR_NAME = 'a1' and META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'" - ) - self.assertEqual(r["status_code"], 200) - - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertEqual(result["rows"][0][0], username) - - # Remove the metadata from the user. - ops = [{"operation": "remove", "attribute": "a1", "value": "v1", "units": "u1"}] - r = self.api.users_groups.modify_metadata(username, ops) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Show the metadata no longer exists on the user. - r = self.api.queries.execute_genquery( - "select USER_NAME where META_USER_ATTR_NAME = 'a1' and META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'" - ) - self.assertEqual(r["status_code"], 200) - - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertEqual(len(result["rows"]), 0) +class UserTests(unittest.TestCase): + """Test iRODS user and group operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for user tests.""" + setup_class(cls, {"endpoint_name": "users-groups"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + def test_create_stat_and_remove_rodsuser(self): + """Test creating, querying, and removing rodsuser users.""" + self.api.set_token(self.rodsadmin_bearer_token) + + new_username = "test_user_rodsuser" + user_type = "rodsuser" + + # Create a new user. + r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) + self.assertEqual(r["status_code"], 200) + + # Stat the user. + r = self.api.users_groups.stat(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual(stat_info["local_unique_name"], f"{new_username}#{self.zone_name}") + self.assertEqual(stat_info["type"], user_type) + + # Remove the user. + r = self.api.users_groups.remove_user(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + def test_set_password(self): + """Test setting user passwords.""" + self.api.set_token(self.rodsadmin_bearer_token) + + new_username = "test_user_rodsuser" + user_type = "rodsuser" + + # Create a new user. + r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) + self.assertEqual(r["status_code"], 200) + + new_password = "new_password" # noqa: S105 + # Set a new password + r = self.api.users_groups.set_password(new_username, self.zone_name, new_password) + self.assertEqual(r["status_code"], 200) + + # Try to get a token for the user + token = self.api.authenticate(new_username, new_password) + self.assertIsInstance(token, str) + + # Remove the user. + r = self.api.users_groups.remove_user(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + def test_create_stat_and_remove_rodsadmin(self): + """Test creating, querying, and removing rodsadmin users.""" + self.api.set_token(self.rodsadmin_bearer_token) + + new_username = "test_user_rodsadmin" + user_type = "rodsadmin" + + # Create a new user. + r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) + self.assertEqual(r["status_code"], 200) + + # Stat the user. + r = self.api.users_groups.stat(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual(stat_info["local_unique_name"], f"{new_username}#{self.zone_name}") + self.assertEqual(stat_info["type"], user_type) + + # Remove the user. + r = self.api.users_groups.remove_user(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + def test_create_stat_and_remove_groupadmin(self): + """Test creating, querying, and removing groupadmin users.""" + self.api.set_token(self.rodsadmin_bearer_token) + + new_username = "test_user_groupadmin" + user_type = "groupadmin" + + # Create a new user. + r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) + self.assertEqual(r["status_code"], 200) + + # Stat the user. + r = self.api.users_groups.stat(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual(stat_info["local_unique_name"], f"{new_username}#{self.zone_name}") + self.assertEqual(stat_info["type"], user_type) + + # Remove the user. + r = self.api.users_groups.remove_user(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + def test_add_remove_user_to_and_from_group(self): + """Test adding and removing users from groups.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Create a new group. + new_group = "test_group" + r = self.api.users_groups.create_group(new_group) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Stat the group. + r = self.api.users_groups.stat(new_group) + self.assertEqual(r["status_code"], 200) + + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertIn("id", stat_info) + self.assertEqual(stat_info["type"], "rodsgroup") + + # Create a new user. + new_username = "test_user_rodsuser" + user_type = "rodsuser" + r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) + self.assertEqual(r["status_code"], 200) + + # Add user to group. + r = self.api.users_groups.add_to_group(new_username, self.zone_name, new_group) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the user is a member of the group. + r = self.api.users_groups.is_member_of_group(new_group, new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["is_member"], True) + + # Remove user from group. + r = self.api.users_groups.remove_from_group(new_username, self.zone_name, new_group) + + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Remove the user. + r = self.api.users_groups.remove_user(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + # Remove group. + r = self.api.users_groups.remove_group(new_group) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that the group no longer exists. + r = self.api.users_groups.stat(new_group) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], False) + + def test_only_a_rodsadmin_can_change_the_type_of_a_user(self): + """Test that only rodsadmin users can change user type.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Create a new user. + new_username = "test_user_rodsuser" + user_type = "rodsuser" + r = self.api.users_groups.create_user(new_username, self.zone_name, user_type) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that a rodsadmin can change the type of the new user. + new_user_type = "groupadmin" + r = self.api.users_groups.set_user_type(new_username, self.zone_name, new_user_type) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show that a non-admin cannot change the type of the new user. + self.api.set_token(self.rodsuser_bearer_token) + r = self.api.users_groups.set_user_type(new_user_type, self.zone_name, new_user_type) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], -13000) + + # Show that the user type matches the type set by the rodsadmin. + r = self.api.users_groups.stat(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + + stat_info = r["data"] + self.assertEqual(stat_info["irods_response"]["status_code"], 0) + self.assertEqual(stat_info["exists"], True) + self.assertEqual(stat_info["local_unique_name"], f"{new_username}#{self.zone_name}") + self.assertEqual(stat_info["type"], new_user_type) + + # Remove the user. + self.api.set_token(self.rodsadmin_bearer_token) + r = self.api.users_groups.remove_user(new_username, self.zone_name) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + def test_listing_all_users_in_zone(self): + """Test listing all users in the zone.""" + self.api.set_token(self.rodsuser_bearer_token) + + r = self.api.users_groups.users() + self.assertEqual(r["status_code"], 200) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertIn({"name": self.rodsadmin_username, "zone": self.zone_name}, result["users"]) + self.assertIn({"name": self.rodsuser_username, "zone": self.zone_name}, result["users"]) + + def test_listing_all_groups_in_zone(self): + """Test listing all groups in the zone.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Create a new group. + new_group = "test_group" + r = self.api.users_groups.create_group(new_group) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + self.api.set_token(self.rodsuser_bearer_token) + # Get all groups. + r = self.api.users_groups.groups() + self.assertEqual(r["status_code"], 200) + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertIn("public", result["groups"]) + self.assertIn(new_group, result["groups"]) + + self.api.set_token(self.rodsadmin_bearer_token) + # Remove the new group. + r = self.api.users_groups.remove_group(new_group) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + def test_modifying_metadata_atomically(self): + """Test atomically modifying user metadata.""" + self.api.set_token(self.rodsadmin_bearer_token) + username = self.rodsuser_username + + # Add metadata to the user. + ops = [{"operation": "add", "attribute": "a1", "value": "v1", "units": "u1"}] + r = self.api.users_groups.modify_metadata(username, ops) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show the metadata exists on the user. + r = self.api.queries.execute_genquery( + "select USER_NAME where META_USER_ATTR_NAME = 'a1' and " + "META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["status_code"], 200) + + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["rows"][0][0], username) + + # Remove the metadata from the user. + ops = [{"operation": "remove", "attribute": "a1", "value": "v1", "units": "u1"}] + r = self.api.users_groups.modify_metadata(username, ops) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Show the metadata no longer exists on the user. + r = self.api.queries.execute_genquery( + "select USER_NAME where META_USER_ATTR_NAME = 'a1' and " + "META_USER_ATTR_VALUE = 'v1' and META_USER_ATTR_UNITS = 'u1'" + ) + self.assertEqual(r["status_code"], 200) + + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(len(result["rows"]), 0) # Tests for zone operations -class zoneTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - setup_class(cls, {"endpoint_name": "zones"}) - - @classmethod - def tearDownClass(cls): - tear_down_class(cls) - - def setUp(self): - self.assertFalse( - self._class_init_error, "Class initialization failed. Cannot continue." - ) - - def test_report_operation(self): - self.api.setToken(self.rodsadmin_bearer_token) - r = self.api.zones.report() - self.assertEqual(r["status_code"], 200) - - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - - zone_report = result["zone_report"] - self.assertIn("zones", zone_report) - self.assertGreaterEqual(len(zone_report["zones"]), 1) - self.assertIn( - "schema_version", zone_report["zones"][0]["servers"][0]["server_config"] - ) - - def test_adding_removing_and_modifying_zones(self): - self.api.setToken(self.rodsadmin_bearer_token) - - # Add a remote zone to the local zone. - # The new zone will not have any connection information or anything else. - zone_name = "other_zone" - r = self.api.zones.add(zone_name) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - try: - # Show the new zone exists by executing the stat operation on it. - r = self.api.zones.stat(zone_name) - self.assertEqual(r["status_code"], 200) - - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertEqual(result["exists"], True) - self.assertEqual(result["info"]["name"], zone_name) - self.assertEqual(result["info"]["type"], "remote") - self.assertEqual(result["info"]["connection_info"], "") - self.assertEqual(result["info"]["comment"], "") - - # The properties to update. - property_map = [ - ("name", "other_zone_renamed"), - ("connection_info", "example.org:1247"), - ("comment", "updated comment"), - ] - - # Change the properties of the new zone. - for p, v in property_map: - with self.subTest(f"Setting property [{p}] to value [{v}]"): - r = self.api.zones.modify(zone_name, p, v) - self.assertEqual(r["status_code"], 200) - self.assertEqual(r["data"]["irods_response"]["status_code"], 0) - - # Capture the new name of the zone following its renaming. - if "name" == p: - zone_name = v - - # Show the new zone was modified successfully. - r = self.api.zones.stat(zone_name) - self.assertEqual(r["status_code"], 200) - - result = r["data"] - self.assertEqual(result["irods_response"]["status_code"], 0) - self.assertEqual(result["exists"], True) - self.assertEqual(result["info"][p], v) - - finally: - # Remove the remote zone. - r = self.api.zones.remove(zone_name) - self.assertEqual(r["status_code"], 200) +class ZoneTests(unittest.TestCase): + """Test iRODS zone operations.""" + + @classmethod + def setUpClass(cls): + """Set up class-level resources for zone tests.""" + setup_class(cls, {"endpoint_name": "zones"}) + + @classmethod + def tearDownClass(cls): + """Tear down class-level resources.""" + tear_down_class(cls) + + def setUp(self): + """Check that class initialization succeeded before each test.""" + self.assertFalse(self._class_init_error, "Class initialization failed. Cannot continue.") + + def test_report_operation(self): + """Test the zone report operation.""" + self.api.set_token(self.rodsadmin_bearer_token) + r = self.api.zones.report() + self.assertEqual(r["status_code"], 200) + + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + + zone_report = result["zone_report"] + self.assertIn("zones", zone_report) + self.assertGreaterEqual(len(zone_report["zones"]), 1) + self.assertIn("schema_version", zone_report["zones"][0]["servers"][0]["server_config"]) + + def test_adding_removing_and_modifying_zones(self): + """Test adding, removing, and modifying zones.""" + self.api.set_token(self.rodsadmin_bearer_token) + + # Add a remote zone to the local zone. + # The new zone will not have any connection information or anything else. + zone_name = "other_zone" + r = self.api.zones.add(zone_name) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + try: + # Show the new zone exists by executing the stat operation on it. + r = self.api.zones.stat(zone_name) + self.assertEqual(r["status_code"], 200) + + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["exists"], True) + self.assertEqual(result["info"]["name"], zone_name) + self.assertEqual(result["info"]["type"], "remote") + self.assertEqual(result["info"]["connection_info"], "") + self.assertEqual(result["info"]["comment"], "") + + # The properties to update. + property_map = [ + ("name", "other_zone_renamed"), + ("connection_info", "example.org:1247"), + ("comment", "updated comment"), + ] + + # Change the properties of the new zone. + for p, v in property_map: + with self.subTest(f"Setting property [{p}] to value [{v}]"): + r = self.api.zones.modify(zone_name, p, v) + self.assertEqual(r["status_code"], 200) + self.assertEqual(r["data"]["irods_response"]["status_code"], 0) + + # Capture the new name of the zone following its renaming. + if p == "name": + zone_name = v + + # Show the new zone was modified successfully. + r = self.api.zones.stat(zone_name) + self.assertEqual(r["status_code"], 200) + + result = r["data"] + self.assertEqual(result["irods_response"]["status_code"], 0) + self.assertEqual(result["exists"], True) + self.assertEqual(result["info"][p], v) + + finally: + # Remove the remote zone. + r = self.api.zones.remove(zone_name) + self.assertEqual(r["status_code"], 200) if __name__ == "__main__": - unittest.main() + unittest.main()