# fastapi-clean-architecture-boilerplate
## Steps To Use It
Here are the steps to use it:
### Clone The Boilerplate
```bash
git clone https://github.com/Camb-ai/fastapi-clean-architecture-boilerplate
```
### Install The Necessary Pip Packages
Before installing packages we need to create a virtual environment for that you can run:
```bash
python3 -m venv <name>
```
After that you can activate the environment on
unix environment:
```bash
source <name>/bin/activate
```
windows environment:
```bash
<name>/Scripts/activate
```
Now you can start installing all necessary packages by:
```bash
pip install -r requirements.txt
```
### Configure environment variables
Create `.env` file with following contents:
```dotenv
# core
MICROSERVICE_NAME = <name>
# banana.dev
BANANA_DEV_API_KEY = <api-key>
# model keys
# <MODEL_NAME>_MODEL_KEY = <model-key>
# ...other model keys
# open telemetry collector
AGENT_HOSTNAME = "otel"
AGENT_PORT = "4317"
# launch darkly
LAUNCH_DARKLY_SDK_KEY = <sdk-key>
# dramatiq
DRAMATIQ_BROKER = "redis://redis:6379/"
DRAMATIQ_BACKEND = "redis://redis:6379/"
```
### Running with docker compose
You can run the app with docker compose as follows:
```bash
./run-docker-compose.sh
```
Then the FastAPI app would be then available on http://localhost:8000 and Dramatiq Monitoring Dashboard would be available on http://localhost:7640
FastAPI app -
![](https://i.imgur.com/piM8PJu.png)
Dramatiq Monitoring Dashboard -
![](https://i.imgur.com/HdXeGiI.png)
## Local Docker Development With VSCode
### Run App With Docker Compose
First run the app using above mentioned method.
### Install Dev Containers VSCode Extension
![](https://i.imgur.com/q92LgHX.png)
### Start VSCode Attached With `fastapi` Container
To do that press `Ctrl + Shift + P` and search dev containers
![](https://i.imgur.com/EE5Ig2R.png)
and click on `Dev Containers: Attach to Running Container...`.
After that select the `fastapi` container
![](https://i.imgur.com/rPxSXT8.png)
Now a new VSCode window would start with that container attached to it.
![](https://i.imgur.com/G8ebo3c.png)
After that open the folder fastapi
![](https://i.imgur.com/jrhSQVN.png)
Now click on remote explorer and then right click on fastapi container and select `show container logs`
![](https://i.imgur.com/TtPiJy5.png)
After that our VSCode is setup and we can do code editing and debugging in VSCode that would sync with local folder also.
## Creating An Endpoint
There are 2 steps for creating an endpoint:
### Create An Use Case
Use case code can be written inside `src/use_cases/<use-case-name>.py`
There are 2 types of use cases:
#### Synchronous Use Case
```python
from src.use_cases.main import BaseSyncUseCase
from pydantic import BaseModel
class EndPointRequest(BaseModel):
field: fieldtype
# ... more fields
class EndPointResponse(BaseModel):
field: fieldtype
# ... more fields
class EndPointUseCase(BaseSyncUseCase[EndPointRequest, EndPointResponse]):
def validation(self, request_payload: EndPointRequest) -> Exception | None:
# validation logic -> return an exception if validation error occured otherwise return nothing/None.
def handle(self, request_payload: EndPointRequest) -> EndPointResponse:
# use case handling code
# return and object resembling EndPointResponse
end_point_use_case = EndPointUseCase()
```
#### Asynchronous Use Case
```python
from src.use_cases.main import BaseAsyncUseCase
from pydantic import BaseModel
class EndPointRequest(BaseModel):
field: fieldtype
# ... more fields
class EndPointResponse(BaseModel):
field: fieldtype
# ... more fields
class EndPointUseCase(BaseAsyncUseCase[EndPointRequest, EndPointResponse]):
def validation(self, request_payload: EndPointRequest) -> Exception | None:
# validation logic -> return an exception if validation error occured otherwise return nothing/None.
def handle(self, request_payload: EndPointRequest) -> EndPointResponse:
# use case handling code -> this code would run inside task queue
# return and object resembling EndPointResponse
end_point_use_case = EndPointUseCase()
```
### Write Endpoint Code
You can write endpoint code in standard fastapi way:
#### For Synchronous Use Case
```python
@<router-name>.<http-method>(<path>, tags=[tag1, tag2, ....])
async def endpoint(req_body: EndPointRequest):
end_point_use_case.execute(req_body)
return #response
# Asynchronous Use Case -
# Creating task in task queueEndPointUseCase.create(req_body)
#
```
#### For Asynchronous Use Case
##### For Creating Task In Task Queue
```python
@<router-name>.<http-method>(<path>, tags=[tag1, tag2, ....])
async def endpoint(req_body: EndPointRequest):
task = end_point_use_case.create(req_body)
return JSONResponse({"task_id": task.message_id})
```
##### For Getting Task Result
```python
@<router-name>.<http-method>("<path>/{task_id}", tags=[tag1, tag2, ....])
async def endpoint(task_id: str):
task_result= end_point_use_case.get_result(task_id)
return task_result
```
## Creating A Router
There are 2 steps for adding a router in app:
### Writing Router Code
Router code can be written inside `src/external_interfaces/web_server/routers/<router-name>.py` like this:
```python
from fastapi import APIRouter
from src.use_cases.end_point_use_case import end_point_use_case, CreateUserIn
router = APIRouter()
@<router-name>.<http-method>("<path>/{task_id}", tags=[tag1, tag2, ....])
async def endpoint(task_id: str):
task_result= end_point_use_case.get_result(task_id)
return task_result
```
### Adding Router To App
Router can be added to app inside `src/external_interfaces/web_server/main.py` like this:
```python
app.include_router(
<router-name>.router,
prefix="<path>",
tags=[tag1, tag2, ....]
)
```
## Writing Function For Executing `banana.dev` Model
Function for executing banana.dev model can be written in `src/interface_adapters/ml_models/<model-name>.py` like this:
```python
from pydantic import BaseModel
from src.external_interfaces.ml_models import run_ml_model
class ModelOutput(BaseModel):
field: fieldtype
# ... more fields
def model() -> ModelOutput:
model_key = os.getenv("<MODEL_NAME>_MODEL_KEY")
modelpayload = {}
out = run_ml_model(model_key, model_payload)
return ModelOutput.parse_obj(out)
```
Then you can execute `model` in any use case you want. You should always execute it in `AsyncUseCase`.
## Using Feature Flags
You can use `check_flag_value` function as follows:
```python
from src.external_interfaces.monitoring.feature_flags import check_flag_value
user = {
field: value
# other fields related to user
}
default_value = value # can be of any type. It is returned when flag does not exists.
flag_value = check_flag_value(flag="<flag-name>", user=user, default_value=default_value)
```
You can get `flag-name` from launchdarkly.
## Tracing Code
You can use tracing as follows:
```python
from src.external_interfaces.metrics.tracer import get_tracer
with get_tracer(module_name=__name__, span="<span-name>"):
# code to trace
```