diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51fcbfd..8b11ad8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ name: CI on: push: - branches: ["TODO-replace-with-main"] + branches: ["main"] pull_request: jobs: @@ -29,10 +29,30 @@ jobs: - name: Install dependencies run: pip install -r requirements.txt - name: Lint - run: echo "TODO implement this step" - - name: Format - run: echo "TODO implement this step" + run: ruff check . + - name: Format check + run: ruff format . --check - name: Test - run: echo "TODO implement this step" - - name: Build image - run: echo "TODO implement this step" + run: API_KEY=test pytest -q + - name: Build Docker image + run: docker build -t barbari-pipeline:${{ github.sha }} . + # The next four steps need the AZURE_CREDENTIALS secret, which GitHub + # withholds on pull_request runs from forks for security. The `if:` guard + # skips them on fork PRs so CI stays green; the central `Grade ACR push` + # workflow on main handles the actual push from base-repo context. + - name: Azure login + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: ACR login + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + run: az acr login --name hyfregistry + + - name: Push image to ACR + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + run: | + docker tag barbari-pipeline:${{ github.sha }} hyfregistry.azurecr.io/barbari-pipeline:${{ github.sha }} + docker push hyfregistry.azurecr.io/barbari-pipeline:${{ github.sha }} + \ No newline at end of file diff --git a/AI_ASSIST.md b/AI_ASSIST.md index 70463b0..3122e01 100644 --- a/AI_ASSIST.md +++ b/AI_ASSIST.md @@ -7,11 +7,12 @@ ## The prompt I gave +I used AI to help me fix my pytest issues. The AI suggested ensuring that all required dependencies (pytest and project modules) are installed, fixing missing imports, TODO: paste your prompt here. ## The code or suggestion it returned - +After applying the changes, all tests passed successfully ```python diff --git a/Dockerfile b/Dockerfile index d665b11..fb72dca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,18 @@ # Replace each TODO comment with the correct Dockerfile instruction. # TODO: set the base image -FROM TODO +FROM python:3.11-slim WORKDIR /app # TODO: copy requirements.txt (before source — this keeps the install layer cached) +COPY requirements.txt . # TODO: install dependencies +RUN pip install --no-cache-dir -r requirements.txt # TODO: copy source code +COPY . . # TODO: set the command that runs when the container starts -CMD ["TODO"] +CMD ["python", "-m", "src.pipeline"] \ No newline at end of file diff --git a/assets/acr_push_week5.png b/assets/acr_push_week5.png new file mode 100644 index 0000000..c273b90 Binary files /dev/null and b/assets/acr_push_week5.png differ diff --git a/requirements.txt b/requirements.txt index 42299cf..d9b8e34 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,14 @@ # ruff== # # Add your pinned dependencies below: +azure-identity==1.17.1 +azure-storage-blob==12.19.1 + +pandas==2.2.2 +numpy==2.0.1 +requests==2.32.3 +pytest==8.4.2 +matplotlib==3.9.2 +ruff==0.8.4 +pyarrow==18.1.0 + diff --git a/src/pipeline.py b/src/pipeline.py index 1cd17a4..0fdcf69 100644 --- a/src/pipeline.py +++ b/src/pipeline.py @@ -7,7 +7,7 @@ Replace every `raise NotImplementedError` below with a real implementation. """ - +import os import logging from pathlib import Path @@ -24,8 +24,22 @@ def get_config() -> dict: Raise RuntimeError with a clear message if a required variable is missing. """ + api_key = os.getenv("API_KEY") + output_dir = os.getenv("OUTPUT_DIR", "output") + + if not api_key: + raise RuntimeError("ERROR: API_KEY is not set in environment variables") + + + return { + "api_key": api_key, + "output_dir": output_dir, + + } raise NotImplementedError("Task 5: read API_KEY and OUTPUT_DIR from the environment") +def clean_name(name: str) -> str: + return name.strip() def fetch_data(api_key: str) -> list[dict]: """ @@ -34,8 +48,12 @@ def fetch_data(api_key: str) -> list[dict]: Return a list of at least one dict representing a record. In a real pipeline you would call requests.get(...) here. """ - raise NotImplementedError("Task 1: return at least one sample record") + logger.info("fetching data") + return [ + {"id": 1, "name": "Alice", "value": 100}, + {"id": 2, "name": "Bob", "value": 200}, + ] def save_results(records: list[dict], output_dir: Path) -> None: """ @@ -44,7 +62,15 @@ def save_results(records: list[dict], output_dir: Path) -> None: Create output_dir if it does not exist. Log the number of records written. """ - raise NotImplementedError("Task 1: write records to output_dir/results.txt") + output_dir.mkdir(parents=True, exist_ok=True) + + file_path = output_dir / "results.txt" + + with file_path.open("w") as f: + for record in records: + f.write(f"{record}\n") + logger.info(f"saved {len(records)} records to {file_path}") + #raise NotImplementedError("Task 1: write records to output_dir/results.txt") def run() -> None: diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 73029e3..6dfcd94 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -3,6 +3,14 @@ import pytest from src.pipeline import fetch_data, get_config, save_results +from src.pipeline import clean_name + +def test_clean_name_strips_whitespace(): + assert clean_name(" Alice ") == "Alice" + + +def test_clean_name_handles_empty(): + assert clean_name("") == "" class TestGetConfig: