airbyte.secrets.base


  1# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
  2"""___"""
  3
  4from __future__ import annotations
  5
  6import json
  7from abc import ABC, abstractmethod
  8from enum import Enum
  9from typing import cast
 10
 11from airbyte import exceptions as exc
 12
 13
 14class SecretSourceEnum(str, Enum):
 15    ENV = "env"
 16    DOTENV = "dotenv"
 17    GOOGLE_COLAB = "google_colab"
 18    GOOGLE_GSM = "google_gsm"  # Not enabled by default
 19
 20    PROMPT = "prompt"
 21
 22
 23class SecretString(str):
 24    """A string that represents a secret.
 25
 26    This class is used to mark a string as a secret. When a secret is printed, it
 27    will be masked to prevent accidental exposure of sensitive information.
 28    """
 29
 30    __slots__ = ()
 31
 32    def __repr__(self) -> str:
 33        return "<SecretString: ****>"
 34
 35    def is_empty(self) -> bool:
 36        """Check if the secret is an empty string."""
 37        return len(self) == 0
 38
 39    def is_json(self) -> bool:
 40        """Check if the secret string is a valid JSON string."""
 41        try:
 42            json.loads(self)
 43        except (json.JSONDecodeError, Exception):
 44            return False
 45
 46        return True
 47
 48    def __bool__(self) -> bool:
 49        """Override the boolean value of the secret string.
 50
 51        Always returns `True` without inspecting contents."""
 52        return True
 53
 54    def parse_json(self) -> dict:
 55        """Parse the secret string as JSON."""
 56        try:
 57            return json.loads(self)
 58        except json.JSONDecodeError as ex:
 59            raise exc.PyAirbyteInputError(
 60                message="Failed to parse secret as JSON.",
 61                context={
 62                    "Message": ex.msg,
 63                    "Position": ex.pos,
 64                    "SecretString_Length": len(self),  # Debug secret blank or an unexpected format.
 65                },
 66            ) from None
 67
 68
 69class SecretManager(ABC):
 70    """Abstract base class for secret managers.
 71
 72    Secret managers are used to retrieve secrets from a secret store.
 73
 74    By registering a secret manager, PyAirbyte can automatically locate and
 75    retrieve secrets from the secret store when needed. This allows you to
 76    securely store and access sensitive information such as API keys, passwords,
 77    and other credentials without hardcoding them in your code.
 78
 79    To create a custom secret manager, subclass this class and implement the
 80    `get_secret` method. By default, the secret manager will be automatically
 81    registered as a global secret source, but will not replace any existing
 82    secret sources. To customize this behavior, override the `auto_register` and
 83    `replace_existing` attributes in your subclass as needed.
 84
 85    Note: Registered secrets managers always have priority over the default
 86    secret sources such as environment variables, dotenv files, and Google Colab
 87    secrets. If multiple secret managers are registered, the last one registered
 88    will take priority.
 89    """
 90
 91    replace_existing = False
 92    as_backup = False
 93
 94    def __init__(self) -> None:
 95        """Instantiate the new secret manager."""
 96        if not hasattr(self, "name"):
 97            # Default to the class name if no name is provided
 98            self.name: str = self.__class__.__name__
 99
100    @abstractmethod
101    def get_secret(self, secret_name: str) -> SecretString | None:
102        """Get a named secret from the secret manager.
103
104        This method should be implemented by subclasses to retrieve secrets from
105        the secret store. If the secret is not found, the method should return `None`.
106        """
107        ...
108
109    def __str__(self) -> str:
110        return self.name
111
112    def __eq__(self, value: object) -> bool:
113        if isinstance(value, SecretManager):
114            return self.name == value.name
115
116        if isinstance(value, str):
117            return self.name == value
118
119        if isinstance(value, SecretSourceEnum):
120            return self.name == str(value)
121
122        return super().__eq__(value)
123
124
125class SecretHandle:
126    """A handle for a secret in a secret manager.
127
128    This class is used to store a reference to a secret in a secret manager.
129    The secret is not retrieved until the `get_value()` method is called on the handle.
130    """
131
132    def __init__(
133        self,
134        parent: SecretManager,
135        secret_name: str,
136    ) -> None:
137        """Instantiate a new secret handle."""
138        self.parent = parent
139        self.secret_name = secret_name
140
141    def get_value(self) -> SecretString:
142        """Get the secret from the secret manager.
143
144        Subclasses can optionally override this method to provide a more optimized code path.
145        """
146        return cast(SecretString, self.parent.get_secret(self.secret_name))
class SecretSourceEnum(builtins.str, enum.Enum):
15class SecretSourceEnum(str, Enum):
16    ENV = "env"
17    DOTENV = "dotenv"
18    GOOGLE_COLAB = "google_colab"
19    GOOGLE_GSM = "google_gsm"  # Not enabled by default
20
21    PROMPT = "prompt"

