dagster-snowflake 0.26.18rc1__tar.gz → 0.27.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {dagster_snowflake-0.26.18rc1/dagster_snowflake.egg-info → dagster_snowflake-0.27.14}/PKG-INFO +4 -3
  2. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake/__init__.py +3 -0
  3. dagster_snowflake-0.27.14/dagster_snowflake/components/__init__.py +5 -0
  4. dagster_snowflake-0.27.14/dagster_snowflake/components/sql_component/__init__.py +0 -0
  5. dagster_snowflake-0.27.14/dagster_snowflake/components/sql_component/component.py +61 -0
  6. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake/resources.py +92 -3
  7. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake/snowflake_io_manager.py +14 -13
  8. dagster_snowflake-0.27.14/dagster_snowflake/version.py +1 -0
  9. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14/dagster_snowflake.egg-info}/PKG-INFO +4 -3
  10. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake.egg-info/SOURCES.txt +5 -1
  11. dagster_snowflake-0.27.14/dagster_snowflake.egg-info/entry_points.txt +2 -0
  12. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake.egg-info/requires.txt +1 -1
  13. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/setup.py +8 -2
  14. dagster_snowflake-0.26.18rc1/dagster_snowflake/version.py +0 -1
  15. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/LICENSE +0 -0
  16. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/MANIFEST.in +0 -0
  17. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/README.md +0 -0
  18. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake/constants.py +0 -0
  19. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake/ops.py +0 -0
  20. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake/py.typed +0 -0
  21. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake.egg-info/dependency_links.txt +0 -0
  22. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake.egg-info/not-zip-safe +0 -0
  23. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/dagster_snowflake.egg-info/top_level.txt +0 -0
  24. {dagster_snowflake-0.26.18rc1 → dagster_snowflake-0.27.14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dagster-snowflake
3
- Version: 0.26.18rc1
3
+ Version: 0.27.14
4
4
  Summary: Package for Snowflake Dagster framework components.
5
5
  Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-snowflake
6
6
  Author: Dagster Labs
@@ -10,11 +10,12 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Classifier: License :: OSI Approved :: Apache Software License
14
15
  Classifier: Operating System :: OS Independent
15
- Requires-Python: >=3.9,<3.13
16
+ Requires-Python: >=3.9,<3.14
16
17
  License-File: LICENSE
17
- Requires-Dist: dagster==1.10.18rc1
18
+ Requires-Dist: dagster==1.11.14
18
19
  Requires-Dist: snowflake-connector-python>=3.4.0
19
20
  Requires-Dist: pyOpenSSL>=22.1.0
20
21
  Provides-Extra: snowflake-sqlalchemy
@@ -1,5 +1,8 @@
1
1
  from dagster_shared.libraries import DagsterLibraryRegistry
2
2
 
3
+ from dagster_snowflake.components import (
4
+ SnowflakeConnectionComponent as SnowflakeConnectionComponent,
5
+ )
3
6
  from dagster_snowflake.ops import snowflake_op_for_query as snowflake_op_for_query
4
7
  from dagster_snowflake.resources import (
5
8
  SnowflakeConnection as SnowflakeConnection,
@@ -0,0 +1,5 @@
1
+ from dagster_snowflake.components.sql_component.component import SnowflakeConnectionComponent
2
+
3
+ __all__ = [
4
+ "SnowflakeConnectionComponent",
5
+ ]
@@ -0,0 +1,61 @@
1
+ from functools import cached_property
2
+ from typing import Any, cast
3
+
4
+ import dagster as dg
5
+ from dagster._annotations import preview, public
6
+ from dagster._core.definitions.definitions_class import Definitions
7
+ from dagster.components.core.context import ComponentLoadContext
8
+ from dagster.components.lib.sql_component.sql_client import SQLClient
9
+ from pydantic import BaseModel, create_model
10
+
11
+ from dagster_snowflake.resources import SnowflakeResource
12
+
13
+
14
+ @public
15
+ @preview
16
+ class SnowflakeConnectionComponentBase(dg.Component, dg.Resolvable, dg.Model, SQLClient):
17
+ """A component that represents a Snowflake connection. Use this component if you are
18
+ also using the TemplatedSqlComponent to execute SQL queries, and need to connect to Snowflake.
19
+ """
20
+
21
+ @cached_property
22
+ def _snowflake_resource(self) -> SnowflakeResource:
23
+ return SnowflakeResource(
24
+ **{
25
+ (field.alias or field_name): getattr(self, field_name)
26
+ for field_name, field in self.__class__.model_fields.items()
27
+ }
28
+ )
29
+
30
+ def connect_and_execute(self, sql: str) -> None:
31
+ """Connect to the SQL database and execute the SQL query."""
32
+ return self._snowflake_resource.connect_and_execute(sql)
33
+
34
+ def build_defs(self, context: ComponentLoadContext) -> Definitions:
35
+ return Definitions()
36
+
37
+
38
+ def _copy_fields_to_model(
39
+ copy_from: type[BaseModel], copy_to: type[BaseModel], new_model_cls_name: str
40
+ ) -> None:
41
+ """Given two models, creates a copy of the second model with the fields of the first model."""
42
+ field_definitions: dict[str, tuple[type, Any]] = {
43
+ field_name: (cast("type", field.annotation), field)
44
+ for field_name, field in copy_from.model_fields.items()
45
+ }
46
+
47
+ return create_model(
48
+ new_model_cls_name,
49
+ __base__=copy_to,
50
+ __doc__=copy_to.__doc__,
51
+ **field_definitions, # type: ignore
52
+ )
53
+
54
+
55
+ SnowflakeConnectionComponent = public(preview)(
56
+ _copy_fields_to_model(
57
+ copy_from=SnowflakeResource,
58
+ copy_to=SnowflakeConnectionComponentBase,
59
+ new_model_cls_name="SnowflakeConnectionComponent",
60
+ )
61
+ )
@@ -19,6 +19,7 @@ from dagster._annotations import public
19
19
  from dagster._core.definitions.resource_definition import dagster_maintained_resource
20
20
  from dagster._core.storage.event_log.sql_event_log import SqlDbConnection
21
21
  from dagster._utils.cached_method import cached_method
22
+ from dagster.components.lib.sql_component.sql_client import SQLClient
22
23
  from pydantic import Field, model_validator, validator
23
24
 
24
25
  from dagster_snowflake.constants import (
@@ -40,7 +41,7 @@ except ImportError:
40
41
  raise
41
42
 
42
43
 
43
- class SnowflakeResource(ConfigurableResource, IAttachDifferentObjectToOpContext):
44
+ class SnowflakeResource(ConfigurableResource, IAttachDifferentObjectToOpContext, SQLClient):
44
45
  """A resource for connecting to the Snowflake data warehouse.
45
46
 
46
47
  If connector configuration is not set, SnowflakeResource.get_connection() will return a
@@ -281,9 +282,9 @@ class SnowflakeResource(ConfigurableResource, IAttachDifferentObjectToOpContext)
281
282
 
282
283
  @validator("connector")
283
284
  def validate_connector(cls, v: Optional[str]) -> Optional[str]:
284
- if v is not None and v != "sqlalchemy":
285
+ if v is not None and v not in ["sqlalchemy", "adbc"]:
285
286
  raise ValueError(
286
- "Snowflake Resource: 'connector' configuration value must be None or sqlalchemy."
287
+ "Snowflake Resource: 'connector' configuration value must be None, sqlalchemy or adbc."
287
288
  )
288
289
  return v
289
290
 
@@ -391,6 +392,81 @@ class SnowflakeResource(ConfigurableResource, IAttachDifferentObjectToOpContext)
391
392
 
392
393
  return sqlalchemy_engine_args
393
394
 
395
+ @property
396
+ @cached_method
397
+ def _adbc_connection_args(self) -> Mapping[str, Any]:
398
+ config = self._resolved_config_dict
399
+ adbc_engine_args = {}
400
+
401
+ if config.get("account"):
402
+ adbc_engine_args["adbc.snowflake.sql.account"] = config["account"]
403
+ if config.get("user"):
404
+ adbc_engine_args["username"] = config["user"]
405
+ if config.get("password"):
406
+ adbc_engine_args["password"] = config["password"]
407
+ if config.get("database"):
408
+ adbc_engine_args["adbc.snowflake.sql.db"] = config["database"]
409
+ if config.get("schema"):
410
+ adbc_engine_args["adbc.snowflake.sql.schema"] = config["schema"]
411
+ if config.get("role"):
412
+ adbc_engine_args["adbc.snowflake.sql.role"] = config["role"]
413
+ if config.get("warehouse"):
414
+ adbc_engine_args["adbc.snowflake.sql.warehouse"] = config["warehouse"]
415
+
416
+ if config.get("authenticator"):
417
+ auth_mapping = {
418
+ "snowflake": "auth_snowflake",
419
+ "oauth": "auth_oauth",
420
+ "externalbrowser": "auth_ext_browser",
421
+ "okta": "auth_okta",
422
+ "jwt": "auth_jwt",
423
+ "snowflake_jwt": "auth_jwt",
424
+ }
425
+ auth_type = auth_mapping.get(config["authenticator"].lower(), config["authenticator"])
426
+ adbc_engine_args["adbc.snowflake.sql.auth_type"] = auth_type
427
+
428
+ if config.get("private_key") or config.get("private_key_path"):
429
+ # ADBC expects the raw private key value as bytes for jwt_private_key_pkcs8_value
430
+ adbc_engine_args["adbc.snowflake.sql.auth_type"] = "auth_jwt"
431
+ if config.get("private_key"):
432
+ adbc_engine_args["adbc.snowflake.sql.client_option.jwt_private_key_pkcs8_value"] = (
433
+ config["private_key"]
434
+ )
435
+ elif config.get("private_key_path"):
436
+ adbc_engine_args["adbc.snowflake.sql.client_option.jwt_private_key"] = config[
437
+ "private_key_path"
438
+ ]
439
+
440
+ if config.get("private_key_password"):
441
+ adbc_engine_args[
442
+ "adbc.snowflake.sql.client_option.jwt_private_key_pkcs8_password"
443
+ ] = config["private_key_password"]
444
+
445
+ if config.get("login_timeout"):
446
+ adbc_engine_args["adbc.snowflake.sql.client_option.login_timeout"] = (
447
+ f"{config['login_timeout']}s"
448
+ )
449
+ if config.get("network_timeout"):
450
+ adbc_engine_args["adbc.snowflake.sql.client_option.request_timeout"] = (
451
+ f"{config['network_timeout']}s"
452
+ )
453
+ if config.get("client_session_keep_alive") is not None:
454
+ adbc_engine_args["adbc.snowflake.sql.client_option.keep_session_alive"] = str(
455
+ config["client_session_keep_alive"]
456
+ ).lower()
457
+
458
+ adbc_engine_args["adbc.snowflake.sql.client_option.app_name"] = (
459
+ SNOWFLAKE_PARTNER_CONNECTION_IDENTIFIER
460
+ )
461
+
462
+ if config.get("additional_snowflake_connection_args"):
463
+ for key, value in config["additional_snowflake_connection_args"].items():
464
+ # Allow direct ADBC option names to be passed through
465
+ if key.startswith("adbc.snowflake."):
466
+ adbc_engine_args[key] = value # noqa: PERF403
467
+
468
+ return adbc_engine_args
469
+
394
470
  def _snowflake_private_key(self, config) -> bytes:
395
471
  # If the user has defined a path to a private key, we will use that.
396
472
  if config.get("private_key_path", None) is not None:
@@ -476,6 +552,15 @@ class SnowflakeResource(ConfigurableResource, IAttachDifferentObjectToOpContext)
476
552
  yield conn
477
553
  conn.close()
478
554
  engine.dispose()
555
+ elif self.connector == "adbc":
556
+ import adbc_driver_snowflake.dbapi
557
+
558
+ conn = adbc_driver_snowflake.dbapi.connect(
559
+ db_kwargs=self._adbc_connection_args, # pyright: ignore[reportArgumentType]
560
+ )
561
+
562
+ yield conn
563
+ conn.close()
479
564
  else:
480
565
  conn = snowflake.connector.connect(**self._connection_args)
481
566
 
@@ -493,6 +578,10 @@ class SnowflakeResource(ConfigurableResource, IAttachDifferentObjectToOpContext)
493
578
  snowflake_connection_resource=self,
494
579
  )
495
580
 
581
+ def connect_and_execute(self, sql: str) -> None:
582
+ with self.get_connection() as conn:
583
+ conn.cursor().execute(sql)
584
+
496
585
 
497
586
  class SnowflakeConnection:
498
587
  """A connection to Snowflake that can execute queries. In general this class should not be
@@ -5,7 +5,7 @@ from typing import Any, Optional, cast
5
5
 
6
6
  from dagster import IOManagerDefinition, OutputContext, io_manager
7
7
  from dagster._config.pythonic_config import ConfigurableIOManagerFactory
8
- from dagster._core.definitions.time_window_partitions import TimeWindow
8
+ from dagster._core.definitions.partitions.utils import TimeWindow
9
9
  from dagster._core.storage.db_io_manager import (
10
10
  DbClient,
11
11
  DbIOManager,
@@ -15,7 +15,6 @@ from dagster._core.storage.db_io_manager import (
15
15
  )
16
16
  from dagster._core.storage.io_manager import dagster_maintained_io_manager
17
17
  from pydantic import Field
18
- from snowflake.connector.errors import ProgrammingError
19
18
 
20
19
  from dagster_snowflake.resources import SnowflakeResource
21
20
 
@@ -30,7 +29,7 @@ def build_snowflake_io_manager(
30
29
  Args:
31
30
  type_handlers (Sequence[DbTypeHandler]): Each handler defines how to translate between
32
31
  slices of Snowflake tables and an in-memory type - e.g. a Pandas DataFrame. If only
33
- one DbTypeHandler is provided, it will be used as teh default_load_type.
32
+ one DbTypeHandler is provided, it will be used as the default_load_type.
34
33
  default_load_type (Type): When an input has no type annotation, load it as this type.
35
34
 
36
35
  Returns:
@@ -59,7 +58,7 @@ def build_snowflake_io_manager(
59
58
 
60
59
  snowflake_io_manager = build_snowflake_io_manager([SnowflakePandasTypeHandler(), SnowflakePySparkTypeHandler()])
61
60
 
62
- defs = Definitions(
61
+ Definitions(
63
62
  assets=[my_table, my_second_table],
64
63
  resources={
65
64
  "io_manager": snowflake_io_manager.configured({
@@ -75,7 +74,7 @@ def build_snowflake_io_manager(
75
74
 
76
75
  .. code-block:: python
77
76
 
78
- defs = Definitions(
77
+ Definitions(
79
78
  assets=[my_table]
80
79
  resources={"io_manager" snowflake_io_manager.configured(
81
80
  {"database": "my_database", "schema": "my_schema", ...} # will be used as the schema
@@ -364,20 +363,22 @@ class SnowflakeDbClient(DbClient):
364
363
 
365
364
  @staticmethod
366
365
  def ensure_schema_exists(context: OutputContext, table_slice: TableSlice, connection) -> None:
367
- schemas = (
368
- connection.cursor()
369
- .execute(f"show schemas like '{table_slice.schema}' in database {table_slice.database}")
370
- .fetchall()
371
- )
366
+ with connection.cursor() as cursor:
367
+ cursor.execute(
368
+ f"show schemas like '{table_slice.schema}' in database {table_slice.database}"
369
+ )
370
+ schemas = cursor.fetchall()
371
+
372
372
  if len(schemas) == 0:
373
- connection.cursor().execute(f"create schema {table_slice.schema};")
373
+ with connection.cursor() as cursor:
374
+ cursor.execute(f"create schema {table_slice.schema};")
374
375
 
375
376
  @staticmethod
376
377
  def delete_table_slice(context: OutputContext, table_slice: TableSlice, connection) -> None:
377
378
  try:
378
379
  connection.cursor().execute(_get_cleanup_statement(table_slice))
379
- except ProgrammingError as e:
380
- if "does not exist" in e.msg: # type: ignore
380
+ except Exception as e:
381
+ if "does not exist or not authorized" in str(e):
381
382
  # table doesn't exist yet, so ignore the error
382
383
  return
383
384
  else:
@@ -0,0 +1 @@
1
+ __version__ = "0.27.14"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dagster-snowflake
3
- Version: 0.26.18rc1
3
+ Version: 0.27.14
4
4
  Summary: Package for Snowflake Dagster framework components.
5
5
  Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-snowflake
6
6
  Author: Dagster Labs
@@ -10,11 +10,12 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Classifier: License :: OSI Approved :: Apache Software License
14
15
  Classifier: Operating System :: OS Independent
15
- Requires-Python: >=3.9,<3.13
16
+ Requires-Python: >=3.9,<3.14
16
17
  License-File: LICENSE
17
- Requires-Dist: dagster==1.10.18rc1
18
+ Requires-Dist: dagster==1.11.14
18
19
  Requires-Dist: snowflake-connector-python>=3.4.0
19
20
  Requires-Dist: pyOpenSSL>=22.1.0
20
21
  Provides-Extra: snowflake-sqlalchemy
@@ -13,6 +13,10 @@ dagster_snowflake/version.py
13
13
  dagster_snowflake.egg-info/PKG-INFO
14
14
  dagster_snowflake.egg-info/SOURCES.txt
15
15
  dagster_snowflake.egg-info/dependency_links.txt
16
+ dagster_snowflake.egg-info/entry_points.txt
16
17
  dagster_snowflake.egg-info/not-zip-safe
17
18
  dagster_snowflake.egg-info/requires.txt
18
- dagster_snowflake.egg-info/top_level.txt
19
+ dagster_snowflake.egg-info/top_level.txt
20
+ dagster_snowflake/components/__init__.py
21
+ dagster_snowflake/components/sql_component/__init__.py
22
+ dagster_snowflake/components/sql_component/component.py
@@ -0,0 +1,2 @@
1
+ [dagster_dg_cli.registry_modules]
2
+ dagster_snowflake = dagster_snowflake
@@ -1,4 +1,4 @@
1
- dagster==1.10.18rc1
1
+ dagster==1.11.14
2
2
  snowflake-connector-python>=3.4.0
3
3
  pyOpenSSL>=22.1.0
4
4
 
@@ -27,14 +27,15 @@ setup(
27
27
  "Programming Language :: Python :: 3.10",
28
28
  "Programming Language :: Python :: 3.11",
29
29
  "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
30
31
  "License :: OSI Approved :: Apache Software License",
31
32
  "Operating System :: OS Independent",
32
33
  ],
33
34
  packages=find_packages(exclude=["dagster_snowflake_tests*"]),
34
35
  include_package_data=True,
35
- python_requires=">=3.9,<3.13",
36
+ python_requires=">=3.9,<3.14",
36
37
  install_requires=[
37
- "dagster==1.10.18rc1",
38
+ "dagster==1.11.14",
38
39
  "snowflake-connector-python>=3.4.0",
39
40
  # Workaround for incorrect pin in the snowflake-connector-python package
40
41
  # See https://github.com/snowflakedb/snowflake-connector-python/issues/2109
@@ -51,4 +52,9 @@ setup(
51
52
  ],
52
53
  },
53
54
  zip_safe=False,
55
+ entry_points={
56
+ "dagster_dg_cli.registry_modules": [
57
+ "dagster_snowflake = dagster_snowflake",
58
+ ],
59
+ },
54
60
  )
@@ -1 +0,0 @@
1
- __version__ = "0.26.18rc1"