<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dat-a-man</title>
    <description>The latest articles on DEV Community by dat-a-man (@dataman).</description>
    <link>https://dev.to/dataman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1508348%2Fff2e46fc-f50c-4833-8a88-d3f717109218.jpeg</url>
      <title>DEV Community: dat-a-man</title>
      <link>https://dev.to/dataman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dataman"/>
    <language>en</language>
    <item>
      <title>From prompt to production: a free course on agentic data engineering</title>
      <dc:creator>dat-a-man</dc:creator>
      <pubDate>Thu, 14 May 2026 05:47:02 +0000</pubDate>
      <link>https://dev.to/dataman/from-prompt-to-production-a-free-course-on-agentic-data-engineering-4f7o</link>
      <guid>https://dev.to/dataman/from-prompt-to-production-a-free-course-on-agentic-data-engineering-4f7o</guid>
      <description>&lt;p&gt;In January 2025, the dlt community shipped &lt;strong&gt;2,400&lt;/strong&gt; custom pipelines. In January 2026, &lt;strong&gt;81,000&lt;/strong&gt;. About 91% were written by agents: Claude, Cursor, Copilot, the tools every Python engineer already has open in a tab.&lt;/p&gt;

&lt;p&gt;Generation stopped being the bottleneck around November 2025, when every frontier model got a major update. They can all write a dlt pipeline now. How well they do it, how cleanly, whether credentials leak on the way, whether the next engineer can read the result depends on the instructions the model has. The dltHub AI Workbench is the instruction set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agentic Data Engineering with dltHub course&lt;/strong&gt; is live today: five lessons, about an hour total. Generate an ingestion pipeline correctly the first time. Explore the data. Confirm or fix the schema. Deploy. Transform. Finish with a result the stakeholder who asked for the data can actually use.&lt;/p&gt;

&lt;p&gt;Enroll at &lt;a href="//dlthub.learnworlds.com/course/agentic-data-engineering"&gt;dlthub.learnworlds.com/course/agentic-data-engineering&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "it runs" doesn't cover
&lt;/h2&gt;

&lt;p&gt;A generated pipeline that ran once is not a pipeline that runs. Production means someone sees it when it breaks and can fix it without re-reading the prompt. Four questions don't go away when the agent writes the first draft:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the data correct?&lt;/li&gt;
&lt;li&gt;Is the schema stable when the upstream API changes?&lt;/li&gt;
&lt;li&gt;Can this run in production, with credentials handled, dependencies pinned, environment validated?&lt;/li&gt;
&lt;li&gt;Can anyone other than the original prompter understand what the agent built?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are workflow problems, not code problems. You can prompt your way to a working extract in five minutes. What happens during and what comes after has always been the senior bar.&lt;/p&gt;

&lt;p&gt;We've measured the difference. In our eval runs, a base agent skips documentation, runs full loads before sampling, and reads &lt;code&gt;secrets.toml&lt;/code&gt; directly. The same agent with the AI Workbench checks docs, samples first, and never touches the secrets file. Same model, same prompt, different workflow, different behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow runs on metadata
&lt;/h2&gt;

&lt;p&gt;Most AI-assisted data engineering today loses context at every step. One tool generates the pipeline. Another validates it. A third builds the dashboard. The agents don't share state. The schema the first tool produced isn't visible to the second; the contract the second wrote isn't readable by the third. Every step starts from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7r1qr3axlu9takcw77j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7r1qr3axlu9takcw77j.png" alt="Four stages of building a pipeline" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI Workbench changes that. Every step produces structured information the next step reads: schemas, contracts, runtime traces, load IDs. The exploration toolkit reads what the REST API toolkit emitted. The transformation toolkit reads what exploration confirmed. The agent doesn't re-examine what you have at every boundary, because the context is metadata, and metadata flows.&lt;/p&gt;

&lt;p&gt;That's how a twelve-step lifecycle becomes one continuous session, and what lets agents handle the whole workflow instead of just the code.&lt;/p&gt;

&lt;p&gt;The session, end to end&lt;/p&gt;

