Skip to content

Commit df78af2

Browse files
authored
private key to okta (#216)
1 parent 5568cab commit df78af2

File tree

4 files changed

+60
-15
lines changed

4 files changed

+60
-15
lines changed

src/koheesio/__about__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
LICENSE_INFO = "Licensed as Apache 2.0"
1414
SOURCE = "https://github.com/Nike-Inc/koheesio"
15+
1516
__version__ = "0.10.6"
1617
__logo__ = (
1718
75,

src/koheesio/integrations/snowflake/__init__.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
Login name for the Snowflake user.
1919
Alias for `sfUser`.
2020
password : SecretStr
21-
Password for the Snowflake user.
22-
Alias for `sfPassword`.
21+
The password for the snowflake user. Alias for `sfPassword`. Either this or private_key must be provided.
22+
private_key : SecretStr
23+
The private_key for the snowflake user. Either this or password must be provided.
2324
database : str
2425
The database to use for the session after connecting.
2526
Alias for `sfDatabase`.
@@ -172,8 +173,9 @@ class SnowflakeBaseModel(BaseModel, ExtraParamsMixin, ABC): # type: ignore[misc
172173
Login name for the Snowflake user.
173174
Alias for `sfUser`.
174175
password : SecretStr
175-
Password for the Snowflake user.
176-
Alias for `sfPassword`.
176+
The password for the snowflake user. Alias for `sfPassword`. Either this or private_key must be provided.
177+
private_key : SecretStr
178+
The private_key for the snowflake user. Either this or password must be provided.
177179
role : str
178180
The default security role to use for the session after connecting.
179181
Alias for `sfRole`.
@@ -199,7 +201,8 @@ class SnowflakeBaseModel(BaseModel, ExtraParamsMixin, ABC): # type: ignore[misc
199201
examples=["example.snowflakecomputing.com"],
200202
)
201203
user: str = Field(default=..., alias="sfUser", description="Login name for the Snowflake user")
202-
password: SecretStr = Field(default=..., alias="sfPassword", description="Password for the Snowflake user")
204+
password: Optional[SecretStr] = Field(default=None, alias="sfPassword", description="Password for the Snowflake user")
205+
private_key: Optional[SecretStr] = Field(default=None, alias="pem_private_key", description="PEM private key for the Snowflake user")
203206
role: str = Field(
204207
default=..., alias="sfRole", description="The default security role to use for the session after connecting"
205208
)
@@ -227,6 +230,17 @@ class SnowflakeBaseModel(BaseModel, ExtraParamsMixin, ABC): # type: ignore[misc
227230
examples=[{"sfCompress": "on", "continue_on_error": "off"}],
228231
)
229232

233+
@model_validator(mode="after")
234+
def check_authentication_method(self) -> "SnowflakeBaseModel":
235+
"""Ensure at least one of password or private_key is provided."""
236+
if not self.password and not self.private_key:
237+
raise ValueError("You must provide either 'password' or 'private_key'.")
238+
if self.password and self.private_key:
239+
raise ValueError(
240+
"You must provide either 'password' or 'private_key', not both."
241+
)
242+
return self
243+
230244
@property
231245
def options(self) -> Dict[str, Any]:
232246
"""Shorthand for accessing self.params provided for backwards compatibility"""
@@ -260,6 +274,7 @@ def get_options(self, by_alias: bool = True, include: Optional[Set[str]] = None)
260274
# schema and password have to be handled separately
261275
"sfSchema",
262276
"password",
277+
"private_key",
263278
} - (include or set())
264279

265280
fields = self.model_dump(
@@ -268,13 +283,22 @@ def get_options(self, by_alias: bool = True, include: Optional[Set[str]] = None)
268283
exclude=exclude_set,
269284
)
270285

