etlpipe 2.0.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.
Files changed (72) hide show
  1. etlpipe-2.0.0/.github/workflows/ci.yml +78 -0
  2. etlpipe-2.0.0/.github/workflows/publish-governance.yml +37 -0
  3. etlpipe-2.0.0/.github/workflows/publish-main.yml +43 -0
  4. etlpipe-2.0.0/.gitignore +47 -0
  5. etlpipe-2.0.0/CHANGELOG.md +74 -0
  6. etlpipe-2.0.0/CONTRIBUTING.md +170 -0
  7. etlpipe-2.0.0/DATABRICKS_GUIDE.md +193 -0
  8. etlpipe-2.0.0/LICENSE +21 -0
  9. etlpipe-2.0.0/PKG-INFO +526 -0
  10. etlpipe-2.0.0/README.md +472 -0
  11. etlpipe-2.0.0/SECURITY.md +63 -0
  12. etlpipe-2.0.0/USER_GUIDE.md +1944 -0
  13. etlpipe-2.0.0/demo/demo_customers.csv +13 -0
  14. etlpipe-2.0.0/demo/demo_pipeline.py +579 -0
  15. etlpipe-2.0.0/demo/demo_pipeline.yaml +65 -0
  16. etlpipe-2.0.0/demo/demo_sales.csv +21 -0
  17. etlpipe-2.0.0/demo/sample_pipeline.yaml +21 -0
  18. etlpipe-2.0.0/demo/sample_pipeline_pandas.yaml +89 -0
  19. etlpipe-2.0.0/demo/sample_pipeline_spark.yaml +89 -0
  20. etlpipe-2.0.0/doc/Developer.md +300 -0
  21. etlpipe-2.0.0/doc/InOut.md +221 -0
  22. etlpipe-2.0.0/doc/Join.md +254 -0
  23. etlpipe-2.0.0/doc/Parse.md +254 -0
  24. etlpipe-2.0.0/doc/Preparation.md +686 -0
  25. etlpipe-2.0.0/doc/Transform.md +280 -0
  26. etlpipe-2.0.0/doc/adr/000-template.md +33 -0
  27. etlpipe-2.0.0/doc/adr/001-dual-backend-architecture.md +52 -0
  28. etlpipe-2.0.0/doc/adr/002-security-eval-removal.md +46 -0
  29. etlpipe-2.0.0/doc/adr/003-governance-spinoff.md +102 -0
  30. etlpipe-2.0.0/pyproject.toml +77 -0
  31. etlpipe-2.0.0/src/etlpipe/__init__.py +53 -0
  32. etlpipe-2.0.0/src/etlpipe/_config.py +70 -0
  33. etlpipe-2.0.0/src/etlpipe/_contracts.py +29 -0
  34. etlpipe-2.0.0/src/etlpipe/_pii.py +27 -0
  35. etlpipe-2.0.0/src/etlpipe/_validators.py +85 -0
  36. etlpipe-2.0.0/src/etlpipe/_version.py +3 -0
  37. etlpipe-2.0.0/src/etlpipe/convert.py +1088 -0
  38. etlpipe-2.0.0/src/etlpipe/developer.py +287 -0
  39. etlpipe-2.0.0/src/etlpipe/engines/__init__.py +4 -0
  40. etlpipe-2.0.0/src/etlpipe/engines/base.py +336 -0
  41. etlpipe-2.0.0/src/etlpipe/engines/pandas_engine.py +1654 -0
  42. etlpipe-2.0.0/src/etlpipe/engines/spark_engine.py +1359 -0
  43. etlpipe-2.0.0/src/etlpipe/in_out.py +228 -0
  44. etlpipe-2.0.0/src/etlpipe/join.py +225 -0
  45. etlpipe-2.0.0/src/etlpipe/parse.py +230 -0
  46. etlpipe-2.0.0/src/etlpipe/pipeline.py +326 -0
  47. etlpipe-2.0.0/src/etlpipe/preparation.py +570 -0
  48. etlpipe-2.0.0/src/etlpipe/transform.py +255 -0
  49. etlpipe-2.0.0/src/etlpipe_governance/README_GOVERNANCE.md +99 -0
  50. etlpipe-2.0.0/src/etlpipe_governance/__init__.py +61 -0
  51. etlpipe-2.0.0/src/etlpipe_governance/_version.py +1 -0
  52. etlpipe-2.0.0/src/etlpipe_governance/contracts.py +545 -0
  53. etlpipe-2.0.0/src/etlpipe_governance/pii.py +398 -0
  54. etlpipe-2.0.0/src/etlpipe_governance/pyproject.toml +50 -0
  55. etlpipe-2.0.0/src/etlpipe_governance/tests/__init__.py +1 -0
  56. etlpipe-2.0.0/src/etlpipe_governance/tests/test_contracts.py +415 -0
  57. etlpipe-2.0.0/src/etlpipe_governance/tests/test_pii.py +314 -0
  58. etlpipe-2.0.0/tests/conftest.py +92 -0
  59. etlpipe-2.0.0/tests/fixtures/sample_workflow.yxmd +140 -0
  60. etlpipe-2.0.0/tests/test_assessment.py +1197 -0
  61. etlpipe-2.0.0/tests/test_contracts.py +244 -0
  62. etlpipe-2.0.0/tests/test_convert.py +393 -0
  63. etlpipe-2.0.0/tests/test_developer.py +163 -0
  64. etlpipe-2.0.0/tests/test_engines.py +89 -0
  65. etlpipe-2.0.0/tests/test_in_out.py +136 -0
  66. etlpipe-2.0.0/tests/test_join.py +176 -0
  67. etlpipe-2.0.0/tests/test_parse.py +128 -0
  68. etlpipe-2.0.0/tests/test_pii.py +268 -0
  69. etlpipe-2.0.0/tests/test_pipeline.py +147 -0
  70. etlpipe-2.0.0/tests/test_preparation.py +449 -0
  71. etlpipe-2.0.0/tests/test_spark_engine.py +142 -0
  72. etlpipe-2.0.0/tests/test_transform.py +203 -0
