Skip to content

Commit b3ad129

Browse files
authored
Implement best practices, refactor tests (#16)
* Implement best practices + mypy-ify * Refactor filesystem tests * Update python-multipart with vulnerability patch * Refactor endpoint integration tests
1 parent 413363e commit b3ad129

38 files changed

Lines changed: 4531 additions & 1054 deletions

.github/workflows/code-checks.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Code checks
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
static_code_checks:
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 10
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Install poetry
18+
run: pipx install poetry
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: '3.11.10'
22+
cache: 'poetry'
23+
- name: Install dependencies
24+
run: poetry install
25+
- name: Ruff check
26+
run: poetry run ruff check .
27+
- name: Ruff format check
28+
run: poetry run ruff format --check .
29+
- name: Mypy check
30+
run: poetry run mypy .
31+
tests:
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 60
34+
steps:
35+
- uses: actions/checkout@v4
36+
- name: Install poetry
37+
run: pipx install poetry
38+
- uses: actions/setup-python@v5
39+
with:
40+
python-version: '3.11.10'
41+
cache: 'poetry'
42+
- name: Install dependencies
43+
run: poetry install
44+
- name: configure AWS
45+
uses: aws-actions/configure-aws-credentials@v4
46+
with:
47+
aws-access-key-id: ${{ secrets.TESTS_AWS_ACCESS_KEY_ID }}
48+
aws-secret-access-key: ${{ secrets.TESTS_AWS_SECRET_ACCESS_KEY }}
49+
aws-region: eu-central-1
50+
- name: Run tests
51+
run: |
52+
poetry run pytest -m "aws or not(aws)" --junitxml=pytest.xml --cov-report=term-missing --cov=api | tee pytest-coverage.txt
53+
echo "test_exit_code=${PIPESTATUS[0]}" >> $GITHUB_ENV
54+
- name: Coverage comment
55+
uses: MishaKav/pytest-coverage-comment@main
56+
with:
57+
pytest-coverage-path: ./pytest-coverage.txt
58+
junitxml-path: ./pytest.xml
59+
- name: Fail on test failure
60+
run: exit ${{ env.test_exit_code }}

.pre-commit-config.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
- id: ruff-check
5+
name: ruff check
6+
entry: poetry run ruff check --fix .
7+
language: system
8+
types: [python]
9+
- id: ruff-format
10+
name: ruff format
11+
entry: poetry run ruff format --check .
12+
language: system
13+
types: [python]
14+
15+
- repo: local
16+
hooks:
17+
- id: mypy
18+
name: mypy
19+
entry: poetry run mypy
20+
language: system
21+
types: [python]

README.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,25 @@ The authenticated users can:
1818
Behind the scenes, the API communicates with the [worker-facing API](https://github.com/ries-lab/DECODE_Cloud_WorkerAPI) of DECODE OpenCloud.
1919
When a user starts a job, it sends it to the [worker-facing API](https://github.com/ries-lab/DECODE_Cloud_WorkerAPI) and gets job updates from it.
2020

21-
## Run
21+
## Development guide
22+
23+
### Prepare the development environment
24+
We use [poetry](https://python-poetry.org/) for dependency tracking.
25+
See online guides on how to use it, but this setup should work:
26+
- `conda create -n "3-11-10" python=3.11.10`
27+
- `conda activate 3-11-10`
28+
- `pip install pipx`
29+
- `pipx install poetry`
30+
- `poetry env use /path/to/conda/env/bin/python`
31+
- `poetry install`
32+
Afterwards, when you need a new package, use `poetry add [--group dev] <package>` to add new dependencies.
33+
The `--group dev` option adds the dependency only for development (e.g., pre-commit hooks, pytest, ...).
34+
35+
Install the pre-commit hooks: `pre-commit install`.
36+
These currently include ruff and mypy.
37+
38+
### Run locally
39+
2240
#### Define the environment variables
2341
Copy the `.env.example` file to a `.env` file at the root of the directory and define its fields appropriately:
2442
- Deployment settings:
@@ -46,13 +64,27 @@ Copy the `.env.example` file to a `.env` file at the root of the directory and d
4664
- `EMAIL_SENDER_SECRET_KEY`: API key secret to use the email sender. Can also be the ARN of an AWS SecretsManager secret.
4765

4866
#### Start the user-facing API
49-
`uvicorn api.main:app --reload --port 8000`
67+
`poetry run serve`
5068

5169
#### View the API documentation
5270
You can find it at `<API_URL>/docs` (if running locally, `<API_URL>=localhost:8000`).
5371

72+
#### Docker
73+
Alternatively, you can build a Docker image.
74+
For this, run `poetry run docker-build`:
75+
This will create a Docker image named `api:<branch_name>`.
76+
To run the Docker container, use `poetry run docker-serve`.
77+
To stop and delete all containers for this package, use `poetry run docker-stop`.
78+
If you want to additionally remove all images for this package and prune dangling images, run `poetry run docker-cleanup`.
79+
80+
### Tests
81+
Run them with `poetry run pytest`.
82+
83+
Note that tests marked with `aws` are skipped by default, to avoid the need for an AWS setup.
84+
They are however ran in the GitHub Action.
85+
For this to work, they must have been ran once locally with an account with sufficient permissions (`poetry run pytest -m "aws"`), since for security reasons, the AWS account used on GitHub does not have permissions to create RDS instances.
5486

55-
## Add/modify runnable applications
87+
### Add/modify runnable applications
5688
#### Dockerize the application
5789
See for example [DECODE](https://github.com/ries-lab/DECODE_Internal/blob/dockerfile_stable/Dockerfile) and [Comet](https://github.com/nolan1999/Comet/blob/docker/Python_interface/Dockerfile).
5890
The image should:

api/core/aws.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import base64
2-
import hmac
32
import hashlib
3+
import hmac
44

55

6-
def calculate_secret_hash(email, client_id, key):
7-
key = bytes(key, "utf-8")
8-
message = bytes(email + client_id, "utf-8")
6+
def calculate_secret_hash(email: str, client_id: str, key: str) -> str:
97
return base64.b64encode(
10-
hmac.new(key, message, digestmod=hashlib.sha256).digest()
8+
hmac.new(
9+
bytes(key, "utf-8"),
10+
bytes(email + client_id, "utf-8"),
11+
digestmod=hashlib.sha256,
12+
).digest()
1113
).decode()

0 commit comments

Comments
 (0)