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

Adding FastAPI #2

Merged
merged 2 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM python:3.11.0-slim-buster AS base

EXPOSE 8000

WORKDIR /src

COPY pyproject.toml .
Expand All @@ -16,3 +18,4 @@ FROM dependencies AS production
COPY src src
COPY settings.conf src
COPY logging.conf src
COPY run.py src
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ local/lint/fix:
poetry run ruff . --fix --exit-non-zero-on-fix

local/run:
poetry run python src/main.py
poetry run python run.py

############################################
# COMMANDS TO RUN USING DOCKER (RECOMMENDED)
Expand All @@ -44,7 +44,7 @@ docker/lint/fix:
docker-compose run ${APP_NAME} poetry run ruff . --fix --exit-non-zero-on-fix

docker/run:
docker-compose run ${APP_NAME} poetry run python src/main.py
docker-compose run --service-ports ${APP_NAME} poetry run python run.py

####################################
# DOCKER IMAGE COMMANDS
Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# fast-api-boilerplate-project

it's a simple and useful boilerplate for python projects using fast api framework
it's a simple and useful boilerplate for python projects using FastAPI framework

## Technology and Resources

Expand All @@ -9,6 +9,8 @@ it's a simple and useful boilerplate for python projects using fast api framewor
- [Docker Compose](https://docs.docker.com/compose/) - **pre-requisite**
- [Poetry](https://python-poetry.org/) - **pre-requisite**
- [Ruff](https://github.com/astral-sh/ruff)
- [FastAPI](https://fastapi.tiangolo.com/)
- [Uvicorn](https://www.uvicorn.org/)

*Please pay attention on **pre-requisites** resources that you must install/configure.*

Expand Down Expand Up @@ -38,6 +40,21 @@ push image | `make docker/image/push` | - | to push the docker image

*Please, check all available commands in the [Makefile](Makefile) for more information*.

### Uvicorn settings

Uvicorn is an ASGI web server implementation for Python and you can [configure](https://www.uvicorn.org/settings/) it overriding these values on the [settings.conf](settings.conf) file.

Variable | Description | Available Values | Default Value | Required
--- | --- | --- | --- | ---
UVICORN_HOST | The host of the application | `a valid host address` | `0.0.0.0` | Yes
UVICORN_PORT | The application port | `a valid port number` | `8000` | Yes
UVICORN_WORKERS | The number of uvicorn workers | `a valid number` | `dev` | No
UVICORN_ACCESS_LOG | Enable or disable the access log | `True / False` | `True` | No
UVICORN_LOG_LEVEL | Set the log level | `critical / error / warning / info / debug / trace'` | `info` | No

*Note: The default value of these configs are available on [run.py](run.py).*


## Logging

This project uses a simple way to configure the log with [logging.conf](logging.conf) to show the logs on the container output console.
Expand Down
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ services:
target: "development"
volumes:
- ".:/src"
env_file: .env
env_file: .env
ports:
- "8000:8000"
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ license = "MIT"

[tool.poetry.dependencies]
python = "^3.11"
fastapi = "0.109.0"
uvicorn = "0.27.0"

[tool.poetry.dev-dependencies]
pytest = "7.4.4"
pytest-cov = "^4.1.0"
ruff = "^0.1.6"
httpx = "0.26.0"

[tool.pytest.ini_options]
testpaths = ["tests",]
pythonpath = ["src",]

[tool.coverage.run]
branch = true
omit = ["*/tests/*"]
omit = ["*/tests/*", "run.py"]

[tool.coverage.report]
show_missing = true
Expand All @@ -32,6 +35,7 @@ directory = "htmlcov"
line-length = 120
select = ["E", "F", "W", "I", "N", "S"]
target-version = "py311"
exclude = ["run.py"]

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
25 changes: 25 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from uvicorn import run

from src.config.settings import settings
from src.api import app as application


def main() -> None:
app_host = settings.get('UVICORN_HOST', '0.0.0.0')
app_port = settings.get('UVICORN_PORT', 8000)
app_num_of_workers = settings.get('UVICORN_WORKERS', 4)
uvicorn_access_log = True if settings.get("UVICORN_ACCESS_LOG", "False") == "True" else False
uvicorn_log_level = settings.get("UVICORN_LOG_LEVEL", "info").lower()

run(
"run:application",
host=app_host,
port=app_port,
workers=app_num_of_workers,
log_level=uvicorn_log_level,
access_log=uvicorn_access_log,
)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions settings.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[default]
app_name=fast-api-boilerplate-project
app_description=fast-api-boilerplate-project
marcieltorres marked this conversation as resolved.
Show resolved Hide resolved

[dev]

Expand Down
10 changes: 10 additions & 0 deletions src/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from fastapi import FastAPI

from src.config.settings import settings

app = FastAPI(title=settings.get('app_name'), description=settings.get('app_description'))


@app.get("/health-check")
async def health_check():
return {"message": "OK"}
16 changes: 0 additions & 16 deletions src/main.py

This file was deleted.

Empty file added tests/it/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions tests/it/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from unittest import TestCase

from fastapi.testclient import TestClient

from src.api import app


class ApiITTest(TestCase):
def setUp(self):
self.client_api = TestClient(app)

def test_when_i_call_health_check_should_be_success(self):
response = self.client_api.get("/health-check")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"message": "OK"})
8 changes: 0 additions & 8 deletions tests/test_main.py

This file was deleted.

Empty file added tests/unit/config/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class SettingsTest(TestCase):
def setUp(self) -> None:
self.settings = Settings(file='./tests/config/settings_to_test.conf')
self.settings = Settings(file='./tests/unit/config/settings_to_test.conf')

def test_get_setting_value_with_success(self):
self.assertEqual(self.settings.get('app_name'), 'app-name')
Expand All @@ -22,20 +22,20 @@ def test_get_setting_float_value_with_success(self):

@mock.patch.dict(os.environ, {'ENV': 'prod'}, clear=True)
def test_get_setting_value_from_production_env_with_success(self):
prod_settings = Settings(file='./tests/config/settings_to_test.conf')
prod_settings = Settings(file='./tests/unit/config/settings_to_test.conf')
self.assertEqual(prod_settings.get('app_var'), 'prod-app-var')

@mock.patch.dict(os.environ, {'ENV': 'qa'}, clear=True)
def test_get_setting_value_from_qa_env_with_success(self):
qa_settings = Settings(file='./tests/config/settings_to_test.conf')
qa_settings = Settings(file='./tests/unit/config/settings_to_test.conf')
self.assertEqual(qa_settings.get('app_var'), 'qa-app-var')

@mock.patch.dict(os.environ, {'ENV': 'test'}, clear=True)
def test_get_setting_value_from_test_env_with_success(self):
test_settings = Settings(file='./tests/config/settings_to_test.conf')
test_settings = Settings(file='./tests/unit/config/settings_to_test.conf')
self.assertEqual(test_settings.get('app_var'), 'test-app-var')

@mock.patch.dict(os.environ, {'ENV': 'dev'}, clear=True)
def test_get_setting_value_from_dev_env_with_success(self):
dev_settings = Settings(file='./tests/config/settings_to_test.conf')
dev_settings = Settings(file='./tests/unit/config/settings_to_test.conf')
self.assertEqual(dev_settings.get('app_var'), 'dev-app-var')
Loading