&lt;p&gt;How a senior engineer builds with agents. Twelve steps, four phases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 - Define&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1. Define the outcome. Which decision will this data change? Which metric or chart? Who reads it? Which sources do you need? You start the AI Workbench session deliberating with Claude, before any pipeline code gets generated. Skip this and the agent will happily build the wrong pipeline. Nothing later in the workflow has anything to verify against. A pipeline is a promise about freshness, shape, and correctness. The part the agent can't infer is the promise to whom. That's where data gets meaning, or doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 - Build the pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Steps 2-6. Develop the ingestion pipeline locally on DuckDB. Validate with a sample load. Explore the data in a Marimo notebook. Deploy to production: the agent strips dev artifacts, pins dependencies, validates credentials without reading them. Verify incremental loading in prod.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faged1miwf3pbl13dn9fq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faged1miwf3pbl13dn9fq.png" alt="Stages in a pipeline" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Phase 3 - Model the data &lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Steps 7-9. Develop the transformation pipeline. The agent builds an ontology of what your data means before writing the model. Validate on the transformed tables, deploy with one command.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Phase 4 - Ship and monitor &lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Steps 10-12. Build the notebook the stakeholder opens. Deploy it. Monitor pipeline health and data quality over time.&lt;/p&gt;

&lt;p&gt;Same code from local prototype to production. Same governance from raw data to dashboard. Same agents writing every step, under workbench rules they can't skip. The shape inside each step is propose-verify-enforce: the agent proposes, the workflow verifies, the rules enforce what doesn't get past review. The next agent, or the next person, can't quietly undo the check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the lessons fit
&lt;/h2&gt;

&lt;p&gt;Five free lessons cover the workflow end to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;00 - Introduction.&lt;/strong&gt; Foundation toolkit. dlt OSS. Get the AI Workbench installed in your editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01 - REST API pipeline toolkit.&lt;/strong&gt; Steps 2-3. dlt OSS. From a single prompt to a production-grade pipeline: endpoint discovery, auth, pagination, schema contracts, incremental loading. Backed by thousands of pre-built REST API contexts so the agent surfaces ambiguity instead of guessing. Validate the ingested data before it goes further.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1sm06p4fc5t600ojyhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1sm06p4fc5t600ojyhm.png" alt="dev to prod" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 - dltHub deployment toolkit.&lt;/strong&gt; Steps 5-6, 9. dltHub Pro, free 30-day trial during the course, no invite needed. Convert your dev workspace into a production profile. Strip development artifacts, pin dependencies, validate credentials, with the agent never touching sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;03 - Data exploration toolkit.&lt;/strong&gt; Steps 4 and 8. dlt OSS. The agent generates validation reports and Marimo dashboards in-session. Feedback loop in minutes, not days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;04 - Data transformation toolkit.&lt;/strong&gt; Step 7. Requires dltHub runtime access; you can start a free trial during the course. Ontology first, then code. The agent maps your sources to canonical entities, builds the entity graph, generates a Kimball CDM, writes the @dlt.hub.transformation script that populates it.&lt;/p&gt;

&lt;p&gt;Each toolkit is a guided sequence of skills, commands, rules, and MCP, with guardrails the agent can't skip. Maintained by dltHub, controlling the infrastructure the pipelines run on. You're not learning a tutorial version. You're learning the version that ships.&lt;/p&gt;

&lt;p&gt;For the conceptual underpinnings, the AI Workbench blog series covers the design choices behind the skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;p&gt;If your build data pipelines, this is for you. Data engineers and analytics engineers who use Claude or Cursor every day and want the pipeline to outlast the prompt. Platform engineers building self-service for the rest of the company. Senior and staff ICs at startups through mid-market who own the surface when something drifts.&lt;/p&gt;

&lt;p&gt;This is not for hobbyists - this is for people who run pipelines in production. You should be comfortable enough to debug a stack trace and read some code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it do for you?
&lt;/h2&gt;

&lt;p&gt;The workflow is general-purpose. What you do with it depends on the seat you sit in and the shape of the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Green field build&lt;/strong&gt; - Building a data stack for the first time? this workflow takes you from ingestion to delivery through a best practice architecture that's ideal for use with agents, providing LLMs with "senior guardrails" to help you create results quickly without tech debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whole-stack rebuild.&lt;/strong&gt; You inherited a warehouse built fast. No clean architecture, tables piled up, business meaning in tribal knowledge, the semantic model wrong or missing. Point the workbench at it. The toolkit reverse-engineers the existing SQL into a draft ontology, generates a canonical model, produces a clean T-layer. Same source data, AI-ready output. No three-engineer hiring cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One component.&lt;/strong&gt; Each toolkit is independent. Use just ingestion. Just exploration. Just transformations on data you already have. The metadata flow makes the pieces compose. You don't have to commit to the whole stack to get something out of any one of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data engineer or analytics engineer.&lt;/strong&gt; Pipeline in an afternoon instead of a sprint. Pull a new source, model it, ship the dashboard same day. "I have a question" to "I have a chart" collapses to one session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GTM engineer.&lt;/strong&gt; Lead enrichment, attribution stitching, churn signals, lifecycle scoring. All of this is complex, takes a lot of code, and you want to focus on outcomes, not plumbing. Build the workflow without queuing on the data team. The agent handles the code, you own the logic.&lt;/p&gt;