@@ -0,0 +1,78 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ name: Lint & Format Check
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+ - name: Install ruff
19
+ run: pip install ruff
20
+ - name: Check formatting
21
+ run: ruff format --check src/ tests/
22
+ - name: Check linting rules
23
+ run: ruff check src/ tests/
24
+
25
+ security:
26
+ name: Security Scan
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: "3.12"
33
+ - name: Install tools
34
+ run: pip install pip-audit bandit
35
+ - name: Audit dependencies for known vulnerabilities
36
+ run: pip install -e ".[dev]" && pip-audit
37
+ - name: Static security analysis
38
+ run: bandit -r src/etlpipe/ -ll --skip B101
39
+
40
+ test:
41
+ name: Tests (Python ${{ matrix.python-version }})
42
+ runs-on: ubuntu-latest
43
+ strategy:
44
+ fail-fast: false
45
+ matrix:
46
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: ${{ matrix.python-version }}
52
+ - name: Install package with dev dependencies
53
+ run: python -m pip install -e ".[dev]"
54
+ - name: Run test suite with coverage
55
+ run: |
56
+ pytest tests/ -v --cov=etlpipe --cov-report=term-missing --cov-report=xml \
57
+ -x --tb=short -q
58
+ - name: Upload coverage
59
+ if: matrix.python-version == '3.12'
60
+ uses: actions/upload-artifact@v4
61
+ with:
62
+ name: coverage-report
63
+ path: coverage.xml
64
+
65
+ spark-test:
66
+ name: Spark Integration Tests
67
+ runs-on: ubuntu-latest
68
+ continue-on-error: true
69
+ steps:
70
+ - uses: actions/checkout@v4
71
+ - uses: actions/setup-python@v5
72
+ with:
73
+ python-version: "3.12"
74
+ - name: Install package with Spark dependencies
75
+ run: python -m pip install setuptools && python -m pip install -e ".[dev,spark]"
76
+ - name: Run Spark integration tests
77
+ run: |
78
+ pytest tests/test_spark_engine.py -v --tb=short -q
@@ -0,0 +1,37 @@
1
+ name: Publish Governance to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ name: Build and publish etlpipe-governance ?? to PyPI
11
+ runs-on: ubuntu-latest
12
+
13
+ permissions:
14
+ id-token: write
15
+ contents: read
16
+
17
+ steps:
18
+ - name: Check out repository
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.11"
25
+
26
+ - name: Install build dependencies
27
+ run: python -m pip install --upgrade pip build
28
+
29
+ - name: Build governance package
30
+ run: |
31
+ cd src/etlpipe_governance
32
+ python -m build --outdir ../../dist_governance
33
+
34
+ - name: Publish governance package
35
+ uses: pypa/gh-action-pypi-publish@release/v1
36
+ with:
37
+ packages-dir: dist_governance/
@@ -0,0 +1,43 @@
1
+ name: Publish Main to PyPI
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["Publish Governance to PyPI"]
6
+ types:
7
+ - completed
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ build-and-publish:
12
+ name: Build and publish etlpipe ?? to PyPI
13
+ runs-on: ubuntu-latest
14
+
15
+ permissions:
16
+ id-token: write
17
+ contents: read
18
+
19
+ steps:
20
+ - name: Check out repository
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.11"
27
+
28
+ - name: Install build dependencies
29
+ run: python -m pip install --upgrade pip build
30
+
31
+ - name: Install package and test dependencies
32
+ run: python -m pip install -e ".[dev]"
33
+
34
+ - name: Run test suite
35
+ run: pytest tests/
36
+
37
+ - name: Build main package
38
+ run: python -m build --outdir dist_main/
39
+
40
+ - name: Publish main package
41
+ uses: pypa/gh-action-pypi-publish@release/v1
42
+ with:
43
+ packages-dir: dist_main/
@@ -0,0 +1,47 @@
1
+ # Environments
2
+ .env
3
+ .venv
4
+ env/
5
+ venv/
6
+ ENV/
7
+ env.bak/
8
+ venv.bak/
9
+
10
+ # Build and Distribution
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # Python and Cache
28
+ __pycache__/
29
+ *.py[cod]
30
+ *$py.class
31
+ .pytest_cache/
32
+ .coverage
33
+ htmlcov/
34
+
35
+ # Editor and IDEs
36
+ .vscode/
37
+ .idea/
38
+ *.swp
39
+ *.swo
40
+
41
+ # Mock data generated during E2E test & demo execution
42
+ sales.csv
43
+ regions.csv
44
+ cli_test_output.csv
45
+ e2e_demo.py
46
+ demo/*_output.csv
47
+ demo/pipeline_output_*.csv
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.0.0] — 2026-07-22
9
+
10
+ ### Added
11
+ - **`.yxmd` Workflow Converter**: New `YxmdConverter` class and `etlpipe-convert` CLI tool to convert proprietary visual ETL workflow XML files into Etlpipe YAML pipelines. Supports 15+ tool mappings with expression translation, topological sorting, and graceful degradation for unparseable tools.
12
+ - **Enterprise Governance Sub-Package (`etlpipe-governance`)**: Extracted and expanded data quality toolkit into a standalone package:
13
+ - `scan_pii()` — Detects PII across 12 international patterns (email, phone, SSN, credit card, Aadhaar, IBAN, passport, etc.) with confidence scoring.
14
+ - `mask_pii()` — Three masking strategies: `redact`, `hash` (SHA-256), and `pseudonymise` (reversible labels with mapping). Competitive differentiator vs. Great Expectations/Pandera.
15
+ - `expect_schema()` / `infer_schema()` — Schema contract validation with dtype aliases, nullability checks, strict/non-strict modes.
16
+ - `profile()` — Statistical profiler with cardinality, null rates, min/max/mean/std, and top-N value distributions.
17
+ - `ContractSuite` — Batch contract runner for pipeline audit checkpoints with PASS/FAIL/SKIPPED/WARN/ERROR reporting.
18
+ - **Structured Logging**: Replaced all `print()` statements in pipeline engine with `logging.getLogger()` using hierarchical names (`etlpipe.pipeline`, `etlpipe.engines.pandas`, etc.). Pipeline metrics emitted as structured JSON.
19
+ - **Pipeline Execution Metrics**: Per-step timing (`duration_s`), row counts, output types, and status tracking. Metrics accessible via `Pipeline.metrics` after execution.
20
+ - **Pipeline Event Hooks**: Four lifecycle callbacks (`on_step_start`, `on_step_complete`, `on_step_error`, `on_pipeline_complete`) for integrating with Slack, PagerDuty, Teams, or any alerting system without adding those as dependencies.
21
+ - **Retry/Backoff for Downloads**: `Developer.download()` now supports configurable `max_retries` (default 3) and `retry_delay` (default 1.0s) with exponential backoff. Handles `URLError`, `TimeoutError`, `OSError`, and HTTP 5xx responses.
22
+ - **Schema Contracts in YAML Pipelines**: Steps can declare `output_schema` to enforce data contracts as part of pipeline execution.
23
+ - **Spark Integration Tests in CI**: Dedicated non-blocking CI job running Spark backend tests on Python 3.12.
24
+ - **Security Policy**: Added `SECURITY.md` with vulnerability reporting process, response timeline, and security track record.
25
+ - **Contributing Guide**: Added `CONTRIBUTING.md` with dev setup, code standards, testing requirements, and PR process.
26
+ - **Architecture Decision Records**: Added `doc/adr/` with template and records for dual-backend architecture and eval() removal.
27
+ - **Enterprise Governance Documentation**: New section in `USER_GUIDE.md` covering PII scanning, data contracts, event hooks, OpenLineage integration patterns, and data catalog recommendations.
28
+
29
+ ### Changed
30
+ - **Version bump to 2.0.0** — reflects new sub-package, converter, and breaking packaging changes.
31
+ - **Development Status upgraded from Alpha to Beta** in PyPI classifiers.
32
+ - **Wheel now bundles `etlpipe_governance`** alongside `etlpipe` — governance features are available without separate installation.
33
+ - **Governance tests included in default pytest** via `testpaths` configuration.
34
+ - **Backward-compatible shim modules** (`etlpipe._contracts`, `etlpipe._pii`) re-export from `etlpipe_governance` with deprecation warnings targeting removal in 3.0.
35
+
36
+ ### Security
37
+ - **Converter uses `defusedxml`** for safe XML parsing of `.yxmd` files (XXE/SSRF protection). Falls back with a hard error if `defusedxml` is not available (no silent downgrade).
38
+
39
+ ## [1.0.0] — 2026-07-04
40
+
41
+ ### Added
42
+ - **Automated CI/CD**: Added a GitHub Actions workflow using OIDC (Trusted Publishing) for automated, secure PyPI deployments on new GitHub Releases.
43
+ - **Enterprise Edge Case Protections**: Built a background transpiler to auto-backtick column headers containing spaces to support dirty real-world enterprise datasets without syntax crashes.
44
+
45
+ ### Changed
46
+ - **Memory Optimization (Pandas)**: The Pandas backend now automatically requires the `pyarrow` multi-threaded C++ engine when loading large CSV files, heavily reducing physical RAM usage and preventing `MemoryError` crashes.
47
+ - **Datatype Coercion**: `Join.join` now dynamically casts mismatching datatypes between left and right anchor columns, eliminating strict PySpark `AnalysisException` errors when migrating disparate schemas.
48
+
49
+ ### Fixed
50
+ - **Critical Security Fix**: Eradicated an arbitrary code execution vulnerability (RCE) in `Preparation.formula` by removing the insecure `eval()` fallback. The engine is now entirely zero-trust and sandboxed via `pd.eval` or strict `lambda` callables.
51
+ - **Spark Architecture (Sorting Death Trap)**: Completely refactored `Preparation.record_id` to utilize parallelized `F.monotonically_increasing_id()` instead of `Window.orderBy`, eliminating massive single-node memory bottlenecks when scaling Spark pipelines.
52
+ - **Spark Architecture (Tuple Bombs)**: Injected native `.persist()` boundaries directly into the upstream nodes of `Join.join` to prevent PySpark from redundantly computing the entire historical DAG multiple times during joins.
53
+ - **Regex Edge Cases**: Fixed an engine logic gap where Regex capture groups incorrectly counted non-capturing groups `(?:)` in the Spark backend.
54
+ ## [0.2.0] — 2026-07-04
55
+
56
+ ### Added
57
+ - **Enterprise Scalability (Dual-Backend Architecture)**: Added native support for distributed execution on big-data clusters using the new `SparkEngine`.
58
+ - **Thread-Safe Context Manager**: Added `Etlpipe.backend()` context manager to allow concurrent multi-threading with different execution engines.
59
+ - **Dynamic Dispatch**: `Etlpipe.set_backend("spark")` now seamlessly reroutes all tool executions down to Spark SQL and Vectorized Pandas UDFs (PyArrow).
60
+
61
+ ### Changed
62
+ - All 55 Core tools have been refactored into thin dispatchers to support multiple engine backends without altering user-facing APIs.
63
+ - Updated `README.md` and created `USER_GUIDE.md` with complete documentation, tool reference tables, and declarative YAML pipeline configurations.
64
+
65
+ ## [0.1.0] — 2025-06-28
66
+
67
+ - **InOut** class — `input_data`, `output_data`, `text_input`, `browse`, `directory`, `date_time_now`
68
+ - **Preparation** class — `filter`, `formula`, `select`, `data_cleansing`, `sort`, `unique`, `sample`, `record_id`, `generate_rows`, `auto_field`, `multi_field_formula`, `multi_row_formula`, `tile`, `imputation`
69
+ - **Join** class — `join`, `join_multiple`, `union`, `find_replace`, `append_fields`, `fuzzy_match`
70
+ - **Transform** class — `summarize`, `transpose`, `cross_tab`, `running_total`, `count_records`
71
+ - **Parse** class — `date_time`, `regex_match`, `regex_parse`, `regex_replace`, `regex_tokenize`, `text_to_columns`, `xml_parse`
72
+ - **Developer** class — `base64_encode`, `base64_decode`, `download`, `column_info`, `dynamic_rename`
73
+ - Full test suite with pytest
74
+ - Type hints and Google-style docstrings on all public methods
@@ -0,0 +1,170 @@
1
+ # Contributing to Etlpipe
2
+
3
+ Thank you for your interest in contributing to Etlpipe! This guide will help you get started.
4
+
5
+ ---
6
+
7
+ ## Development Setup
8
+
9
+ ### Prerequisites
10
+
11
+ - Python 3.10+
12
+ - Git
13
+
14
+ ### Getting Started
15
+
16
+ ```bash
17
+ # Clone the repository
18
+ git clone https://github.com/tonystark7cris/etlpipe.git
19
+ cd etlpipe
20
+
21
+ # Create a virtual environment
22
+ python -m venv venv
23
+ source venv/bin/activate # Linux/Mac
24
+ venv\Scripts\activate # Windows
25
+
26
+ # Install with development dependencies
27
+ pip install -e ".[dev]"
28
+
29
+ # Verify installation
30
+ pytest tests/ -v
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Code Standards
36
+
37
+ ### Style & Formatting
38
+
39
+ Etlpipe uses **[Ruff](https://docs.astral.sh/ruff/)** for both linting and formatting. Configuration is in [pyproject.toml](pyproject.toml):
40
+
41
+ - **Target**: Python 3.10
42
+ - **Line length**: 120 characters
43
+ - **Lint rules**: E, F, I, W, UP, B, SIM
44
+
45
+ ```bash
46
+ # Check formatting
47
+ ruff format --check src/ tests/
48
+
49
+ # Auto-format
50
+ ruff format src/ tests/
51
+
52
+ # Run linter
53
+ ruff check src/ tests/
54
+
55
+ # Auto-fix lint issues
56
+ ruff check --fix src/ tests/
57
+ ```
58
+
59
+ ### Type Hints
60
+
61
+ All public methods **must** have full type annotations. Use `from __future__ import annotations` at the top of every module.
62
+
63
+ ### Docstrings
64
+
65
+ All public methods **must** have Google-style docstrings including:
66
+ - One-line summary
67
+ - `Args:` section with parameter descriptions
68
+ - `Returns:` section
69
+ - `Raises:` section (if applicable)
70
+ - `Example:` section with a short usage snippet
71
+
72
+ ### Immutability
73
+
74
+ All Etlpipe tool functions **must** be pure — they return new DataFrames and never mutate inputs. This is a core architectural invariant.
75
+
76
+ ---
77
+
78
+ ## Testing
79
+
80
+ ### Running Tests
81
+
82
+ ```bash
83
+ # Full test suite with coverage
84
+ pytest tests/ -v --cov=etlpipe --cov-report=term-missing
85
+
86
+ # Run specific test file
87
+ pytest tests/test_preparation.py -v
88
+
89
+ # Run a single test
90
+ pytest tests/test_contracts.py::TestExpectSchemaPass::test_round_trip -v
91
+ ```
92
+
93
+ ### Writing Tests
94
+
95
+ - Place test files in `tests/` with the naming convention `test_<module>.py`
96
+ - Use shared fixtures from `tests/conftest.py` where possible
97
+ - Test both happy paths and error cases
98
+ - For dual-engine features, ensure Pandas tests exist (Spark tests are optional but encouraged)
99
+
100
+ ### Coverage Requirements
101
+
102
+ New code should maintain or improve overall test coverage. Critical paths (security, data contracts, PII scanning) require >90% coverage.
103
+
104
+ ---
105
+
106
+ ## Architecture
107
+
108
+ ### Engine Pattern
109
+
110
+ Etlpipe uses an abstract **BackendEngine** pattern:
111
+
112
+ 1. **Public API** classes (`Preparation`, `Join`, etc.) are thin dispatchers
113
+ 2. They call `get_engine()` to obtain the active backend
114
+ 3. The backend (`PandasEngine` or `SparkEngine`) contains the actual implementation
115
+ 4. Both backends inherit from `BackendEngine` (ABC)
116
+
117
+ When adding a new tool:
118
+ 1. Add the abstract method signature to `engines/base.py`
119
+ 2. Implement in `engines/pandas_engine.py`
120
+ 3. Implement in `engines/spark_engine.py`
121
+ 4. Add the public static method to the appropriate palette class
122
+ 5. Add tests for both backends
123
+
124
+ ### Architecture Decision Records
125
+
126
+ Significant design decisions are documented as ADRs in `doc/adr/`. When proposing a major change, create a new ADR using the template at `doc/adr/000-template.md`.
127
+
128
+ ---
129
+
130
+ ## Pull Request Process
131
+
132
+ 1. **Branch**: Create a feature branch from `main` (e.g., `feature/add-pivot-tool`)
133
+ 2. **Implement**: Make your changes following the code standards above
134
+ 3. **Test**: Ensure all tests pass and add new tests for your changes
135
+ 4. **Lint**: Run `ruff format` and `ruff check` before pushing
136
+ 5. **PR**: Open a pull request with:
137
+ - Clear description of what changed and why
138
+ - Link to any related issues
139
+ - Screenshots/examples if applicable
140
+ 6. **Review**: Address review feedback promptly
141
+ 7. **Merge**: Squash-merge after approval
142
+
143
+ ---
144
+
145
+ ## Release Process
146
+
147
+ Etlpipe follows [Semantic Versioning](https://semver.org/):
148
+
149
+ - **PATCH** (1.0.x): Bug fixes, no API changes
150
+ - **MINOR** (1.x.0): New features, backward-compatible
151
+ - **MAJOR** (x.0.0): Breaking API changes
152
+
153
+ ### Release Checklist
154
+
155
+ 1. Update `src/etlpipe/_version.py` with the new version
156
+ 2. Update `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/) format
157
+ 3. Create a GitHub Release with a tag matching the version (e.g., `v1.1.0`)
158
+ 4. The CI/CD pipeline will automatically publish to PyPI via OIDC Trusted Publishing
159
+
160
+ ---
161
+
162
+ ## Security
163
+
164
+ If you discover a security vulnerability, please report it privately. See [SECURITY.md](SECURITY.md) for details.
165
+
166
+ ---
167
+
168
+ ## License
169
+
170
+ By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
@@ -0,0 +1,193 @@
1
+ # End-to-End Etlpipe Implementation on Databricks
2
+
3
+ This guide provides a step-by-step walkthrough for implementing a full, production-ready Etlpipe project on **Databricks**.
4
+
5
+ Because Etlpipe features a dynamic dual-engine architecture, you can write familiar Etlpipe-like logic in Python, and Etlpipe will translate it natively into distributed Spark execution under the hood.
6
+
7
+ ---
8
+
9
+ ## Architecture Overview
10
+
11
+ 1. **Ingestion**: Read raw data from Databricks Delta Tables (or DBFS/S3).
12
+ 2. **Transformation**: Execute Etlpipe logic utilizing the `spark` backend engine.
13
+ 3. **Validation**: Assert data quality before loading.
14
+ 4. **Load**: Write the transformed data back to a curated Delta Table.
15
+ 5. **Orchestration**: Schedule the notebook/script via Databricks Workflows (Jobs).
16
+
17
+ ---
18
+
19
+ ## Step 1: Cluster Setup
20
+
21
+ To run Etlpipe on Databricks, you just need to install the core package. Databricks already provides the Spark backend natively.
22
+
23
+ 1. Navigate to your Databricks Workspace -> **Compute**.
24
+ 2. Select your target cluster (e.g., Databricks Runtime 13.3 LTS).
25
+ 3. Click the **Libraries** tab -> **Install New**.
26
+ 4. Select **PyPI** and enter: `etlpipe`
27
+ 5. Click **Install**.
28
+
29
+ Alternatively, if you are using Databricks Repos, add `etlpipe` to your `requirements.txt`.
30
+
31
+ ---
32
+
33
+ ## Step 2: Project Structure
34
+
35
+ When using Databricks Repos (Git integration), we recommend structuring your Etlpipe project like a standard software engineering repository:
36
+
37
+ ```text
38
+ /my-databricks-project
39
+ ├── notebooks/
40
+ │ └── 01_run_pipeline.py # Main entry point for Databricks Jobs
41
+ ├── src/
42
+ │ ├── config.yaml # Pipeline configuration
43
+ │ └── pipeline.py # Core Etlpipe logic
44
+ ├── tests/
45
+ │ └── test_pipeline.py # Unit tests (run locally via Pytest)
46
+ └── requirements.txt # Etlpipe[spark], etc.
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Step 3: Core Pipeline Implementation (`src/pipeline.py`)
52
+
53
+ Here is the core business logic. We explicitly configure Etlpipe to use the `spark` backend so that operations execute on the cluster rather than the driver node.
54
+
55
+ ```python
56
+ import etlpipe
57
+ from etlpipe import InOut, Preparation, Join, Transform, Developer
58
+
59
+ def run_customer_360_pipeline():
60
+ # ==========================================
61
+ # 0. Set Backend to Spark
62
+ # ==========================================
63
+ etlpipe.set_backend("spark")
64
+ print("Executing Etlpipe Pipeline on Apache Spark Backend...")
65
+
66
+ # ==========================================
67
+ # 1. Ingest Data from Delta Tables
68
+ # ==========================================
69
+ # In Databricks, you can query Delta tables directly via Spark SQL syntax
70
+ # Etlpipe's spark backend treats SQL queries natively.
71
+ df_customers = InOut.input_data("dbfs:/mnt/lakehouse/raw/customers")
72
+ df_orders = InOut.input_data("dbfs:/mnt/lakehouse/raw/orders")
73
+
74
+ # ==========================================
75
+ # 2. Data Cleansing & Preparation
76
+ # ==========================================
77
+ # Cleanse Customer data: upper case names, strip whitespace, handle nulls
78
+ df_customers_clean = Preparation.data_cleansing(
79
+ df_customers,
80
+ replace_nulls_with="",
81
+ strip_whitespace=True,
82
+ modify_case="upper"
83
+ )
84
+
85
+ # Filter for completed orders
86
+ df_orders_valid, df_orders_invalid = Preparation.filter(
87
+ df_orders,
88
+ "OrderStatus = 'COMPLETED'"
89
+ )
90
+
91
+ # ==========================================
92
+ # 3. Join Datasets
93
+ # ==========================================
94
+ # Join (L: Unjoined Customers, J: Joined Data, R: Unjoined Orders)
95
+ left_unjoined, joined_data, right_unjoined = Join.join(
96
+ df_customers_clean,
97
+ df_orders_valid,
98
+ on="CustomerID"
99
+ )
100
+
101
+ # ==========================================
102
+ # 4. Transform & Aggregate
103
+ # ==========================================
104
+ # Summarize Total Lifetime Value (LTV) and Order Count by Customer
105
+ summary_data = Transform.summarize(
106
+ joined_data,
107
+ group_by=["CustomerID", "CustomerName", "Region"],
108
+ aggregations={
109
+ "OrderAmount": ["sum", "mean"],
110
+ "OrderID": "count distinct"
111
+ }
112
+ )
113
+
114
+ # Apply business logic using Formula
115
+ final_data = Preparation.formula(
116
+ summary_data,
117
+ column="CustomerTier",
118
+ expression="IF Sum_OrderAmount > 10000 THEN 'Platinum' ELSE 'Standard' ENDIF"
119
+ )
120
+
121
+ # ==========================================
122
+ # 5. Data Quality Testing
123
+ # ==========================================
124
+ # Developer.test evaluates natively on the Spark DataFrame
125
+ Developer.test(
126
+ final_data,
127
+ condition_func=lambda df: df["Sum_OrderAmount"].min() >= 0,
128
+ error_msg="Data Quality Failure: Negative LTV detected."
129
+ )
130
+
131
+ # ==========================================
132
+ # 6. Load Output to Delta Lake
133
+ # ==========================================
134
+ # Write the output back to the Databricks Lakehouse as a Delta table
135
+ InOut.output_data(
136
+ final_data,
137
+ "dbfs:/mnt/lakehouse/curated/customer_360",
138
+ format="delta",
139
+ mode="overwrite"
140
+ )
141
+
142
+ print("Pipeline executed successfully. Output written to Curated layer.")
143
+
144
+ if __name__ == "__main__":
145
+ run_customer_360_pipeline()
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Step 4: The Databricks Entrypoint (`notebooks/01_run_pipeline.py`)
151
+
152
+ In Databricks, you typically create a notebook to serve as the entry point for your Workflows/Jobs. Because we defined our code in a `src` module, the notebook is extremely clean:
153
+
154
+ ```python
155
+ # COMMAND ----------
156
+ # MAGIC %pip install -r ../requirements.txt
157
+ # COMMAND ----------
158
+
159
+ import sys
160
+ import os
161
+
162
+ # Ensure the src directory is in the Python path
163
+ sys.path.append(os.path.abspath("../src"))
164
+
165
+ from pipeline import run_customer_360_pipeline
166
+
167
+ # COMMAND ----------
168
+ # Run the pipeline
169
+ run_customer_360_pipeline()
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Step 5: Scheduling via Databricks Workflows
175
+
176
+ To run this pipeline automatically:
177
+
178
+ 1. Navigate to **Workflows** in the Databricks sidebar and click **Create Job**.
179
+ 2. Name the Job: `Etlpipe_Customer360_ETL`.
180
+ 3. In the task configuration:
181
+ - **Type**: Notebook
182
+ - **Source**: Workspace (or Git if using Repos).
183
+ - **Path**: Select `notebooks/01_run_pipeline.py`.
184
+ - **Compute**: Select the cluster you configured in Step 1 (or define a Job Cluster for cheaper, ephemeral execution).
185
+ 4. **Schedule**: Set a trigger (e.g., Daily at 2:00 AM) and configure Failure Alerts to notify your team via Email/Slack.
186
+
187
+ ---
188
+
189
+ ## Databricks Specific Best Practices
190
+
191
+ - **Leverage Delta Lake**: When using `InOut.input_data` and `InOut.output_data`, specify paths starting with `dbfs:/` and utilize the `format="delta"` argument to leverage Databricks' optimized storage layer.
192
+ - **Job Clusters vs. All-Purpose Clusters**: For scheduled Etlpipe pipelines, use **Job Clusters**. They are significantly cheaper and automatically terminate when the Etlpipe workflow completes.
193
+ - **Avoid `.browse()` in Production**: While `InOut.browse()` is fantastic for debugging interactively in a Notebook, remove it from production scripts, as it forces Spark to collect data to the driver node, which can cause Out-Of-Memory (OOM) errors on massive datasets.
etlpipe-2.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 etlpipe Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.