286+
fields.update({ "sfSchema" if by_alias else "schema": self.sfSchema })
287+
271288
# handle schema and password
272-
fields.update(
273-
{
274-
"sfSchema" if by_alias else "schema": self.sfSchema,
275-
"sfPassword" if by_alias else "password": self.password.get_secret_value(),
276-
}
277-
)
289+
if self.password:
290+
fields.update(
291+
{
292+
"sfPassword" if by_alias else "password": self.password.get_secret_value(),
293+
}
294+
)
295+
elif self.private_key:
296+
fields.update(
297+
{
298+
"private_key": self.private_key.get_secret_value(),
299+
"pem_private_key": self.private_key.get_secret_value(),
300+
}
301+
)
278302

279303
# handle include
280304
if include:
@@ -379,6 +403,7 @@ def get_options(self, by_alias: bool = False, include: Optional[Set[str]] = None
379403
"database",
380404
"schema",
381405
"password",
406+
"private_key",
382407
}
383408
return super().get_options(by_alias=by_alias, include=include)
384409

@@ -389,6 +414,8 @@ def conn(self) -> Generator:
389414
raise RuntimeError("Snowflake connector is not installed. Please install `snowflake-connector-python`.")
390415

391416
sf_options = self.get_options()
417+
418+
392419
_conn = self._snowflake_connector.connect(**sf_options)
393420
self.log.info(f"Connected to Snowflake account: {sf_options['account']}")
394421

@@ -442,7 +469,9 @@ class GrantPrivilegesOnObject(SnowflakeRunQueryPython):
442469
user : str
443470
The username. Alias for `sfUser`
444471
password : SecretStr
445-
The password. Alias for `sfPassword`
472+
The password for the snowflake user. Alias for `sfPassword`. Either this or private_key must be provided.
473+
private_key : SecretStr
474+
The private_key for the snowflake user. Either this or password must be provided.
446475
role : str
447476
The role name
448477
object : str

src/koheesio/integrations/spark/snowflake.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,10 @@ class SnowflakeWriter(SnowflakeBaseModel, Writer):
640640
def execute(self) -> SnowflakeWriter.Output:
641641
"""Write to Snowflake"""
642642
self.log.debug(f"writing to {self.table} with mode {self.insert_type}")
643-
self.df.write.format(self.format).options(**self.get_options()).option("dbtable", self.table).mode(
643+
644+
options = self.get_options()
645+
646+
self.df.write.format(self.format).options(**options).option("dbtable", self.table).mode(
644647
self.insert_type
645648
).save()
646649

src/koheesio/spark/readers/jdbc.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ class JdbcReader(Reader, ExtraParamsMixin):
7575
"the hostname of the server.",
7676
)
7777
user: str = Field(default=..., description="User to authenticate to the server")
78-
password: SecretStr = Field(default=..., description="Password belonging to the username")
78+
password: Optional[SecretStr] = Field(default=None, description="Password belonging to the username")
79+
private_key: Optional[SecretStr] = Field(default=None, alias="pem_private_key", description="Private key for authentication")
7980
dbtable: Optional[str] = Field(
8081
default=None,
8182
description="Database table name, also include schema name",
@@ -114,6 +115,14 @@ def check_dbtable_or_query(self) -> "JdbcReader":
114115
if self.query and self.dbtable:
115116
self.log.warning("Query is filled in, dbtable will be ignored!")
116117

118+
"""Ensure at least one of password or private_key is provided."""
119+
if not self.password and not self.private_key:
120+
raise ValueError("You must provide either 'password' or 'private_key'.")
121+
if self.password and self.private_key:
122+
raise ValueError(
123+
"You must provide either 'password' or 'private_key', not both."
124+
)
125+
117126
return self
118127

119128
@property
@@ -124,8 +133,11 @@ def options(self) -> Dict[str, Any]:
124133
def execute(self) -> "JdbcReader.Output":
125134
"""Wrapper around Spark's jdbc read format"""
126135
options = self.get_options()
127-
136+
128137
if pw := self.password:
129138
options["password"] = pw.get_secret_value()
139+
if pk := self.private_key:
140+
options["pem_private_key"] = pk.get_secret_value()
141+
options["private_key"] = pk.get_secret_value()
130142

131143
self.output.df = self.spark.read.format(self.format).options(**options).load()

0 commit comments

Comments
 (0)