An enumeration.

ENV = <SecretSourceEnum.ENV: 'env'>
DOTENV = <SecretSourceEnum.DOTENV: 'dotenv'>
GOOGLE_COLAB = <SecretSourceEnum.GOOGLE_COLAB: 'google_colab'>
GOOGLE_GSM = <SecretSourceEnum.GOOGLE_GSM: 'google_gsm'>
PROMPT = <SecretSourceEnum.PROMPT: 'prompt'>
Inherited Members
enum.Enum
name
value
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
removeprefix
removesuffix
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class SecretString(builtins.str):
24class SecretString(str):
25    """A string that represents a secret.
26
27    This class is used to mark a string as a secret. When a secret is printed, it
28    will be masked to prevent accidental exposure of sensitive information.
29    """
30
31    __slots__ = ()
32
33    def __repr__(self) -> str:
34        return "<SecretString: ****>"
35
36    def is_empty(self) -> bool:
37        """Check if the secret is an empty string."""
38        return len(self) == 0
39
40    def is_json(self) -> bool:
41        """Check if the secret string is a valid JSON string."""
42        try:
43            json.loads(self)
44        except (json.JSONDecodeError, Exception):
45            return False
46
47        return True
48
49    def __bool__(self) -> bool:
50        """Override the boolean value of the secret string.
51
52        Always returns `True` without inspecting contents."""
53        return True
54
55    def parse_json(self) -> dict:
56        """Parse the secret string as JSON."""
57        try:
58            return json.loads(self)
59        except json.JSONDecodeError as ex:
60            raise exc.PyAirbyteInputError(
61                message="Failed to parse secret as JSON.",
62                context={
63                    "Message": ex.msg,
64                    "Position": ex.pos,
65                    "SecretString_Length": len(self),  # Debug secret blank or an unexpected format.
66                },
67            ) from None

A string that represents a secret.

This class is used to mark a string as a secret. When a secret is printed, it will be masked to prevent accidental exposure of sensitive information.

def is_empty(self) -> bool:
36    def is_empty(self) -> bool:
37        """Check if the secret is an empty string."""
38        return len(self) == 0

Check if the secret is an empty string.

def is_json(self) -> bool:
40    def is_json(self) -> bool:
41        """Check if the secret string is a valid JSON string."""
42        try:
43            json.loads(self)
44        except (json.JSONDecodeError, Exception):
45            return False
46
47        return True

Check if the secret string is a valid JSON string.

def parse_json(self) -> dict:
55    def parse_json(self) -> dict:
56        """Parse the secret string as JSON."""
57        try:
58            return json.loads(self)
59        except json.JSONDecodeError as ex:
60            raise exc.PyAirbyteInputError(
61                message="Failed to parse secret as JSON.",
62                context={
63                    "Message": ex.msg,
64                    "Position": ex.pos,
65                    "SecretString_Length": len(self),  # Debug secret blank or an unexpected format.
66                },
67            ) from None

Parse the secret string as JSON.

Inherited Members
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
removeprefix
removesuffix
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class SecretManager(abc.ABC):
 70class SecretManager(ABC):
 71    """Abstract base class for secret managers.
 72
 73    Secret managers are used to retrieve secrets from a secret store.
 74
 75    By registering a secret manager, PyAirbyte can automatically locate and
 76    retrieve secrets from the secret store when needed. This allows you to
 77    securely store and access sensitive information such as API keys, passwords,
 78    and other credentials without hardcoding them in your code.
 79
 80    To create a custom secret manager, subclass this class and implement the
 81    `get_secret` method. By default, the secret manager will be automatically
 82    registered as a global secret source, but will not replace any existing
 83    secret sources. To customize this behavior, override the `auto_register` and
 84    `replace_existing` attributes in your subclass as needed.
 85
 86    Note: Registered secrets managers always have priority over the default
 87    secret sources such as environment variables, dotenv files, and Google Colab
 88    secrets. If multiple secret managers are registered, the last one registered
 89    will take priority.
 90    """
 91
 92    replace_existing = False
 93    as_backup = False
 94
 95    def __init__(self) -> None:
 96        """Instantiate the new secret manager."""
 97        if not hasattr(self, "name"):
 98            # Default to the class name if no name is provided
 99            self.name: str = self.__class__.__name__
