Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Docker support for disability-max-ratings-api #23

Merged
merged 7 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.github
.gitignore
.env
*.pyc
__pycache__/
tests/
docs/
.dockerignore
Dockerfile
docker-compose.yml
.pytest_cache/
.coverage
htmlcov/
build/
dist/
*.egg-info/
55 changes: 55 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Stage 1: Builder
FROM python:3.12.3-slim AS builder

WORKDIR /app

# Install system dependencies required for building
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*

# Install Poetry, plugin, and initialize config
RUN pip install --no-cache-dir poetry==1.8.5 poetry-plugin-export==1.7.1 && \
poetry config virtualenvs.create false && \
poetry config virtualenvs.in-project false && \
poetry config warnings.export false

# Copy only the poetry files to leverage caching
COPY pyproject.toml poetry.lock ./

# Install dependencies
RUN poetry install --no-interaction --no-ansi --no-root

# Stage 2: Runner
FROM python:3.12.3-slim AS runner

WORKDIR /app

# Install curl for healthcheck
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/* && \
useradd -m appuser

# Copy installed packages and binaries from builder
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin/* /usr/local/bin/

# Copy Poetry config from builder
# NOTE: This is a workaround to ensure the Poetry config is copied correctly, see https://github.com/python-poetry/poetry/issues/6459#issuecomment-2255631116
USER appuser
COPY --from=builder /root/.config/pypoetry /home/appuser/.config/pypoetry

# Copy the application code
COPY . .

# Set ownership to appuser
USER root
RUN chown -R appuser:appuser /app /home/appuser/.config
USER appuser

# Expose the application port
EXPOSE 8130

# Run the application using Poetry
CMD ["poetry", "run", "uvicorn", "src.python_src.api:app", "--host", "0.0.0.0", "--port", "8130"]
92 changes: 62 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Disability Max Ratings API

[![Tests](https://github.com/department-of-veterans-affairs/disability-max-ratings-api/actions/workflows/test-code.yml/badge.svg)](https://github.com/department-of-veterans-affairs/disability-max-ratings-api/actions/workflows/test-code.yml)
[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
![Python Version from PEP 621 TOML](https://img.shields.io/badge/Python-3.12-blue)
Expand All @@ -7,7 +8,6 @@
[![Linting: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)


> **Note:** This API was formerly known as Max CFI (Claim for Increase) API. All functionality remains the same.

`/disability-max-ratings` maps a list of disabilities to their max ratings, if any.
Expand Down Expand Up @@ -35,13 +35,15 @@ Follow the directions on the [Poetry website](https://python-poetry.org/docs/#in
By default, Poetry will create its own virtual environment (see [here](https://python-poetry.org/docs/basic-usage/#using-your-virtual-environment)), but it will
also detect and respect an existing virtual environment if you have one activated.

#### Other options:
#### Other options

* Create a virtual environment with python and activate it like so:

```bash
python -m venv ~/.virtualenvs/domain-ee # or wherever you want
source ~/.virtualenvs/domain-ee/bin/activate
```

* Use [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) to create and activate virtual environments with `pyenv`.
* Use [Poetry](https://python-poetry.org/docs/basic-usage/#activating-the-virtual-environment) to explicitly create and use a virtual environment.

Expand Down Expand Up @@ -69,6 +71,7 @@ poetry run pre-commit install
```

To run the pre-commit hooks at any time, run the following command:

```bash
poetry run pre-commit run --all-files
```
Expand All @@ -81,42 +84,71 @@ Using Poetry, run the following command from the root of the repository:
poetry run uvicorn src.python_src.api:app --port 8130 --reload
```

## Run the server with Docker
## Run with Docker

TODO: update this to use the new disability-max-ratings-api Docker Compose file - <https://github.com/department-of-veterans-affairs/abd-vro/issues/3833>
You can also run the service using Docker:

## Testing it all together
1. Build & Start Services

Run the Python webserver (uvicorn command above). Now you should be able to make a post request to the `/disability-max-ratings/`
endpoint with a request body of the format:
```bash
docker compose down
docker compose build --no-cache
docker compose up -d
docker compose ps
```

```json
{
"diagnostic_codes": [
6260
]
}
```
Expected: `disability-max-ratings-api` running on port 8130

This should result in a response with the following body:

```json
{
"ratings": [
{
"diagnostic_code": 6260,
"max_rating": 10
}
]
}
```
2. Check Endpoints

```bash
# API docs
curl http://localhost:8130/docs

# Health endpoint
curl http://localhost:8130/health

# Main endpoint
curl -X POST 'http://localhost:8130/disability-max-ratings' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"diagnostic_codes": [6260]}'
```

Expected main endpoint response:

```json
{
"ratings": [
{ "diagnostic_code": 6260, "max_rating": 10 }
]
}
```

3. View API documentation:

```bash
curl http://localhost:8130/docs
```

4. Development Environment

```bash
# Run tests inside container using Poetry
docker compose run --rm api poetry run pytest

# Check user
docker compose run --rm api id
```

Expected: All tests pass (>80% coverage), user should be non-root (uid=1000)

### Notes on usage:
### Notes on usage

#### Requests

* The `diagnostic_codes` array in the request are integers within the range of `5000 - 10000`.
* Any request with an any entry that falls outside the range `5000 - 10000` will yield a `400`.
* Any request with an any entry that falls outside the range `5000 - 10000` will yield a `400`.
* An invalid request such as missing/invalid field will result in `422` status code.
* Duplicate entries in the `diagnostic_codes` array will yield a ratings array with unique entries.
* An empty `diagnostic_codes` array will yield an empty ratings array.
Expand All @@ -125,8 +157,8 @@ This should result in a response with the following body:
#### Response

* The response contains a `ratings` array where each item contains a `diagnostic_code` and the associated `max_rating`.
* The `diagnostic_code` corresponds to an entry in the requests `diagnostic_codes` array.
* The `max_rating` item is a percentage expressed as an integer in the range of `0 - 100`.
* The `diagnostic_code` corresponds to an entry in the requests `diagnostic_codes` array.
* The `max_rating` item is a percentage expressed as an integer in the range of `0 - 100`.
* Each entry in `diagnostic_codes` array of the request with an associated max rating will yield an item in
the `ratings` array of the response body.
* If any entry of the `diagnostic_codes` is not found, the response `ratings` array will not contain the corresponding
Expand Down
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
api:
build: .
container_name: disability-max-ratings-api
ports:
- "8130:8130"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8130/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
environment:
- ENVIRONMENT=local
- PYTHONPATH=/app
volumes:
- .:/app
security_opt:
- no-new-privileges:true
Loading
Loading