| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
This project implements a Python data pipeline that can run independently through main.py or be orchestrated with Apache Airflow. The pipeline extracts data from a public REST API, persists raw records as JSON, validates and transforms the data, saves the processed dataset as CSV, loads it into SQLite, and validates the final result. The project also demonstrates artifact-based communication between Airflow tasks, metadata-only XComs, automated testing, failure handling, idempotent loading, logging, containerization, and reproducible environment management.
The pipeline follows an artifact-based architecture. Complete datasets are persisted as JSON, CSV, and SQLite artifacts, while each processing stage validates or transforms the data before passing a reference to the next stage.
flowchart TD
A["JSONPlaceholder REST API"]
B["Extract users"]
C["Persist raw data"]
D["Raw JSON artifact"]
E["Validate raw users"]
F["Transform users"]
G["Persist processed data"]
H["Processed CSV artifact"]
I["Create SQLite table"]
J["Load users using full refresh"]
K[("SQLite users table")]
L["Validate final record count"]
M["Execution logs"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K
K --> L
B -.-> M
E -.-> M
F -.-> M
J -.-> M
L -.-> M
classDef external fill:#F3F4F6,stroke:#4B5563,color:#111827;
classDef process fill:#E8EEF7,stroke:#4A6280,color:#111827;
classDef artifact fill:#E8F5E9,stroke:#2E7D32,color:#111827;
classDef validation fill:#FFF8E1,stroke:#F9A825,color:#111827;
classDef observability fill:#F3E8FF,stroke:#7E22CE,color:#111827;
class A external;
class B,C,F,G,I,J process;
class D,H,K artifact;
class E,L validation;
class M observability;
Complete datasets are persisted outside Airflow's metadata database:
This prevents XCom from being used as dataset storage.
Airflow tasks exchange only small metadata contracts, including:
Complete user lists and pandas DataFrames are not transported through XCom.
Extraction, validation, transformation, persistence, and database logic remain inside the modules under src/.
Airflow is responsible only for:
This allows the same pipeline logic to run independently through main.py.
The pipeline supports two execution modes:
This separation makes it possible to validate the pipeline independently of Airflow.
The SQLite load uses a full-refresh strategy.
Repeated executions replace the target dataset instead of appending duplicate records:
First execution → 10 records
Second execution → 10 records
The pipeline validates data at important boundaries:
Invalid, empty, corrupted, or inconsistent data causes the pipeline to fail with a contextual error message.
The same pipeline logic can be executed in two ways:
In the Airflow implementation, extraction and raw persistence are grouped into one task. Transformation and processed-data persistence are also grouped into one task.
extract_and_save_raw_task
→ validate_raw_users_task
→ transform_and_save_processed_task
→ create_users_table_task
→ load_users_to_database_task
→ validate_database_load_task
The business, transformation, validation, and loading logic remains inside the modules under src. Airflow is responsible for orchestration, task dependencies, execution state, and operational control.
The pipeline separates the complete datasets from the metadata used to coordinate the workflow.
The data plane contains the actual pipeline data:
These artifacts persist the complete datasets outside Airflow's metadata database.
The control plane contains the information required to coordinate and validate the workflow:
Airflow XComs transport only these small metadata contracts. Complete user lists and pandas DataFrames are not transported between tasks.
flowchart LR
subgraph DATA_PLANE["Data Plane — Complete Datasets"]
direction TB
A["Raw JSON artifact"]
B["Processed CSV artifact"]
C[("SQLite users table")]
A --> B
B --> C
end
subgraph CONTROL_PLANE["Control Plane — Airflow Metadata"]
direction TB
D["RawMetadata<br/>path, count, file size"]
E["ValidationMetadata<br/>validated path and count"]
F["ProcessedMetadata<br/>path, count, columns"]
G["TableMetadata<br/>database path and table name"]
H["LoadMetadata<br/>loaded count and load mode"]
I["Final validation metadata<br/>expected and actual count"]
D --> E
E --> F
F --> G
G --> H
H --> I
end
A -. "referenced by" .-> D
A -. "validated through" .-> E
B -. "referenced by" .-> F
C -. "described by" .-> G
C -. "load result" .-> H
C -. "validated through" .-> I
classDef data fill:#E8EEF7,stroke:#4A6280,color:#111827;
classDef metadata fill:#E8F5E9,stroke:#2E7D32,color:#111827;
class A,B,C data;
class D,E,F,G,H,I metadata;
style DATA_PLANE fill:#F5F8FC,stroke:#4A6280,stroke-width:2px
style CONTROL_PLANE fill:#F4FBF5,stroke:#2E7D32,stroke-width:2px
This separation prevents Airflow's metadata database from being used as dataset storage and reduces coupling between task execution processes.
Each downstream task receives a serializable metadata reference through XCom and reads the corresponding persisted artifact only when the complete dataset is required.
The structure below focuses on source code, configuration, tests, orchestration, and documentation. Local or generated files—such as .env, .vscode/, .pytest_cache/, __pycache__/, execution logs, JSON and CSV artifacts, SQLite databases, and backup files—are omitted.
mini_pipeline_python/
│
├── airflow/
│ ├── config/
│ │ └── .gitkeep
│ │
│ ├── dags/
│ │ ├── minimal_airflow_validation.py
│ │ └── users_api_to_sqlite_pipeline.py
│ │
│ ├── plugins/
│ │ └── .gitkeep
│ │
│ ├── .env.example
│ └── docker-compose.yaml
│
├── data/
│ ├── database/
│ ├── processed/
│ └── raw/
│
├── logs/
│
├── src/
│ ├── __init__.py
│ ├── config.py
│ ├── contracts.py
│ ├── database.py
│ ├── extract.py
│ ├── load.py
│ ├── logger_config.py
│ ├── transform.py
│ └── validate.py
│
├── tests/
│ ├── test_database_load.py
│ ├── test_pipeline_functions.py
│ ├── test_processed_artifact.py
│ └── test_raw_validation.py
│
├── .dockerignore
├── .env.example
├── .gitignore
├── Dockerfile
├── environment.yml
├── main.py
├── pytest.ini
├── README.md
└── requirements.txt
The project uses a dedicated Conda environment named mini_pipeline_python to isolate its Python version and dependencies.
From the project root, run:
conda env create -f environment.ymlconda activate mini_pipeline_pythonpython -c "import sys; print(sys.executable)"The returned path should contain:
\anaconda3\envs\mini_pipeline_python\python.exe
python -c "import pandas, requests, dotenv, pytest; print('Dependencies: OK')"The environment is ready when the project interpreter is active and all required dependencies are successfully imported.
Create a .env file in the project root based on .env.example.
On Windows PowerShell:
Copy-Item .env.example .envOn Linux, macOS, or Git Bash:
cp .env.example .envExample content:
API_URL=https://jsonplaceholder.typicode.com/users
API_TIMEOUT_SECONDS=10The .env file contains local configuration values and must not be committed to version control.
The pipeline can be executed independently of Airflow through main.py.
First, activate the project environment:
conda activate mini_pipeline_pythonThen run the pipeline from the project root:
python main.pyA successful execution performs the following steps:
The expected outputs are:
Running the standalone pipeline validates the complete Python workflow independently of Airflow orchestration.
Activate the project environment:
conda activate mini_pipeline_pythonRun the complete test suite from the project root:
python -m pytest tests -qThe automated tests cover:
A successful result should report that all tests passed.
Docker provides an isolated execution environment for the standalone Python pipeline.
From the project root:
docker build -t mini-pipeline-python .docker run --rm --env-file .env mini-pipeline-pythonThe container and its generated files are removed after execution.
On Windows PowerShell:
docker run --rm --env-file .env `
--mount type=bind,source="${PWD}\data",target=/app/data `
--mount type=bind,source="${PWD}\logs",target=/app/logs `
mini-pipeline-pythonThe bind mounts preserve generated data and logs on the host machine.
Each successful pipeline execution may generate:
| Artifact | Location | Purpose |
|---|---|---|
| Raw JSON | data/raw/ | Preserves the API response before transformation |
| Processed CSV | data/processed/ | Stores the transformed tabular dataset |
| SQLite database | data/database/ | Stores the final users table |
| Execution log | logs/ | Records pipeline events and failures |
These runtime artifacts are ignored by Git and are not committed to the repository.
The users table contains the following columns:
user_id
name
email
city
zipcode
latitude
longitude
company_name
processed_at
The processed_at field is stored in ISO 8601 format to preserve an unambiguous and sortable timestamp representation.
The project uses different validation levels because one successful execution does not prove that every pipeline layer works correctly.
Unit tests
→ validate isolated Python functions
Standalone execution
→ validates integration between the Python modules
Airflow DAG execution
→ validates orchestration and task dependencies
Repeated execution
→ validates idempotency
Artifact and record-count checks
→ validate consistency across pipeline stages
The expected record-count relationship is:
Raw records
=
Processed records
=
SQLite records
For the current JSONPlaceholder dataset, the expected result is ten user records.
During regression testing, standalone execution detected an integration mismatch: save_processed_data() expected a list of dictionaries, while main.py passed a pandas DataFrame.
The handoff was corrected so that the persistence function receives the transformed records before the DataFrame is created for the SQLite load.
The project includes a local Apache Airflow environment based on:
The main DAG is:
users_api_to_sqlite_pipeline
It contains six tasks:
extract_and_save_raw_task
→ validate_raw_users_task
→ transform_and_save_processed_task
→ create_users_table_task
→ load_users_to_database_task
→ validate_database_load_task
The reusable pipeline logic remains in src/. Airflow coordinates task execution, dependencies, state, and operational behavior.
cd airflowCopy-Item .env.example .envReview the local values in .env, especially the administrator username and password.
docker compose up airflow-initThe initialization service should finish successfully with exit code 0.
docker compose up -dVerify their status:
docker compose ps -aExpected services include:
postgres
airflow-api-server
airflow-scheduler
airflow-dag-processor
airflow-triggerer
http://localhost:8080
The administrator credentials are defined in airflow/.env.
Confirm the installed version:
docker compose exec airflow-scheduler airflow versionConfirm the executor:
docker compose exec airflow-scheduler `
airflow config get-value core executorList the recognized DAGs:
docker compose exec airflow-scheduler airflow dags listCheck for DAG import errors:
docker compose exec airflow-scheduler `
airflow dags list-import-errors --localValidate the metadata database:
docker compose exec airflow-scheduler airflow db checkList the tasks in the main DAG:
docker compose exec airflow-scheduler `
airflow tasks list users_api_to_sqlite_pipelineIn the Airflow interface:
The XCom values must contain metadata and artifact references rather than complete datasets.
Stop the services without removing their containers:
docker compose stopRestart the existing containers:
docker compose restartRemove the containers while preserving the PostgreSQL volume:
docker compose downRecreate them:
docker compose up -dDo not use the following command when Airflow metadata must be preserved:
docker compose down --volumesThe --volumes option removes the persistent PostgreSQL volume.
The SQLite load uses a full-refresh strategy.
Before inserting the current dataset, the pipeline removes the existing target records. Repeated executions therefore replace the dataset instead of appending duplicates.
Expected behavior:
First execution → 10 records
Second execution → 10 records
An incorrect append-based implementation would produce twenty records after the second execution.
The pipeline also applies explicit validation at important boundaries and fails with contextual error messages when data is missing, empty, corrupted, or inconsistent.
Completed:
Planned future improvements include:
This project was developed for educational and portfolio purposes.
| Back | FazBrowse Home | New Git URL |