Skip to content

Commit

Permalink
Merge pull request #4 from gauge-sh/tests
Browse files Browse the repository at this point in the history
ci
  • Loading branch information
caelean authored Jul 23, 2024
2 parents ad32fdc + 2d1a50b commit 466b159
Show file tree
Hide file tree
Showing 25 changed files with 232 additions and 61 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: ci

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Check ruff
run: |
ruff check
ruff format --check
- name: Test with pytest and report coverage
run: |
coverage run --branch -m pytest
coverage report
- name: Check types with pyright
run: |
pyright --pythonversion ${{ matrix.python-version }}
- name: Check tach
run: tach check
39 changes: 39 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
release:
types: [published]

permissions:
contents: read

jobs:
deploy:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
8 changes: 5 additions & 3 deletions api/src/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

from typing import Any

from fastapi import FastAPI, HTTPException, Request, Response
from fastapi import FastAPI, Request, Response

from src import settings
from src.deploy.routes import router as deploy_router
Expand All @@ -14,10 +15,10 @@

@app.middleware("http")
async def auth_check(request: Request, call_next: Any):
print(settings.CLIENT_SECRET, request.headers.get("X-Client-Secret"))
print(settings.CLIENT_SECRET, request.headers.get("X-Client-Secret")) # type: ignore
if (
request.url.path not in AUTH_EXEMPT
and settings.CLIENT_SECRET != request.headers.get("X-Client-Secret")
and settings.CLIENT_SECRET != request.headers.get("X-Client-Secret") # type: ignore
):
return Response(status_code=403)

Expand All @@ -28,4 +29,5 @@ async def auth_check(request: Request, call_next: Any):
def healthcheck():
return {"ok": True}


app.include_router(deploy_router)
31 changes: 22 additions & 9 deletions api/src/build/python_lambda.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
from pathlib import Path
from __future__ import annotations

import subprocess
import sys
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pathlib import Path

PIP_PLATFORM = "manylinux2014_x86_64"


def install_deps_to_dir(dependencies: list[str], python_version: str, output_dir: Path) -> None:
def install_deps_to_dir(
dependencies: list[str], python_version: str, output_dir: Path
) -> None:
dependencies.append("gauge-serverless")
output_dir.mkdir(parents=True, exist_ok=True)

pip_command = [
sys.executable, "-m", "pip",
sys.executable,
"-m",
"pip",
"install",
"--platform", PIP_PLATFORM,
"--implementation", "cp",
"--python-version", python_version,
"--only-binary", ":all:",
"--target", str(output_dir),
"--platform",
PIP_PLATFORM,
"--implementation",
"cp",
"--python-version",
python_version,
"--only-binary",
":all:",
"--target",
str(output_dir),
"--upgrade",
"--no-deps"
"--no-deps",
]
pip_command.extend(dependencies)

Expand Down
19 changes: 13 additions & 6 deletions api/src/build/zip.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from __future__ import annotations

import tempfile
from typing import TYPE_CHECKING
from zipfile import ZipFile, is_zipfile
from pathlib import Path

if TYPE_CHECKING:
from pathlib import Path


def write_to_zipfile(content: bytes, output_path: Path) -> None:
Expand All @@ -13,18 +18,20 @@ def write_to_zipfile(content: bytes, output_path: Path) -> None:
output_path.write_bytes(content)


def write_extended_zipfile(existing_zipfile: Path, additional_paths: list[Path], output_path: Path) -> None:
with ZipFile(existing_zipfile, 'r') as existing_zip:
with ZipFile(output_path, 'w') as new_zip:
def write_extended_zipfile(
existing_zipfile: Path, additional_paths: list[Path], output_path: Path
) -> None:
with ZipFile(existing_zipfile, "r") as existing_zip:
with ZipFile(output_path, "w") as new_zip:
for item in existing_zip.infolist():
data = existing_zip.read(item.filename)
new_zip.writestr(item, data)

for path in additional_paths:
if path.is_file():
new_zip.write(path, path.name)
elif path.is_dir():
for file_path in path.rglob('*'):
for file_path in path.rglob("*"):
if file_path.is_file():
# When adding a directory, only the contents are appended to the zip
# e.g. adding "build/" will not create paths beginning with "build/" in the zip
Expand Down
17 changes: 10 additions & 7 deletions api/src/deploy/lambda_deploy.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from __future__ import annotations

import json
from pathlib import Path
from typing import TYPE_CHECKING

import boto3
from botocore.exceptions import ClientError

from src import settings

if TYPE_CHECKING:
from pathlib import Path

LAMBDA_RUNTIMES = ["python3.12", "python3.11", "python3.10", "python3.9", "python3.8"]


Expand All @@ -27,7 +30,7 @@ def deploy_python_lambda_function(
handler: str = "lambda_function.lambda_handler",
):
# Initialize the Lambda client
lambda_client = boto3.client("lambda", region_name=settings.AWS_DEFAULT_REGION)
lambda_client = boto3.client("lambda", region_name=settings.AWS_DEFAULT_REGION) # type: ignore
lambda_runtime = translate_python_version_to_lambda_runtime(python_version)

