cartage 0.1.0__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.
- cartage-0.1.0/.github/workflows/publish.yml +67 -0
- cartage-0.1.0/.gitignore +5 -0
- cartage-0.1.0/PKG-INFO +188 -0
- cartage-0.1.0/README.md +161 -0
- cartage-0.1.0/docs/cartage-demo.gif +0 -0
- cartage-0.1.0/docs/cartage-demo.tape +66 -0
- cartage-0.1.0/docs/superpowers/plans/2026-09-30-cartage-v0.1.md +4680 -0
- cartage-0.1.0/docs/superpowers/specs/2026-09-30-cartage-v0.1-design.md +438 -0
- cartage-0.1.0/pyproject.toml +52 -0
- cartage-0.1.0/src/cartage/__init__.py +1 -0
- cartage-0.1.0/src/cartage/adapters/__init__.py +0 -0
- cartage-0.1.0/src/cartage/adapters/destinations/__init__.py +0 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/__init__.py +0 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/bapi_meta/BAPI_MATERIAL_SAVEDATA.yaml +24 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/destination.py +64 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/mapping.py +82 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/meta.py +59 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/transports/__init__.py +12 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/transports/mock.py +163 -0
- cartage-0.1.0/src/cartage/adapters/destinations/sap/transports/rfc.py +8 -0
- cartage-0.1.0/src/cartage/adapters/engines/__init__.py +0 -0
- cartage-0.1.0/src/cartage/adapters/engines/dlt.py +80 -0
- cartage-0.1.0/src/cartage/adapters/engines/python.py +26 -0
- cartage-0.1.0/src/cartage/adapters/orchestrators/__init__.py +0 -0
- cartage-0.1.0/src/cartage/adapters/orchestrators/airflow.py +115 -0
- cartage-0.1.0/src/cartage/adapters/orchestrators/templates/cartage/airflow_dag.py.j2 +47 -0
- cartage-0.1.0/src/cartage/adapters/sources/__init__.py +0 -0
- cartage-0.1.0/src/cartage/adapters/sources/dlt.py +57 -0
- cartage-0.1.0/src/cartage/adapters/sources/filesystem.py +82 -0
- cartage-0.1.0/src/cartage/adapters/sources/s3.py +122 -0
- cartage-0.1.0/src/cartage/checks.py +44 -0
- cartage-0.1.0/src/cartage/cli.py +250 -0
- cartage-0.1.0/src/cartage/config.py +245 -0
- cartage-0.1.0/src/cartage/core.py +91 -0
- cartage-0.1.0/src/cartage/project_template/cartage.yaml +18 -0
- cartage-0.1.0/src/cartage/project_template/connections.yaml +29 -0
- cartage-0.1.0/src/cartage/project_template/data/materials/materials.csv +21 -0
- cartage-0.1.0/src/cartage/project_template/gitignore +1 -0
- cartage-0.1.0/src/cartage/project_template/pipelines/materials.yaml +28 -0
- cartage-0.1.0/src/cartage/project_template/transforms/__init__.py +0 -0
- cartage-0.1.0/src/cartage/project_template/transforms/materials.py +22 -0
- cartage-0.1.0/src/cartage/registry.py +34 -0
- cartage-0.1.0/src/cartage/runner.py +124 -0
- cartage-0.1.0/src/cartage/scaffold.py +32 -0
- cartage-0.1.0/src/cartage/secrets.py +79 -0
- cartage-0.1.0/src/cartage/state.py +79 -0
- cartage-0.1.0/src/cartage/transforms.py +160 -0
- cartage-0.1.0/src/cartage/ui.py +218 -0
- cartage-0.1.0/tests/conftest.py +15 -0
- cartage-0.1.0/tests/test_airflow.py +135 -0
- cartage-0.1.0/tests/test_airflow_integration.py +29 -0
- cartage-0.1.0/tests/test_cli_misc.py +59 -0
- cartage-0.1.0/tests/test_cli_run.py +90 -0
- cartage-0.1.0/tests/test_cli_validate_plan.py +83 -0
- cartage-0.1.0/tests/test_config.py +111 -0
- cartage-0.1.0/tests/test_core.py +64 -0
- cartage-0.1.0/tests/test_dlt_engine.py +50 -0
- cartage-0.1.0/tests/test_dlt_source.py +90 -0
- cartage-0.1.0/tests/test_filesystem.py +78 -0
- cartage-0.1.0/tests/test_registry.py +32 -0
- cartage-0.1.0/tests/test_runner.py +66 -0
- cartage-0.1.0/tests/test_s3.py +101 -0
- cartage-0.1.0/tests/test_sap_destination.py +73 -0
- cartage-0.1.0/tests/test_sap_mapping.py +64 -0
- cartage-0.1.0/tests/test_sap_mock.py +136 -0
- cartage-0.1.0/tests/test_scaffold.py +22 -0
- cartage-0.1.0/tests/test_secrets.py +99 -0
- cartage-0.1.0/tests/test_state.py +63 -0
- cartage-0.1.0/tests/test_transforms.py +160 -0
- cartage-0.1.0/uv.lock +4278 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Test and build distributions
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out source
|
|
16
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
|
+
with:
|
|
18
|
+
persist-credentials: false
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
cache-dependency-glob: uv.lock
|
|
25
|
+
|
|
26
|
+
- name: Install Python
|
|
27
|
+
run: uv python install 3.11
|
|
28
|
+
|
|
29
|
+
- name: Install test dependencies
|
|
30
|
+
run: uv sync --locked --python 3.11 --group dev --extra dlt
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: uv run --locked pytest
|
|
34
|
+
|
|
35
|
+
- name: Build distributions
|
|
36
|
+
run: uv build --no-sources
|
|
37
|
+
|
|
38
|
+
- name: Upload distributions
|
|
39
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
40
|
+
with:
|
|
41
|
+
name: python-package-distributions
|
|
42
|
+
path: dist/
|
|
43
|
+
if-no-files-found: error
|
|
44
|
+
|
|
45
|
+
publish:
|
|
46
|
+
name: Publish distributions
|
|
47
|
+
needs: build
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment:
|
|
50
|
+
name: pypi
|
|
51
|
+
url: https://pypi.org/p/cartage
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write
|
|
54
|
+
steps:
|
|
55
|
+
- name: Install uv
|
|
56
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
57
|
+
with:
|
|
58
|
+
enable-cache: false
|
|
59
|
+
|
|
60
|
+
- name: Download distributions
|
|
61
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
62
|
+
with:
|
|
63
|
+
name: python-package-distributions
|
|
64
|
+
path: dist/
|
|
65
|
+
|
|
66
|
+
- name: Publish to PyPI
|
|
67
|
+
run: uv publish
|
cartage-0.1.0/.gitignore
ADDED
cartage-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cartage
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Declarative data migrations: YAML pipelines, Python transforms, any engine, any orchestrator.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11.4
|
|
7
|
+
Requires-Dist: jinja2>=3.1
|
|
8
|
+
Requires-Dist: pydantic>=2.6
|
|
9
|
+
Requires-Dist: rich>=13.7
|
|
10
|
+
Requires-Dist: ruamel-yaml>=0.18
|
|
11
|
+
Requires-Dist: typer>=0.12
|
|
12
|
+
Provides-Extra: airflow
|
|
13
|
+
Requires-Dist: apache-airflow-providers-standard>=1.0; extra == 'airflow'
|
|
14
|
+
Requires-Dist: apache-airflow>=3.0; extra == 'airflow'
|
|
15
|
+
Requires-Dist: kubernetes>=29; extra == 'airflow'
|
|
16
|
+
Provides-Extra: all
|
|
17
|
+
Requires-Dist: apache-airflow-providers-standard>=1.0; extra == 'all'
|
|
18
|
+
Requires-Dist: apache-airflow>=3.0; extra == 'all'
|
|
19
|
+
Requires-Dist: boto3>=1.34; extra == 'all'
|
|
20
|
+
Requires-Dist: dlt>=1.20; extra == 'all'
|
|
21
|
+
Requires-Dist: kubernetes>=29; extra == 'all'
|
|
22
|
+
Provides-Extra: dlt
|
|
23
|
+
Requires-Dist: dlt>=1.20; extra == 'dlt'
|
|
24
|
+
Provides-Extra: s3
|
|
25
|
+
Requires-Dist: boto3>=1.34; extra == 's3'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Cartage
|
|
29
|
+
|
|
30
|
+
Declarative data migrations. Describe sources, destinations and pipelines in YAML, put custom logic in plain
|
|
31
|
+
Python, run locally to test, and generate thin Airflow DAGs for production.
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
- **Sources:** local CSV folders, S3, and any [dlt](https://dlthub.com) source.
|
|
36
|
+
- **Destinations:** SAP via BAPIs (v0.1 ships a mock SAP; RFC is planned).
|
|
37
|
+
- **Engines:** `python` (a plain loop) and `dlt`.
|
|
38
|
+
- **Orchestrators:** Airflow. The DAG only calls `cartage run`, so local and production run the same code.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install "cartage[dlt]"
|
|
44
|
+
cartage init demo && cd demo
|
|
45
|
+
cartage validate
|
|
46
|
+
cartage plan materials # dry run: records before/after transforms and the BAPI payloads
|
|
47
|
+
cartage run materials # reads 20 rows, filters 2, sends 18: 16 load, 2 fail on purpose (exit 1)
|
|
48
|
+
cartage run materials --advance-state
|
|
49
|
+
cartage run materials # incremental: nothing new to load
|
|
50
|
+
cartage generate # dags/materials_to_sap.py
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Split-screen demo: run `cartage sap mock` in one terminal, set `url: http://localhost:8765` on `sap_erp.dev`
|
|
54
|
+
in `connections.yaml`, and run the pipeline in another terminal.
|
|
55
|
+
|
|
56
|
+
## Project layout
|
|
57
|
+
|
|
58
|
+
| Path | Purpose |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `cartage.yaml` | environments, default engine, state location, orchestrator settings |
|
|
61
|
+
| `connections.yaml` | named connections with settings per environment — secrets only as references |
|
|
62
|
+
| `pipelines/*.yaml` | source → transforms → destination (+ schedule) |
|
|
63
|
+
| `transforms/*.py` | `map` / `filter` / `batch` functions referenced as `module:function` |
|
|
64
|
+
| `templates/airflow/dag.py.j2` | optional DAG template override (`{% extends "cartage/airflow_dag.py.j2" %}`) |
|
|
65
|
+
| `.cartage/` | git-ignored: `secrets.yaml`, `state/`, `rejects/` |
|
|
66
|
+
|
|
67
|
+
## YAML configuration
|
|
68
|
+
|
|
69
|
+
Cartage uses three YAML layers: `cartage.yaml` sets project-wide defaults, `connections.yaml` defines named services
|
|
70
|
+
per environment, and each `pipelines/*.yaml` file describes one source-to-destination flow.
|
|
71
|
+
|
|
72
|
+
### `cartage.yaml`
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
project: inventory
|
|
76
|
+
environments: [dev, prd]
|
|
77
|
+
default_env: dev
|
|
78
|
+
|
|
79
|
+
defaults:
|
|
80
|
+
engine: dlt # or python
|
|
81
|
+
|
|
82
|
+
state:
|
|
83
|
+
dev: { path: .cartage/state }
|
|
84
|
+
|
|
85
|
+
orchestrators:
|
|
86
|
+
airflow:
|
|
87
|
+
dags_dir: dags
|
|
88
|
+
default_args: { owner: data-team, retries: 1 }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The `default_env` must be listed in `environments`. A pipeline can override the default engine with its own `engine`.
|
|
92
|
+
State settings are optional; when omitted, Cartage stores local state under `.cartage/state`.
|
|
93
|
+
|
|
94
|
+
### `connections.yaml`
|
|
95
|
+
|
|
96
|
+
Connections have a `type` and an `envs` map. Put service-specific settings under the environment where they apply;
|
|
97
|
+
pipelines refer to the connection by name. Keep credentials out of the file and use secret or environment references.
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
connections:
|
|
101
|
+
local_files:
|
|
102
|
+
type: filesystem
|
|
103
|
+
envs:
|
|
104
|
+
dev: { path: ./data }
|
|
105
|
+
|
|
106
|
+
sap_erp:
|
|
107
|
+
type: sap
|
|
108
|
+
envs:
|
|
109
|
+
dev: { transport: mock, client: "100" }
|
|
110
|
+
prd:
|
|
111
|
+
transport: rfc
|
|
112
|
+
ashost: sap.example.com
|
|
113
|
+
sysnr: "00"
|
|
114
|
+
client: "100"
|
|
115
|
+
user: "${secret:sap.user}"
|
|
116
|
+
passwd: "${secret:sap.passwd}"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`${secret:key}` resolves from `CARTAGE_SECRET__<KEY>` (dots become double underscores and names are uppercased),
|
|
120
|
+
then `.cartage/secrets.yaml`. `${env:NAME}` reads an environment variable directly. See [Secrets](#secrets) for details.
|
|
121
|
+
|
|
122
|
+
### `pipelines/*.yaml`
|
|
123
|
+
|
|
124
|
+
Each pipeline names a source connection, applies an ordered list of transforms, and writes to a destination. A step
|
|
125
|
+
must have exactly one of `map`, `filter`, or `batch`; each reference uses the `module:function` format.
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
name: materials_to_sap
|
|
129
|
+
source:
|
|
130
|
+
connection: local_files
|
|
131
|
+
format: csv
|
|
132
|
+
path: materials/*.csv
|
|
133
|
+
incremental: true
|
|
134
|
+
|
|
135
|
+
transforms:
|
|
136
|
+
- map: transforms.materials:normalize_uom
|
|
137
|
+
- filter: transforms.materials:is_active
|
|
138
|
+
- batch: transforms.materials:dedupe
|
|
139
|
+
with: { key: material }
|
|
140
|
+
|
|
141
|
+
destination:
|
|
142
|
+
connection: sap_erp
|
|
143
|
+
bapi: BAPI_MATERIAL_SAVEDATA
|
|
144
|
+
mapping:
|
|
145
|
+
material: HEADDATA.MATERIAL
|
|
146
|
+
description: MATERIALDESCRIPTION[].MATL_DESC
|
|
147
|
+
uom: CLIENTDATA.BASE_UOM
|
|
148
|
+
constants:
|
|
149
|
+
MATERIALDESCRIPTION[].LANGU_ISO: EN
|
|
150
|
+
commit: per_record
|
|
151
|
+
|
|
152
|
+
schedule:
|
|
153
|
+
airflow:
|
|
154
|
+
schedule: "0 3 * * *"
|
|
155
|
+
tags: [sap, materials]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Source and destination fields other than `connection` are adapter options. Transform functions live in your `transforms/`
|
|
159
|
+
package; `with` passes keyword arguments to the function. The optional `schedule.airflow` block controls DAG generation
|
|
160
|
+
with `cartage generate`. Run `cartage validate` after editing YAML to check the project and references.
|
|
161
|
+
|
|
162
|
+
## Batches
|
|
163
|
+
|
|
164
|
+
- `batch` transforms see one source batch at a time (default 100 rows, `batch_size` source option), never across files.
|
|
165
|
+
- `commit: per_batch` commits each batch the engine hands to the destination (the dlt engine re-chunks at 100).
|
|
166
|
+
|
|
167
|
+
## Secrets
|
|
168
|
+
|
|
169
|
+
`${secret:sap.passwd}` reads `CARTAGE_SECRET__SAP__PASSWD`, then `.cartage/secrets.yaml`. `${env:VAR}` reads an
|
|
170
|
+
environment variable. Resolved values are never printed.
|
|
171
|
+
|
|
172
|
+
## State and rejects
|
|
173
|
+
|
|
174
|
+
Incremental state is saved only when a run has no record errors (or with `--advance-state`). Rejected records
|
|
175
|
+
go to `.cartage/rejects/<pipeline>/<run_id>.jsonl`. `cartage state show|reset <pipeline>` inspects or clears state.
|
|
176
|
+
|
|
177
|
+
## Exit codes
|
|
178
|
+
|
|
179
|
+
`0` ok · `1` record errors · `2` configuration error · `3` fatal run error (connection, transport, `on_error: fail`).
|
|
180
|
+
|
|
181
|
+
## Extending
|
|
182
|
+
|
|
183
|
+
Adapters are entry points in the groups `cartage.sources`, `cartage.destinations`, `cartage.engines` and
|
|
184
|
+
`cartage.orchestrators`. `cartage plugins` lists what is installed.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
Apache-2.0
|
cartage-0.1.0/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Cartage
|
|
2
|
+
|
|
3
|
+
Declarative data migrations. Describe sources, destinations and pipelines in YAML, put custom logic in plain
|
|
4
|
+
Python, run locally to test, and generate thin Airflow DAGs for production.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
- **Sources:** local CSV folders, S3, and any [dlt](https://dlthub.com) source.
|
|
9
|
+
- **Destinations:** SAP via BAPIs (v0.1 ships a mock SAP; RFC is planned).
|
|
10
|
+
- **Engines:** `python` (a plain loop) and `dlt`.
|
|
11
|
+
- **Orchestrators:** Airflow. The DAG only calls `cartage run`, so local and production run the same code.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "cartage[dlt]"
|
|
17
|
+
cartage init demo && cd demo
|
|
18
|
+
cartage validate
|
|
19
|
+
cartage plan materials # dry run: records before/after transforms and the BAPI payloads
|
|
20
|
+
cartage run materials # reads 20 rows, filters 2, sends 18: 16 load, 2 fail on purpose (exit 1)
|
|
21
|
+
cartage run materials --advance-state
|
|
22
|
+
cartage run materials # incremental: nothing new to load
|
|
23
|
+
cartage generate # dags/materials_to_sap.py
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Split-screen demo: run `cartage sap mock` in one terminal, set `url: http://localhost:8765` on `sap_erp.dev`
|
|
27
|
+
in `connections.yaml`, and run the pipeline in another terminal.
|
|
28
|
+
|
|
29
|
+
## Project layout
|
|
30
|
+
|
|
31
|
+
| Path | Purpose |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `cartage.yaml` | environments, default engine, state location, orchestrator settings |
|
|
34
|
+
| `connections.yaml` | named connections with settings per environment — secrets only as references |
|
|
35
|
+
| `pipelines/*.yaml` | source → transforms → destination (+ schedule) |
|
|
36
|
+
| `transforms/*.py` | `map` / `filter` / `batch` functions referenced as `module:function` |
|
|
37
|
+
| `templates/airflow/dag.py.j2` | optional DAG template override (`{% extends "cartage/airflow_dag.py.j2" %}`) |
|
|
38
|
+
| `.cartage/` | git-ignored: `secrets.yaml`, `state/`, `rejects/` |
|
|
39
|
+
|
|
40
|
+
## YAML configuration
|
|
41
|
+
|
|
42
|
+
Cartage uses three YAML layers: `cartage.yaml` sets project-wide defaults, `connections.yaml` defines named services
|
|
43
|
+
per environment, and each `pipelines/*.yaml` file describes one source-to-destination flow.
|
|
44
|
+
|
|
45
|
+
### `cartage.yaml`
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
project: inventory
|
|
49
|
+
environments: [dev, prd]
|
|
50
|
+
default_env: dev
|
|
51
|
+
|
|
52
|
+
defaults:
|
|
53
|
+
engine: dlt # or python
|
|
54
|
+
|
|
55
|
+
state:
|
|
56
|
+
dev: { path: .cartage/state }
|
|
57
|
+
|
|
58
|
+
orchestrators:
|
|
59
|
+
airflow:
|
|
60
|
+
dags_dir: dags
|
|
61
|
+
default_args: { owner: data-team, retries: 1 }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `default_env` must be listed in `environments`. A pipeline can override the default engine with its own `engine`.
|
|
65
|
+
State settings are optional; when omitted, Cartage stores local state under `.cartage/state`.
|
|
66
|
+
|
|
67
|
+
### `connections.yaml`
|
|
68
|
+
|
|
69
|
+
Connections have a `type` and an `envs` map. Put service-specific settings under the environment where they apply;
|
|
70
|
+
pipelines refer to the connection by name. Keep credentials out of the file and use secret or environment references.
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
connections:
|
|
74
|
+
local_files:
|
|
75
|
+
type: filesystem
|
|
76
|
+
envs:
|
|
77
|
+
dev: { path: ./data }
|
|
78
|
+
|
|
79
|
+
sap_erp:
|
|
80
|
+
type: sap
|
|
81
|
+
envs:
|
|
82
|
+
dev: { transport: mock, client: "100" }
|
|
83
|
+
prd:
|
|
84
|
+
transport: rfc
|
|
85
|
+
ashost: sap.example.com
|
|
86
|
+
sysnr: "00"
|
|
87
|
+
client: "100"
|
|
88
|
+
user: "${secret:sap.user}"
|
|
89
|
+
passwd: "${secret:sap.passwd}"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`${secret:key}` resolves from `CARTAGE_SECRET__<KEY>` (dots become double underscores and names are uppercased),
|
|
93
|
+
then `.cartage/secrets.yaml`. `${env:NAME}` reads an environment variable directly. See [Secrets](#secrets) for details.
|
|
94
|
+
|
|
95
|
+
### `pipelines/*.yaml`
|
|
96
|
+
|
|
97
|
+
Each pipeline names a source connection, applies an ordered list of transforms, and writes to a destination. A step
|
|
98
|
+
must have exactly one of `map`, `filter`, or `batch`; each reference uses the `module:function` format.
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
name: materials_to_sap
|
|
102
|
+
source:
|
|
103
|
+
connection: local_files
|
|
104
|
+
format: csv
|
|
105
|
+
path: materials/*.csv
|
|
106
|
+
incremental: true
|
|
107
|
+
|
|
108
|
+
transforms:
|
|
109
|
+
- map: transforms.materials:normalize_uom
|
|
110
|
+
- filter: transforms.materials:is_active
|
|
111
|
+
- batch: transforms.materials:dedupe
|
|
112
|
+
with: { key: material }
|
|
113
|
+
|
|
114
|
+
destination:
|
|
115
|
+
connection: sap_erp
|
|
116
|
+
bapi: BAPI_MATERIAL_SAVEDATA
|
|
117
|
+
mapping:
|
|
118
|
+
material: HEADDATA.MATERIAL
|
|
119
|
+
description: MATERIALDESCRIPTION[].MATL_DESC
|
|
120
|
+
uom: CLIENTDATA.BASE_UOM
|
|
121
|
+
constants:
|
|
122
|
+
MATERIALDESCRIPTION[].LANGU_ISO: EN
|
|
123
|
+
commit: per_record
|
|
124
|
+
|
|
125
|
+
schedule:
|
|
126
|
+
airflow:
|
|
127
|
+
schedule: "0 3 * * *"
|
|
128
|
+
tags: [sap, materials]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Source and destination fields other than `connection` are adapter options. Transform functions live in your `transforms/`
|
|
132
|
+
package; `with` passes keyword arguments to the function. The optional `schedule.airflow` block controls DAG generation
|
|
133
|
+
with `cartage generate`. Run `cartage validate` after editing YAML to check the project and references.
|
|
134
|
+
|
|
135
|
+
## Batches
|
|
136
|
+
|
|
137
|
+
- `batch` transforms see one source batch at a time (default 100 rows, `batch_size` source option), never across files.
|
|
138
|
+
- `commit: per_batch` commits each batch the engine hands to the destination (the dlt engine re-chunks at 100).
|
|
139
|
+
|
|
140
|
+
## Secrets
|
|
141
|
+
|
|
142
|
+
`${secret:sap.passwd}` reads `CARTAGE_SECRET__SAP__PASSWD`, then `.cartage/secrets.yaml`. `${env:VAR}` reads an
|
|
143
|
+
environment variable. Resolved values are never printed.
|
|
144
|
+
|
|
145
|
+
## State and rejects
|
|
146
|
+
|
|
147
|
+
Incremental state is saved only when a run has no record errors (or with `--advance-state`). Rejected records
|
|
148
|
+
go to `.cartage/rejects/<pipeline>/<run_id>.jsonl`. `cartage state show|reset <pipeline>` inspects or clears state.
|
|
149
|
+
|
|
150
|
+
## Exit codes
|
|
151
|
+
|
|
152
|
+
`0` ok · `1` record errors · `2` configuration error · `3` fatal run error (connection, transport, `on_error: fail`).
|
|
153
|
+
|
|
154
|
+
## Extending
|
|
155
|
+
|
|
156
|
+
Adapters are entry points in the groups `cartage.sources`, `cartage.destinations`, `cartage.engines` and
|
|
157
|
+
`cartage.orchestrators`. `cartage plugins` lists what is installed.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
Apache-2.0
|
|
Binary file
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Output docs/cartage-demo.gif
|
|
2
|
+
Require uv
|
|
3
|
+
Set Shell "zsh"
|
|
4
|
+
Set FontSize 14
|
|
5
|
+
Set Width 1100
|
|
6
|
+
Set Height 680
|
|
7
|
+
Set TypingSpeed 25ms
|
|
8
|
+
Set Theme "Catppuccin Mocha"
|
|
9
|
+
Set LoopOffset 4%
|
|
10
|
+
|
|
11
|
+
Hide
|
|
12
|
+
Type `CARTAGE_ROOT="$PWD"; DEMO_DIR="$(mktemp -d /tmp/cartage-demo.XXXXXX)"; trap 'rm -rf "$DEMO_DIR"' EXIT; uv run cartage init "$DEMO_DIR" >/dev/null && source "$CARTAGE_ROOT/.venv/bin/activate" && export PATH="$CARTAGE_ROOT/.venv/bin:$PATH" && hash -r && cd "$DEMO_DIR" && clear`
|
|
13
|
+
Enter
|
|
14
|
+
Show
|
|
15
|
+
Sleep 700ms
|
|
16
|
+
|
|
17
|
+
Type "Let's see what Cartage can do..."
|
|
18
|
+
Sleep 1s
|
|
19
|
+
Backspace 32
|
|
20
|
+
Type "cartage --help"
|
|
21
|
+
Enter
|
|
22
|
+
Wait+Line />$/
|
|
23
|
+
Sleep 3s
|
|
24
|
+
|
|
25
|
+
Type "First, let's validate the demo project."
|
|
26
|
+
Sleep 1s
|
|
27
|
+
Backspace 39
|
|
28
|
+
Type "cartage validate"
|
|
29
|
+
Enter
|
|
30
|
+
Wait+Line />$/
|
|
31
|
+
Sleep 2s
|
|
32
|
+
|
|
33
|
+
Type "Watch the UOM conversion: KGS to KG."
|
|
34
|
+
Sleep 1s
|
|
35
|
+
Backspace 36
|
|
36
|
+
Type "cartage plan materials -n 3"
|
|
37
|
+
Enter
|
|
38
|
+
Wait+Line />$/
|
|
39
|
+
ScrollUp 30
|
|
40
|
+
Sleep 1s
|
|
41
|
+
ScrollDown@100ms 30
|
|
42
|
+
Sleep 2s
|
|
43
|
+
|
|
44
|
+
Type "Now generate the production Airflow DAG."
|
|
45
|
+
Sleep 1s
|
|
46
|
+
Backspace 40
|
|
47
|
+
Type "cartage generate"
|
|
48
|
+
Enter
|
|
49
|
+
Wait+Line />$/
|
|
50
|
+
Sleep 2s
|
|
51
|
+
|
|
52
|
+
Type "Let's read the generated Airflow DAG."
|
|
53
|
+
Sleep 1s
|
|
54
|
+
Backspace 37
|
|
55
|
+
Type "cat dags/materials_to_sap.py"
|
|
56
|
+
Enter
|
|
57
|
+
Wait+Line />$/
|
|
58
|
+
Sleep 4s
|
|
59
|
+
|
|
60
|
+
Type "Finally, run the sample materials pipeline."
|
|
61
|
+
Sleep 1s
|
|
62
|
+
Backspace 43
|
|
63
|
+
Type "cartage run materials"
|
|
64
|
+
Enter
|
|
65
|
+
Wait+Line />$/
|
|
66
|
+
Sleep 4s
|