Skip to content

Commit 00c3c0d

Browse files
aaronsteersjatinyadav-cc
authored andcommitted
AirbyteLib: support secrets in dotenv files (airbytehq#35244)
1 parent cf54761 commit 00c3c0d

File tree

5 files changed

+108
-38
lines changed

5 files changed

+108
-38
lines changed

airbyte-lib/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ airbyte-lib is a library that allows to run Airbyte syncs embedded into any Pyth
2121
AirbyteLib can auto-import secrets from the following sources:
2222

2323
1. Environment variables.
24-
2. [Google Colab secrets](https://medium.com/@parthdasawant/how-to-use-secrets-in-google-colab-450c38e3ec75).
25-
3. Manual entry via [`getpass`](https://docs.python.org/3.9/library/getpass.html).
24+
2. Variables defined in a local `.env` ("Dotenv") file.
25+
3. [Google Colab secrets](https://medium.com/@parthdasawant/how-to-use-secrets-in-google-colab-450c38e3ec75).
26+
4. Manual entry via [`getpass`](https://docs.python.org/3.9/library/getpass.html).
2627

2728
_Note: Additional secret store options may be supported in the future. [More info here.](https://github.com/airbytehq/airbyte-lib-private-beta/discussions/5)_
2829

airbyte-lib/airbyte_lib/secrets.py

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,90 @@
22
"""Secrets management for AirbyteLib."""
33
from __future__ import annotations
44

5+
import contextlib
56
import os
67
from enum import Enum, auto
78
from getpass import getpass
9+
from typing import TYPE_CHECKING
10+
11+
from dotenv import dotenv_values
812

913
from airbyte_lib import exceptions as exc
1014

1115

16+
if TYPE_CHECKING:
17+
from collections.abc import Callable
18+
19+
20+
try:
21+
from google.colab import userdata as colab_userdata
22+
except ImportError:
23+
colab_userdata = None
24+
25+
1226
class SecretSource(Enum):
1327
ENV = auto()
28+
DOTENV = auto()
1429
GOOGLE_COLAB = auto()
1530
ANY = auto()
1631

1732
PROMPT = auto()
1833

1934

20-
ALL_SOURCES = [
21-
SecretSource.ENV,
22-
SecretSource.GOOGLE_COLAB,
23-
]
35+
def _get_secret_from_env(
36+
secret_name: str,
37+
) -> str | None:
38+
if secret_name not in os.environ:
39+
return None
2440

25-
try:
26-
from google.colab import userdata as colab_userdata
27-
except ImportError:
28-
colab_userdata = None
41+
return os.environ[secret_name]
42+
43+
44+
def _get_secret_from_dotenv(
45+
secret_name: str,
46+
) -> str | None:
47+
try:
48+
dotenv_vars: dict[str, str | None] = dotenv_values()
49+
except Exception:
50+
# Can't locate or parse a .env file
51+
return None
52+
53+
if secret_name not in dotenv_vars:
54+
# Secret not found
55+
return None
56+
57+
return dotenv_vars[secret_name]
58+
59+
60+
def _get_secret_from_colab(
61+
secret_name: str,
62+
) -> str | None:
63+
if colab_userdata is None:
64+
# The module doesn't exist. We probably aren't in Colab.
65+
return None
66+
67+
try:
68+
return colab_userdata.get(secret_name)
69+
except Exception:
70+
# Secret name not found. Continue.
71+
return None
72+
73+
74+
def _get_secret_from_prompt(
75+
secret_name: str,
76+
) -> str | None:
77+
with contextlib.suppress(Exception):
78+
return getpass(f"Enter the value for secret '{secret_name}': ")
79+
80+
return None
81+
82+
83+
_SOURCE_FUNCTIONS: dict[SecretSource, Callable] = {
84+
SecretSource.ENV: _get_secret_from_env,
85+
SecretSource.DOTENV: _get_secret_from_dotenv,
86+
SecretSource.GOOGLE_COLAB: _get_secret_from_colab,
87+
SecretSource.PROMPT: _get_secret_from_prompt,
88+
}
2989

3090

3191
def get_secret(
@@ -45,8 +105,9 @@ def get_secret(
45105
user will be prompted to enter the secret if it is not found in any of the other sources.
46106
"""
47107
sources = [source] if not isinstance(source, list) else source
108+
all_sources = set(_SOURCE_FUNCTIONS.keys()) - {SecretSource.PROMPT}
48109
if SecretSource.ANY in sources:
49-
sources += [s for s in ALL_SOURCES if s not in sources]
110+
sources += [s for s in all_sources if s not in sources]
50111
sources.remove(SecretSource.ANY)
51112

52113
if prompt or SecretSource.PROMPT in sources:
@@ -55,32 +116,13 @@ def get_secret(
55116

56117
sources.append(SecretSource.PROMPT) # Always check prompt last
57118

58-
for s in sources:
59-
val = _get_secret_from_source(secret_name, s)
119+
for source in sources:
120+
fn = _SOURCE_FUNCTIONS[source] # Get the matching function for this source
121+
val = fn(secret_name)
60122
if val:
61123
return val
62124

63125
raise exc.AirbyteLibSecretNotFoundError(
64126
secret_name=secret_name,
65127
sources=[str(s) for s in sources],
66128
)
67-
68-
69-
def _get_secret_from_source(
70-
secret_name: str,
71-
source: SecretSource,
72-
) -> str | None:
73-
if source in [SecretSource.ENV, SecretSource.ANY] and secret_name in os.environ:
74-
return os.environ[secret_name]
75-
76-
if (
77-
source in [SecretSource.GOOGLE_COLAB, SecretSource.ANY]
78-
and colab_userdata is not None
79-
and colab_userdata.get(secret_name)
80-
):
81-
return colab_userdata.get(secret_name)
82-
83-
if source == SecretSource.PROMPT:
84-
return getpass(f"Enter the value for secret '{secret_name}': ")
85-
86-
return None

airbyte-lib/docs/generated/airbyte_lib.html

Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

airbyte-lib/poetry.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

airbyte-lib/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pyarrow = "^14.0.2"
3030
# psycopg = {extras = ["binary", "pool"], version = "^3.1.16"}
3131
rich = "^13.7.0"
3232
pendulum = "<=3.0.0"
33+
python-dotenv = "^1.0.1"
3334

3435

3536
[tool.poetry.group.dev.dependencies]

0 commit comments

Comments
 (0)