&lt;p&gt;Team lead or platform engineer. The workbench is a senior architect's judgment encoded in software. Guardrails the agent can't skip. Conventions the next hire picks up by reading the project. The stack stops drifting; entropy stays low.&lt;/p&gt;

&lt;p&gt;You don't have to plan the whole pipeline upfront. Just dive in and ask for what you need, and let the agent figure it out. Because metadata flows through every step, if transformation or visualization needs an endpoint or field that wasn't ingested, the agent goes back and pulls it. Most of the course teaches you to understand the workflow, plan the outcome, and validate the result. The agent does the execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to start
&lt;/h2&gt;

&lt;p&gt;The course is hosted here and guides you through everything you need to get to your desired outcome.&lt;/p&gt;

&lt;p&gt;Free, self-paced, sign up here! Estimated time 1 hour.&lt;/p&gt;

&lt;p&gt;&lt;a href="//dlthub.learnworlds.com/course/agentic-data-engineering"&gt;dlthub.learnworlds.com/course/agentic-data-engineering.&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
Let the agent write the code, while you learn the principles of how to steer it.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>agenticdataengineering</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>dlt adds Reverse ETL - build a custom destination in minutes</title>
      <dc:creator>dat-a-man</dc:creator>
      <pubDate>Tue, 21 May 2024 03:14:40 +0000</pubDate>
      <link>https://dev.to/dataman/dlt-adds-reverse-etl-build-a-custom-destination-in-minutes-ngd</link>
      <guid>https://dev.to/dataman/dlt-adds-reverse-etl-build-a-custom-destination-in-minutes-ngd</guid>
      <description>&lt;h1&gt;
  
  
  Pythonic reverse ETL is here
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Why Python is the right approach for doing Reverse ETL
&lt;/h2&gt;

&lt;p&gt;Reverse ETL is generally about putting data into a business application. This data would often come from a SQL database used as a middle layer for data integrations and calculations.&lt;/p&gt;

&lt;p&gt;That’s fine - but nowadays most data people speak Python, and the types of things we want to put into an operational application don’t always come from a DB, they often come from other business applications, or from things like a dataframe on which we did some scoring, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EWbXFde_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://storage.googleapis.com/dlt-blog-images/reverse-etl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EWbXFde_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://storage.googleapis.com/dlt-blog-images/reverse-etl.png" alt="reverse etl" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The full potential of Reverse ETL is in the flexibility of sources
&lt;/h3&gt;

&lt;p&gt;SQL databases are a good start, but in reality very often our data source is something else. More often than not, it’s a Python analyst’s implementation of some scoring or some business calculation.&lt;/p&gt;

&lt;p&gt;Other times, it’s a business application - for example, we might have a form that sends the response data to a webhook, from where it could end up in Salesforce, DWH, and Slack as a notification. And of course, if this is done by a data person it will be done in Python.&lt;/p&gt;

&lt;p&gt;Such, it follows that if we want to cater to the data crowd, we need to be Pythonic.&lt;/p&gt;

&lt;h2&gt;
  
  
  There’s synergy with ETL
&lt;/h2&gt;

&lt;p&gt;Reverse ETL is ultimately ETL. Data is extracted from a source, its transformed, and then loaded to a destination. The challenges are similar, the most notable difference being that pulling data from a strongly typed environment like a DB and converting it to weakly typed JSON is MUCH easier than the other way around. You can argue that Reverse ETL is simpler than ETL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flavors of Reverse ETL
&lt;/h3&gt;

&lt;p&gt;Just like we have ETL and ELT, we also have flavors of Reverse ETL&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reverse ETL or TEL:&lt;/strong&gt; Transform the data to a specification, read it from DB, and send it to an application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool Reverse ETL or ETL:&lt;/strong&gt; Extract from DB, map fields to destination in the tool, load to destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pythonic Freestyle Reverse ETL:&lt;/strong&gt; You extract data from wherever you want and put it anywhere except storage/DB. Transformations are optional.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of Python reverse ETL&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read data from Mongo, do anomaly detection, and notify anomalies to Slack.&lt;/li&gt;
&lt;li&gt;Read membership data from Stripe, calculate the chance to churn, and upload to CRM for account managers.&lt;/li&gt;
&lt;li&gt;Capture a form response with a webhook and send the information to CRM, DWH, and Slack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add python? - new skills unlocked!
&lt;/h2&gt;

