Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasKs committed May 4, 2021
0 parents commit 84e1231
Show file tree
Hide file tree
Showing 28 changed files with 1,971 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
env
.dockerignore
Dockerfile
Dockerfile.prod
.venv
59 changes: 59 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[flake8]
max-line-length = 120

ignore=
# Missing type annotations for **args
ANN002
# Missing type annotations for **kwargs
ANN003
# Missing type annotations for self
ANN101
# Missing type annotation for cls in classmethod
ANN102

# Complains on function calls in argument defaults
B008

# Docstring at the top of a public module
D100
# Docstring at the top of a public class (method is enough)
D101
# Missing docstring in __init__
D107
# Missing docstring in public package
D104
# Make docstrings one line if it can fit.
D200
# 1 blank line required between summary line and description
D205
# First line should end with a period - here we have a few cases where the first line is too long, and
# this issue can't be fixed without using noqa notation
D400
# Imperative docstring declarations
D401

# Whitespace before ':'. Black formats code this way.
E203
# E501: Line length
E501

# Missing f-string, we ignore this due to URL patterns
FS003

# Missing type annotations for `**kwargs`
TYP003
# Type annotation for `self`
TYP101
TYP102 # for cls

# W503 line break before binary operator - conflicts with black
W503


exclude =
.git,
.idea,
__pycache__,
tests/*,
venv,
.venv
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
*.pyc
.idea/*
env/
.env
venv/
.venv/
build/
dist/
*.egg-info/
notes
.pytest_cache
.coverage
htmlcov/
coverage.xml
41 changes: 41 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
exclude: README.md
repos:
- repo: https://github.com/ambv/black
rev: '20.8b1'
hooks:
- id: black
args: ['--quiet']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-case-conflict
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-ast
- id: check-json
- id: check-merge-conflict
- id: detect-private-key
- id: double-quote-string-fixer
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear==20.1.4', # Looks for likely bugs and design problems
'flake8-comprehensions==3.2.3', # Looks for unnecessary generator functions that can be converted to list comprehensions
'flake8-deprecated==1.3', # Looks for method deprecations
'flake8-use-fstring==1.1', # Enforces use of f-strings over .format and %s
'flake8-print==3.1.4', # Checks for print statements
'flake8-docstrings==1.5.0', # Verifies that all functions/methods have docstrings
'flake8-type-annotations==0.1.0', # Looks for misconfigured type annotations
'flake8-annotations==2.4.0', # Enforces type annotation
]
args: ['--enable-extensions=G']
- repo: https://github.com/pycqa/isort
rev: 5.5.2
hooks:
- id: isort
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.800'
hooks:
- id: mypy
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Contributing to ACI-watcher-backend
===================================

1. Install poetry running ``pip install poetry`` or ``curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -``

2. Install dependencies by running ``poetry install``

3. Activate the environment

4. Install pre-commit (for linting) by running ``pre-commit install``
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# klipp

An API for your S3 buckets
Empty file added app/__init__.py
Empty file.
Empty file added app/api/__init__.py
Empty file.
Empty file added app/api/api_v1/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions app/api/api_v1/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from fastapi import APIRouter

from api.api_v1.endpoints import file

api_router = APIRouter()
api_router.include_router(file.router, tags=['files'])
Empty file.
27 changes: 27 additions & 0 deletions app/api/api_v1/endpoints/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Any

from aiobotocore.client import BaseClient
from fastapi import APIRouter, Depends, UploadFile

from api.dependencies import file_format, get_boto
from core.config import Settings, load_settings

router = APIRouter()


@router.post('/upload/')
async def upload_file(session: BaseClient = Depends(get_boto), file: UploadFile = Depends(file_format)) -> Any:
"""
Retrieve contracts
"""
await session.put_object(Bucket='klipp', Key=f'jonas/{file.filename}', Body=await file.read())
# session.put_object(Bucket='klipp', key='test', Body='lol')
return {'filename': file.filename, 'type': file.content_type}


@router.get('/ping')
async def pong(settings: Settings = Depends(load_settings)) -> dict:
"""
Ping function for testing
"""
return {'ping': 'pong!', 'environment': settings.ENVIRONMENT, 'testing': settings.TESTING}
32 changes: 32 additions & 0 deletions app/api/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from aiobotocore import get_session
from aiobotocore.session import ClientCreatorContext
from fastapi import Depends, File, UploadFile
from fastapi.exceptions import HTTPException

from core.config import Settings, load_settings
from schemas.file import AllowedFile


async def file_format(file: UploadFile = File(...)) -> UploadFile:
"""
Check file format is text
"""
if file.content_type == AllowedFile.JPEG:
return file
raise HTTPException(status_code=422, detail='File format not accepted')


session = get_session()


async def get_boto(settings: Settings = Depends(load_settings)) -> ClientCreatorContext:
"""
Create a boto client which can be shared
"""
async with session.create_client(
's3',
region_name='eu-north-1',
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
) as client:
yield client
Empty file added app/core/__init__.py
Empty file.
60 changes: 60 additions & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from functools import lru_cache
from typing import List, Optional, Union

from decouple import config
from pydantic import AnyHttpUrl, BaseSettings, HttpUrl, validator
from pydantic.networks import AnyUrl


class Credentials(BaseSettings):
AWS_ACCESS_KEY_ID = config('AWS_ID')
AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_KEY')

DATABASE_URL: AnyUrl = config('DATABASE_URL')


class Settings(Credentials):
API_V1_STR: str = '/api/v1'

ENVIRONMENT: str = config('ENVIRONMENT', 'dev')
TESTING: bool = config('TESTING', False)
SECRET_KEY: str = config('SECRET_KEY', None)

# BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
# e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
# "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []

@validator('BACKEND_CORS_ORIGINS', pre=True)
def assemble_cors_origins(cls, value: Union[str, List[str]]) -> Union[List[str], str]:
"""
Validate cors list
"""
if isinstance(value, str) and not value.startswith('['):
return [i.strip() for i in value.split(',')]
elif isinstance(value, (list, str)):
return value
raise ValueError(value)

PROJECT_NAME: str = 'klipp'
SENTRY_DSN: Optional[HttpUrl] = None

@validator('SENTRY_DSN', pre=True)
def sentry_dsn_can_be_blank(cls, value: str) -> Optional[str]:
"""
Validate sentry DSN
"""
if not value:
return None
return value

class Config: # noqa
case_sensitive = True


@lru_cache
def load_settings() -> Settings:
"""
Load all settings
"""
return Settings()
Empty file added app/db/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from api.api_v1.api import api_router
from core.config import load_settings

settings = load_settings()

app = FastAPI(title=settings.PROJECT_NAME, openapi_url=f'{settings.API_V1_STR}/openapi.json')

# Set all CORS enabled origins
if settings.BACKEND_CORS_ORIGINS:
app.add_middleware(
CORSMiddleware,
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)

app.include_router(api_router, prefix=settings.API_V1_STR)
Empty file added app/schemas/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions app/schemas/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from enum import Enum


class AllowedFile(str, Enum):
JPEG = 'image/jpeg'
Empty file added app/tests/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions dev/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# pull official base image
FROM python:3.9.2-slim-buster

# set working directory
WORKDIR /usr/src/app


# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV POETRY_VIRTUALENVS_CREATE=false

# install system dependencies
RUN apt-get update \
&& apt-get -y install netcat gcc \
&& apt-get clean

# install python dependencies
RUN pip install --upgrade pip
RUN pip install poetry==1.1.4
COPY ../../poetry.lock .
COPY ../../pyproject.toml .
RUN poetry install

# add app
COPY ../../app .


# add entrypoint.sh
COPY dev/app/entrypoint.sh .
RUN chmod +x /usr/src/app/entrypoint.sh

ENTRYPOINT ["/bin/sh", "-c", "/usr/src/app/entrypoint.sh"]
11 changes: 11 additions & 0 deletions dev/app/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

echo "Waiting for postgres..."

while ! nc -z klipp-postgres-db 5432; do
sleep 0.1
done

echo "PostgreSQL started"

exec "$@"
5 changes: 5 additions & 0 deletions dev/db/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# pull official base image
FROM postgres:13-alpine

# run create.sql on init
ADD dev/db/create.sql /docker-entrypoint-initdb.d
2 changes: 2 additions & 0 deletions dev/db/create.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CREATE DATABASE web_dev;
CREATE DATABASE web_test;
26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: '3.8'

services:
klipp-api:
build:
context: .
dockerfile: dev/app/Dockerfile
command: uvicorn main:app --reload --workers 1 --host 0.0.0.0 --port 8004
volumes:
- ${PWD}/app:/usr/src/app:Z
ports:
- 127.0.0.1:8004:8004
env_file: ${PWD}/.env
environment:
- ENVIRONMENT=dev
- TESTING=1
depends_on:
- klipp-postgres-db

klipp-postgres-db:
build:
context: .
dockerfile: dev/db/Dockerfile
expose:
- 5432
env_file: ${PWD}/.env
11 changes: 11 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Global options

[mypy]
python_version = 3.9
# flake8-mypy expects the two following for sensible formatting
show_column_numbers = True
show_error_context = False
# Enables or disables strict Optional checks: https://readthedocs.org/projects/mypy/downloads/pdf/stable/
strict_optional = True
# Allow no return statement
warn_no_return = False
Loading

0 comments on commit 84e1231

Please sign in to comment.