100
101    @abstractmethod
102    def get_secret(self, secret_name: str) -> SecretString | None:
103        """Get a named secret from the secret manager.
104
105        This method should be implemented by subclasses to retrieve secrets from
106        the secret store. If the secret is not found, the method should return `None`.
107        """
108        ...
109
110    def __str__(self) -> str:
111        return self.name
112
113    def __eq__(self, value: object) -> bool:
114        if isinstance(value, SecretManager):
115            return self.name == value.name
116
117        if isinstance(value, str):
118            return self.name == value
119
120        if isinstance(value, SecretSourceEnum):
121            return self.name == str(value)
122
123        return super().__eq__(value)

Abstract base class for secret managers.

Secret managers are used to retrieve secrets from a secret store.

By registering a secret manager, PyAirbyte can automatically locate and retrieve secrets from the secret store when needed. This allows you to securely store and access sensitive information such as API keys, passwords, and other credentials without hardcoding them in your code.

To create a custom secret manager, subclass this class and implement the get_secret method. By default, the secret manager will be automatically registered as a global secret source, but will not replace any existing secret sources. To customize this behavior, override the auto_register and replace_existing attributes in your subclass as needed.

Note: Registered secrets managers always have priority over the default secret sources such as environment variables, dotenv files, and Google Colab secrets. If multiple secret managers are registered, the last one registered will take priority.

SecretManager()
95    def __init__(self) -> None:
96        """Instantiate the new secret manager."""
97        if not hasattr(self, "name"):
98            # Default to the class name if no name is provided
99            self.name: str = self.__class__.__name__

Instantiate the new secret manager.

replace_existing = False
as_backup = False
@abstractmethod
def get_secret(self, secret_name: str) -> SecretString | None:
101    @abstractmethod
102    def get_secret(self, secret_name: str) -> SecretString | None:
103        """Get a named secret from the secret manager.
104
105        This method should be implemented by subclasses to retrieve secrets from
106        the secret store. If the secret is not found, the method should return `None`.
107        """
108        ...

Get a named secret from the secret manager.

This method should be implemented by subclasses to retrieve secrets from the secret store. If the secret is not found, the method should return None.

class SecretHandle:
126class SecretHandle:
127    """A handle for a secret in a secret manager.
128
129    This class is used to store a reference to a secret in a secret manager.
130    The secret is not retrieved until the `get_value()` method is called on the handle.
131    """
132
133    def __init__(
134        self,
135        parent: SecretManager,
136        secret_name: str,
137    ) -> None:
138        """Instantiate a new secret handle."""
139        self.parent = parent
140        self.secret_name = secret_name
141
142    def get_value(self) -> SecretString:
143        """Get the secret from the secret manager.
144
145        Subclasses can optionally override this method to provide a more optimized code path.
146        """
147        return cast(SecretString, self.parent.get_secret(self.secret_name))

A handle for a secret in a secret manager.

This class is used to store a reference to a secret in a secret manager. The secret is not retrieved until the get_value() method is called on the handle.

SecretHandle(parent: SecretManager, secret_name: str)
133    def __init__(
134        self,
135        parent: SecretManager,
136        secret_name: str,
137    ) -> None:
138        """Instantiate a new secret handle."""
139        self.parent = parent
140        self.secret_name = secret_name

Instantiate a new secret handle.

parent
secret_name
def get_value(self) -> SecretString:
142    def get_value(self) -> SecretString:
143        """Get the secret from the secret manager.
144
145        Subclasses can optionally override this method to provide a more optimized code path.
146        """
147        return cast(SecretString, self.parent.get_secret(self.secret_name))

Get the secret from the secret manager.

Subclasses can optionally override this method to provide a more optimized code path.