From e1bfccbfe6db376ac992ad58c77e62356abac38d Mon Sep 17 00:00:00 2001
From: Pedram Navid <1045990+PedramNavid@users.noreply.github.com>
Date: Sat, 24 Feb 2024 12:19:13 -0800
Subject: [PATCH 1/7] Add sling docs
---
docs/content/integrations/embedded-elt.mdx | 27 +++++++++++++---------
1 file changed, 16 insertions(+), 11 deletions(-)
diff --git a/docs/content/integrations/embedded-elt.mdx b/docs/content/integrations/embedded-elt.mdx
index b53203da5700b..db38a819f4f33 100644
--- a/docs/content/integrations/embedded-elt.mdx
+++ b/docs/content/integrations/embedded-elt.mdx
@@ -13,25 +13,21 @@ We plan on adding additional embedded ELT tool integrations in the future.
---
-## Relevant APIs
-
-| Name | Description |
-| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
-| | The core Sling asset factory for building syncs |
-| | The Sling Resource used for handing credentials to databases and object stores |
-
---
## Getting started
-To get started with `dagster-embedded-elt` and Sling, familiarize yourself with Sling by reading their docs which describe how sources and targets are configured.
+To get started with `dagster-embedded-elt` and Sling, familiarize yourself with Sling's replication configuration.
The typical pattern for building an ELT pipeline with Sling has three steps:
-1. First, create a which is a container for the source and the target.
-2. In the define both a and a which holds the source and target credentials that Sling will use to sync data.
-3. Finally, create an asset that syncs between two connections. You can use the factory for most use cases.
+1. First, define a `replication.yaml` file that specifies the source and target connections, as well as which streams to sync from.
+2. Next, define a
+for each connection, ensuring you name the resource using the same name given to the connection in the Sling configuration.
+3. Create a Resource object and pass all the connections from the previous step to it.
+4. Use the decorator to define an asset that will run the Sling replication job and yield from the method to run the sync.
+```python
---
## Step 1: Setting up a Sling resource
@@ -169,3 +165,12 @@ asset_def = build_sling_asset(
primary_key="id",
)
```
+## Relevant APIs
+
+| Name | Description |
+| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
+| | The core Sling asset factory for building syncs |
+| | The Sling Resource used for handing credentials to databases and object stores |
+| | The core Sling asset decorator for running a Sling replication job |
+| | A translator for specifying how to map between Sling and Dagster types |
+
From 6236e1fffeb0b107ee13cf393db053b3277d648f Mon Sep 17 00:00:00 2001
From: Pedram Navid <1045990+PedramNavid@users.noreply.github.com>
Date: Sat, 24 Feb 2024 19:31:00 -0800
Subject: [PATCH 2/7] docs: improve embedded-elt and sling APIs documentation
Refactor documentation for the 'embedded-elt' and 'sling' APIs for improved readability and clarity.
Add code snippets and API usage examples, correct hyperlink references,
and adjust API definitions to match their actual usage within the python modules.
Also, deprecate 'build_sling_asset' function and use '@public' decorators to indicate public methods.
docs: update documentation for Dagster Embedded ELT
Updated the documentation for the Sling implementation of the Embedded ELT package to reflect changes to the way connections and replication configurations are setup. Also simplified examples and provided more detailed instructions for setting up resources, assets, and replication configurations.
Improved inline code examples by adhering to code standards and removed the use of os to get environment variables, instead using `EnvVar`.
docs: update embedded ELT and api documentation
- Corrected the description of the embedded ELT feature in _apidocs.mdx
- Added details on configuration, credential management and resource creation for the Sling integration in embedded-elt.mdx
docs: update embedded ELT and api documentation
- Corrected the description of the embedded ELT feature in _apidocs.mdx
- Added details on configuration, credential management and resource creation for the Sling integration in embedded-elt.mdx
Revert
---
docs/content/_apidocs.mdx | 7 +
docs/content/integrations/embedded-elt.mdx | 263 ++++++++++++------
.../libraries/dagster-embedded-elt.rst | 27 +-
.../embedded_elt/postgres_snowflake.py | 52 ++--
.../embedded_elt/replication_config.py | 10 +
.../integrations/embedded_elt/s3_snowflake.py | 50 ++--
.../sling_connection_resources.py | 32 +++
.../embedded_elt/sling_dagster_translator.py | 31 +++
.../integrations_tests/test_embedded_elt.py | 10 +-
.../sling/asset_decorator.py | 2 +-
.../sling/dagster_sling_translator.py | 33 ++-
.../dagster_embedded_elt/sling/resources.py | 6 +-
12 files changed, 366 insertions(+), 157 deletions(-)
create mode 100644 examples/docs_snippets/docs_snippets/integrations/embedded_elt/replication_config.py
create mode 100644 examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_connection_resources.py
create mode 100644 examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_dagster_translator.py
diff --git a/docs/content/_apidocs.mdx b/docs/content/_apidocs.mdx
index 3d9ce3a746fa7..4a3a11bff9ab9 100644
--- a/docs/content/_apidocs.mdx
+++ b/docs/content/_apidocs.mdx
@@ -458,6 +458,13 @@ Dagster also provides a growing set of optional add-on libraries to integrate wi
Fivetran (
diff --git a/docs/content/integrations/embedded-elt.mdx b/docs/content/integrations/embedded-elt.mdx
index db38a819f4f33..5db2a7966ef7f 100644
--- a/docs/content/integrations/embedded-elt.mdx
+++ b/docs/content/integrations/embedded-elt.mdx
@@ -5,172 +5,253 @@ description: Lightweight ELT framework for building ELT pipelines with Dagster,
# Dagster Embedded ELT
-This package provides a framework for building ELT pipelines with Dagster through helpful pre-built assets and resources. It is currently in experimental development, and we'd love to hear your feedback.
+This package provides a framework for building ELT pipelines with Dagster through helpful asset decorators and resources. It is in experimental development, and we'd love to hear your feedback.
-This package currently includes a single implementation using Sling, which provides a simple way to sync data between databases and file systems.
+This package includes a single implementation using Sling, which provides a simple way to sync data between databases and file systems.
We plan on adding additional embedded ELT tool integrations in the future.
---
----
-
-## Getting started
+## Overview
-To get started with `dagster-embedded-elt` and Sling, familiarize yourself with Sling's replication configuration.
+To get started with `dagster-embedded-elt` and Sling, first, familiarize yourself with Sling's replication configuration. The replication configuration is a YAML file that specifies the source and target connections, as well as which streams to sync from. The `dagser-embedded-elt` integration uses this configuration to build the assets for both sources and destinations.
The typical pattern for building an ELT pipeline with Sling has three steps:
-1. First, define a `replication.yaml` file that specifies the source and target connections, as well as which streams to sync from.
-2. Next, define a
-for each connection, ensuring you name the resource using the same name given to the connection in the Sling configuration.
-3. Create a Resource object and pass all the connections from the previous step to it.
-4. Use the decorator to define an asset that will run the Sling replication job and yield from the method to run the sync.
+1. First, define a `replication.yaml` file that specifies the source and target connections, as well as which streams to sync from.
+
+2. Next, create a and pass a list of for each connection to the `connection` parameter, ensuring you name the resource using the same name given to the connection in the Sling configuration.
+
+3. Use the decorator to define an asset that will run the Sling replication job and yield from the method to run the sync.
+
+Each step is explained in detail below:
-```python
---
-## Step 1: Setting up a Sling resource
+## Step 1: Setting up a Sling replication configuration
-A Sling resource is a Dagster resource that contains references to both a source connection and a target connection. Sling is versatile in what a source or destination can represent. You can provide arbitrary keywords to the and classes.
+Dagster's Sling integration is built around Sling's replication configuration. You may provide either a path to an existing `replication.yaml` file, or construct a dictionary that represents the configuration in Python.
-The types and parameters for each connection are defined by [Sling's connections](https://docs.slingdata.io/connections/database-connections).
+This configuration is passed to the Sling CLI to run the replication job.
-The simplest connection is a file connection, which can be defined as:
+Here's an example of a `replication.yaml` file:
-```python
-from dagster_embedded_elt.sling import SlingSourceConnection
-source = SlingSourceConnection(type="file")
-sling = SlingResource(source_connection=source, ...)
-```
+```yaml
+SOURCE: MY_POSTGRES
+TARGET: MY_SNOWFLAKE
-Note that no path is required in the source connection, as that is provided by the asset itself.
+defaults:
+ mode: full-refresh
+ object: "{stream_schema}_{stream_table}"
-```python
-asset_def = build_sling_asset(
- asset_spec=AssetSpec("my_file"),
- source_stream=f"file://{path_to_file}",
- ...
-)
+streams:
+ public.accounts:
+ public.users:
+ public.finance_departments:
+ object: "departments"
```
-For database connections, you can provide a connection string or a dictionary of keyword arguments. For example, to connect to a SQLite database, you can provide a path to the database using the `instance` keyword, which is specified in [Sling's SQLite connection](https://docs.slingdata.io/connections/database-connections/sqlite) documentation.
-
-```python
-source = SlingSourceConnection(type="sqlite", instance="path/to/sqlite.db")
+Or in Python:
+
+```python file=/integrations/embedded_elt/replication_config.py
+replication_config = {
+ "SOURCE": "MY_POSTGRES",
+ "TARGET": "MY_DUCKDB",
+ "defaults": {"mode": "full-refresh", "object": "{stream_schema}_{stream_table}"},
+ "streams": {
+ "public.accounts": None,
+ "public.users": None,
+ "public.finance_departments": {"object": "departments"},
+ },
+}
```
----
+## Step 2: Creating a Sling resource
-## Step 2: Creating a Sling sync
+Next, you will need to create a object that contains references to the connections specified in the replication configuration.
-To create a Sling sync, once you have defined your resource, you can use the factory to create an asset.
+A takes a `connections` parameter, where each represents a connection to a source or target database. You may provide as many connections to the `SlingResource` as needed.
-```python
+The `name` parameter in the should match the `SOURCE` and `TARGET` keys in the replication configuration.
-sling_resource = SlingResource(
- source_connection=SlingSourceConnection(type="file"),
- target_connection=SlingTargetConnection(
- type="sqlite", connection_string="sqlite://path/to/sqlite.db"
- ),
-)
+You may pass a connection string or arbitrary keyword arguments to the to specify the connection details. See the Sling connections reference for the specific connection types and parameters.
-asset_spec = AssetSpec(
- key=["main", "tbl"],
- group_name="etl",
- description="ETL Test",
- deps=["foo"],
+```python file=/integrations/embedded_elt/sling_connection_resources.py
+from dagster_embedded_elt.sling.resources import (
+ SlingConnectionResource,
+ SlingResource,
)
-asset_def = build_sling_asset(
- asset_spec=asset_spec,
- source_stream="file://path/to/file.csv",
- target_object="main.dest_tbl",
- mode=SlingMode.INCREMENTAL,
- primary_key="id",
+from dagster import EnvVar
+
+sling_resource = SlingResource(
+ connections=[
+ # Using a connection string from an environment variable
+ SlingConnectionResource(
+ name="MY_POSTGRES",
+ type="postgres",
+ connection_string=EnvVar("POSTGRES_CONNECTION_STRING"),
+ ),
+ # Using a hard-coded connection string
+ SlingConnectionResource(
+ name="MY_DUCKDB",
+ type="duckdb",
+ connection_string="duckdb:///var/tmp/duckdb.db",
+ ),
+ # Using a keyword-argument constructor
+ SlingConnectionResource(
+ name="MY_SNOWFLAKE",
+ type="snowflake",
+ host=EnvVar("SNOWFLAKE_HOST"),
+ user=EnvVar("SNOWFLAKE_USER"),
+ role="REPORTING",
+ ),
+ ]
)
+```
+
+## Step 3: Define the Sling assets
+
+Now you can define a Sling asset using the decorator. Dagster will read the replication configuration to produce Assets.
-sling_job = build_assets_job(
- "sling_job",
- [asset_def],
- resource_defs={"sling": sling_resource},
+Each stream will render two assets, one for the source stream and one for the target destination. You may override how assets are named by passing in a custom object.
+
+```python file=/integrations/embedded_elt/sling_dagster_translator.py
+from dagster_embedded_elt import sling
+from dagster_embedded_elt.sling import (
+ DagsterSlingTranslator,
+ SlingResource,
+ sling_assets,
)
+from dagster import Definitions, file_relative_path
+
+replication_config = file_relative_path(__file__, "../sling_replication.yaml")
+sling_resource = SlingResource(connections=[...]) # Add connections here
+
+
+@sling_assets(replication_config=replication_config)
+def my_assets(context, sling: SlingResource):
+ yield from sling.replicate(
+ replication_config=replication_config,
+ dagster_sling_translator=DagsterSlingTranslator(),
+ )
+ for row in sling.stream_raw_logs():
+ context.log.info(row)
+
+
+defs = Definitions(
+ assets=[
+ my_assets,
+ ],
+ resources={
+ "sling": sling_resource,
+ },
+)
```
----
+That's it! You should now be able to view your assets in Dagster and run the replication job.
## Examples
This is an example of how to setup a Sling sync between Postgres and Snowflake:
```python file=/integrations/embedded_elt/postgres_snowflake.py
-import os
-
from dagster_embedded_elt.sling import (
- SlingMode,
+ DagsterSlingTranslator,
+ SlingConnectionResource,
SlingResource,
- SlingSourceConnection,
- SlingTargetConnection,
- build_sling_asset,
+ sling_assets,
)
-from dagster import AssetSpec
+from dagster import EnvVar
-source = SlingSourceConnection(
+source = SlingConnectionResource()(
+ name="MY_PG",
type="postgres",
host="localhost",
port=5432,
database="my_database",
user="my_user",
- password=os.getenv("PG_PASS"),
+ password=EnvVar("PG_PASS"),
)
-target = SlingTargetConnection(
+target = SlingConnectionResource(
+ name="MY_SF",
type="snowflake",
host="hostname.snowflake",
user="username",
database="database",
- password=os.getenv("SF_PASSWORD"),
+ password=EnvVar("SF_PASSWORD"),
role="role",
)
-sling = SlingResource(source_connection=source, target_connection=target)
-
-asset_def = build_sling_asset(
- asset_spec=AssetSpec("my_asset_name"),
- source_stream="public.my_table",
- target_object="marts.my_table",
- mode=SlingMode.INCREMENTAL,
- primary_key="id",
+sling = SlingResource(
+ connections=[
+ source,
+ target,
+ ]
)
+replication_config = {
+ "SOURCE": "MY_PG",
+ "TARGET": "MY_SF",
+ "defaults": {"mode": "full-refresh", "object": "{stream_schema}_{stream_table}"},
+ "streams": {
+ "public.accounts": None,
+ "public.users": None,
+ "public.finance_departments": {"object": "departments"},
+ },
+}
+
+
+@sling_assets(replication_config=replication_config)
+def my_assets(context, sling: SlingResource):
+ yield from sling.replicate(
+ replication_config=replication_config,
+ dagster_sling_translator=DagsterSlingTranslator(),
+ )
```
Similarily, you can define file/storage connections:
```python startafter=start_storage_config endbefore=end_storage_config file=/integrations/embedded_elt/s3_snowflake.py
-source = SlingSourceConnection(
+source = SlingConnectionResource()(
+ name="MY_S3",
type="s3",
bucket="sling-bucket",
- access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
- secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
+ access_key_id=EnvVar("AWS_ACCESS_KEY_ID"),
+ secret_access_key=EnvVar("AWS_SECRET_ACCESS_KEY"),
)
-sling = SlingResource(source_connection=source, target_connection=target)
-
-asset_def = build_sling_asset(
- asset_spec=AssetSpec("my_asset_name"),
- source_stream="s3://my-bucket/my_file.parquet",
- target_object="marts.my_table",
- primary_key="id",
-)
+sling = SlingResource(connections=[source, target])
+
+replication_config = {
+ "SOURCE": "MY_S3",
+ "TARGET": "MY_SF",
+ "defaults": {"mode": "full-refresh", "object": "{stream_schema}_{stream_table}"},
+ "streams": {
+ "s3://my-bucket/my_file.parquet": {
+ "object": "marts.my_table",
+ "primary_key": "id",
+ },
+ },
+}
+
+
+@sling_assets
+def my_assets(context, sling: SlingResource):
+ yield from sling.replicate(
+ replication_config=replication_config,
+ dagster_sling_translator=DagsterSlingTranslator(),
+ )
```
+
## Relevant APIs
| Name | Description |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
-| | The core Sling asset factory for building syncs |
+| | The core Sling asset factory for building syncs |
| | The Sling Resource used for handing credentials to databases and object stores |
-| | The core Sling asset decorator for running a Sling replication job |
| | A translator for specifying how to map between Sling and Dagster types |
-
+| | A Sling connection resource for specifying the connection details |
diff --git a/docs/sphinx/sections/api/apidocs/libraries/dagster-embedded-elt.rst b/docs/sphinx/sections/api/apidocs/libraries/dagster-embedded-elt.rst
index 6cc1d23e1660b..69b5fcdb03277 100644
--- a/docs/sphinx/sections/api/apidocs/libraries/dagster-embedded-elt.rst
+++ b/docs/sphinx/sections/api/apidocs/libraries/dagster-embedded-elt.rst
@@ -10,23 +10,30 @@ provides a simple way to sync data between databases and file systems.
Related documentation pages: `embedded-elt `_.
+.. currentmodule:: dagster_embedded_elt.sling
-******
-Sling
-******
+***************************
+dagster-embedded-elt.sling
+***************************
-.. currentmodule:: dagster_embedded_elt.sling
+Assets (Sling)
+==============
-Assets
-======
+.. autodecorator:: sling_assets
-.. autofunction:: build_sling_asset
+.. autoclass:: DagsterSlingTranslator
-Resources
-=========
+Resources (Sling)
+=================
.. autoclass:: SlingResource
- :members: sync
+ :members: sync, replicate
+.. autoclass:: SlingConnectionResource
+
+Deprecated
+-----------
+
+.. autofunction:: build_sling_asset
.. autoclass:: dagster_embedded_elt.sling.resources.SlingSourceConnection
.. autoclass:: dagster_embedded_elt.sling.resources.SlingTargetConnection
diff --git a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/postgres_snowflake.py b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/postgres_snowflake.py
index cb667468d6c27..cca09ed35d820 100644
--- a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/postgres_snowflake.py
+++ b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/postgres_snowflake.py
@@ -1,42 +1,56 @@
# pyright: reportCallIssue=none
# pyright: reportOptionalMemberAccess=none
-import os
-
from dagster_embedded_elt.sling import (
- SlingMode,
+ DagsterSlingTranslator,
+ SlingConnectionResource,
SlingResource,
- SlingSourceConnection,
- SlingTargetConnection,
- build_sling_asset,
+ sling_assets,
)
-from dagster import AssetSpec
+from dagster import EnvVar
-source = SlingSourceConnection(
+source = SlingConnectionResource(
+ name="MY_PG",
type="postgres",
host="localhost",
port=5432,
database="my_database",
user="my_user",
- password=os.getenv("PG_PASS"),
+ password=EnvVar("PG_PASS"),
)
-target = SlingTargetConnection(
+target = SlingConnectionResource(
+ name="MY_SF",
type="snowflake",
host="hostname.snowflake",
user="username",
database="database",
- password=os.getenv("SF_PASSWORD"),
+ password=EnvVar("SF_PASSWORD"),
role="role",
)
-sling = SlingResource(source_connection=source, target_connection=target)
-
-asset_def = build_sling_asset(
- asset_spec=AssetSpec("my_asset_name"),
- source_stream="public.my_table",
- target_object="marts.my_table",
- mode=SlingMode.INCREMENTAL,
- primary_key="id",
+sling = SlingResource(
+ connections=[
+ source,
+ target,
+ ]
)
+replication_config = {
+ "SOURCE": "MY_PG",
+ "TARGET": "MY_SF",
+ "defaults": {"mode": "full-refresh", "object": "{stream_schema}_{stream_table}"},
+ "streams": {
+ "public.accounts": None,
+ "public.users": None,
+ "public.finance_departments": {"object": "departments"},
+ },
+}
+
+
+@sling_assets(replication_config=replication_config)
+def my_assets(context, sling: SlingResource):
+ yield from sling.replicate(
+ replication_config=replication_config,
+ dagster_sling_translator=DagsterSlingTranslator(),
+ )
diff --git a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/replication_config.py b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/replication_config.py
new file mode 100644
index 0000000000000..b2a5abca43540
--- /dev/null
+++ b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/replication_config.py
@@ -0,0 +1,10 @@
+replication_config = {
+ "SOURCE": "MY_POSTGRES",
+ "TARGET": "MY_DUCKDB",
+ "defaults": {"mode": "full-refresh", "object": "{stream_schema}_{stream_table}"},
+ "streams": {
+ "public.accounts": None,
+ "public.users": None,
+ "public.finance_departments": {"object": "departments"},
+ },
+}
diff --git a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/s3_snowflake.py b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/s3_snowflake.py
index c8f8a66580f2c..229afeda16ecc 100644
--- a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/s3_snowflake.py
+++ b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/s3_snowflake.py
@@ -1,42 +1,56 @@
# pyright: reportCallIssue=none
# pyright: reportOptionalMemberAccess=none
-import os
-
from dagster_embedded_elt.sling import (
+ DagsterSlingTranslator,
+ SlingConnectionResource,
SlingResource,
- SlingSourceConnection,
- SlingTargetConnection,
- build_sling_asset,
+ sling_assets,
)
-from dagster import AssetSpec
+from dagster import EnvVar
-target = SlingTargetConnection(
+target = SlingConnectionResource(
+ name="MY_SF",
type="snowflake",
host="hostname.snowflake",
user="username",
database="database",
- password=os.getenv("SF_PASSWORD"),
+ password=EnvVar("SF_PASSWORD"),
role="role",
)
# start_storage_config
-source = SlingSourceConnection(
+source = SlingConnectionResource(
+ name="MY_S3",
type="s3",
bucket="sling-bucket",
- access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
- secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
+ access_key_id=EnvVar("AWS_ACCESS_KEY_ID"),
+ secret_access_key=EnvVar("AWS_SECRET_ACCESS_KEY"),
)
-sling = SlingResource(source_connection=source, target_connection=target)
+sling = SlingResource(connections=[source, target])
+
+replication_config = {
+ "SOURCE": "MY_S3",
+ "TARGET": "MY_SF",
+ "defaults": {"mode": "full-refresh", "object": "{stream_schema}_{stream_table}"},
+ "streams": {
+ "s3://my-bucket/my_file.parquet": {
+ "object": "marts.my_table",
+ "primary_key": "id",
+ },
+ },
+}
+
+
+@sling_assets
+def my_assets(context, sling: SlingResource):
+ yield from sling.replicate(
+ replication_config=replication_config,
+ dagster_sling_translator=DagsterSlingTranslator(),
+ )
-asset_def = build_sling_asset(
- asset_spec=AssetSpec("my_asset_name"),
- source_stream="s3://my-bucket/my_file.parquet",
- target_object="marts.my_table",
- primary_key="id",
-)
# end_storage_config
diff --git a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_connection_resources.py b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_connection_resources.py
new file mode 100644
index 0000000000000..bd6c76014e188
--- /dev/null
+++ b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_connection_resources.py
@@ -0,0 +1,32 @@
+# pyright: reportCallIssue=none
+from dagster_embedded_elt.sling.resources import (
+ SlingConnectionResource,
+ SlingResource,
+)
+
+from dagster import EnvVar
+
+sling_resource = SlingResource(
+ connections=[
+ # Using a connection string from an environment variable
+ SlingConnectionResource(
+ name="MY_POSTGRES",
+ type="postgres",
+ connection_string=EnvVar("POSTGRES_CONNECTION_STRING"),
+ ),
+ # Using a hard-coded connection string
+ SlingConnectionResource(
+ name="MY_DUCKDB",
+ type="duckdb",
+ connection_string="duckdb:///var/tmp/duckdb.db",
+ ),
+ # Using a keyword-argument constructor
+ SlingConnectionResource(
+ name="MY_SNOWFLAKE",
+ type="snowflake",
+ host=EnvVar("SNOWFLAKE_HOST"),
+ user=EnvVar("SNOWFLAKE_USER"),
+ role="REPORTING",
+ ),
+ ]
+)
diff --git a/examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_dagster_translator.py b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_dagster_translator.py
new file mode 100644
index 0000000000000..09226414221d5
--- /dev/null
+++ b/examples/docs_snippets/docs_snippets/integrations/embedded_elt/sling_dagster_translator.py
@@ -0,0 +1,31 @@
+from dagster_embedded_elt import sling
+from dagster_embedded_elt.sling import (
+ DagsterSlingTranslator,
+ SlingResource,
+ sling_assets,
+)
+
+from dagster import Definitions, file_relative_path
+
+replication_config = file_relative_path(__file__, "../sling_replication.yaml")
+sling_resource = SlingResource(connections=[...]) # Add connections here
+
+
+@sling_assets(replication_config=replication_config)
+def my_assets(context, sling: SlingResource):
+ yield from sling.replicate(
+ replication_config=replication_config,
+ dagster_sling_translator=DagsterSlingTranslator(),
+ )
+ for row in sling.stream_raw_logs():
+ context.log.info(row)
+
+
+defs = Definitions(
+ assets=[
+ my_assets,
+ ],
+ resources={
+ "sling": sling_resource,
+ },
+)
diff --git a/examples/docs_snippets/docs_snippets_tests/integrations_tests/test_embedded_elt.py b/examples/docs_snippets/docs_snippets_tests/integrations_tests/test_embedded_elt.py
index 8dde56f5a2e43..955598028019b 100644
--- a/examples/docs_snippets/docs_snippets_tests/integrations_tests/test_embedded_elt.py
+++ b/examples/docs_snippets/docs_snippets_tests/integrations_tests/test_embedded_elt.py
@@ -1,11 +1,15 @@
from docs_snippets.integrations.embedded_elt.postgres_snowflake import (
- asset_def as asset_def_postgres,
+ my_assets as asset_def_postgres,
)
from docs_snippets.integrations.embedded_elt.s3_snowflake import (
- asset_def as asset_def_s3,
+ my_assets as asset_def_s3,
+)
+from docs_snippets.integrations.embedded_elt.sling_connection_resources import (
+ sling_resource,
)
-def test_asset_defs():
+def test_asset_defs() -> None:
assert asset_def_postgres
assert asset_def_s3
+ assert sling_resource
diff --git a/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/asset_decorator.py b/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/asset_decorator.py
index 4dd0c4e813e07..1952d46ee1fa8 100644
--- a/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/asset_decorator.py
+++ b/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/asset_decorator.py
@@ -39,7 +39,7 @@ def sling_assets(
Args:
replication_config: Union[Mapping[str, Any], str, Path]: A path to a Sling replication config, or a dictionary
- of a replication config.
+ of a replication config.
dagster_sling_translator: DagsterSlingTranslator: Allows customization of how to map a Sling stream to a Dagster
AssetKey.
partitions_def: Optional[PartitionsDefinition]: The partitions definition for this asset.
diff --git a/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/dagster_sling_translator.py b/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/dagster_sling_translator.py
index 27962adbda73a..2df39a75a6da6 100644
--- a/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/dagster_sling_translator.py
+++ b/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/dagster_sling_translator.py
@@ -49,10 +49,12 @@ def get_asset_key(self, stream_definition: Mapping[str, Any]) -> AssetKey:
For example:
- stream_definition = {"public.users":
- {'sql': 'select all_user_id, name from public."all_Users"',
- 'object': 'public.all_users'}
- }
+ .. code-block:: python
+
+ stream_definition = {"public.users":
+ {'sql': 'select all_user_id, name from public."all_Users"',
+ 'object': 'public.all_users'}
+ }
By default, this returns the class's target_prefix paramater concatenated with the stream name.
A stream named "public.accounts" will create an AssetKey named "target_public_accounts".
@@ -78,11 +80,13 @@ def get_asset_key(self, stream_definition: Mapping[str, Any]) -> AssetKey:
Examples:
Using a custom mapping for streams:
- class CustomSlingTranslator(DagsterSlingTranslator):
- @classmethod
- def get_asset_key_for_target(self, stream_definition) -> AssetKey:
- map = {"stream1": "asset1", "stream2": "asset2"}
- return AssetKey(map[stream_name])
+ .. code-block:: python
+
+ class CustomSlingTranslator(DagsterSlingTranslator):
+ @classmethod
+ def get_asset_key_for_target(self, stream_definition) -> AssetKey:
+ map = {"stream1": "asset1", "stream2": "asset2"}
+ return AssetKey(map[stream_name])
"""
config = stream_definition.get("config", {}) or {}
object_key = config.get("object")
@@ -131,12 +135,13 @@ def get_deps_asset_key(cls, stream_definition: Mapping[str, Any]) -> Iterable[As
Examples:
Using a custom mapping for streams:
- class CustomSlingTranslator(DagsterSlingTranslator):
- @classmethod
- def get_deps_asset_key(cls, stream_name: str) -> AssetKey:
- map = {"stream1": "asset1", "stream2": "asset2"}
- return AssetKey(map[stream_name])
+ .. code-block:: python
+ class CustomSlingTranslator(DagsterSlingTranslator):
+ @classmethod
+ def get_deps_asset_key(cls, stream_name: str) -> AssetKey:
+ map = {"stream1": "asset1", "stream2": "asset2"}
+ return AssetKey(map[stream_name])
"""
config = stream_definition.get("config", {}) or {}
diff --git a/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/resources.py b/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/resources.py
index d7afbeefdfcd9..803086dd99a2d 100644
--- a/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/resources.py
+++ b/python_modules/libraries/dagster-embedded-elt/dagster_embedded_elt/sling/resources.py
@@ -18,7 +18,7 @@
PermissiveConfig,
get_dagster_logger,
)
-from dagster._annotations import deprecated, experimental
+from dagster._annotations import deprecated, experimental, public
from dagster._utils.env import environ
from dagster._utils.warnings import deprecation_warning
from pydantic import Field
@@ -33,6 +33,7 @@
DEPRECATION_WARNING_TEXT = "{name} has been deprecated, use `SlingConnectionResource` for both source and target connections."
+@public
class SlingMode(str, Enum):
"""The mode to use when syncing.
@@ -123,6 +124,7 @@ class SlingTargetConnection(PermissiveConfig):
)
+@public
class SlingConnectionResource(PermissiveConfig):
"""A representation a connection to a database or file to be used by Sling. This resource can be used as a source or a target for a Sling sync.
@@ -318,6 +320,7 @@ def sync(
yield from self._exec_sling_cmd(cmd, encoding=encoding)
+ @public
def replicate(
self,
*,
@@ -325,6 +328,7 @@ def replicate(
dagster_sling_translator: DagsterSlingTranslator,
debug: bool = False,
):
+ """Docs go here."""
replication_config = validate_replication(replication_config)
stream_definition = get_streams_from_replication(replication_config)
From dd809bc2818a789bbd96054149dfb6224a503fb2 Mon Sep 17 00:00:00 2001
From: Pedram Navid <1045990+PedramNavid@users.noreply.github.com>
Date: Tue, 27 Feb 2024 20:34:18 -0800
Subject: [PATCH 3/7] lint
---
docs/content/integrations/embedded-elt.mdx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/content/integrations/embedded-elt.mdx b/docs/content/integrations/embedded-elt.mdx
index 5db2a7966ef7f..e66802c89f0b7 100644
--- a/docs/content/integrations/embedded-elt.mdx
+++ b/docs/content/integrations/embedded-elt.mdx
@@ -167,7 +167,7 @@ from dagster_embedded_elt.sling import (
from dagster import EnvVar
-source = SlingConnectionResource()(
+source = SlingConnectionResource(
name="MY_PG",
type="postgres",
host="localhost",
@@ -216,7 +216,7 @@ def my_assets(context, sling: SlingResource):
Similarily, you can define file/storage connections:
```python startafter=start_storage_config endbefore=end_storage_config file=/integrations/embedded_elt/s3_snowflake.py
-source = SlingConnectionResource()(
+source = SlingConnectionResource(
name="MY_S3",
type="s3",
bucket="sling-bucket",
From 2b622bed572df0e60a0edd4463b2715cd1651bd3 Mon Sep 17 00:00:00 2001
From: Pedram Navid <1045990+PedramNavid@users.noreply.github.com>
Date: Tue, 27 Feb 2024 20:43:05 -0800
Subject: [PATCH 4/7] add blobs
---
docs/content/api/modules.json.gz | Bin 1381365 -> 1386682 bytes
docs/content/api/searchindex.json.gz | Bin 68239 -> 68567 bytes
docs/content/api/sections.json.gz | Bin 502305 -> 505263 bytes
3 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/docs/content/api/modules.json.gz b/docs/content/api/modules.json.gz
index b849b1b8c817c3d3eab9da1d61860270b5b2492a..e44c646c2e87769ea308b6abaeffe5f08dc26af1 100644
GIT binary patch
delta 1342408
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≥1K0<9F-oT>!!8TX+;e}!qNAQ!C<3ak6li~z
zJIl;WveH+6!b{EJfJDj`dDfLJlk<}p)Ok-F1FdlzCbgI}N{J$hgAg>zKq!@XgoF1i
za0Nl05{lx7io&|OkYJrqgkYE}Vaq;p((1t9gd5Xc!QI?-w&OwT>);q?iwI{g00<0$
z0mD2k3_m1|QM(tr-|b=5!I#@+=w`CUBUxji9YyB+%Hfhl`=tu4>GWvPVDc5v5zr%L*U5z2&k(+zGcntp77{(S1C%8w%*6>wqGzoSv`dJk>>~{
z8wVXPuUh6Ne{e!6HDxBx*Wi6V8fV3oYP{DpWX2H8lUF%)t$3dL)qkeKpA&NX{vSaY
zx?A+jsLSvI(p@d1N!Vg#*n9;Bzuy{KF0tFL;~ng{#6SJOu~qt~TUpIfZWC
ze)Q;V7raD&N_c8YS!}s33XHV5D-BgoeU*A4m_kTKe|>FODr>RU%G?wav?u!JgNT@p
zNM!dBQ{iyv;z*?}h5G*7
z;kBi#vo4~M0z!sBcAOQbW)c=L0$S`im<*ZvEZeh@sX+^6c|!~nCKKg3mDd|$Avp;?
zU1819H3v}8n0Rqei%$QW6s8DVOcF>puuWzEV?(rN@e}1A+7q@&^Pm*UcdghbNfxygV0h+>olWc<~yih g&-TB(
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 |