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
106 changes: 100 additions & 6 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -31,17 +30,31 @@ jobs:
with:
version: latest

- name: Generate cache key
id: cache-key
run: |
echo "value=build-${{ hashFiles('pyproject.toml', 'uv.lock') }}-${{ runner.os }}" >> $GITHUB_OUTPUT

- name: Install dependencies
run: ./scripts/setup.sh

- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
.venv
.uv
key: ${{ steps.cache-key.outputs.value }}

- name: Run linting
run: ./scripts/lint.sh

test:
name: Test
build:
name: Build
runs-on: ubuntu-latest
needs: lint

outputs:
cache-key: ${{ steps.cache-key.outputs.value }}
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -59,5 +72,86 @@ jobs:
- name: Install dependencies
run: ./scripts/setup.sh

- name: Run tests
run: ./scripts/test.sh
- name: Build Lambda package
run: ./scripts/build.sh

- name: Generate cache key
id: cache-key
run: |
echo "value=build-${{ hashFiles('pyproject.toml', 'uv.lock') }}-${{ runner.os }}" >> $GITHUB_OUTPUT

- name: Cache dependencies and build artifacts
uses: actions/cache@v4
with:
path: |
.venv
.uv
dist
key: ${{ steps.cache-key.outputs.value }}

unit-test:
name: Unit Test
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
uses: astral-sh/setup-uv@v6.4.3
with:
version: latest

- name: Restore dependencies cache
uses: actions/cache@v4
with:
path: |
.venv
.uv
dist
key: ${{ needs.build.outputs.cache-key }}

- name: Run unit tests
run: ./scripts/unit-test.sh

integration-test:
name: Integration Test
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
uses: astral-sh/setup-uv@v6.4.3
with:
version: latest

- name: Restore dependencies and build cache
uses: actions/cache@v4
with:
path: |
.venv
.uv
dist
key: ${{ needs.build.outputs.cache-key }}

- name: Start Lambda container
run: ./scripts/local-instance.sh start

- name: Run integration tests
run: ./scripts/integration-test.sh

- name: Stop Lambda container
if: always()
run: ./scripts/local-instance.sh stop
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,6 @@ cython_debug/
marimo/_static/
marimo/_lsp/
__marimo__/

# Lambda
.lambda_task
44 changes: 37 additions & 7 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,55 @@ version: "3"

tasks:
default:
deps:
- setup
- format
- lint
- test
cmds:
- task: setup
- task: format
- task: lint
- task: build
- task: unit-test
- task: integration-test
silent: true

setup:
run: once
cmds:
- ./scripts/setup.sh

format:
run: once
cmds:
- ./scripts/format.sh

lint:
run: once
cmds:
- ./scripts/lint.sh

test:
build:
run: once
cmds:
- ./scripts/build.sh

unit-test:
run: once
cmds:
- ./scripts/unit-test.sh

integration-test:
run: once
deps:
- build
cmds:
- task: start-local-instance
- ./scripts/integration-test.sh
- task: stop-local-instance

start-local-instance:
run: once
cmds:
- ./scripts/local-instance.sh start

stop-local-instance:
run: once
cmds:
- ./scripts/test.sh
- ./scripts/local-instance.sh stop
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = []
dev = [
"pytest>=8.3",
"pytest-cov>=6.2",
"requests>=2.32.4",
"ruff>=0.12",
]

Expand All @@ -20,6 +21,15 @@ python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]

[tool.ruff]
# Exclude test files from linting
exclude = [
"tests/",
"**/test_*.py",
"**/*_test.py",
"**/tests.py",
]

[tool.coverage.run]
source = ["src"]
omit = [
Expand Down
104 changes: 104 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/bin/bash

set -e # Exit on any error


ENTRYPOINT_CODE_FILE="main.py"
DIST_FILE="lambda.zip"
DIST_PATH="dist/${DIST_FILE}"


function cleanup() {
echo "Cleaning up previous build..."
rm -rf dist
mkdir -p dist
}


function build() {
echo "Building Lambda deployment package..."
# Create zip with source files at root level, excluding test and cache files
pushd src > /dev/null
zip -r ../${DIST_PATH} . \
-x "*.pyc" \
-x "__pycache__/*" \
-x "*.pyo" \
-x "*.pyd" \
-x "*.so" \
-x "*.egg" \
-x "*.egg-info" \
-x "*.test.py" \
-x "*_test.py" \
-x "test_*.py" \
-x "tests/*" \
-x ".pytest_cache/*" \
-x ".coverage" \
-x "*.log" \
-x ".DS_Store" \
-x "Thumbs.db"

popd > /dev/null

echo "Build completed successfully!"
}

function verify_build() {
echo "Verifying archive structure..."

# Verify no test files are included
if unzip -l ${DIST_PATH} | grep -q "test"; then
echo "ERROR: Test files found in archive ${DIST_PATH}!"
echo "Contents:"
unzip -l ${DIST_PATH} | grep "test"
exit 1
fi

# Verify no cache files are included
if unzip -l ${DIST_PATH} | grep -q "__pycache__\|\.pyc\|\.pyo"; then
echo "ERROR: Cache files found in archive ${DIST_PATH}!"
echo "Contents:"
unzip -l ${DIST_PATH} | grep "__pycache__\|\.pyc\|\.pyo"
exit 1
fi

# Verify source files are at root level
if ! zipinfo -1 ${DIST_PATH} | grep -q "^${ENTRYPOINT_CODE_FILE}$"; then
echo "ERROR: Entrypoint file ${ENTRYPOINT_CODE_FILE} not found at root level in ${DIST_PATH}!"
echo "Contents:"
unzip -l ${DIST_PATH} | grep "^${ENTRYPOINT_CODE_FILE}$"
exit 1
fi

# Verify archive is not empty
ARCHIVE_SIZE=$(unzip -l ${DIST_PATH} | tail -1 | awk '{print $2}')
if [ "$ARCHIVE_SIZE" -eq 0 ]; then
echo "ERROR: Archive ${DIST_PATH} is empty!"
exit 1
fi

echo "Archive structure is correct."
}

function sha256_hash() {
# shasum is available on macOS, sha256sum is available on Linux
if uname -a | grep -q "Darwin"; then
shasum -a 256 ${DIST_PATH} | awk '{print $1}'
else
sha256sum ${DIST_PATH} | awk '{print $1}'
fi
}

function main() {
cleanup
build
verify_build

zip_size=$(ls -lh dist/lambda.zip | awk '{print $5}')
zip_hash=$(sha256_hash)
echo "Lambda deployment package built"
echo "path=${DIST_PATH}"
echo "size=${zip_size}"
echo "hash=${zip_hash}"
}

main
3 changes: 3 additions & 0 deletions scripts/integration-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

uv run pytest ./tests/ -v
Loading