irods-http 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, The University of North Carolina at Chapel Hill
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: irods-http
3
+ Version: 0.1.0
4
+ Summary: A Python wrapper for the iRODS HTTP API
5
+ Author-email: iRODS Consortium <info@irods.org>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Repository, https://github.com/irods/irods_client_http_python
8
+ Project-URL: Issues, https://github.com/irods/irods_client_http_python/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests
15
+ Dynamic: license-file
16
+
17
+ # iRODS HTTP API Python Wrapper
18
+
19
+ This is a Python wrapper for the [iRODS HTTP API](https://github.com/irods/irods_client_http_api).
20
+
21
+ Documentation for the endpoint operations can be found [here](https://github.com/irods/irods_client_http_api/blob/main/API.md).
22
+
23
+ ## Install
24
+
25
+ This wrapper is available via pip:
26
+
27
+ ```
28
+ pip install irods-http
29
+ ```
30
+
31
+ ## Usage
32
+ To use the wrapper, follow the steps listed below.
33
+
34
+ ```py
35
+ import irods_http
36
+
37
+ # Placeholder values needed for irods_http.authenticate()
38
+ url_base = "http://<host>:<port>/irods-http-api/<version>"
39
+ username = "<username>"
40
+ password = "<password>"
41
+
42
+ # Create an IRODSHTTPSession to an iRODS HTTP API server
43
+ session = irods_http.authenticate(url_base, username, password)
44
+
45
+ # Use the session for all other operations
46
+ response = irods_http.collections.create(session, '/<zone_name>/home/<username>/new_collection')
47
+
48
+ # Check the resopnse for errors
49
+ if response['status_code'] != 200:
50
+ # Handle HTTP error.
51
+
52
+ if response['data']['irods_response']['status_code'] < 0:
53
+ # Handle iRODS error.
54
+ ```
55
+
56
+ The response dict will have this format:
57
+ ```py
58
+ {
59
+ 'status_code': <integer>,
60
+ 'data': <dict>
61
+ }
62
+ ```
63
+ where `status_code` is the HTTP status code from the response, and `data` is the result of the iRODS operation.
64
+
65
+ `response['data']` will contain a dict named `irods_response`, which will contain the `status_code` returned by the iRODS Server as well as any other expected properties.
66
+
67
+ ```py
68
+ {
69
+ 'irods_response': {
70
+ 'status_code': <integer>
71
+ # Other properties vary between endpoints
72
+ }
73
+ }
74
+ ```
75
+
76
+ When calling `data_objects.read()`, the `response['data']` will contain the raw bytes instead of a dict.
77
+
78
+ More information regarding iRODS HTTP API response data is available [here](https://github.com/irods/irods_client_http_api/blob/main/API.md).
@@ -0,0 +1,62 @@
1
+ # iRODS HTTP API Python Wrapper
2
+
3
+ This is a Python wrapper for the [iRODS HTTP API](https://github.com/irods/irods_client_http_api).
4
+
5
+ Documentation for the endpoint operations can be found [here](https://github.com/irods/irods_client_http_api/blob/main/API.md).
6
+
7
+ ## Install
8
+
9
+ This wrapper is available via pip:
10
+
11
+ ```
12
+ pip install irods-http
13
+ ```
14
+
15
+ ## Usage
16
+ To use the wrapper, follow the steps listed below.
17
+
18
+ ```py
19
+ import irods_http
20
+
21
+ # Placeholder values needed for irods_http.authenticate()
22
+ url_base = "http://<host>:<port>/irods-http-api/<version>"
23
+ username = "<username>"
24
+ password = "<password>"
25
+
26
+ # Create an IRODSHTTPSession to an iRODS HTTP API server
27
+ session = irods_http.authenticate(url_base, username, password)
28
+
29
+ # Use the session for all other operations
30
+ response = irods_http.collections.create(session, '/<zone_name>/home/<username>/new_collection')
31
+
32
+ # Check the resopnse for errors
33
+ if response['status_code'] != 200:
34
+ # Handle HTTP error.
35
+
36
+ if response['data']['irods_response']['status_code'] < 0:
37
+ # Handle iRODS error.
38
+ ```
39
+
40
+ The response dict will have this format:
41
+ ```py
42
+ {
43
+ 'status_code': <integer>,
44
+ 'data': <dict>
45
+ }
46
+ ```
47
+ where `status_code` is the HTTP status code from the response, and `data` is the result of the iRODS operation.
48
+
49
+ `response['data']` will contain a dict named `irods_response`, which will contain the `status_code` returned by the iRODS Server as well as any other expected properties.
50
+
51
+ ```py
52
+ {
53
+ 'irods_response': {
54
+ 'status_code': <integer>
55
+ # Other properties vary between endpoints
56
+ }
57
+ }
58
+ ```
59
+
60
+ When calling `data_objects.read()`, the `response['data']` will contain the raw bytes instead of a dict.
61
+
62
+ More information regarding iRODS HTTP API response data is available [here](https://github.com/irods/irods_client_http_api/blob/main/API.md).
@@ -0,0 +1,31 @@
1
+ """iRODS HTTP client library for Python."""
2
+
3
+ from . import (
4
+ collections,
5
+ data_objects,
6
+ queries,
7
+ resources,
8
+ rules,
9
+ tickets,
10
+ users_groups,
11
+ zones,
12
+ )
13
+ from .irods_http import (
14
+ IRODSHTTPSession,
15
+ authenticate,
16
+ get_server_info,
17
+ )
18
+
19
+ __all__ = [
20
+ "IRODSHTTPSession",
21
+ "authenticate",
22
+ "collections",
23
+ "data_objects",
24
+ "get_server_info",
25
+ "queries",
26
+ "resources",
27
+ "rules",
28
+ "tickets",
29
+ "users_groups",
30
+ "zones",
31
+ ]
@@ -0,0 +1,307 @@
1
+ """Collection operations for iRODS HTTP API."""
2
+
3
+ import builtins
4
+ import json
5
+
6
+ import requests
7
+
8
+ from . import common
9
+ from .irods_http import IRODSHTTPSession # noqa: TC001
10
+
11
+
12
+ def create(session: IRODSHTTPSession, lpath: str, create_intermediates: int = 0) -> dict:
13
+ """
14
+ Create a new collection.
15
+
16
+ Args:
17
+ session: IRODSHTTPSession object containing the base URL and authentication token.
18
+ lpath: The absolute logical path of the collection to be created.
19
+ create_intermediates: Set to 1 to create intermediates, otherwise set to 0. Defaults to 0.
20
+
21
+ Returns:
22
+ A dict containing the HTTP status code and iRODS response.
23
+ The iRODS response is only valid if no error occurred during HTTP communication.
24
+ """
25
+ common.validate_not_none(session.token)
26
+ common.validate_instance(lpath, str)
27
+ common.validate_0_or_1(create_intermediates)
28
+
29
+ data = {
30
+ "op": "create",
31
+ "lpath": lpath,
32
+ "create-intermediates": create_intermediates,
33
+ }
34
+
35
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
36
+ return common.process_response(r)
37
+
38
+
39
+ def remove(session: IRODSHTTPSession, lpath: str, recurse: int = 0, no_trash: int = 0) -> dict:
40
+ """
41
+ Remove an existing collection.
42
+
43
+ Args:
44
+ session: IRODSHTTPSession object containing the base URL and authentication token.
45
+ lpath: The absolute logical path of the collection to be removed.
46
+ recurse: Set to 1 to remove contents of the collection, otherwise set to 0. Defaults to 0.
47
+ no_trash: Set to 1 to permanently remove, 0 to move to trash. Defaults to 0.
48
+
49
+ Returns:
50
+ A dict containing the HTTP status code and iRODS response.
51
+ The iRODS response is only valid if no error occurred during HTTP communication.
52
+ """
53
+ common.validate_not_none(session.token)
54
+ common.validate_instance(lpath, str)
55
+ common.validate_0_or_1(recurse)
56
+ common.validate_0_or_1(no_trash)
57
+
58
+ data = {
59
+ "op": "remove",
60
+ "lpath": lpath,
61
+ "recurse": recurse,
62
+ "no-trash": no_trash,
63
+ }
64
+
65
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
66
+ return common.process_response(r)
67
+
68
+
69
+ def stat(session: IRODSHTTPSession, lpath: str, ticket: str = "") -> dict:
70
+ """
71
+ Give information about a collection.
72
+
73
+ Args:
74
+ session: IRODSHTTPSession object containing the base URL and authentication token.
75
+ lpath: The absolute logical path of the collection being accessed.
76
+ ticket: Ticket to be enabled before the operation. Defaults to an empty string.
77
+
78
+ Returns:
79
+ A dict containing the HTTP status code and iRODS response.
80
+ The iRODS response is only valid if no error occurred during HTTP communication.
81
+ """
82
+ common.validate_not_none(session.token)
83
+ common.validate_instance(lpath, str)
84
+ common.validate_instance(ticket, str)
85
+
86
+ params = {"op": "stat", "lpath": lpath, "ticket": ticket}
87
+
88
+ r = requests.get(session.url_base + "/collections", params=params, headers=session.get_headers) # noqa: S113
89
+ return common.process_response(r)
90
+
91
+
92
+ def list(session: IRODSHTTPSession, lpath: str, recurse: int = 0, ticket: str = "") -> dict: # noqa: A001
93
+ """
94
+ Show the contents of a collection.
95
+
96
+ Args:
97
+ session: IRODSHTTPSession object containing the base URL and authentication token.
98
+ lpath: The absolute logical path of the collection to have its contents listed.
99
+ recurse: Set to 1 to list the contents of objects in the collection,
100
+ otherwise set to 0. Defaults to 0.
101
+ ticket: Ticket to be enabled before the operation. Defaults to an empty string.
102
+
103
+ Returns:
104
+ A dict containing the HTTP status code and iRODS response.
105
+ The iRODS response is only valid if no error occurred during HTTP communication.
106
+ """
107
+ common.validate_not_none(session.token)
108
+ common.validate_instance(lpath, str)
109
+ common.validate_0_or_1(recurse)
110
+ common.validate_instance(ticket, str)
111
+
112
+ params = {"op": "list", "lpath": lpath, "recurse": recurse, "ticket": ticket}
113
+
114
+ r = requests.get(session.url_base + "/collections", params=params, headers=session.get_headers) # noqa: S113
115
+ return common.process_response(r)
116
+
117
+
118
+ def set_permission(
119
+ session: IRODSHTTPSession,
120
+ lpath: str,
121
+ entity_name: str,
122
+ permission: str,
123
+ admin: int = 0,
124
+ ) -> dict:
125
+ """
126
+ Set the permission of a user for a given collection.
127
+
128
+ Args:
129
+ session: IRODSHTTPSession object containing the base URL and authentication token.
130
+ lpath: The absolute logical path of the collection to have a permission set.
131
+ entity_name: The name of the user or group having its permission set.
132
+ permission: The permission level being set. Either 'null', 'read', 'write', or 'own'.
133
+ admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0.
134
+
135
+ Returns:
136
+ A dict containing the HTTP status code and iRODS response.
137
+ The iRODS response is only valid if no error occurred during HTTP communication.
138
+
139
+ Raises:
140
+ ValueError: If permission is not 'null', 'read', 'write', or 'own'.
141
+ """
142
+ common.validate_not_none(session.token)
143
+ common.validate_instance(lpath, str)
144
+ common.validate_instance(entity_name, str)
145
+ common.validate_instance(permission, str)
146
+ if permission not in ["null", "read", "write", "own"]:
147
+ raise ValueError("permission must be either 'null', 'read', 'write', or 'own'")
148
+ common.validate_0_or_1(admin)
149
+
150
+ data = {
151
+ "op": "set_permission",
152
+ "lpath": lpath,
153
+ "entity-name": entity_name,
154
+ "permission": permission,
155
+ "admin": admin,
156
+ }
157
+
158
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
159
+ return common.process_response(r)
160
+
161
+
162
+ def set_inheritance(session: IRODSHTTPSession, lpath: str, enable: int, admin: int = 0) -> dict:
163
+ """
164
+ Set the inheritance for a collection.
165
+
166
+ Args:
167
+ session: IRODSHTTPSession object containing the base URL and authentication token.
168
+ lpath: The absolute logical path of the collection to have its inheritance set.
169
+ enable: Set to 1 to enable inheritance, or 0 to disable.
170
+ admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0.
171
+
172
+ Returns:
173
+ A dict containing the HTTP status code and iRODS response.
174
+ The iRODS response is only valid if no error occurred during HTTP communication.
175
+ """
176
+ common.validate_not_none(session.token)
177
+ common.validate_instance(lpath, str)
178
+ common.validate_0_or_1(enable)
179
+ common.validate_0_or_1(admin)
180
+
181
+ data = {
182
+ "op": "set_inheritance",
183
+ "lpath": lpath,
184
+ "enable": enable,
185
+ "admin": admin,
186
+ }
187
+
188
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
189
+ return common.process_response(r)
190
+
191
+
192
+ def modify_permissions(session: IRODSHTTPSession, lpath: str, operations: dict, admin: int = 0) -> dict:
193
+ """
194
+ Modify permissions for multiple users or groups for a collection.
195
+
196
+ Args:
197
+ session: IRODSHTTPSession object containing the base URL and authentication token.
198
+ lpath: The absolute logical path of the collection to have its permissions modified.
199
+ operations: Dictionary containing the operations to carry out. Should contain names
200
+ and permissions for all operations.
201
+ admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0.
202
+
203
+ Returns:
204
+ A dict containing the HTTP status code and iRODS response.
205
+ The iRODS response is only valid if no error occurred during HTTP communication.
206
+ """
207
+ common.validate_not_none(session.token)
208
+ common.validate_instance(lpath, str)
209
+ common.validate_instance(operations, builtins.list)
210
+ common.validate_instance(operations[0], dict)
211
+ common.validate_0_or_1(admin)
212
+
213
+ data = {
214
+ "op": "modify_permissions",
215
+ "lpath": lpath,
216
+ "operations": json.dumps(operations),
217
+ "admin": admin,
218
+ }
219
+
220
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
221
+ return common.process_response(r)
222
+
223
+
224
+ def modify_metadata(session: IRODSHTTPSession, lpath: str, operations: dict, admin: int = 0) -> dict:
225
+ """
226
+ Modify the metadata for a collection.
227
+
228
+ Args:
229
+ session: IRODSHTTPSession object containing the base URL and authentication token.
230
+ lpath: The absolute logical path of the collection to have its metadata modified.
231
+ operations: Dictionary containing the operations to carry out. Should contain the
232
+ operation, attribute, value, and optionally units.
233
+ admin: Set to 1 to run this operation as an admin, otherwise set to 0. Defaults to 0.
234
+
235
+ Returns:
236
+ A dict containing the HTTP status code and iRODS response.
237
+ The iRODS response is only valid if no error occurred during HTTP communication.
238
+ """
239
+ common.validate_not_none(session.token)
240
+ common.validate_instance(lpath, str)
241
+ common.validate_instance(operations, builtins.list)
242
+ common.validate_instance(operations[0], dict)
243
+ common.validate_0_or_1(admin)
244
+
245
+ data = {
246
+ "op": "modify_metadata",
247
+ "lpath": lpath,
248
+ "operations": json.dumps(operations),
249
+ "admin": admin,
250
+ }
251
+
252
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
253
+ return common.process_response(r)
254
+
255
+
256
+ def rename(session: IRODSHTTPSession, old_lpath: str, new_lpath: str) -> dict:
257
+ """
258
+ Rename or move a collection.
259
+
260
+ Args:
261
+ session: IRODSHTTPSession object containing the base URL and authentication token.
262
+ old_lpath: The current absolute logical path of the collection.
263
+ new_lpath: The absolute logical path of the destination for the collection.
264
+
265
+ Returns:
266
+ A dict containing the HTTP status code and iRODS response.
267
+ The iRODS response is only valid if no error occurred during HTTP communication.
268
+ """
269
+ common.validate_not_none(session.token)
270
+ common.validate_instance(old_lpath, str)
271
+ common.validate_instance(new_lpath, str)
272
+
273
+ data = {"op": "rename", "old-lpath": old_lpath, "new-lpath": new_lpath}
274
+
275
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
276
+ return common.process_response(r)
277
+
278
+
279
+ def touch(session: IRODSHTTPSession, lpath: str, seconds_since_epoch: int = -1, reference: str = "") -> dict:
280
+ """
281
+ Update mtime for a collection.
282
+
283
+ Args:
284
+ session: IRODSHTTPSession object containing the base URL and authentication token.
285
+ lpath: The absolute logical path of the collection being touched.
286
+ seconds_since_epoch: The value to set mtime to, defaults to -1 as a flag.
287
+ reference: The absolute logical path of the collection to use as a reference for mtime.
288
+
289
+ Returns:
290
+ A dict containing the HTTP status code and iRODS response.
291
+ The iRODS response is only valid if no error occurred during HTTP communication.
292
+ """
293
+ common.validate_not_none(session.token)
294
+ common.validate_instance(lpath, str)
295
+ common.validate_gte_minus1(seconds_since_epoch)
296
+ common.validate_instance(reference, str)
297
+
298
+ data = {"op": "touch", "lpath": lpath}
299
+
300
+ if seconds_since_epoch != -1:
301
+ data["seconds-since-epoch"] = seconds_since_epoch
302
+
303
+ if reference != "":
304
+ data["reference"] = reference
305
+
306
+ r = requests.post(session.url_base + "/collections", headers=session.post_headers, data=data) # noqa: S113
307
+ return common.process_response(r)
@@ -0,0 +1,107 @@
1
+ """Common utility functions for iRODS HTTP client operations."""
2
+
3
+
4
+ def process_response(r):
5
+ """
6
+ Process an HTTP response and return standardized response dict.
7
+
8
+ Args:
9
+ r: The HTTP response object.
10
+
11
+ Returns:
12
+ A dict with 'status_code' and 'data' keys containing the HTTP status code
13
+ and parsed JSON response body (or None if response is empty).
14
+ """
15
+ rdict = r.json() if r.text != "" else None
16
+ return {"status_code": r.status_code, "data": rdict}
17
+
18
+
19
+ def validate_not_none(x):
20
+ """
21
+ Validate that a value is not None.
22
+
23
+ Args:
24
+ x: The value to validate.
25
+
26
+ Raises:
27
+ ValueError: If x is None
28
+ """
29
+ if x is None:
30
+ raise ValueError
31
+
32
+
33
+ def validate_instance(x, expected_type):
34
+ """
35
+ Validate that a value is an instance of the expected type.
36
+
37
+ Args:
38
+ x: The value to validate.
39
+ expected_type: The expected type.
40
+
41
+ Raises:
42
+ TypeError: If x is not an instance of expected_type.
43
+ """
44
+ if not isinstance(x, expected_type):
45
+ raise TypeError
46
+
47
+
48
+ def validate_0_or_1(x):
49
+ """
50
+ Validate that a value is either 0 or 1.
51
+
52
+ Args:
53
+ x: The value to validate (must be an int).
54
+
55
+ Raises:
56
+ TypeError: If x is not an integer.
57
+ ValueError: If x is not 0 or 1.
58
+ """
59
+ validate_instance(x, int)
60
+ if x not in [0, 1]:
61
+ raise ValueError(f"{x} must be 0 or 1")
62
+
63
+
64
+ def validate_gte_zero(x):
65
+ """
66
+ Validate that a value is greater than or equal to zero.
67
+
68
+ Args:
69
+ x: The value to validate (must be an int).
70
+
71
+ Raises:
72
+ TypeError: If x is not an integer.
73
+ ValueError: If x is less than 0.
74
+ """
75
+ validate_instance(x, int)
76
+ if not x >= 0:
77
+ raise ValueError(f"{x} must be >= 0")
78
+
79
+
80
+ def validate_gte_minus1(x):
81
+ """
82
+ Validate that a value is greater than or equal to -1.
83
+
84
+ Args:
85
+ x: The value to validate (must be an int).
86
+
87
+ Raises:
88
+ TypeError: If x is not an integer.
89
+ ValueError: If x is less than -1.
90
+ """
91
+ validate_instance(x, int)
92
+ if not x >= -1:
93
+ raise ValueError(f"{x} must be >= 0, or flag value of -1")
94
+
95
+
96
+ def assert_success(cls, response):
97
+ """
98
+ Validate HTTP and iRODS status codes are successes.
99
+
100
+ Args:
101
+ cls: The unittest.TestCase class
102
+ response: The response from the iRODS HTTP API request
103
+ """
104
+ # HTTP status code
105
+ cls.assertEqual(response["status_code"], 200)
106
+ # iRODS status code
107
+ cls.assertEqual(response["data"]["irods_response"]["status_code"], 0)