@@ -111,9 +111,11 @@ def mask_secret(secret: str | dict | Iterable, name: str | None = None) -> None:
111
111
_secrets_masker ().add_mask (secret , name )
112
112
113
113
114
- def redact (value : Redactable , name : str | None = None , max_depth : int | None = None ) -> Redacted :
115
- """Redact any secrets found in ``value``."""
116
- return _secrets_masker ().redact (value , name , max_depth )
114
+ def redact (
115
+ value : Redactable , name : str | None = None , max_depth : int | None = None , replacement : str = "***"
116
+ ) -> Redacted :
117
+ """Redact any secrets found in ``value`` with the given replacement."""
118
+ return _secrets_masker ().redact (value , name , max_depth , replacement = replacement )
117
119
118
120
119
121
@overload
@@ -244,59 +246,85 @@ def filter(self, record) -> bool:
244
246
245
247
# Default on `max_depth` is to support versions of the OpenLineage plugin (not the provider) which called
246
248
# this function directly. New versions of that provider, and this class itself call it with a value
247
- def _redact_all (self , item : Redactable , depth : int , max_depth : int = MAX_RECURSION_DEPTH ) -> Redacted :
249
+ def _redact_all (
250
+ self ,
251
+ item : Redactable ,
252
+ depth : int ,
253
+ max_depth : int = MAX_RECURSION_DEPTH ,
254
+ * ,
255
+ replacement : str = "***" ,
256
+ ) -> Redacted :
248
257
if depth > max_depth or isinstance (item , str ):
249
- return "***"
258
+ return replacement
250
259
if isinstance (item , dict ):
251
260
return {
252
- dict_key : self ._redact_all (subval , depth + 1 , max_depth ) for dict_key , subval in item .items ()
261
+ dict_key : self ._redact_all (subval , depth + 1 , max_depth , replacement = replacement )
262
+ for dict_key , subval in item .items ()
253
263
}
254
264
if isinstance (item , (tuple , set )):
255
265
# Turn set in to tuple!
256
- return tuple (self ._redact_all (subval , depth + 1 , max_depth ) for subval in item )
266
+ return tuple (
267
+ self ._redact_all (subval , depth + 1 , max_depth , replacement = replacement ) for subval in item
268
+ )
257
269
if isinstance (item , list ):
258
- return list (self ._redact_all (subval , depth + 1 , max_depth ) for subval in item )
270
+ return list (
271
+ self ._redact_all (subval , depth + 1 , max_depth , replacement = replacement ) for subval in item
272
+ )
259
273
return item
260
274
261
- def _redact (self , item : Redactable , name : str | None , depth : int , max_depth : int ) -> Redacted :
275
+ def _redact (
276
+ self , item : Redactable , name : str | None , depth : int , max_depth : int , replacement : str = "***"
277
+ ) -> Redacted :
262
278
# Avoid spending too much effort on redacting on deeply nested
263
279
# structures. This also avoid infinite recursion if a structure has
264
280
# reference to self.
265
281
if depth > max_depth :
266
282
return item
267
283
try :
268
284
if name and should_hide_value_for_key (name ):
269
- return self ._redact_all (item , depth , max_depth )
285
+ return self ._redact_all (item , depth , max_depth , replacement = replacement )
270
286
if isinstance (item , dict ):
271
287
to_return = {
272
- dict_key : self ._redact (subval , name = dict_key , depth = (depth + 1 ), max_depth = max_depth )
288
+ dict_key : self ._redact (
289
+ subval , name = dict_key , depth = (depth + 1 ), max_depth = max_depth , replacement = replacement
290
+ )
273
291
for dict_key , subval in item .items ()
274
292
}
275
293
return to_return
276
294
if isinstance (item , Enum ):
277
- return self ._redact (item = item .value , name = name , depth = depth , max_depth = max_depth )
295
+ return self ._redact (
296
+ item = item .value , name = name , depth = depth , max_depth = max_depth , replacement = replacement
297
+ )
278
298
if _is_v1_env_var (item ) and hasattr (item , "to_dict" ):
279
299
tmp : dict = item .to_dict ()
280
300
if should_hide_value_for_key (tmp .get ("name" , "" )) and "value" in tmp :
281
- tmp ["value" ] = "***"
301
+ tmp ["value" ] = replacement
282
302
else :
283
- return self ._redact (item = tmp , name = name , depth = depth , max_depth = max_depth )
303
+ return self ._redact (
304
+ item = tmp , name = name , depth = depth , max_depth = max_depth , replacement = replacement
305
+ )
284
306
return tmp
285
307
if isinstance (item , str ):
286
308
if self .replacer :
287
309
# We can't replace specific values, but the key-based redacting
288
310
# can still happen, so we can't short-circuit, we need to walk
289
311
# the structure.
290
- return self .replacer .sub ("***" , str (item ))
312
+ return self .replacer .sub (replacement , str (item ))
291
313
return item
292
314
if isinstance (item , (tuple , set )):
293
315
# Turn set in to tuple!
294
316
return tuple (
295
- self ._redact (subval , name = None , depth = (depth + 1 ), max_depth = max_depth ) for subval in item
317
+ self ._redact (
318
+ subval , name = None , depth = (depth + 1 ), max_depth = max_depth , replacement = replacement
319
+ )
320
+ for subval in item
296
321
)
297
322
if isinstance (item , list ):
298
323
return [
299
- self ._redact (subval , name = None , depth = (depth + 1 ), max_depth = max_depth ) for subval in item
324
+ self ._redact (
325
+ subval , name = None , depth = (depth + 1 ), max_depth = max_depth , replacement = replacement
326
+ )
327
+ for subval in item
300
328
]
301
329
return item
302
330
# I think this should never happen, but it does not hurt to leave it just in case
@@ -317,10 +345,12 @@ def _merge(
317
345
self ,
318
346
new_item : Redacted ,
319
347
old_item : Redactable ,
348
+ * ,
320
349
name : str | None ,
321
350
depth : int ,
322
351
max_depth : int ,
323
352
force_sensitive : bool = False ,
353
+ replacement : str ,
324
354
) -> Redacted :
325
355
"""Merge a redacted item with its original unredacted counterpart."""
326
356
if depth > max_depth :
@@ -345,6 +375,7 @@ def _merge(
345
375
depth = depth + 1 ,
346
376
max_depth = max_depth ,
347
377
force_sensitive = is_sensitive ,
378
+ replacement = replacement ,
348
379
)
349
380
else :
350
381
merged [key ] = new_item [key ]
@@ -366,6 +397,7 @@ def _merge(
366
397
depth = depth + 1 ,
367
398
max_depth = max_depth ,
368
399
force_sensitive = is_sensitive ,
400
+ replacement = replacement ,
369
401
)
370
402
)
371
403
else :
@@ -390,25 +422,38 @@ def _merge(
390
422
except (TypeError , AttributeError , ValueError ):
391
423
return new_item
392
424
393
- def redact (self , item : Redactable , name : str | None = None , max_depth : int | None = None ) -> Redacted :
425
+ def redact (
426
+ self ,
427
+ item : Redactable ,
428
+ name : str | None = None ,
429
+ max_depth : int | None = None ,
430
+ replacement : str = "***" ,
431
+ ) -> Redacted :
394
432
"""
395
433
Redact an any secrets found in ``item``, if it is a string.
396
434
397
435
If ``name`` is given, and it's a "sensitive" name (see
398
436
:func:`should_hide_value_for_key`) then all string values in the item
399
437
is redacted.
400
438
"""
401
- return self ._redact (item , name , depth = 0 , max_depth = max_depth or self .MAX_RECURSION_DEPTH )
439
+ return self ._redact (
440
+ item , name , depth = 0 , max_depth = max_depth or self .MAX_RECURSION_DEPTH , replacement = replacement
441
+ )
402
442
403
443
def merge (
404
- self , new_item : Redacted , old_item : Redactable , name : str | None = None , max_depth : int | None = None
444
+ self ,
445
+ new_item : Redacted ,
446
+ old_item : Redactable ,
447
+ name : str | None = None ,
448
+ max_depth : int | None = None ,
449
+ replacement : str = "***" ,
405
450
) -> Redacted :
406
451
"""
407
452
Merge a redacted item with its original unredacted counterpart.
408
453
409
454
Takes a user-modified redacted item and merges it with the original unredacted item.
410
- For sensitive fields that still contain "***" (unchanged), the original value is restored.
411
- For fields that have been updated, the new value is preserved.
455
+ For sensitive fields that still contain "***" (or whatever the ``replacement`` is specified as), the
456
+ original value is restored. For fields that have been updated, the new value is preserved.
412
457
"""
413
458
return self ._merge (
414
459
new_item ,
@@ -417,6 +462,7 @@ def merge(
417
462
depth = 0 ,
418
463
max_depth = max_depth or self .MAX_RECURSION_DEPTH ,
419
464
force_sensitive = False ,
465
+ replacement = replacement ,
420
466
)
421
467
422
468
@cached_property
0 commit comments