try:
Expand All @@ -37,21 +40,21 @@ def deploy_python_lambda_function(

try:
# Try to get the function configuration
lambda_client.get_function(FunctionName=function_name)
lambda_client.get_function(FunctionName=function_name) # type: ignore

# If we reach here, the function exists, so we update it
response = lambda_client.update_function_code(
response = lambda_client.update_function_code( # type: ignore
FunctionName=function_name, ZipFile=bytes_content
)
print(f"Updated existing Lambda function: {function_name}")

except ClientError as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
if e.response["Error"]["Code"] == "ResourceNotFoundException": # type: ignore
# The function doesn't exist, so we create it
response = lambda_client.create_function(
response = lambda_client.create_function( # type: ignore
FunctionName=function_name,
Runtime=lambda_runtime,
Role=settings.LAMBDA_ROLE_ARN,
Role=settings.LAMBDA_ROLE_ARN, # type: ignore
Handler=handler,
Code=dict(ZipFile=bytes_content),
)
Expand Down
36 changes: 25 additions & 11 deletions api/src/deploy/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
import tempfile
from pathlib import Path
from typing import Annotated
from typing import Annotated # type: ignore

from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import JSONResponse
Expand All @@ -27,45 +27,59 @@ class DeploymentConfig(BaseModel):

UPLOADED_BUNDLE_FILENAME = "uploaded_bundle.zip"


@router.post("/deploy/")
async def deploy_zip(
file: Annotated[UploadFile, File()],
json_data: Annotated[str, Form()]
file: Annotated[UploadFile, File()], # type: ignore
json_data: Annotated[str, Form()], # type: ignore
):
try:
deployment_data = json.loads(json_data)
deployment_data = json.loads(json_data) # type: ignore
deployments: list[DeploymentConfig] = [
DeploymentConfig(
name=name,
path=deployment["reference"],
python_version=deployment["python_version"],
requirements=deployment["dependencies"]
requirements=deployment["dependencies"],
)
for name, deployment in deployment_data.items()
]
except Exception:
raise HTTPException(status_code=422, detail="Couldn't process deployments data.")
raise HTTPException(
status_code=422, detail="Couldn't process deployments data."
)

with tempfile.TemporaryDirectory() as tmp_dir:
tmp_dir = Path(tmp_dir)
zipfile_path = tmp_dir / UPLOADED_BUNDLE_FILENAME
try:
write_to_zipfile(await file.read(), output_path=zipfile_path)
write_to_zipfile(await file.read(), output_path=zipfile_path) # type: ignore
except ValueError as err:
return JSONResponse(status_code=400, content={"error": str(err)})
for deployment in deployments:
# TODO: validate deployment name
build_path = tmp_dir / deployment.name
build_path.mkdir(parents=True, exist_ok=True)
build_lambda_handler(symbol_path=deployment.path, output_path=build_path / "lambda_function.py")
install_deps_to_dir(dependencies=deployment.requirements, python_version=deployment.python_version, output_dir=build_path)
build_lambda_handler(
symbol_path=deployment.path,
output_path=build_path / "lambda_function.py",
)
install_deps_to_dir(
dependencies=deployment.requirements,
python_version=deployment.python_version,
output_dir=build_path,
)
deployment_package_path = tmp_dir / f"{deployment.name}.zip"
write_extended_zipfile(existing_zipfile=zipfile_path, additional_paths=[build_path], output_path=deployment_package_path)
write_extended_zipfile(
existing_zipfile=zipfile_path,
additional_paths=[build_path],
output_path=deployment_package_path,
)

deploy_python_lambda_function(
function_name=deployment.name,
zip_file=deployment_package_path,
python_version=deployment.python_version
python_version=deployment.python_version,
)
# TODO: after each deployment is done, update record in RDS
return {"status": "OK"}
1 change: 1 addition & 0 deletions api/src/settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type: ignore
from __future__ import annotations

from environs import Env
Expand Down
2 changes: 1 addition & 1 deletion api/src/transform/build_lambda_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
def build_lambda_handler(symbol_path: str, output_path: Path):
try:
mod_path, target_symbol = symbol_path.split(":")
except:
except ValueError: # not enough values to unpack/too many values to unpack
raise ValueError(
f"Could not resolve module path and target symbol from: '{symbol_path}'"
)
Expand Down
5 changes: 5 additions & 0 deletions gauge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

from gauge.sdk.main import endpoint

__all__ = ["endpoint"]
File renamed without changes.
5 changes: 4 additions & 1 deletion cli/console.py → gauge/cli/console.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from __future__ import annotations

from collections.abc import Generator
from contextlib import contextmanager
from datetime import datetime
from typing import TYPE_CHECKING

from rich.console import Console

if TYPE_CHECKING:
from collections.abc import Generator


@contextmanager
def log_task(start_message: str, end_message: str) -> Generator[None, None, None]:
Expand Down
Loading

0 comments on commit 466b159

Please sign in to comment.