Skip to content

Commit

Permalink
Fastcore and poetry lock updates
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroKpaxo committed Oct 21, 2024
0 parents commit c95da17
Show file tree
Hide file tree
Showing 38 changed files with 2,455 additions and 0 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode/
118 changes: 118 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
.vscode/
# Created by https://www.gitignore.io

# Local
__local_data/*
__data/
__local_dev/
__local_scripts/
TerraDatabase/production_data/local_data/*
local_backup/*
downloads/*
local_scripts/
### OSX ###
.DS_Store
.AppleDouble
.LSOverride

places_detail_states.py

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear on external disk
.Spotlight-V100
.Trashes

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Temporary files
tmp/

### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py

.env
db.sqlite3

### IDEs ###
.vscode

### Platforms ###
# Divio
.divio
/data.tar.gz
/data
Binary file added README.MD
Binary file not shown.
Empty file added app/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions app/interfaces/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pydantic import BaseModel, Field


class HasId(BaseModel):
"""
A base class for models that have an ID field.
It is designed to work with mongoDB because it uses the `_id` field.
Args:
BaseModel (_type_): _description_
"""
id: str = Field(alias='_id', description='The unique identifier for the record.')
47 changes: 47 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import logging
import os

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from dotenv import load_dotenv


load_dotenv()

# The main FastAPI app
# NOTE: We are using the `StaticFiles` class to serve static files
app = FastAPI(
title="Sigmine API",
description="A real time API for sigmine records", # noqa
version="0.1.0",
contact={
"name": "Pedro Cavalcanti",
"url": "https://github.com/pedrokpaxo",
"email": "[email protected]"
}
)

CORS_ALLOWED_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
# Deal with cors
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)


@app.get("/health")
async def root():
"""
Performs a health check.
On both the Redis and MongoDB databases.
"""
try:
return {"database_status": True}

except Exception as e:
logging.error(e)
return {"database_status": False}
9 changes: 9 additions & 0 deletions app/routes/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from fastapi.routing import APIRouter
from app.utils.logging import setup_logger


class BaseRouter(APIRouter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Basic Logger
self.logger = setup_logger(__class__.__name__)
57 changes: 57 additions & 0 deletions app/utils/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import logging
from types import MappingProxyType
from typing import Literal

LEVELS = MappingProxyType({
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
})

LOG_LEVELS = Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']


class ColorfulFormatter(logging.Formatter):
"""
ColorfulFormatter class for logging
"""
COLORS = {
'DEBUG': '\033[94m', # Blue
'INFO': '\033[92m', # Green
'WARNING': '\033[93m', # Yellow
'ERROR': '\033[91m', # Red
'CRITICAL': '\033[91m', # Red
}

RESET = '\033[0m'

def __init__(self, app_name: str):
super().__init__()
self.app_name = app_name

def format(self, record):
color = self.COLORS.get(record.levelname, self.RESET)
message = f"{color}[{self.app_name}] {record.levelname}{self.RESET}: {record.msg}" # noqa
return f'{message}'


def setup_logger(app_name: str, debug_level: LOG_LEVELS = 'DEBUG') -> logging.Logger:
"""
This function sets up the logger for the application
"""
logger = logging.getLogger(app_name)
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()
formatter = ColorfulFormatter(app_name)
handler.setFormatter(formatter)
logger.addHandler(handler)

if debug_level in LEVELS:
logger.setLevel(LEVELS[debug_level])
else:
raise ValueError(f"Invalid log level: {debug_level}")

return logger
45 changes: 45 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
version: '3.8'
services:
# Application service
web:
build:
context: .
args:
requirements_file: requirements/local.txt
stdin_open: true
tty: true
ports:
- "8000:8000"
volumes:
- .:/app
- ./data:/data
depends_on:
- mongodb
env_file: .env
restart: always
networks:
- app-network

entrypoint: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

# MongoDB Database
mongodb:
image: mongo:latest
env_file: .env
ports:
- '27017:27017'
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
volumes:
- mongo_data:/data/db
command: [ "mongod", "--config", "/etc/mongo/mongod.conf" ]
networks:
- app-network

networks:
app-network:
driver: bridge

volumes:
mongo_data:
36 changes: 36 additions & 0 deletions dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Use an official Python runtime as a parent image
FROM python:3.10-slim as base

LABEL author="Pedro Cavalcanti"

# Set environment variables for Python
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

# Set working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y curl build-essential libpq-dev

# Install Poetry
RUN pip install --no-cache-dir poetry

# Copy pyproject.toml and poetry.lock for better caching
COPY pyproject.toml poetry.lock* /app/

# Install project dependencies with Poetry, without dev dependencies
RUN poetry config virtualenvs.create false \
&& poetry install --no-dev --no-interaction --no-ansi

# Copy the entire application code into the container
COPY . /app

# Expose the FastAPI port
EXPOSE 8000

# Healthcheck to monitor the application
HEALTHCHECK CMD curl --fail http://localhost:8000/health || exit 1

# Command to run the FastAPI app with Uvicorn
CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
3 changes: 3 additions & 0 deletions fastcore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

from .request import UserRequest
from .app import CustomClientApp
31 changes: 31 additions & 0 deletions fastcore/abstract/abstract_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from abc import ABC, abstractmethod

from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient

from fastcore.logger import setup_logger


class AbstractApp(FastAPI, ABC):
"""
An abstract base class for custom FastAPI apps.
This enforces that derived classes implement specific methods for managing settings and resources.
"""
client: AsyncIOMotorClient

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = setup_logger(self._type())

def _type(self):
return self.__class__.__name__

@abstractmethod
async def set_client(self):
"""Abstract method to set up custom settings, like database connections."""
pass

@abstractmethod
def shutdown(self):
"""Method to close any resources like database connections."""
pass
15 changes: 15 additions & 0 deletions fastcore/abstract/abstract_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from abc import ABC, abstractmethod

from dotenv import load_dotenv


class AbstractSettings(ABC):

def __init__(self) -> None:
super().__init__()
load_dotenv()

@abstractmethod
def load_settings(self):
"""Method to initialize settings such as database connections."""
pass
12 changes: 12 additions & 0 deletions fastcore/abstract/abstract_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pydantic import BaseModel
from typing import TypeVar


# NOTE It is also ok to get the starlette SimpleUser

class AbstractUser(BaseModel):
username: str
password: str


TUser = TypeVar('TUser', bound=AbstractUser)
Loading

0 comments on commit c95da17

Please sign in to comment.