&lt;p&gt;So why is it much better to do reverse ETL in Python?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Streaming and Flexibility&lt;/strong&gt;: Python's ability to handle live data streams and integrate with various APIs and services surpasses the capabilities of SQL-based data warehouses designed for batch processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-End Workflow&lt;/strong&gt;: Employing Python from data extraction to operational integration facilitates a streamlined workflow, enabling data teams to maintain consistency and efficiency across the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization and Scalability&lt;/strong&gt;: Python's versatility allows for tailored solutions that can scale with minimal overhead, reducing the reliance on additional tools and simplifying maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration and Governance&lt;/strong&gt;: By keeping the entire data workflow within Python, teams can ensure better governance, compliance, and collaboration, leveraging common tools and repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example: Building a Custom Destination and a pipeline in under 1h
&lt;/h2&gt;

&lt;p&gt;Documentation used:&lt;br&gt;
Building a destination: &lt;a href="https://dlthub.com/devel/dlt-ecosystem/destinations/destination"&gt;docs&lt;/a&gt;&lt;br&gt;
SQL source: &lt;a href="https://dlthub.com/devel/dlt-ecosystem/verified-sources/sql_database"&gt;docs&lt;/a&gt;&lt;br&gt;
In this example, you will see why it’s faster to build a custom destination than set up a separate tool.&lt;/p&gt;

&lt;p&gt;dlt allows you to define custom destination functions. You'll write a function that extracts the relevant data from your dataframe and formats it for the Notion API.&lt;/p&gt;

&lt;p&gt;This example assumes you have set up Google Sheets API access and obtained the necessary credentials to authenticate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Setting Up Google Sheets API (10min)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Enable the Google Sheets API in the Google Developers Console.&lt;/li&gt;
&lt;li&gt;Download the credentials JSON file.&lt;/li&gt;
&lt;li&gt;Share the target Google Sheet with the email address found in your credentials JSON file.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Step 2: Define the Destination method in its own file &lt;code&gt;sheets_destination.py&lt;/code&gt; (20min)
&lt;/h3&gt;

&lt;p&gt;Install the required package for the Google API client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; google-api-python-client google-auth-httplib2 google-auth-oauthlib

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s how to define a destination function to update a Google Sheet.&lt;br&gt;
In our case we wrote a slightly complex function that checks the headers and aligns the columns with the existing ones before inserting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dlt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.oauth2.service_account&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient.discovery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;


&lt;span class="nd"&gt;@dlt.destination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;google_sheets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;table_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;sheets_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dlt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;credentials_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dlt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;range_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sheet1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Send data to a Google Sheet.
    :param items: Batch of items to send.
    :param table_schema: Schema of the table (unused in this example but required by dlt).
    :param sheets_id: ID of the Google Sheet, retrieved from config.
    :param credentials_json: Google Service Account credentials, retrieved from secrets.
    :param range_name: The specific range within the Sheet where data should be appended.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_service_account_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sheets&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch existing headers from the sheet
&lt;/span&gt;    &lt;span class="n"&gt;existing_headers_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;spreadsheetId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sheets_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sheet1!A1:1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;existing_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_headers_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[]])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing_headers_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Determine new headers from items
&lt;/span&gt;    &lt;span class="n"&gt;new_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Identify headers that need to be added (not already existing)
&lt;/span&gt;    &lt;span class="n"&gt;headers_to_add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_keys&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;existing_headers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# New comprehensive headers list, preserving the order of existing headers and adding new ones at the end
&lt;/span&gt;    &lt;span class="n"&gt;comprehensive_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_headers&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;headers_to_add&lt;/span&gt;

    &lt;span class="c1"&gt;# If there are headers to add, update the first row with comprehensive headers
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headers_to_add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;update_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;comprehensive_headers&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;spreadsheetId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sheets_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sheet1!A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;valueInputOption&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RAW&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;update_body&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Prepare the data rows according to the comprehensive headers list
&lt;/span&gt;    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;comprehensive_headers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Fill missing keys with empty string
&lt;/span&gt;        &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Append the data rows
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;append_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;append_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;spreadsheetId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sheets_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;range_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;valueInputOption&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RAW&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insertDataOption&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;INSERT_ROWS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;append_body&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;append_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;updates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;updatedRows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows have been added to the sheet.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Configure secrets (5min)
&lt;/h3&gt;

