Skip to content

Instantly share code, notes, and snippets.

@mpkocher
Created November 23, 2024 04:34
Show Gist options
  • Save mpkocher/8410ace54c03a8017f21aa729759132d to your computer and use it in GitHub Desktop.
Save mpkocher/8410ace54c03a8017f21aa729759132d to your computer and use it in GitHub Desktop.
Modeling Secrets in Pydantic
"""
Pydantic Secrets
https://docs.pydantic.dev/latest/api/types/#pydantic.types.SecretStr
- Why does the "Box'ed" secret container understand (or leak) information of the secret?
- Internally, the "Box" shouldn't use `.get_secret_value()`. Otherwise, it's leaking info.
- Mixing up the Box and `.get_secret_value()` undermines the point of using `.get_secret_value()`
- Why is the hash value leaking? Comparing two secrets should require and explicit `s1.get_secret_value() == s2.get_secret_value()` call.
- Why does the repr/str communicate "non-empty" values? This is encouraging and enabling an anti-pattern.
- Documenting this functionality is confusing because the "Box" knows something about the secret.
For "non-provided" Secrets (e.g., Model(secret="")), it's better to model them as `None | Secret[T]`, where `T` would be a non-empty value.
Here's an example:
"""
from typing import Annotated
from pydantic import StringConstraints
from pydantic.types import Secret
StrNonEmpty = Annotated[str, StringConstraints(min_length=1)]
class SecretStrNonEmpty(Secret[StrNonEmpty]):
def _display(self) -> str:
return "*" * 5
def __eq__(self, other) -> bool:
# If you want to access the secret, then
# explicitly call, .get_secret_value()
return False
def __hash__(self) -> int:
# Same reasoning as __eq___
return id(self)
class A(BaseModel):
x: SecretStrNonEmpty
class B(BaseModel):
x: SecretStrNonEmpty | None
def example():
axs = tuple(map(lambda x: A(x=x), ("a", "aa", "aaa")))
print(axs)
bxs = tuple(map(lambda x: B(x=x), ('b', "b", None)))
print(bxs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment