Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ai-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ name: AI Code Reviewer
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- '**.py'
- 'pants.toml'
- '3rdparty/**'
- 'scripts/**'

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
pull-requests: write
Expand Down
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Pre-commit configuration for Pants Build Monorepo
# See https://pre-commit.com for more information

repos:
- repo: local
hooks:
- id: pants-fmt
name: Pants Format
entry: ./pants fmt
language: system
types: [python]

- id: pants-lint
name: Pants Lint
entry: ./pants lint
language: system
types: [python]
1 change: 1 addition & 0 deletions 3rdparty/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Third-party Python dependencies for the data engineering projects.
pyspark>=3.3.0,<4.0.0
pre-commit>=3.0.0
263 changes: 261 additions & 2 deletions 3rdparty/user_reqs.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
// {
// "version": 3,
// "valid_for_interpreter_constraints": [
// "CPython==3.10.*"
// "CPython==3.10.9"
// ],
// "generated_with_requirements": [
// "pre-commit>=3.0.0",
// "pyspark<4.0.0,>=3.3.0"
// ],
// "manylinux": "manylinux2014",
Expand All @@ -27,6 +28,140 @@
"locked_resolves": [
{
"locked_requirements": [
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0",
"url": "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132",
"url": "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz"
}
],
"project_name": "cfgv",
"requires_dists": [],
"requires_python": ">=3.10",
"version": "3.5.0"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b",
"url": "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed",
"url": "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz"
}
],
"project_name": "distlib",
"requires_dists": [],
"requires_python": null,
"version": "0.4.3"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767",
"url": "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a",
"url": "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz"
}
],
"project_name": "filelock",
"requires_dists": [],
"requires_python": ">=3.10",
"version": "3.29.4"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a",
"url": "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842",
"url": "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz"
}
],
"project_name": "identify",
"requires_dists": [
"ukkonen; extra == \"license\""
],
"requires_python": ">=3.10",
"version": "2.6.19"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827",
"url": "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb",
"url": "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz"
}
],
"project_name": "nodeenv",
"requires_dists": [],
"requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7",
"version": "1.10.0"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a",
"url": "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7",
"url": "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz"
}
],
"project_name": "platformdirs",
"requires_dists": [],
"requires_python": ">=3.10",
"version": "4.10.0"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b",
"url": "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9",
"url": "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz"
}
],
"project_name": "pre-commit",
"requires_dists": [
"cfgv>=2.0.0",
"identify>=1.0.0",
"nodeenv>=0.11.1",
"pyyaml>=5.1",
"virtualenv>=20.10.0"
],
"requires_python": ">=3.10",
"version": "4.6.0"
},
{
"artifacts": [
{
Expand Down Expand Up @@ -73,6 +208,129 @@
],
"requires_python": ">=3.8",
"version": "3.5.8"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500",
"url": "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690",
"url": "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz"
}
],
"project_name": "python-discovery",
"requires_dists": [
"covdefaults>=2.3; extra == \"testing\"",
"coverage>=7.5.4; extra == \"testing\"",
"filelock>=3.15.4",
"furo>=2025.12.19; extra == \"docs\"",
"platformdirs<5,>=4.3.6",
"pytest-mock>=3.14; extra == \"testing\"",
"pytest>=8.3.5; extra == \"testing\"",
"setuptools>=75.1; extra == \"testing\"",
"sphinx-autodoc-typehints>=3.6.3; extra == \"docs\"",
"sphinx>=9.1; extra == \"docs\"",
"sphinxcontrib-mermaid>=2; extra == \"docs\"",
"sphinxcontrib-towncrier>=0.4; extra == \"docs\"",
"towncrier>=25.8; extra == \"docs\""
],
"requires_python": ">=3.8",
"version": "1.4.2"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69",
"url": "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl"
},
{
"algorithm": "sha256",
"hash": "02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956",
"url": "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl"
},
{
"algorithm": "sha256",
"hash": "d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f",
"url": "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz"
},
{
"algorithm": "sha256",
"hash": "66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198",
"url": "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl"
},
{
"algorithm": "sha256",
"hash": "b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8",
"url": "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl"
},
{
"algorithm": "sha256",
"hash": "9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b",
"url": "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl"
},
{
"algorithm": "sha256",
"hash": "418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0",
"url": "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl"
},
{
"algorithm": "sha256",
"hash": "214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b",
"url": "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl"
}
],
"project_name": "pyyaml",
"requires_dists": [],
"requires_python": ">=3.8",
"version": "6.0.3"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548",
"url": "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
"url": "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz"
}
],
"project_name": "typing-extensions",
"requires_dists": [],
"requires_python": ">=3.9",
"version": "4.15.0"
},
{
"artifacts": [
{
"algorithm": "sha256",
"hash": "55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783",
"url": "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl"
},
{
"algorithm": "sha256",
"hash": "dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8",
"url": "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz"
}
],
"project_name": "virtualenv",
"requires_dists": [
"distlib<1,>=0.3.7",
"filelock<4,>=3.24.2; python_version >= \"3.10\"",
"filelock<=3.19.1,>=3.16.1; python_version < \"3.10\"",
"platformdirs<5,>=3.9.1",
"python-discovery>=1.4.2",
"typing-extensions>=4.13.2; python_version < \"3.11\""
],
"requires_python": ">=3.9",
"version": "21.5.1"
}
],
"platform_tag": null
Expand All @@ -85,10 +343,11 @@
"pip_version": "24.0",
"prefer_older_binary": false,
"requirements": [
"pre-commit>=3.0.0",
"pyspark<4.0.0,>=3.3.0"
],
"requires_python": [
"==3.10.*"
"==3.10.9"
],
"resolver_version": "pip-2020-resolver",
"style": "universal",
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ I manage this monorepo using **Pantsbuild (Pants)**, targeting **Python 3.10.x**
* **Target Language:** Python 3.10.9 (configured via `.python-version` and Pyenv)
* **Format & Lint:** [Ruff](https://github.com/astral-sh/ruff) (unified linter and formatter)
* **Dependency Management:** Single shared lockfile (`3rdparty/user_reqs.lock`)
* **Git Hooks:** [pre-commit](https://pre-commit.com/) (runs Pants formatting and linting locally)
* **CI Reviewer:** Gemini API code reviewer via GitHub Actions and `google-generativeai`

---

## 📁 Repository Structure

```text
data-engineering/
├── .github/
│ └── workflows/
│ └── ai-review.yml # CI pipeline for automated AI code reviews
├── .gitignore # Python, Pants, and OS ignore rules
├── .pre-commit-config.yaml # Configures the local pre-commit hook
├── .python-version # Sets project-local Python to 3.10.9
├── pants # Pants launcher binary (scie-pants)
├── pants.toml # Main configuration for Pants and tool backends
Expand All @@ -28,7 +34,10 @@ data-engineering/
│ ├── BUILD # Configures global dependency targets
│ ├── requirements.txt # Lists project requirements (pandas, PySpark, etc.)
│ └── user_reqs.lock # Pants generated dependency lockfile
└── projects/ # Directory containing all sub-projects
├── projects/ # Directory containing all sub-projects
└── scripts/
├── BUILD # Configures scripts targets for Pants
└── ai_pr_reviewer.py # Python script that runs Gemini AI code reviews
```

---
Expand All @@ -51,6 +60,12 @@ Pants manages its own virtual environments, but I like to export one symlinked t
source .venv/bin/activate
```

### 3. Set Up Pre-commit Hooks
The project uses `pre-commit` to automatically run Pants formatters and linters on staged files. Run the following command inside the virtual environment to install the hooks:
```bash
pre-commit install
```

---

## ⚡ CLI Cheatsheet
Expand Down
5 changes: 5 additions & 0 deletions agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ This repository is a **Python Data Engineering Monorepo** managed by the **Pants
* **Data Processing:** PySpark (`3.5.8`)

### Project Structure
* `.github/workflows/ai-review.yml`: Automated Gemini AI PR reviewer workflow on GitHub Actions.
* `.pre-commit-config.yaml`: Pre-commit hooks configuration to run `./pants fmt` and `./pants lint` locally.
* `3rdparty/`: Contains requirements (`requirements.txt`) and Pants lockfiles (`user_reqs.lock`).
* `projects/essentials/`: First sub-project (derived generic implementation from *Hello Modern Data Pipelines*, Chapter 2).
* `word_count.py`: Local text processing WordCount Spark script.
* `employee_partition_by_hire_date.py`: Local partitioning Spark script.
* `input_data/`: Small CSV/txt sample inputs.
* `output_data/`: Automatically generated Spark output targets (ignored by git).
* `scripts/`: Python utility scripts.
* `ai_pr_reviewer.py`: The AI code reviewer script powered by the Gemini API.
* `BUILD`: Pants build definition for the scripts directory.
* `.venv`: A root-level symlink pointing to the current active Pants-generated virtual environment.

---
Expand Down
1 change: 1 addition & 0 deletions scripts/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python_sources()
Loading
Loading