3535import irods .parallel as parallel
3636from irods .parallel import deferred_call
3737
38+
3839logger = logging .getLogger (__name__ )
3940
4041_update_types = []
@@ -302,6 +303,24 @@ def get(
302303 raise ex .DataObjectDoesNotExist ()
303304 return iRODSDataObject (self , parent , results )
304305
306+ @staticmethod
307+ def _resolve_force_put_option (options , default_setting = None , true_value = "" ):
308+ """If 'default_setting' is True or the force flag is already set in 'options', leave (or put) the flag there,
309+ with the value set to the 'truth_value'.
310+
311+ If the result of this resolves as False for the force flag in the 'options' dict, we then choose to remove the flag
312+ from `options`, since it is the flag's mere presence in the API call that achieves the forcing behavior.
313+
314+ For the same reason, we also count string values (even empty ones) as unconditionally True, since that is convention
315+ for server APIs that use the FORCE_FLAG_KW.
316+ """
317+
318+ force = options .setdefault (kw .FORCE_FLAG_KW , default_setting )
319+ if force or isinstance (force , str ):
320+ options [kw .FORCE_FLAG_KW ] = true_value
321+ else :
322+ del options [kw .FORCE_FLAG_KW ]
323+
305324 def put (
306325 self ,
307326 local_path ,
@@ -311,13 +330,20 @@ def put(
311330 updatables = (),
312331 ** options
313332 ):
333+ # Decide if a put option should be used and modify options accordingly.
334+ self ._resolve_force_put_option (
335+ options , default_setting = client_config .data_objects .force_put_by_default
336+ )
314337
315338 if self .sess .collections .exists (irods_path ):
316339 obj = iRODSCollection .normalize_path (
317340 irods_path , os .path .basename (local_path )
318341 )
319342 else :
320343 obj = irods_path
344+ if kw .FORCE_FLAG_KW not in options and self .exists (obj ):
345+ raise ex .OVERWRITE_WITHOUT_FORCE_FLAG
346+ options .pop (kw .FORCE_FLAG_KW , None )
321347
322348 with open (local_path , "rb" ) as f :
323349 sizelist = []
@@ -458,8 +484,28 @@ def parallel_put(
458484 updatables = updatables ,
459485 )
460486
461- def create (self , path , resource = None , force = False , ** options ):
462- options [kw .DATA_TYPE_KW ] = "generic"
487+ @staticmethod
488+ def _call_thru (c ):
489+ return c () if callable (c ) else c
490+
491+ def create (
492+ self ,
493+ path ,
494+ resource = None ,
495+ force = client_config .getter ("data_objects" , "force_create_by_default" ),
496+ ** options
497+ ):
498+ """
499+ Create a new data object with the given logical path.
500+
501+ 'resource', if provided, is the root node of a storage resource hierarchy where the object is preferentially to be created.
502+ 'force', when False, raises an DataObjectExistsAtLogicalPath if there is already a data object at the logical path specified.
503+ """
504+
505+ if not self ._call_thru (force ) and self .exists (path ):
506+ raise ex .DataObjectExistsAtLogicalPath
507+
508+ options = {** options , kw .DATA_TYPE_KW : "generic" }
463509
464510 if resource :
465511 options [kw .DEST_RESC_NAME_KW ] = resource
@@ -470,9 +516,6 @@ def create(self, path, resource=None, force=False, **options):
470516 except AttributeError :
471517 pass
472518
473- if force :
474- options [kw .FORCE_FLAG_KW ] = ""
475-
476519 message_body = FileOpenRequest (
477520 objPath = path ,
478521 createMode = 0o644 ,
0 commit comments