diff --git a/.gitignore b/.gitignore index 247a900..783571e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,9 @@ nosetests.xml Session.vim .netrwhist *~ + +# emacs +\#*\# + +# virtualenv +testenv diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c5cacd5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +python-irodsclient was originally created by Chris LaRose when he was a member +of the atmosphere team at the iPlant Collaborative. + +Antoine de Torcy recently provided support for iRODS 4 and integration into +the iRODS build system. + +Chris LaRose +Antoine de Torcy +J. Matt Peterson +David Knapp +Steve "esteve" Gregory \ No newline at end of file diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..83d579d --- /dev/null +++ b/CHANGES @@ -0,0 +1 @@ +0.3.1 - Initial pypi release. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51273ad --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2010-2014, The Arizona Board of Regents on behalf of The University of Arizona + +All rights reserved. + +Developed by: iPlant Collaborative as a collaboration between participants at BIO5 at The University of Arizona (the primary hosting institution), Cold Spring Harbor Laboratory, The University of Texas at Austin, and individual contributors. Find out more at http://www.iplantcollaborative.org/. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the iPlant Collaborative, BIO5, The University of Arizona, Cold Spring Harbor Laboratory, The University of Texas at Austin, nor the names of other 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/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..868732e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include AUTHORS CHANGES LICENSE README.md \ No newline at end of file diff --git a/README.md b/README.md index 1e143a8..5c8238f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,15 @@ -python-irodsclient +# Now maintained by iRODS at [irods/python-irodsclient](https://github.com/irods/python-irodsclient/) + +For version 0.5.0, see https://github.com/irods/python-irodsclient/ + +Or, install via GitHub: +``` +pip install git+git://github.com/irods/python-irodsclient.git +``` + +--- + +python-irodsclient - version 0.4.0 ============ [iRODS](https://www.irods.org) is an open-source distributed filesystem manager. This a client API implemented in python. diff --git a/Testing.md b/Testing.md deleted file mode 100644 index f65ad33..0000000 --- a/Testing.md +++ /dev/null @@ -1,16 +0,0 @@ -TESTING NOTES -============= - -| Test | Status | Notes | -| ------------- |-------------|------------| -| browse_test.py | fail | `File "browse_test.py", line 12, in test_get_collection coll = sess.get_collection(path) AttributeError: 'iRODSSession' object has no attribute 'get_collection'`| -| coll_test.py | fail | requires username, password, host; fixable| -| file_test.py | fail |`File "file_test.py", line 9, in obj = sess.get_data_object("/tempZone/home/rods/test1") AttributeError: 'iRODSSession' object has no attribute 'get_data_object'`| -| message_test.py | pass || -| meta_test.py | fail | ` - File "meta_test.py", line 10, in - obj = sess.get_data_object("/tempZone/home/rods/test1") -AttributeError: 'iRODSSession' object has no attribute 'get_data_object'` | -| query_test.py | || -| test_connection.py | || - diff --git a/irods/api_number.py b/irods/api_number.py index ca8030b..01d3fed 100644 --- a/irods/api_number.py +++ b/irods/api_number.py @@ -150,7 +150,7 @@ "DATA_OBJ_WRITE201_AN": 604, "DATA_OBJ_CLOSE201_AN": 605, "DATA_OBJ_LSEEK201_AN": 612, - "COLL_CREATE201_AN": 616, + "COLL_CREATE_AN": 681, "RM_COLL_OLD201_AN": 617, "REG_COLL201_AN": 618, "MOD_COLL201_AN": 646, diff --git a/irods/collection.py b/irods/collection.py index d72ae85..a2bb5bb 100644 --- a/irods/collection.py +++ b/irods/collection.py @@ -42,6 +42,9 @@ def data_objects(self): def remove(self, recurse=True, force=False, additional_flags={}): self.manager.remove(self.path, recurse, force, additional_flags) + + def move(self, path): + pass def walk(self, topdown=True): """ diff --git a/irods/connection.py b/irods/connection.py index c22ee23..2b73d25 100644 --- a/irods/connection.py +++ b/irods/connection.py @@ -4,9 +4,8 @@ import hashlib from irods.message import (iRODSMessage, StartupPack, AuthResponse, AuthChallenge, - FileReadRequest, FileWriteRequest, FileSeekRequest, FileSeekResponse, - FileCloseRequest) -from irods.exception import get_exception_by_code + OpenedDataObjRequest, FileSeekResponse, StringStringMap) +from irods.exception import get_exception_by_code, NetworkException from irods import MAX_PASSWORD_LENGTH from irods.api_number import api_number @@ -27,7 +26,12 @@ def __del__(self): def send(self, message): str = message.pack() logger.debug(str) - return self.socket.sendall(str) + try: + self.socket.sendall(str) + except: + logger.error("Unable to send message. Connection to remote host may have closed. Releasing connection from pool.") + self.release(True) + raise NetworkException("Unable to send message") def recv(self): msg = iRODSMessage.recv(self.socket) @@ -41,8 +45,8 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): self.release() - def release(self): - self.pool.release_connection(self) + def release(self, destroy=False): + self.pool.release_connection(self, destroy) def _connect(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -90,12 +94,17 @@ def _login(self): auth_response = self.recv() def read_file(self, desc, size): - message_body = FileReadRequest( - l1descInx=desc, - len=size + message_body = OpenedDataObjRequest( + l1descInx=desc, + len=size, + whence=0, + oprType=0, + offset=0, + bytesWritten=0, + KeyValPair_PI=StringStringMap() ) message = iRODSMessage('RODS_API_REQ', msg=message_body, - int_info=api_number['DATA_OBJ_READ201_AN']) + int_info=api_number['DATA_OBJ_READ_AN']) logger.debug(desc) self.send(message) @@ -103,25 +112,34 @@ def read_file(self, desc, size): return response.bs def write_file(self, desc, string): - message_body = FileWriteRequest( - dataObjInx=desc, - len=len(string) + message_body = OpenedDataObjRequest( + l1descInx=desc, + len=len(string), + whence=0, + oprType=0, + offset=0, + bytesWritten=0, + KeyValPair_PI=StringStringMap() ) message = iRODSMessage('RODS_API_REQ', msg=message_body, bs=string, - int_info=api_number['DATA_OBJ_WRITE201_AN']) + int_info=api_number['DATA_OBJ_WRITE_AN']) self.send(message) response = self.recv() return response.int_info def seek_file(self, desc, offset, whence): - message_body = FileSeekRequest( - fileInx=desc, - offset=offset, - whence=whence + message_body = OpenedDataObjRequest( + l1descInx=desc, + len=0, + whence=whence, + oprType=0, + offset=offset, + bytesWritten=0, + KeyValPair_PI=StringStringMap() ) message = iRODSMessage('RODS_API_REQ', msg=message_body, - int_info=api_number['DATA_OBJ_LSEEK201_AN']) + int_info=api_number['DATA_OBJ_LSEEK_AN']) self.send(message) response = self.recv() @@ -129,11 +147,17 @@ def seek_file(self, desc, offset, whence): return offset def close_file(self, desc): - message_body = FileCloseRequest( - l1descInx=desc + message_body = OpenedDataObjRequest( + l1descInx=desc, + len=0, + whence=0, + oprType=0, + offset=0, + bytesWritten=0, + KeyValPair_PI=StringStringMap() ) message = iRODSMessage('RODS_API_REQ', msg=message_body, - int_info=api_number['DATA_OBJ_CLOSE201_AN']) + int_info=api_number['DATA_OBJ_CLOSE_AN']) self.send(message) response = self.recv() diff --git a/irods/exception.py b/irods/exception.py index f7e88c1..ed424e1 100644 --- a/irods/exception.py +++ b/irods/exception.py @@ -4,6 +4,9 @@ class PycommandsException(Exception): pass +class NetworkException(PycommandsException): + pass + class DoesNotExist(PycommandsException): pass diff --git a/irods/message/__init__.py b/irods/message/__init__.py index 9484f2d..a3a9dd5 100644 --- a/irods/message/__init__.py +++ b/irods/message/__init__.py @@ -181,36 +181,22 @@ class FileOpenRequest(Message): oprType = IntegerProperty() KeyValPair_PI = SubmessageProperty(StringStringMap) -#define dataObjReadInp_PI "int l1descInx; int len;" -class FileReadRequest(Message): - _name = 'dataObjReadInp_PI' +#define OpenedDataObjInp_PI "int l1descInx; int len; int whence; int oprType; double offset; double bytesWritten; struct KeyValPair_PI;" +class OpenedDataObjRequest(Message): + _name = 'OpenedDataObjInp_PI' l1descInx = IntegerProperty() len = IntegerProperty() - -#define dataObjWriteInp_PI "int dataObjInx; int len;" -class FileWriteRequest(Message): - _name = 'dataObjWriteInp_PI' - dataObjInx = IntegerProperty() - len = IntegerProperty() - -#define fileLseekInp_PI "int fileInx; double offset; int whence" -class FileSeekRequest(Message): - _name = 'fileLseekInp_PI' - fileInx = IntegerProperty() - offset = LongProperty() whence = IntegerProperty() + oprType = IntegerProperty() + offset = LongProperty() + bytesWritten = LongProperty() + KeyValPair_PI = SubmessageProperty(StringStringMap) #define fileLseekOut_PI "double offset;" class FileSeekResponse(Message): _name = 'fileLseekOut_PI' offset = LongProperty() -#define dataObjCloseInp_PI "int l1descInx; double bytesWritten;" -class FileCloseRequest(Message): - _name = 'dataObjCloseInp_PI' - l1descInx = IntegerProperty() - bytesWritten = LongProperty() - #define ModAVUMetadataInp_PI "str *arg0; str *arg1; str *arg2; str *arg3; str *arg4; str *arg5; str *arg6; str *arg7; str *arg8; str *arg9;" class MetadataRequest(Message): _name = 'ModAVUMetadataInp_PI' @@ -233,8 +219,10 @@ def __init__(self, *args): #define CollInp_PI "str collName[MAX_NAME_LEN]; struct KeyValPair_PI;" class CollectionRequest(Message): - _name = 'CollInp_PI' + _name = 'CollInpNew_PI' collName = StringProperty() + flags = IntegerProperty() + oprType = IntegerProperty() KeyValPair_PI = SubmessageProperty(StringStringMap) def empty_gen_query_out(cols): diff --git a/irods/pool.py b/irods/pool.py index d2bfcad..d6ebd93 100644 --- a/irods/pool.py +++ b/irods/pool.py @@ -22,8 +22,10 @@ def get_connection(self): logger.debug('num active: %d' % len(self.active)) return conn - def release_connection(self, conn): + def release_connection(self, conn, destroy=False): with self._lock: - self.active.remove(conn) - self.idle.add(conn) + if conn in self.active: + self.active.remove(conn) + if not destroy: + self.idle.add(conn) logger.debug('num idle: %d' % len(self.idle)) diff --git a/irods/resource_manager/collection_manager.py b/irods/resource_manager/collection_manager.py index 2f70f6b..481876d 100644 --- a/irods/resource_manager/collection_manager.py +++ b/irods/resource_manager/collection_manager.py @@ -20,7 +20,7 @@ def create(self, path): KeyValPair_PI=StringStringMap() ) message = iRODSMessage('RODS_API_REQ', msg=message_body, - int_info=api_number['COLL_CREATE201_AN']) + int_info=api_number['COLL_CREATE_AN']) with self.sess.pool.get_connection() as conn: conn.send(message) response = conn.recv() @@ -38,10 +38,9 @@ def remove(self, path, recurse=True, force=False, additional_flags={}): KeyValPair_PI=StringStringMap(options) ) message = iRODSMessage('RODS_API_REQ', msg=message_body, - int_info=api_number['RM_COLL_OLD201_AN']) + int_info=api_number['RM_COLL_AN']) with self.sess.pool.get_connection() as conn: conn.send(message) response = conn.recv() - def move(self, path): - pass + diff --git a/irods/resource_manager/data_object_manager.py b/irods/resource_manager/data_object_manager.py index 437cd64..b5c5231 100644 --- a/irods/resource_manager/data_object_manager.py +++ b/irods/resource_manager/data_object_manager.py @@ -2,11 +2,8 @@ from irods.models import DataObject from irods.resource_manager import ResourceManager -from irods.message import (iRODSMessage, FileReadRequest, FileWriteRequest, - FileSeekRequest, FileSeekResponse, FileOpenRequest, FileCloseRequest, - StringStringMap) -from irods.exception import (DataObjectDoesNotExist, CollectionDoesNotExist, - NoResultFound) +from irods.message import (iRODSMessage, FileOpenRequest, StringStringMap) +from irods.exception import (DataObjectDoesNotExist, CollectionDoesNotExist) from irods.api_number import api_number from irods.data_object import iRODSDataObject diff --git a/irods/resource_manager/metadata_manager.py b/irods/resource_manager/metadata_manager.py index 18938cd..613a5c1 100644 --- a/irods/resource_manager/metadata_manager.py +++ b/irods/resource_manager/metadata_manager.py @@ -17,7 +17,7 @@ def _model_class_to_resource_type(model_cls): DataObject: 'd', Collection: 'c', Resource: 'r', - User: 'r', + User: 'u', }[model_cls] def get(self, model_cls, path): diff --git a/irods/results.py b/irods/results.py index 7b01b72..60422b2 100644 --- a/irods/results.py +++ b/irods/results.py @@ -34,3 +34,14 @@ def __iter__(self): def __len__(self): return self.length + + # For testing. Might go somewhere else... + def has_value(self, value): + found = False + + for row in self.rows: + if value in row.values(): + found = True + + return found + diff --git a/irods/session.py b/irods/session.py index ebfd5d9..edb4a23 100644 --- a/irods/session.py +++ b/irods/session.py @@ -13,6 +13,17 @@ def __init__(self, *args, **kwargs): self.collections = CollectionManager(self) self.data_objects = DataObjectManager(self) self.metadata = MetadataManager(self) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.cleanup() + + def cleanup(self): + for conn in self.pool.active | self.pool.idle: + conn.disconnect() + conn.release(True) def configure(self, host=None, port=1247, user=None, zone=None, password=None, client_user=None, client_zone=None): diff --git a/irods/test/Testing.md b/irods/test/Testing.md index c4eb821..3ed8011 100644 --- a/irods/test/Testing.md +++ b/irods/test/Testing.md @@ -27,7 +27,7 @@ This imports all tests in the `test` directory and runs them. It will not die up Test Dependencies ----------------- -Testing appears to rely on a local irods directory. +A valid account on a running iRODS grid. See `./config.py`. Current Test Results -------------------- @@ -35,11 +35,10 @@ Current Test Results | Test | Status | Notes | | ------------- |-------------|------------| -| browse_test.py | fails | `File "browse_test.py", line 12, in test_get_collection coll = sess.get_collection(path) AttributeError: 'iRODSSession' object has no attribute 'get_collection'`| -| coll_test.py | fails | requires username, password, host; fixable| -| file_test.py | fails | `File "file_test.py", line 9, in obj = sess.get_data_object("/tempZone/home/rods/test1") AttributeError: 'iRODSSession' object has no attribute 'get_data_object'`| -| message_test.py | succeeds || -| meta_test.py | fails || -| query_test.py | fails || -| test_connection.py | fails || +| collection_test.py | OK || +| connection_test.py | OK || +| file_test.py | OK || +| message_test.py | OK || +| meta_test.py | OK || +| query_test.py | OK || diff --git a/irods/test/collection_test.py b/irods/test/collection_test.py index 7072b20..b652760 100644 --- a/irods/test/collection_test.py +++ b/irods/test/collection_test.py @@ -2,30 +2,30 @@ import os import sys import unittest +from irods.session import iRODSSession +import config class TestCollection(unittest.TestCase): - test_coll_path = "/tempZone/home/rods/test_dir" + test_coll_path = '/{0}/home/{1}/test_dir'.format(config.IRODS_SERVER_ZONE, config.IRODS_USER_USERNAME) def setUp(self): - from irods.session import iRODSSession - import config - self.sess = iRODSSession(host=config.IRODS_SERVER_HOST, port=config.IRODS_SERVER_PORT, user=config.IRODS_USER_USERNAME, password=config.IRODS_USER_PASSWORD, zone=config.IRODS_SERVER_ZONE) - self.coll = self.sess.create_collection(self.test_coll_path) + self.coll = self.sess.collections.create(self.test_coll_path) def tearDown(self): """ Delete the test collection after each test """ - self.coll.remove() + self.coll.remove(recurse=True, force=True) + self.sess.cleanup() def test_get_collection(self): #path = "/tempZone/home/rods" - coll = self.sess.get_collection(self.test_coll_path) + coll = self.sess.collections.get(self.test_coll_path) self.assertEquals(self.test_coll_path, coll.path) #def test_new_collection(self): @@ -43,6 +43,7 @@ def test_update_in_collection(self): """ Modify a file in a collection """ pass + @unittest.skip('Renaming collections is not yet implemented') def test_move_collection(self): new_path = "/tempZone/home/rods/test_dir_moved" self.coll.move(new_path) diff --git a/irods/test/connection_test.py b/irods/test/connection_test.py index 952cd81..ed7f217 100644 --- a/irods/test/connection_test.py +++ b/irods/test/connection_test.py @@ -2,6 +2,8 @@ import unittest import os import sys +from irods.session import iRODSSession +import config class TestConnections(unittest.TestCase): @@ -9,14 +11,16 @@ class TestConnections(unittest.TestCase): """ def setUp(self): - from irods.session import iRODSSession - import config - self.sess = iRODSSession(host=config.IRODS_SERVER_HOST, port=config.IRODS_SERVER_PORT, # 4444: why? user=config.IRODS_USER_USERNAME, password=config.IRODS_USER_PASSWORD, zone=config.IRODS_SERVER_ZONE) + + def tearDown(self): + '''Close connections + ''' + self.sess.cleanup() def test_connection(self): """ @@ -24,7 +28,7 @@ def test_connection(self): There should be a better way to test this... Wouldn't the iRODSSession init establish the connection? """ - coll = self.sess.get_collection('/tempZone/home/rods') + coll = self.sess.collections.get('/{0}/home/{1}'.format(config.IRODS_SERVER_ZONE, config.IRODS_USER_USERNAME)) self.assertTrue(coll, "Connection failed.") @unittest.skip("unimplemented") diff --git a/irods/test/file_test.py b/irods/test/file_test.py index 23a428c..22bf052 100644 --- a/irods/test/file_test.py +++ b/irods/test/file_test.py @@ -2,49 +2,86 @@ import unittest import os import sys +from irods.session import iRODSSession +import config class TestFiles(unittest.TestCase): - """ - """ + '''Suite of data object I/O unit tests + ''' + test_coll_path = '/{0}/home/{1}/test_dir'.format(config.IRODS_SERVER_ZONE, config.IRODS_USER_USERNAME) + test_obj_name = 'test1' + content_str = 'blah' + write_str = '0123456789' + write_str1 = 'INTERRUPT' + + test_obj_path = test_coll_path + '/' + test_obj_name def setUp(self): - from irods.session import iRODSSession - import config - self.sess = iRODSSession(host=config.IRODS_SERVER_HOST, port=config.IRODS_SERVER_PORT, # 4444: why? user=config.IRODS_USER_USERNAME, password=config.IRODS_USER_PASSWORD, zone=config.IRODS_SERVER_ZONE) + + # Create test collection + self.test_coll = self.sess.collections.create(self.test_coll_path) + + # Create test object and write to it + self.test_obj = self.sess.data_objects.create(self.test_obj_path) + with self.test_obj.open('r+') as f: + f.write(self.content_str) + + + def tearDown(self): + '''Remove test data and close connections + ''' + self.test_coll.remove(recurse=True, force=True) + self.sess.cleanup() def test_file_open(self): #from irods.models import Collection, User, DataObject - obj = self.sess.get_data_object("/tempZone/home/rods/test1") + obj = self.sess.data_objects.get(self.test_obj_path) f = obj.open('r+') f.seek(0, 0) # what does this return? - str1 = f.read() + + # for lack of anything better... + assert (f.tell() == 0) + + #str1 = f.read() #self.assertTrue(expr, msg) f.close() def test_file_read(self): #from irods.models import Collection, User, DataObject - obj = self.sess.get_data_object("/tempZone/home/rods/test1") + obj = self.sess.data_objects.get(self.test_obj_path) f = obj.open('r+') str1 = f.read(1024) #self.assertTrue(expr, msg) + + # check content of test file + assert(str1 == self.content_str) + f.close() def test_file_write(self): #from irods.models import Collection, User, DataObject - obj = self.sess.get_data_object("/tempZone/home/rods/test1") + obj = self.sess.data_objects.get(self.test_obj_path) f = obj.open('w+') - f.write("NEW STRING.py") + f.write(self.write_str) f.seek(-6, 2) - f.write("INTERRUPT") + f.write(self.write_str1) + + # reset stream position for reading + f.seek(0, 0) + + # check new content of file after our write + str1 = f.read(1024) + assert(str1 == (self.write_str[:-6] + self.write_str1)) + #self.assertTrue(expr, msg) f.close() diff --git a/irods/test/message_test.py b/irods/test/message_test.py index 020ebfb..dfa10f5 100644 --- a/irods/test/message_test.py +++ b/irods/test/message_test.py @@ -16,7 +16,7 @@ class TestMessages(unittest.TestCase): def test_startup_pack(self): - sup = StartupPack() + sup = StartupPack(('rods', 'tempZone'), ('rods', 'tempZone')) sup.irodsProt = 2 sup.reconnFlag = 3 sup.proxyUser = "rods" @@ -30,6 +30,7 @@ def test_startup_pack(self): expected = "\ 2\ 3\ +0\ rods\ tempZone\ rods\ @@ -40,7 +41,7 @@ def test_startup_pack(self): " self.assertEqual(xml_str, expected) - sup2 = StartupPack() + sup2 = StartupPack(('rods', 'tempZone'), ('rods', 'tempZone')) sup2.unpack(ET.fromstring(expected)) self.assertEqual(sup2.irodsProt, 2) self.assertEqual(sup2.reconnFlag, 3) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index a96f034..6811fea 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -2,54 +2,148 @@ import unittest import os import sys +from irods.meta import iRODSMeta +from irods.models import (DataObject, Collection, Resource, User, DataObjectMeta, + CollectionMeta, ResourceMeta, UserMeta) +from irods.session import iRODSSession +import config + + class TestMeta(unittest.TestCase): - """ - """ + '''Suite of tests on metadata operations + ''' + + # test data + coll_path = '/{0}/home/{1}/test_dir'.format(config.IRODS_SERVER_ZONE, config.IRODS_USER_USERNAME) + obj_name = 'test1' + obj_path = '{0}/{1}'.format(coll_path, obj_name) + + # test metadata + (attr0, value0, unit0) = ('attr0', 'value0', 'unit0') + (attr1, value1, unit1) = ('attr1', 'value1', 'unit1') + def setUp(self): - from irods.session import iRODSSession - import config - self.sess = iRODSSession(host=config.IRODS_SERVER_HOST, port=config.IRODS_SERVER_PORT, user=config.IRODS_USER_USERNAME, password=config.IRODS_USER_PASSWORD, zone=config.IRODS_SERVER_ZONE) + + # Create test collection and (empty) test object + self.coll = self.sess.collections.create(self.coll_path) + self.obj = self.sess.data_objects.create(self.obj_path) + + + def tearDown(self): + '''Remove test data and close connections + ''' + self.coll.remove(recurse=True, force=True) + self.sess.cleanup() - def test_get_meta(self): + def test_get_obj_meta(self): """ """ - #from irods.meta import iRODSMeta - - #obj = self.sess.get_data_object("/tempZone/home/rods/test1") - meta = self.sess.get_meta('d', "/tempZone/home/rods/test1") - print meta + # get object metadata + meta = self.sess.metadata.get(DataObject, self.obj_path) + + # there should be no metadata at this point + assert (len(meta) == 0) + #self.assertEqual(first, second, msg) - def test_add_meta(self): + + def test_add_obj_meta(self): + """ + """ + + # add metadata to test object + self.sess.metadata.add(DataObject, self.obj_path, + iRODSMeta(self.attr0, self.value0)) + self.sess.metadata.add(DataObject, self.obj_path, + iRODSMeta(self.attr1, self.value1, self.unit1)) + + # get object metadata + meta = self.sess.metadata.get(DataObject, self.obj_path) + + # assertions + assert(meta[0].name == self.attr0) + assert(meta[0].value == self.value0) + + assert(meta[1].name == self.attr1) + assert(meta[1].value == self.value1) + assert(meta[1].units == self.unit1) + + + def test_copy_obj_meta(self): """ """ - from irods.meta import iRODSMeta + + # test destination object for copy + dest_obj_path = self.coll_path + '/test2' + self.sess.data_objects.create(dest_obj_path) + + # add metadata to test object + self.sess.metadata.add(DataObject, self.obj_path, + iRODSMeta(self.attr0, self.value0)) + + # copy metadata + self.sess.metadata.copy(DataObject, DataObject, self.obj_path, dest_obj_path) + + # get destination object metadata + dest_meta = self.sess.metadata.get(DataObject, dest_obj_path) + + # check metadata + assert(dest_meta[0].name == self.attr0) - self.sess.add_meta('d', '/tempZone/home/rods/test1', - iRODSMeta('key8', 'value5')) - def test_copy_meta(self): + def test_remove_obj_meta(self): """ """ - #from irods.meta import iRODSMeta - self.sess.copy_meta('d', 'd', '/tempZone/home/rods/test1', - '/tempZone/home/rods/test2') + + # add metadata to test object + self.sess.metadata.add(DataObject, self.obj_path, + iRODSMeta(self.attr0, self.value0)) + + # check that metadata is there + meta = self.sess.metadata.get(DataObject, self.obj_path) + assert(meta[0].name == self.attr0) - def test_remove_meta(self): + # remove metadata from object + self.sess.metadata.remove(DataObject, self.obj_path, + iRODSMeta(self.attr0, self.value0)) + + # check that metadata is gone + meta = self.sess.metadata.get(DataObject, self.obj_path) + assert (len(meta) == 0) + + + def test_add_coll_meta(self): """ """ - from irods.meta import iRODSMeta - self.sess.remove_meta('d', '/tempZone/home/rods/test1', - iRODSMeta('key8', 'value5')) + # add metadata to test collection + self.sess.metadata.add(Collection, self.coll_path, + iRODSMeta(self.attr0, self.value0)) + + # get collection metadata + meta = self.sess.metadata.get(Collection, self.coll_path) + + # assertions + assert(meta[0].name == self.attr0) + assert(meta[0].value == self.value0) + + # remove collection metadata + self.sess.metadata.remove(Collection, self.coll_path, + iRODSMeta(self.attr0, self.value0)) + + # check that metadata is gone + meta = self.sess.metadata.get(Collection, self.coll_path) + assert (len(meta) == 0) + + if __name__ == '__main__': diff --git a/irods/test/query_test.py b/irods/test/query_test.py index 559b0e8..3e92a7f 100644 --- a/irods/test/query_test.py +++ b/irods/test/query_test.py @@ -2,48 +2,64 @@ import unittest import os import sys +from irods.models import User, Collection, Keywords, DataObject +from irods.session import iRODSSession +import config class TestQuery(unittest.TestCase): """ """ + # test data + coll_path = '/{0}/home/{1}/test_dir'.format(config.IRODS_SERVER_ZONE, config.IRODS_USER_USERNAME) + obj_name = 'test1' + obj_path = '{0}/{1}'.format(coll_path, obj_name) + def setUp(self): - from irods.session import iRODSSession - import config - self.sess = iRODSSession(host=config.IRODS_SERVER_HOST, port=config.IRODS_SERVER_PORT, # 4444 why? user=config.IRODS_USER_USERNAME, password=config.IRODS_USER_PASSWORD, zone=config.IRODS_SERVER_ZONE) - - def test_query(self): - #from irods.query import Query - from irods.models import User, Collection, Keywords - - q1 = self.sess.query(User, Collection.name) - q2 = q1.filter(User.name == 'cjlarose') - q3 = q2.filter(Keywords.chksum == '12345') - - f = open('select', 'w') - f.write(q3._select_message().pack()) - - f = open('conds', 'w') - f.write(q3._conds_message().pack()) - - f = open('condskw', 'w') - f.write(q3._kw_message().pack()) - - f = open('genq', 'w') - f.write(q3._message().pack()) - - self.sess.query(Collection.id, Collection.name).all() + + # Create test collection and (empty) test object + self.coll = self.sess.collections.create(self.coll_path) + self.obj = self.sess.data_objects.create(self.obj_path) + + def tearDown(self): + '''Remove test data and close connections + ''' + self.coll.remove(recurse=True, force=True) + self.sess.cleanup() + + + def test_collections_query(self): + # collection query test + result = self.sess.query(Collection.id, Collection.name).all() + assert ( result.has_value(self.coll_path) ) + +# q1 = self.sess.query(User, Collection.name) +# q2 = q1.filter(User.name == 'cjlarose') +# q3 = q2.filter(Keywords.chksum == '12345') +# +# f = open('select', 'w') +# f.write(q1._select_message().pack()) +# +# f = open('conds', 'w') +# f.write(q1._conds_message().pack()) +# +# f = open('condskw', 'w') +# f.write(q1._kw_message().pack()) +# +# f = open('genq', 'w') +# f.write(q1._message().pack()) + + #print result """ cut-n-pasted from collection_test... """ - from irods.models import Collection, User, DataObject #q1 = sess.query(Collection.id).filter(Collection.name == "'/tempZone/home/rods'") #q1.all() @@ -54,10 +70,16 @@ def test_query(self): #result = sess.query(Collection.id, Collection.owner_name, User.id, User.name)\ # .filter(Collection.owner_name == "'rods'")\ # .all() - + + #print result + + + + def test_files_query(self): + # file query test result = self.sess.query(DataObject.id, DataObject.collection_id, DataObject.name, User.name, Collection.name).all() + assert ( result.has_value(self.obj_name) ) - print str(result) if __name__ == '__main__': diff --git a/setup.py b/setup.py index 23c9b60..3c8939d 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ -from distutils.core import setup -setup( - name='python-irodsclient', - version='0.1', - author='Chris LaRose', - author_email='cjlarose@iplantcollaborative.org', - packages=['irods', 'irods.message', 'irods.resource_manager'], - install_requires=[ - 'PrettyTable>=0.7,<1.0' - ] -) +from setuptools import setup, find_packages + + +setup(name='python-irodsclient', + version='0.4.0', + author='iPlant Collaborative', + author_email='atmodevs@gmail.com', + description='A python API for iRODS', + url='https://github.com/iPlantCollaborativeOpenSource/python-irodsclient', + packages=find_packages(), + install_requires=['PrettyTable>=0.7.2'])