Skip to content

Commit 84e1231

Browse files
committed
WIP
0 parents  commit 84e1231

28 files changed

+1971
-0
lines changed

.dockerignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
env
2+
.dockerignore
3+
Dockerfile
4+
Dockerfile.prod
5+
.venv

.flake8

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[flake8]
2+
max-line-length = 120
3+
4+
ignore=
5+
# Missing type annotations for **args
6+
ANN002
7+
# Missing type annotations for **kwargs
8+
ANN003
9+
# Missing type annotations for self
10+
ANN101
11+
# Missing type annotation for cls in classmethod
12+
ANN102
13+
14+
# Complains on function calls in argument defaults
15+
B008
16+
17+
# Docstring at the top of a public module
18+
D100
19+
# Docstring at the top of a public class (method is enough)
20+
D101
21+
# Missing docstring in __init__
22+
D107
23+
# Missing docstring in public package
24+
D104
25+
# Make docstrings one line if it can fit.
26+
D200
27+
# 1 blank line required between summary line and description
28+
D205
29+
# First line should end with a period - here we have a few cases where the first line is too long, and
30+
# this issue can't be fixed without using noqa notation
31+
D400
32+
# Imperative docstring declarations
33+
D401
34+
35+
# Whitespace before ':'. Black formats code this way.
36+
E203
37+
# E501: Line length
38+
E501
39+
40+
# Missing f-string, we ignore this due to URL patterns
41+
FS003
42+
43+
# Missing type annotations for `**kwargs`
44+
TYP003
45+
# Type annotation for `self`
46+
TYP101
47+
TYP102 # for cls
48+
49+
# W503 line break before binary operator - conflicts with black
50+
W503
51+
52+
53+
exclude =
54+
.git,
55+
.idea,
56+
__pycache__,
57+
tests/*,
58+
venv,
59+
.venv

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
*.pyc
2+
.idea/*
3+
env/
4+
.env
5+
venv/
6+
.venv/
7+
build/
8+
dist/
9+
*.egg-info/
10+
notes
11+
.pytest_cache
12+
.coverage
13+
htmlcov/
14+
coverage.xml

.pre-commit-config.yaml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
exclude: README.md
2+
repos:
3+
- repo: https://github.com/ambv/black
4+
rev: '20.8b1'
5+
hooks:
6+
- id: black
7+
args: ['--quiet']
8+
- repo: https://github.com/pre-commit/pre-commit-hooks
9+
rev: v2.4.0
10+
hooks:
11+
- id: check-case-conflict
12+
- id: end-of-file-fixer
13+
- id: trailing-whitespace
14+
- id: check-ast
15+
- id: check-json
16+
- id: check-merge-conflict
17+
- id: detect-private-key
18+
- id: double-quote-string-fixer
19+
- repo: https://gitlab.com/pycqa/flake8
20+
rev: 3.8.3
21+
hooks:
22+
- id: flake8
23+
additional_dependencies: [
24+
'flake8-bugbear==20.1.4', # Looks for likely bugs and design problems
25+
'flake8-comprehensions==3.2.3', # Looks for unnecessary generator functions that can be converted to list comprehensions
26+
'flake8-deprecated==1.3', # Looks for method deprecations
27+
'flake8-use-fstring==1.1', # Enforces use of f-strings over .format and %s
28+
'flake8-print==3.1.4', # Checks for print statements
29+
'flake8-docstrings==1.5.0', # Verifies that all functions/methods have docstrings
30+
'flake8-type-annotations==0.1.0', # Looks for misconfigured type annotations
31+
'flake8-annotations==2.4.0', # Enforces type annotation
32+
]
33+
args: ['--enable-extensions=G']
34+
- repo: https://github.com/pycqa/isort
35+
rev: 5.5.2
36+
hooks:
37+
- id: isort
38+
- repo: https://github.com/pre-commit/mirrors-mypy
39+
rev: 'v0.800'
40+
hooks:
41+
- id: mypy

CONTRIBUTING.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Contributing to ACI-watcher-backend
2+
===================================
3+
4+
1. Install poetry running ``pip install poetry`` or ``curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -``
5+
6+
2. Install dependencies by running ``poetry install``
7+
8+
3. Activate the environment
9+
10+
4. Install pre-commit (for linting) by running ``pre-commit install``

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# klipp
2+
3+
An API for your S3 buckets

app/__init__.py

Whitespace-only changes.

app/api/__init__.py

Whitespace-only changes.

app/api/api_v1/__init__.py

Whitespace-only changes.

app/api/api_v1/api.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from fastapi import APIRouter
2+
3+
from api.api_v1.endpoints import file
4+
5+
api_router = APIRouter()
6+
api_router.include_router(file.router, tags=['files'])

app/api/api_v1/endpoints/__init__.py

Whitespace-only changes.

app/api/api_v1/endpoints/file.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Any
2+
3+
from aiobotocore.client import BaseClient
4+
from fastapi import APIRouter, Depends, UploadFile
5+
6+
from api.dependencies import file_format, get_boto
7+
from core.config import Settings, load_settings
8+
9+
router = APIRouter()
10+
11+
12+
@router.post('/upload/')
13+
async def upload_file(session: BaseClient = Depends(get_boto), file: UploadFile = Depends(file_format)) -> Any:
14+
"""
15+
Retrieve contracts
16+
"""
17+
await session.put_object(Bucket='klipp', Key=f'jonas/{file.filename}', Body=await file.read())
18+
# session.put_object(Bucket='klipp', key='test', Body='lol')
19+
return {'filename': file.filename, 'type': file.content_type}
20+
21+
22+
@router.get('/ping')
23+
async def pong(settings: Settings = Depends(load_settings)) -> dict:
24+
"""
25+
Ping function for testing
26+
"""
27+
return {'ping': 'pong!', 'environment': settings.ENVIRONMENT, 'testing': settings.TESTING}

app/api/dependencies.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from aiobotocore import get_session
2+
from aiobotocore.session import ClientCreatorContext
3+
from fastapi import Depends, File, UploadFile
4+
from fastapi.exceptions import HTTPException
5+
6+
from core.config import Settings, load_settings
7+
from schemas.file import AllowedFile
8+
9+
10+
async def file_format(file: UploadFile = File(...)) -> UploadFile:
11+
"""
12+
Check file format is text
13+
"""
14+
if file.content_type == AllowedFile.JPEG:
15+
return file
16+
raise HTTPException(status_code=422, detail='File format not accepted')
17+
18+
19+
session = get_session()
20+
21+
22+
async def get_boto(settings: Settings = Depends(load_settings)) -> ClientCreatorContext:
23+
"""
24+
Create a boto client which can be shared
25+
"""
26+
async with session.create_client(
27+
's3',
28+
region_name='eu-north-1',
29+
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
30+
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
31+
) as client:
32+
yield client

app/core/__init__.py

Whitespace-only changes.

app/core/config.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from functools import lru_cache
2+
from typing import List, Optional, Union
3+
4+
from decouple import config
5+
from pydantic import AnyHttpUrl, BaseSettings, HttpUrl, validator
6+
from pydantic.networks import AnyUrl
7+
8+
9+
class Credentials(BaseSettings):
10+
AWS_ACCESS_KEY_ID = config('AWS_ID')
11+
AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_KEY')
12+
13+
DATABASE_URL: AnyUrl = config('DATABASE_URL')
14+
15+
16+
class Settings(Credentials):
17+
API_V1_STR: str = '/api/v1'
18+
19+
ENVIRONMENT: str = config('ENVIRONMENT', 'dev')
20+
TESTING: bool = config('TESTING', False)
21+
SECRET_KEY: str = config('SECRET_KEY', None)
22+
23+
# BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
24+
# e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
25+
# "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
26+
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
27+
28+
@validator('BACKEND_CORS_ORIGINS', pre=True)
29+
def assemble_cors_origins(cls, value: Union[str, List[str]]) -> Union[List[str], str]:
30+
"""
31+
Validate cors list
32+
"""
33+
if isinstance(value, str) and not value.startswith('['):
34+
return [i.strip() for i in value.split(',')]
35+
elif isinstance(value, (list, str)):
36+
return value
37+
raise ValueError(value)
38+
39+
PROJECT_NAME: str = 'klipp'
40+
SENTRY_DSN: Optional[HttpUrl] = None
41+
42+
@validator('SENTRY_DSN', pre=True)
43+
def sentry_dsn_can_be_blank(cls, value: str) -> Optional[str]:
44+
"""
45+
Validate sentry DSN
46+
"""
47+
if not value:
48+
return None
49+
return value
50+
51+
class Config: # noqa
52+
case_sensitive = True
53+
54+
55+
@lru_cache
56+
def load_settings() -> Settings:
57+
"""
58+
Load all settings
59+
"""
60+
return Settings()

app/db/__init__.py

Whitespace-only changes.

app/main.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from fastapi import FastAPI
2+
from fastapi.middleware.cors import CORSMiddleware
3+
4+
from api.api_v1.api import api_router
5+
from core.config import load_settings
6+
7+
settings = load_settings()
8+
9+
app = FastAPI(title=settings.PROJECT_NAME, openapi_url=f'{settings.API_V1_STR}/openapi.json')
10+
11+
# Set all CORS enabled origins
12+
if settings.BACKEND_CORS_ORIGINS:
13+
app.add_middleware(
14+
CORSMiddleware,
15+
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
16+
allow_credentials=True,
17+
allow_methods=['*'],
18+
allow_headers=['*'],
19+
)
20+
21+
app.include_router(api_router, prefix=settings.API_V1_STR)

app/schemas/__init__.py

Whitespace-only changes.

app/schemas/file.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from enum import Enum
2+
3+
4+
class AllowedFile(str, Enum):
5+
JPEG = 'image/jpeg'

app/tests/__init__.py

Whitespace-only changes.

dev/app/Dockerfile

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# pull official base image
2+
FROM python:3.9.2-slim-buster
3+
4+
# set working directory
5+
WORKDIR /usr/src/app
6+
7+
8+
# set environment variables
9+
ENV PYTHONDONTWRITEBYTECODE 1
10+
ENV PYTHONUNBUFFERED 1
11+
ENV POETRY_VIRTUALENVS_CREATE=false
12+
13+
# install system dependencies
14+
RUN apt-get update \
15+
&& apt-get -y install netcat gcc \
16+
&& apt-get clean
17+
18+
# install python dependencies
19+
RUN pip install --upgrade pip
20+
RUN pip install poetry==1.1.4
21+
COPY ../../poetry.lock .
22+
COPY ../../pyproject.toml .
23+
RUN poetry install
24+
25+
# add app
26+
COPY ../../app .
27+
28+
29+
# add entrypoint.sh
30+
COPY dev/app/entrypoint.sh .
31+
RUN chmod +x /usr/src/app/entrypoint.sh
32+
33+
ENTRYPOINT ["/bin/sh", "-c", "/usr/src/app/entrypoint.sh"]

dev/app/entrypoint.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
3+
echo "Waiting for postgres..."
4+
5+
while ! nc -z klipp-postgres-db 5432; do
6+
sleep 0.1
7+
done
8+
9+
echo "PostgreSQL started"
10+
11+
exec "$@"

dev/db/Dockerfile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# pull official base image
2+
FROM postgres:13-alpine
3+
4+
# run create.sql on init
5+
ADD dev/db/create.sql /docker-entrypoint-initdb.d

dev/db/create.sql

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CREATE DATABASE web_dev;
2+
CREATE DATABASE web_test;

docker-compose.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: '3.8'
2+
3+
services:
4+
klipp-api:
5+
build:
6+
context: .
7+
dockerfile: dev/app/Dockerfile
8+
command: uvicorn main:app --reload --workers 1 --host 0.0.0.0 --port 8004
9+
volumes:
10+
- ${PWD}/app:/usr/src/app:Z
11+
ports:
12+
- 127.0.0.1:8004:8004
13+
env_file: ${PWD}/.env
14+
environment:
15+
- ENVIRONMENT=dev
16+
- TESTING=1
17+
depends_on:
18+
- klipp-postgres-db
19+
20+
klipp-postgres-db:
21+
build:
22+
context: .
23+
dockerfile: dev/db/Dockerfile
24+
expose:
25+
- 5432
26+
env_file: ${PWD}/.env

mypy.ini

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Global options
2+
3+
[mypy]
4+
python_version = 3.9
5+
# flake8-mypy expects the two following for sensible formatting
6+
show_column_numbers = True
7+
show_error_context = False
8+
# Enables or disables strict Optional checks: https://readthedocs.org/projects/mypy/downloads/pdf/stable/
9+
strict_optional = True
10+
# Allow no return statement
11+
warn_no_return = False

0 commit comments

Comments
 (0)