Skip to content

Secret

Secret

Bases: Generic[T], SecretMonad

The base container for holding secrets.

This class can be instantiated directly with any ProtectedValue, but using the monad-like Secret.wrap method is preferred, as it will use specialized subclasses that provide extra functionality.

Source code in secret_type/containers/secret.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
class Secret(Generic[T], SecretMonad):
    """The base container for holding secrets.

    This class can be instantiated directly with any [`ProtectedValue`][secret_type.typing.types.ProtectedValue],
    but using the monad-like [`Secret.wrap`][secret_type.monad.SecretMonad.wrap] method is preferred,
    as it will use specialized subclasses that provide extra functionality."""

    @classmethod
    def token(cls, length: Optional[int] = None) -> "SecretStr":
        """Generate a cryptographically secure random token, and wrap it in a [`SecretStr`][secret_type.containers.SecretStr].

        Args:
            length: The length of the token to generate.
        """
        return SecretStr(secrets.token_hex(length // 2 if length else None))

    def __init__(self, value: T):
        key = self.__key = Fernet.generate_key()
        self.__value = Fernet(key).encrypt(pickle.dumps(value, pickle.HIGHEST_PROTOCOL))

    def __del__(self):
        del self.__key
        del self.__value
        gc.collect()

    def cast(self, t: Type[T2], *args, **kwargs) -> "Secret[T2]":
        """Casts the content of the secret to a new type.
        Any additional arguments are passed to the constructor of the new type.

        Returns:
            A new [`Secret`][secret_type.Secret] of the new type.

        Args:
            t: The primitive type to cast to.

        Raises:
            ValueError: If the value cannot be cast to the new type.
        """
        # Up to the user to provide a valid cast
        return self.dangerous_map(lambda x: t(x, *args, **kwargs))  # type: ignore

    @property
    def protected_type(self) -> type:
        """The type of the protected value."""
        return type(self._dangerous_extract())

    def __int__(self) -> int:
        raise SecretException()

    def __float__(self) -> float:
        raise SecretException()

    def __complex__(self) -> complex:
        raise SecretException()

    def __str__(self) -> str:
        raise SecretException()

    def __bytes__(self) -> bytes:
        raise SecretException()

    def encode(self, encoding: str = "utf-8") -> bytes:
        raise SecretException()

    def __hash__(self) -> str:
        raise SecretKeyException()

    def __bool__(self) -> "SecretBool":
        return SecretBool(bool(self._dangerous_extract()))

    def __repr__(self) -> str:
        return f"Secret({self.protected_type}, <hidden>)"

    def _dangerous_map(self, fn: Callable[[T], R], *args, **kwargs) -> R:
        return fn(
            pickle.loads(Fernet(self.__key).decrypt(self.__value)), *args, **kwargs
        )

    def _dangerous_extract(self) -> T:
        return self._dangerous_map(lambda x: x)

    def dangerous_apply(self, fn: ApplyFn[T, P], *args, **kwargs) -> None:
        """Apply a function to the secret value, discarding the result.
        Any additional arguments are passed to the function.

        Use this method when you want to enact a side-effect with the contents of your secret.
        If you want to return a new secret, use [`dangerous_map`][secret_type.Secret.dangerous_map] instead.

        Args:
            fn (Callable[[T, ...], Any]): The function to apply to the secret value.
                The first positional argument must be the unwrapped value.

        Examples: Example:
            This is how one would print the secret value.

            ```python
            Secret.wrap("hello").dangerous_apply(print)
            ```
        """
        self._dangerous_map(fn, *args, **kwargs)

    def dangerous_map(self, fn: MapFn[T, P, T2], *args, **kwargs) -> "Secret[T2]":
        """Apply a function to the secret value, and wrap the result in a new secret.
        Any additional arguments are passed to the function.

        You must ensure the return type of `fn` is a [`Secret`][secret_type.Secret] or a primitive type.

        Args:
            fn (Callable[[T, ...], Union[Secret[T2], T2]]): The function to apply to the secret value.
                The first positional argument must be the unwrapped value.

        Returns:
            A new [`Secret`][secret_type.Secret] of the return type of `fn`.

        Examples: Example:
            This is how one would hash the secret value.

            ```python
            Secret.wrap("foobar").cast(bytes).dangerous_map(lambda x: hashlib.sha1(x).hexdigest())
            ```

        Raises:
            TypeError: If the return of `fn` cannot be wrapped in a [`Secret`][secret_type.Secret].
        """
        return SecretMonad.wrap(self._dangerous_map(fn, *args, **kwargs))

    @contextmanager
    def dangerous_reveal(self) -> Generator[T, None, None]:
        """A context manager that provides the unwrapped secret value.

        This method is intended to be used in a `with` statement,
        and allows you to operate on the secret value in it's original form.

        Examples: Example:
            This is how one would persist the secret value.

            ```python
            with Secret.wrap("foobar").dangerous_reveal() as value:
                save_to_db(value)
            ```
        """
        yield self._dangerous_extract()

    def __eq__(self, o: Union["Secret[T2]", R]) -> "SecretBool":
        a, b = SecretMonad.unwrap(self), SecretMonad.unwrap(o)
        aval = a if isinstance(a, (str, bytes)) else str(a)
        if isinstance(b, type(a)):
            bval = b if isinstance(b, (str, bytes)) else str(b)
            return SecretBool(secrets.compare_digest(aval, bval))
        else:
            # If the types don't match, we want to always return False
            bval = type(aval)()
            secrets.compare_digest(aval, bval)
            return SecretBool(False)

    def __ne__(self, o: object) -> "SecretBool":
        return self.__eq__(o).flip()

    def __add__(self, other) -> "Secret[T]":
        return Secret.wrap(self._dangerous_extract() + other)

    def __radd__(self, other) -> "Secret[T]":
        return Secret.wrap(other + self._dangerous_extract())

    def __mul__(self, other) -> "Secret[T]":
        return Secret.wrap(self._dangerous_extract() * other)

    def __rmul__(self, other) -> "Secret[T]":
        return Secret.wrap(other * self._dangerous_extract())

    def __getattr__(self, name: str) -> Any:
        # Wrap any additional type methods that return a ProtectedValue
        if name not in dir(self.protected_type):
            raise SecretAttributeError(self, name)

        fn = getattr(self._dangerous_extract(), name)
        if not isinstance(fn, Callable):
            raise SecretAttributeError(self, name)

        @wraps(fn)
        def wrapped(*args, **kwargs):
            val = fn(*args, **kwargs)
            try:
                return SecretMonad.wrap(val)
            except TypeError:
                raise NotImplementedError(fn.__name__)

        return wrapped

protected_type: type property

The type of the protected value.

token(length: Optional[int] = None) -> SecretStr classmethod

Generate a cryptographically secure random token, and wrap it in a SecretStr.

PARAMETER DESCRIPTION
length

The length of the token to generate.

TYPE: Optional[int] DEFAULT: None

Source code in secret_type/containers/secret.py
26
27
28
29
30
31
32
33
@classmethod
def token(cls, length: Optional[int] = None) -> "SecretStr":
    """Generate a cryptographically secure random token, and wrap it in a [`SecretStr`][secret_type.containers.SecretStr].

    Args:
        length: The length of the token to generate.
    """
    return SecretStr(secrets.token_hex(length // 2 if length else None))

cast(t: Type[T2], *args, **kwargs) -> Secret[T2]

Casts the content of the secret to a new type. Any additional arguments are passed to the constructor of the new type.

RETURNS DESCRIPTION
Secret[T2]

A new Secret of the new type.

PARAMETER DESCRIPTION
t

The primitive type to cast to.

TYPE: Type[T2]

RAISES DESCRIPTION
ValueError

If the value cannot be cast to the new type.

Source code in secret_type/containers/secret.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def cast(self, t: Type[T2], *args, **kwargs) -> "Secret[T2]":
    """Casts the content of the secret to a new type.
    Any additional arguments are passed to the constructor of the new type.

    Returns:
        A new [`Secret`][secret_type.Secret] of the new type.

    Args:
        t: The primitive type to cast to.

    Raises:
        ValueError: If the value cannot be cast to the new type.
    """
    # Up to the user to provide a valid cast
    return self.dangerous_map(lambda x: t(x, *args, **kwargs))  # type: ignore

dangerous_apply(fn: ApplyFn[T, P], *args, **kwargs) -> None

Apply a function to the secret value, discarding the result. Any additional arguments are passed to the function.

Use this method when you want to enact a side-effect with the contents of your secret. If you want to return a new secret, use dangerous_map instead.

PARAMETER DESCRIPTION
fn

The function to apply to the secret value. The first positional argument must be the unwrapped value.

TYPE: Callable[[T, ...], Any]

Example:

This is how one would print the secret value.

Secret.wrap("hello").dangerous_apply(print)
Source code in secret_type/containers/secret.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def dangerous_apply(self, fn: ApplyFn[T, P], *args, **kwargs) -> None:
    """Apply a function to the secret value, discarding the result.
    Any additional arguments are passed to the function.

    Use this method when you want to enact a side-effect with the contents of your secret.
    If you want to return a new secret, use [`dangerous_map`][secret_type.Secret.dangerous_map] instead.

    Args:
        fn (Callable[[T, ...], Any]): The function to apply to the secret value.
            The first positional argument must be the unwrapped value.

    Examples: Example:
        This is how one would print the secret value.

        ```python
        Secret.wrap("hello").dangerous_apply(print)
        ```
    """
    self._dangerous_map(fn, *args, **kwargs)

dangerous_map(fn: MapFn[T, P, T2], *args, **kwargs) -> Secret[T2]

Apply a function to the secret value, and wrap the result in a new secret. Any additional arguments are passed to the function.

You must ensure the return type of fn is a Secret or a primitive type.

PARAMETER DESCRIPTION
fn

The function to apply to the secret value. The first positional argument must be the unwrapped value.

TYPE: Callable[[T, ...], Union[Secret[T2], T2]]

RETURNS DESCRIPTION
Secret[T2]

A new Secret of the return type of fn.

Example:

This is how one would hash the secret value.

Secret.wrap("foobar").cast(bytes).dangerous_map(lambda x: hashlib.sha1(x).hexdigest())
RAISES DESCRIPTION
TypeError

If the return of fn cannot be wrapped in a Secret.

Source code in secret_type/containers/secret.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def dangerous_map(self, fn: MapFn[T, P, T2], *args, **kwargs) -> "Secret[T2]":
    """Apply a function to the secret value, and wrap the result in a new secret.
    Any additional arguments are passed to the function.

    You must ensure the return type of `fn` is a [`Secret`][secret_type.Secret] or a primitive type.

    Args:
        fn (Callable[[T, ...], Union[Secret[T2], T2]]): The function to apply to the secret value.
            The first positional argument must be the unwrapped value.

    Returns:
        A new [`Secret`][secret_type.Secret] of the return type of `fn`.

    Examples: Example:
        This is how one would hash the secret value.

        ```python
        Secret.wrap("foobar").cast(bytes).dangerous_map(lambda x: hashlib.sha1(x).hexdigest())
        ```

    Raises:
        TypeError: If the return of `fn` cannot be wrapped in a [`Secret`][secret_type.Secret].
    """
    return SecretMonad.wrap(self._dangerous_map(fn, *args, **kwargs))

dangerous_reveal() -> Generator[T, None, None]

A context manager that provides the unwrapped secret value.

This method is intended to be used in a with statement, and allows you to operate on the secret value in it's original form.

Example:

This is how one would persist the secret value.

with Secret.wrap("foobar").dangerous_reveal() as value:
    save_to_db(value)
Source code in secret_type/containers/secret.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
@contextmanager
def dangerous_reveal(self) -> Generator[T, None, None]:
    """A context manager that provides the unwrapped secret value.

    This method is intended to be used in a `with` statement,
    and allows you to operate on the secret value in it's original form.

    Examples: Example:
        This is how one would persist the secret value.

        ```python
        with Secret.wrap("foobar").dangerous_reveal() as value:
            save_to_db(value)
        ```
    """
    yield self._dangerous_extract()