Create a new Secret parameter or as keyword
Add support for secret parameter types, similar to Pydantic's SecretStr/SecretBytes implementation, to help protect sensitive information from being accidentally exposed in logs, debugging output, or string representations.
I imagine either having a dedicated param:
import param
class Config(param.Parameterized):
api_key = param.Secret(default="sk_test_abcdef")
config = Config()
print(config.api_key) # Would display: "Secret('**********')"
print(config.api_key.get_secret_value()) # Would return: "sk_test_abcdef"
Or, a secret kwarg on String
import param
class Config(param.Parameterized):
api_key = param.String(default="sk_test_abcdef", secret=True)
config = Config()
print(config.api_key) # Would display: "**********"
print(config.get_secret_value("api_key")) # Would return: "sk_test_abcdef"
Why not just overwrite __str__ and __repr__ to return ***?
Why not just overwrite
__str__and__repr__to return***?
Is that how the param.Secret parameter should be implemented?
Interesting use case! Pydantic wraps the string/byte input in a custom class. I think that means this shouldn't be implemented with a secret kwarg on String, as the Parameter value will no longer be a str object. I am in fact not sure there's an existing Parameter that casts the input value like this one would do (str to custom Secret object). It doesn't mean we shouldn't do it, just that it might be a new pattern introduced in the code base.
Why not just overwrite
__str__and__repr__to return***?
Yep that's not very clear. But if the implementation wraps the input string/byte in a new object then yes this object should define __str__/__repr__ to hide the secret in all the places it could show up (logs, error messages, etc.).
This use case is related to what pickle_default_value (removed as described in https://github.com/holoviz/param/issues/1007 ) was meant for. In that case the Parameter value didn't need special behavior for displaying representations on screen, but it did need need special handling for state saving and for script_repr.
Is that how the param.Secret parameter should be implemented?
The key is in memory, so as long as we don't print (and also don't dump it with serialize), I don't think there is much more we can do - I could be wrong!
Edit: A small example of how it could look like.
class SecretStr(str):
def __str__(self):
return "***"
def __repr__(self):
return "***"
class Secret(String):
_slot_defaults = dict(Parameter._slot_defaults, default=SecretStr(), regex=None)
def __init__(self, default=Undefined, *, regex=Undefined, **kwargs):
if isinstance(default, str):
default = SecretStr(default)
super().__init__(default=default, **kwargs)
def __set__(self, obj, val):
if isinstance(val, str):
val = SecretStr(val)
return super().__set__(obj, val)
@classmethod
def serialize(cls, value):
return "***"
@classmethod
def deserialize(cls, value):
return "***"
def _validate_value(self, val, allow_None):
if allow_None and val is None:
return
if not isinstance(val, SecretStr):
raise ValueError(
f'{_validate_error_prefix(self)} only takes a string value, '
f'not value of {type(val)}.'
)
The serialize/deserialize and repr implementations are part of this but I'd say the primary reason for param.Secret to exist would be semantic, i.e. downstream libraries will know that Secret strings should be treated differently. I'm not sure if wrapping the string type is desirable.
Right. An example of such downstream handling is in https://github.com/holoviz/param/issues/1007, where Topographica knew not to serialize parameters with the indicated metadata. But in that case it was a Path parameter, not a String, and those two types don't share a parent class other than Parameterized. I guess my vote would be for param.Secret rather than a flag on String, since it will stand out more and be clearer that it needs special handling.
Is the PasswordInput in pmui related to this? Does it achieve (part of) the goal that @Andrew describes at the top of this issue?
And if so, which existing Params in param is the component using? Or is it done in a different way?
Or is the purpose of the component purely to obfuscate the value during input, and it does not do anything special in terms of storing / marking it as secret?
https://panel-material-ui.holoviz.org/reference/widgets/PasswordInput.html