66import os
77import weakref
88from typing import Any , List , Type
9- from irods .models import DataObject , Collection
10- from irods .manager import Manager
11- from irods .manager ._internal import _api_impl , _logical_path
12- from irods .message import (
13- iRODSMessage ,
14- FileOpenRequest ,
15- ObjCopyRequest ,
16- StringStringMap ,
17- DataObjInfo_for_session ,
18- ModDataObjMeta_for_session ,
19- DataObjChksumRequest ,
20- DataObjChksumResponse ,
21- RErrorStack ,
22- STR_PI ,
23- )
9+
10+ import irods .client_configuration as client_config
2411import irods .exception as ex
12+ import irods .keywords as kw
13+ from irods import parallel
2514from irods .api_number import api_number
2615from irods .collection import iRODSCollection
2716from irods .data_object import (
28- iRODSDataObject ,
29- iRODSDataObjectFileRaw ,
3017 chunks ,
31- irods_dirname ,
3218 irods_basename ,
19+ irods_dirname ,
20+ iRODSDataObject ,
21+ iRODSDataObjectFileRaw ,
3322)
34- import irods .client_configuration as client_config
35- import irods .keywords as kw
36- import irods .parallel as parallel
23+ from irods .manager import Manager
24+ from irods .manager ._internal import _api_impl , _logical_path
25+ from irods .message import (
26+ STR_PI ,
27+ DataObjChksumRequest ,
28+ DataObjChksumResponse ,
29+ DataObjInfo_for_session ,
30+ FileOpenRequest ,
31+ ModDataObjMeta_for_session ,
32+ ObjCopyRequest ,
33+ StringStringMap ,
34+ iRODSMessage ,
35+ )
36+ from irods .models import Collection , DataObject
3737from irods .parallel import deferred_call
3838
39-
4039logger = logging .getLogger (__name__ )
4140
4241_update_types : List [Type ] = []
@@ -218,27 +217,29 @@ def should_parallelize_transfer(
218217 if size is not None and isinstance (open_options , dict ):
219218 open_options [kw .DATA_SIZE_KW ] = size
220219
221- def _download (self , obj , local_path , num_threads , updatables = (), ** options ):
220+ def _download (self , obj_path , local_path , num_threads , updatables = (), ** options ):
222221 """Transfer the contents of a data object to a local file.
223222
224223 Called from get() when a local path is named.
225224 """
226- if os .path .isdir (local_path ):
227- local_file = os .path .join (local_path , irods_basename (obj ))
228- else :
229- local_file = local_path
225+
226+ local_file = (
227+ os .path .join (local_path , irods_basename (obj_path )) # noqa: PTH118
228+ if os .path .isdir (local_path ) # noqa: PTH112
229+ else local_path
230+ )
230231
231232 # Check for force flag if local_file exists
232233 if os .path .exists (local_file ) and kw .FORCE_FLAG_KW not in options :
233234 raise ex .OVERWRITE_WITHOUT_FORCE_FLAG
234235
235236 data_open_returned_values_ = {}
236- with self .open (obj , "r" , returned_values = data_open_returned_values_ , ** options ) as o :
237+ with self .open (obj_path , "r" , returned_values = data_open_returned_values_ , ** options ) as o :
237238 if self .should_parallelize_transfer (num_threads , o , open_options = options .items ()):
238239 error = RuntimeError ("parallel get failed" )
239240 try :
240241 if not self .parallel_get (
241- (obj , o ),
242+ (obj_path , o ),
242243 local_file ,
243244 num_threads = num_threads ,
244245 target_resource_name = options .get (kw .RESC_NAME_KW , "" ),
@@ -256,12 +257,46 @@ def _download(self, obj, local_path, num_threads, updatables=(), **options):
256257 f .write (chunk )
257258 do_progress_updates (updatables , len (chunk ))
258259
259- def get (self , path , local_path = None , num_threads = DEFAULT_NUMBER_OF_THREADS , updatables = (), ** options ):
260+ def get (
261+ self ,
262+ path ,
263+ local_path = None ,
264+ num_threads = DEFAULT_NUMBER_OF_THREADS ,
265+ updatables = (),
266+ replica_sort_function = None ,
267+ ** options ,
268+ ):
260269 """
261- Get a reference to the data object at the specified `path'.
262-
263- Only download the object if the local_path is a string (specifying
264- a path in the local filesystem to use as a destination file).
270+ Create an iRODSDataObject instance representing the data object at the specified path.
271+
272+ If local_path is not None, it names a local file to which the content of the
273+ data object will be downloaded.
274+
275+ Args:
276+ path: an absolute logical path where the data object may be found.
277+ local_path: a filename within the local filesystem, the target for download ("GET") of the data
278+ object if one is requested. Directory components in the path must already exist.
279+ num_threads: in the case of a parallel data transfer (for large files), specifies the number of
280+ transfer control threads to be spawned. If not specified, this number will be DEFAULT_NUMBER_OF_THREADS,
281+ i.e. a reasonable small integer value.
282+ updatables: a tuple or list in which each element is a tqdm-like progress bar object, or the equivalent:
283+ an instance on which func.update(n) can be called with n being the number of bytes successfully
284+ copied in the data transfer increment just completed. If not a tuple or list, this argument
285+ is interpreted as a single updatable. The parameter defaults to (), meaning no such objects will be
286+ slated for update.
287+ replica_sort_function: a sort key function dictating the order of replica query results in 'self.replicas'.
288+ If not specified, a default value of None will cause irods.data_objects._DEFAULT_SORT_KEY_FN to be
289+ selected to determine the sort order.
290+ **options: a combination of possible iRODS keyword options to be relayed to the data object open() call.
291+ For a download request, FORCE_FLAG_KW may be used to ensure any pre-existing file at the 'local_path'
292+ will be overwritten.
293+
294+ Returns:
295+ an iRODSDataObject representing the object at the given path.
296+
297+ Raises:
298+ DataObjectDoesNotExist: if the specified path does not exist, or exists as a collection rather than a data
299+ object.
265300 """
266301 parent = self .sess .collections .get (irods_dirname (path ))
267302
@@ -284,7 +319,7 @@ def get(self, path, local_path=None, num_threads=DEFAULT_NUMBER_OF_THREADS, upda
284319 results = query .all () # get up to max_rows replicas
285320 if len (results ) <= 0 :
286321 raise ex .DataObjectDoesNotExist ()
287- return iRODSDataObject (self , parent , results )
322+ return iRODSDataObject (self , parent , results , replica_sort_function = replica_sort_function )
288323
289324 @staticmethod
290325 def _resolve_force_put_option (options , default_setting = None , true_value = "" ):
@@ -317,23 +352,25 @@ def put(
317352 self ._resolve_force_put_option (options , default_setting = client_config .data_objects .force_put_by_default )
318353
319354 if self .sess .collections .exists (irods_path ):
320- obj = iRODSCollection .normalize_path (irods_path , os .path .basename (local_path ))
355+ obj_path = iRODSCollection .normalize_path (irods_path , os .path .basename (local_path )) # noqa: PTH119
321356 else :
322- obj = irods_path
323- if kw .FORCE_FLAG_KW not in options and self .exists (obj ):
357+ obj_path = irods_path
358+ if kw .FORCE_FLAG_KW not in options and self .exists (obj_path ):
324359 raise ex .OVERWRITE_WITHOUT_FORCE_FLAG
325360 options .pop (kw .FORCE_FLAG_KW , None )
326361
362+ replica_sort_function = options .pop ('replica_sort_function' , None )
363+
327364 with open (local_path , "rb" ) as f :
328365 sizelist = []
329366 if self .should_parallelize_transfer (num_threads , f , measured_obj_size = sizelist , open_options = options ):
330- o = deferred_call (self .open , (obj , "w" ), options )
367+ o = deferred_call (self .open , (obj_path , "w" ), options )
331368 f .close ()
332369 error = RuntimeError ("parallel put failed" )
333370 try :
334371 if not self .parallel_put (
335372 local_path ,
336- (obj , o ),
373+ (obj_path , o ),
337374 total_bytes = sizelist [0 ],
338375 num_threads = num_threads ,
339376 target_resource_name = options .get (kw .RESC_NAME_KW , "" ) or options .get (kw .DEST_RESC_NAME_KW , "" ),
@@ -346,7 +383,7 @@ def put(
346383 except BaseException as e :
347384 raise error from e
348385 else :
349- with self .open (obj , "w" , ** options ) as o :
386+ with self .open (obj_path , "w" , ** options ) as o :
350387 # Set operation type to trigger acPostProcForPut
351388 if kw .OPR_TYPE_KW not in options :
352389 options [kw .OPR_TYPE_KW ] = 1 # PUT_OPR
@@ -360,10 +397,11 @@ def put(
360397 # Requested to register checksum without verifying, but source replica has a checksum. This can result
361398 # in multiple replicas being marked good with different checksums, which is an inconsistency.
362399 del repl_options [kw .REG_CHKSUM_KW ]
363- self .replicate (obj , ** repl_options )
400+ self .replicate (obj_path , ** repl_options )
364401
365402 if return_data_object :
366- return self .get (obj )
403+ return self .get (obj_path , replica_sort_function = replica_sort_function )
404+ return None
367405
368406 def chksum (self , path , ** options ):
369407 """
@@ -480,6 +518,7 @@ def create(
480518 raise ex .DataObjectExistsAtLogicalPath
481519
482520 options = {** options , kw .DATA_TYPE_KW : "generic" }
521+ replica_sort_function = options .pop ('replica_sort_function' , None )
483522
484523 if resource :
485524 options [kw .DEST_RESC_NAME_KW ] = resource
@@ -508,7 +547,7 @@ def create(
508547 desc = response .int_info
509548 conn .close_file (desc )
510549
511- return self .get (path )
550+ return self .get (path , replica_sort_function = replica_sort_function )
512551
513552 def open_with_FileRaw (self , * arg , ** kw_options ):
514553 holder = []
0 commit comments