&lt;p&gt;For the custom destination, you can follow this example. Configure the source as instructed in the source &lt;a href="https://dlthub.com/devel/dlt-ecosystem/verified-sources/shopify"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;secrets.toml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[destination.google_sheets]&lt;/span&gt;
&lt;span class="py"&gt;credentials_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'''
{
  "type": "service_account",
  "project_id": "your_project_id",
  "private_key_id": "your_private_key_id",
  ...
}
'''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;config.toml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;sheets_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1xj6APSKhepp8-sJIucbD9DDx7eyBt4UI2KlAYaQ9EKs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Running the pipeline in &lt;code&gt;sheets_destination.py&lt;/code&gt;(10min)
&lt;/h3&gt;

&lt;p&gt;Now, assuming you have a source function &lt;strong&gt;&lt;code&gt;dict_row_generator()&lt;/code&gt;&lt;/strong&gt;, you can set up and run your pipeline as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ... destination code from above
&lt;/span&gt;
&lt;span class="c1"&gt;# pass some destination arguments explicitly (`range_name`)
&lt;/span&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dlt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_google_sheets_pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;google_sheets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;named_range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Use the source function and specify the resource "people_report"
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dict_row_generator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;



&lt;span class="c1"&gt;# Now, run the pipeline with the specified source
&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict_row_generator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup, &lt;strong&gt;&lt;code&gt;append_to_google_sheets&lt;/code&gt;&lt;/strong&gt; acts as a custom destination within your dlt pipeline, pushing the fetched data to the specified Google Sheet. This method enables streamlined and secure data operations, fully utilizing Python's capabilities for Reverse ETL processes into Google Sheets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does dlt do for me here?
&lt;/h2&gt;

&lt;p&gt;Using dlt for reverse ETL instead of plain Python, especially with its &lt;strong&gt;&lt;code&gt;@dlt.destination&lt;/code&gt;&lt;/strong&gt; decorator, provides a structured framework that streamlines the process of data integrating into various destinations. Here’s how the dlt decorator specifically aids you compared to crafting everything from scratch in plain Python:&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster time to Production grade pipelines
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;&lt;code&gt;@dlt.destination&lt;/code&gt;&lt;/strong&gt; decorator significantly reduces the need for custom boilerplate code. It provides a structured approach to manage batch processing, error handling, and retries, which would otherwise require complex custom implementations in plain Python. This built-in functionality ensures reliability and resilience in your data pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on custom business logic and adding value
&lt;/h3&gt;

&lt;p&gt;The flexibility of creating custom destinations with dlt shifts the focus from the possibilities to the necessities of your specific use case. This empowers you to concentrate on implementing the best solutions for your unique business requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability and efficient resource use
&lt;/h3&gt;

&lt;p&gt;dlt facilitates efficient handling of large data loads through chunking and batching, allowing for optimal use of computing resources. This means even small worker machines can stream data effectively into your chosen endpoint instead of wasting a large machine waiting for the network. The library design supports easy scaling and adjustments. Making changes to batch sizes or configurations is straightforward, ensuring your data pipelines can grow and evolve with minimal effort. This approach simplifies maintenance and ensures that once a solution is implemented, it's broadly applicable across your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Conclusion
&lt;/h3&gt;

&lt;p&gt;Reverse ETL is just a piece of the ETL puzzle. It could be done cleaner and better when done in Python end to end.&lt;/p&gt;

&lt;p&gt;Tools will always appeal to the non-technical folks. However, anyone with the ability to do Python pipelines can do Reverse ETL pipelines too, bringing typical benefits of code vs tool to a dev team - customisation, collaboration, best practices, etc.&lt;/p&gt;

&lt;p&gt;So read more about &lt;a href="https://dlthub.com/devel/dlt-ecosystem/destinations/destination"&gt;how to built a dlt destination&lt;/a&gt; and consider giving it a try in your next reverse ETL pipeline.&lt;/p&gt;

</description>
      <category>dataengineer</category>
      <category>reverseetl</category>
      <category>datascience</category>
      <category>datapipelines</category>
    </item>
  </channel>
</rss>
