File tree 16 files changed +362
-11
lines changed
16 files changed +362
-11
lines changed Original file line number Diff line number Diff line change
1
+ .pytest_cache
2
+ tests
3
+ docker
4
+ src_local
5
+
6
+ # Git
7
+ .git
8
+ .gitignore
9
+ .gitattributes
10
+
11
+
12
+ # CI
13
+ .codeclimate.yml
14
+ .travis.yml
15
+ .taskcluster.yml
16
+
17
+ # Docker
18
+ docker-compose.yml
19
+ Dockerfile
20
+ .docker
21
+ .dockerignore
22
+
23
+ # Byte-compiled / optimized / DLL files
24
+ ** /__pycache__ /
25
+ ** /* .py [cod ]
26
+
27
+ # C extensions
28
+ * .so
29
+
30
+ # Distribution / packaging
31
+ .Python
32
+ env /
33
+ build /
34
+ develop-eggs /
35
+ dist /
36
+ downloads /
37
+ eggs /
38
+ lib /
39
+ lib64 /
40
+ parts /
41
+ sdist /
42
+ var /
43
+ * .egg-info /
44
+ .installed.cfg
45
+ * .egg
46
+
47
+ # PyInstaller
48
+ # Usually these files are written by a python script from a template
49
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
50
+ * .manifest
51
+ * .spec
52
+
53
+ # Installer logs
54
+ pip-log.txt
55
+ pip-delete-this-directory.txt
56
+
57
+ # Unit test / coverage reports
58
+ htmlcov /
59
+ .tox /
60
+ .coverage
61
+ .cache
62
+ nosetests.xml
63
+ coverage.xml
64
+
65
+ # Translations
66
+ * .mo
67
+ * .pot
68
+
69
+ # Django stuff:
70
+ * .log
71
+
72
+ # Sphinx documentation
73
+ docs /_build /
74
+
75
+ # PyBuilder
76
+ target /
77
+
78
+ # Virtual environment
79
+ .env
80
+ .venv /
81
+ venv /
82
+
83
+ # PyCharm
84
+ .idea
85
+
86
+ # Python mode for VIM
87
+ .ropeproject
88
+ ** /.ropeproject
89
+
90
+ # Vim swap files
91
+ ** /* .swp
92
+
93
+ # VS Code
94
+ .vscode /
Original file line number Diff line number Diff line change
1
+ FROM python:3.10-alpine
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
4
+
5
+ ARG HOME_DIR=/app
6
+
7
+ WORKDIR $HOME_DIR
8
+
9
+ EXPOSE 8000
10
+
11
+ COPY requirements.txt requirements.txt
12
+
13
+ RUN apk update \
14
+ && apk add build-base \
15
+ && pip install --no-cache-dir --upgrade pip \
16
+ && pip install --no-cache-dir -r requirements.txt
Original file line number Diff line number Diff line change 14
14
- Проходим ревью
15
15
- Вливаем в ветку main (через squash & merge)
16
16
- Оповещаем всех остальных (чтобы они подтянули изменения из main и сразу порешали merge conflict)
17
+
18
+ ## Запуск проекта
19
+ 1 . Создать файл .env в корне проекта и заполнить его по шаблону .env.example
20
+ 2 . Запустить базу данных, если она еще не запущена
21
+ 3 . Выполнить ` docker-compose up -d ` . Эта команда поднимает следующие сервисы: само приложение, elasticsearch и redis
Original file line number Diff line number Diff line change
1
+ version : ' 3'
2
+
3
+ services :
4
+ app :
5
+ build : .
6
+ command : uvicorn main:app --host 0.0.0.0 --port 8000
7
+ environment :
8
+ - REDIS_HOST=redis
9
+ - ELASTIC_HOST=es
10
+ ports :
11
+ - " 8000:8000"
12
+ volumes :
13
+ - " ./:/app"
14
+ depends_on :
15
+ - es
16
+ - redis
17
+ es :
18
+ image : docker.io/elastic/elasticsearch:7.7.0
19
+ env_file :
20
+ - .env
21
+ environment :
22
+ - discovery.type=single-node
23
+ volumes :
24
+ - esdata01:/usr/share/elasticsearch/data
25
+ ports :
26
+ - ${ELASTIC_PORT}:9200
27
+ deploy :
28
+ resources :
29
+ limits :
30
+ memory : ${ELASTIC_MEM_LIMIT}
31
+ redis :
32
+ image : redis:7-alpine
33
+ env_file :
34
+ - .env
35
+ ports :
36
+ - ${REDIS_PORT}:6379
37
+
38
+ volumes :
39
+ esdata01 :
Original file line number Diff line number Diff line change
1
+ import logging
2
+
3
+ import aioredis
4
+ import uvicorn as uvicorn
5
+ from elasticsearch import AsyncElasticsearch
6
+ from fastapi import FastAPI
7
+ from fastapi .responses import ORJSONResponse
8
+
9
+ from src .api .v1 import films
10
+ from src .core .config import settings
11
+ from src .core .logger import LOGGING
12
+ from src .db import elastic , redis
13
+
14
+ app = FastAPI (
15
+ title = settings .PROJECT_NAME ,
16
+ docs_url = '/api/openapi' ,
17
+ openapi_url = '/api/openapi.json' ,
18
+ default_response_class = ORJSONResponse ,
19
+ )
20
+
21
+
22
+ @app .on_event ('startup' )
23
+ async def startup ():
24
+ redis .redis = await aioredis .from_url (f'redis://{ settings .REDIS_HOST } :{ settings .REDIS_PORT } ' )
25
+ elastic .es = AsyncElasticsearch (hosts = [f'http://{ settings .ELASTIC_HOST } :{ settings .ELASTIC_PORT } ' ])
26
+
27
+
28
+ @app .on_event ('shutdown' )
29
+ async def shutdown ():
30
+ await redis .redis .close ()
31
+ await elastic .es .close ()
32
+
33
+
34
+ app .include_router (films .router , prefix = '/api/v1/films' , tags = ['films' ])
35
+
36
+ if __name__ == '__main__' :
37
+ uvicorn .run (
38
+ 'main:app' ,
39
+ host = '0.0.0.0' ,
40
+ port = 8000 ,
41
+ log_config = LOGGING ,
42
+ log_level = logging .DEBUG ,
43
+ )
Original file line number Diff line number Diff line change 1
1
aioredis == 2.0.1
2
- elasticsearch == 7.9.1
2
+ elasticsearch [ async ] == 7.9.1
3
3
fastapi == 0.78.0
4
4
orjson == 3.7.7
5
5
pydantic == 1.9.1
@@ -11,3 +11,4 @@ flake8-broken-line==0.4.0
11
11
flake8-quotes == 3.3.1
12
12
isort == 5.10.1
13
13
pre-commit == 2.20.0
14
+ loguru == 0.6.0
File renamed without changes.
Original file line number Diff line number Diff line change
1
+ from http import HTTPStatus
2
+ from typing import List
3
+
4
+ from fastapi import APIRouter , Depends , HTTPException
5
+ from pydantic import BaseModel
6
+
7
+ from src .models .film import Genre , Person
8
+ from src .models .mixins import UUIDMixin
9
+ from src .services .film import FilmService , get_film_service
10
+
11
+ router = APIRouter ()
12
+
13
+
14
+ class FilmAPI (UUIDMixin , BaseModel ):
15
+ title : str
16
+ imdb_rating : float
17
+ description : str
18
+ genre : List [Genre ]
19
+ actors : List [Person ]
20
+ writers : List [Person ]
21
+ directors : List [Person ]
22
+
23
+
24
+ @router .get ('/{film_id}' , response_model = FilmAPI )
25
+ async def film_details (film_id : str , film_service : FilmService = Depends (get_film_service )) -> FilmAPI :
26
+ film = await film_service .get_by_id (film_id )
27
+ if not film :
28
+ raise HTTPException (status_code = HTTPStatus .NOT_FOUND , detail = 'film not found' )
29
+
30
+ return FilmAPI .parse_obj (film .dict (by_alias = True ))
Original file line number Diff line number Diff line change 1
1
import os
2
2
from logging import config as logging_config
3
+ from pathlib import Path
3
4
4
5
from pydantic import BaseSettings
5
6
6
- from core .logger import LOGGING
7
+ from src .core .logger import LOGGING
8
+
9
+ logging_config .dictConfig (LOGGING )
10
+
11
+ BASE_DIR = Path (__file__ ).resolve ().parent
7
12
8
13
9
14
class Settings (BaseSettings ):
10
- PROJECT_NAME : str
11
- REDIS_HOST : str
12
- REDIS_PORT : str
13
- ELASTIC_HOST : str
14
- ELASTIC_PORT : str
15
- BASE_DIR = os .path .dirname (os .path .dirname (os .path .abspath (__file__ )))
15
+ PROJECT_NAME : str = 'movies'
16
+ REDIS_HOST : str = '127.0.0.1'
17
+ REDIS_PORT : str = '6379'
18
+ ELASTIC_HOST : str = '127.0.0.1'
19
+ ELASTIC_PORT : str = '9200'
16
20
17
21
class Config :
18
- env_file = '.env'
22
+ env_file = os . path . join ( BASE_DIR , '.env' )
19
23
env_file_encoding = 'utf-8'
20
24
21
25
22
26
settings = Settings ()
23
-
24
- logging_config .dictConfig (LOGGING )
Original file line number Diff line number Diff line change
1
+ from typing import List
2
+
3
+ import orjson
4
+
5
+ from src .models .genre import Genre
6
+ from src .models .mixins import UUIDMixin
7
+ from src .models .person import Person
8
+ from src .models .utils import orjson_dumps
9
+
10
+
11
+ class Film (UUIDMixin ):
12
+ title : str
13
+ imdb_rating : float
14
+ description : str = ''
15
+ genre : List [Genre ] = []
16
+ actors : List [Person ] = []
17
+ writers : List [Person ] = []
18
+ directors : List [Person ] = []
19
+
20
+ class Config :
21
+ json_loads = orjson .loads
22
+ json_dumps = orjson_dumps
Original file line number Diff line number Diff line change
1
+ import orjson
2
+
3
+ from src .models .mixins import UUIDMixin
4
+ from src .models .utils import orjson_dumps
5
+
6
+
7
+ class Genre (UUIDMixin ):
8
+ name : str
9
+
10
+ class Config :
11
+ json_loads = orjson .loads
12
+ json_dumps = orjson_dumps
Original file line number Diff line number Diff line change
1
+ from pydantic import BaseModel , Field
2
+
3
+
4
+ class UUIDMixin (BaseModel ):
5
+ uuid : str = Field (alias = 'id' )
Original file line number Diff line number Diff line change
1
+ import orjson
2
+ from pydantic import Field
3
+
4
+ from src .models .mixins import UUIDMixin
5
+ from src .models .utils import orjson_dumps
6
+
7
+
8
+ class Person (UUIDMixin ):
9
+ full_name : str = Field (alias = 'name' )
10
+
11
+ class Config :
12
+ json_loads = orjson .loads
13
+ json_dumps = orjson_dumps
Original file line number Diff line number Diff line change
1
+ import orjson
2
+
3
+
4
+ def orjson_dumps (v , * , default ):
5
+ return orjson .dumps (v , default = default ).decode ()
You can’t perform that action at this time.
0 commit comments