├── .env_dev ├── db ├── __init__.py ├── base.py ├── users.py └── jobs.py ├── repositories ├── base.py ├── users.py └── jobs.py ├── models ├── token.py ├── jobs.py └── user.py ├── docker-compose.dev.yaml ├── core ├── config.py └── security.py ├── Pipfile ├── readme.md ├── main.py ├── endpoints ├── auth.py ├── depends.py ├── users.py └── jobs.py ├── .gitignore └── Pipfile.lock /.env_dev: -------------------------------------------------------------------------------- 1 | export EE_DATABASE_URL="postgresql://root:root@localhost:32700/employment_exchange" -------------------------------------------------------------------------------- /db/__init__.py: -------------------------------------------------------------------------------- 1 | from .users import users 2 | from .jobs import jobs 3 | from .base import metadata, engine 4 | 5 | metadata.create_all(bind=engine) -------------------------------------------------------------------------------- /repositories/base.py: -------------------------------------------------------------------------------- 1 | from databases import Database 2 | 3 | class BaseRepository: 4 | def __init__(self, database: Database): 5 | self.database = database -------------------------------------------------------------------------------- /models/token.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, EmailStr 2 | 3 | class Token(BaseModel): 4 | access_token: str 5 | token_type: str 6 | 7 | class Login(BaseModel): 8 | email: EmailStr 9 | password: str -------------------------------------------------------------------------------- /db/base.py: -------------------------------------------------------------------------------- 1 | from databases import Database 2 | from sqlalchemy import create_engine, MetaData 3 | from core.config import DATABASE_URL 4 | 5 | database = Database(DATABASE_URL) 6 | metadata = MetaData() 7 | engine = create_engine( 8 | DATABASE_URL, 9 | ) 10 | 11 | -------------------------------------------------------------------------------- /docker-compose.dev.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | image: postgres:11 5 | ports: 6 | - 32700:5432 7 | volumes: 8 | - ./data:/var/lib/postgresql/data 9 | environment: 10 | POSTGRES_USER: root 11 | POSTGRES_PASSWORD: root 12 | POSTGRES_DB: employment_exchange -------------------------------------------------------------------------------- /core/config.py: -------------------------------------------------------------------------------- 1 | from starlette.config import Config 2 | 3 | config = Config(".env") 4 | 5 | DATABASE_URL = config("EE_DATABASE_URL", cast=str, default="") 6 | ACCESS_TOKEN_EXPIRE_MINUTES = 60 7 | ALGORITHM = "HS256" 8 | SECRET_KEY = config("EE_SECRET_KEY", cast=str, default="2b2d197649061838c0c381612cb117d5f562ff181f2ed68c7847471af22f83ce") -------------------------------------------------------------------------------- /models/jobs.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from pydantic import BaseModel 3 | 4 | 5 | class BaseJob(BaseModel): 6 | title: str 7 | description: str 8 | salary_from: int 9 | salary_to: int 10 | is_active: bool = True 11 | 12 | class Job(BaseJob): 13 | id: int 14 | user_id: int 15 | created_at: datetime.datetime 16 | updated_at: datetime.datetime 17 | 18 | 19 | class JobIn(BaseJob): 20 | pass 21 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | fastapi = "*" 8 | uvicorn = "*" 9 | sqlalchemy = "*" 10 | databases = {extras = ["postgresql"], version = "*"} 11 | psycopg2-binary = "*" 12 | pydantic = {extras = ["email"], version = "*"} 13 | passlib = "*" 14 | bcrypt = "*" 15 | jose = "*" 16 | python-jose = "*" 17 | 18 | [dev-packages] 19 | 20 | [requires] 21 | python_version = "3.8" 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Employment exchange 2 | 3 | Пример приложения FastAPI CRUD 4 | 5 | Сделано для туториалов: 6 | https://www.youtube.com/watch?v=PVebRy0_K0s 7 | https://www.youtube.com/watch?v=qduT62Bygyw 8 | 9 | Как домашнее задание советую реализовать: 10 | - возможность откликатся на вакансии 11 | - добавить проверку, чтобы вакансию могли создать только компании, а откликатся только соискатели. 12 | - oбработать все эксепшены которые могут быть от базы данныех и вывести красиво ответы с кодами ошибок. 13 | - добавить супер-юзера у которого доступ ко всему приложению. 14 | - сделать фильтры для списков пользователей и списков вакансий. -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from db.base import database 3 | from endpoints import users, auth, jobs 4 | import uvicorn 5 | 6 | app = FastAPI(title="Employment exchange") 7 | app.include_router(users.router, prefix="/users", tags=["users"]) 8 | app.include_router(auth.router, prefix="/auth", tags=["auth"]) 9 | app.include_router(jobs.router, prefix="/jobs", tags=["jobs"]) 10 | 11 | 12 | @app.on_event("startup") 13 | async def startup(): 14 | await database.connect() 15 | 16 | @app.on_event("shutdown") 17 | async def shutdown(): 18 | await database.disconnect() 19 | 20 | if __name__ == "__main__": 21 | uvicorn.run("main:app", port=8000, host="0.0.0.0", reload=True) 22 | -------------------------------------------------------------------------------- /db/users.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | from .base import metadata 3 | import datetime 4 | 5 | users = sqlalchemy.Table( 6 | "users", 7 | metadata, 8 | sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True, autoincrement=True, unique=True), 9 | sqlalchemy.Column("email", sqlalchemy.String, primary_key=True, unique=True), 10 | sqlalchemy.Column("name", sqlalchemy.String), 11 | sqlalchemy.Column("hashed_password", sqlalchemy.String), 12 | sqlalchemy.Column("is_company", sqlalchemy.Boolean), 13 | sqlalchemy.Column("created_at", sqlalchemy.DateTime, default=datetime.datetime.utcnow), 14 | sqlalchemy.Column("updated_at", sqlalchemy.DateTime, default=datetime.datetime.utcnow) 15 | ) -------------------------------------------------------------------------------- /models/user.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | from pydantic import BaseModel, EmailStr, validator, constr 4 | 5 | class User(BaseModel): 6 | id: Optional[str] = None 7 | name: str 8 | email: EmailStr 9 | hashed_password: str 10 | is_company: bool 11 | created_at: datetime.datetime 12 | updated_at: datetime.datetime 13 | 14 | class UserIn(BaseModel): 15 | name: str 16 | email: EmailStr 17 | password: constr(min_length=8) 18 | password2: str 19 | is_company: bool = False 20 | 21 | @validator("password2") 22 | def password_match(cls, v, values, **kwargs): 23 | if 'password' in values and v != values["password"]: 24 | raise ValueError("passwords don't match") 25 | return v 26 | 27 | -------------------------------------------------------------------------------- /endpoints/auth.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException, status, Depends 2 | from models.token import Token, Login 3 | from repositories.users import UserRepository 4 | from core.security import verify_password, create_access_token 5 | from .depends import get_user_repository 6 | router = APIRouter() 7 | 8 | @router.post("/", response_model=Token) 9 | async def login(login: Login, users: UserRepository = Depends(get_user_repository)): 10 | user = await users.get_by_email(login.email) 11 | if user is None or not verify_password(login.password, user.hashed_password): 12 | raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password") 13 | return Token( 14 | access_token=create_access_token({"sub": user.email}), 15 | token_type="Bearer" 16 | ) -------------------------------------------------------------------------------- /db/jobs.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | from .base import metadata 3 | import datetime 4 | 5 | jobs = sqlalchemy.Table( 6 | "jobs", 7 | metadata, 8 | sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True, autoincrement=True, unique=True), 9 | sqlalchemy.Column("user_id", sqlalchemy.Integer, sqlalchemy.ForeignKey('users.id'), nullable=False), 10 | sqlalchemy.Column("title", sqlalchemy.String), 11 | sqlalchemy.Column("description", sqlalchemy.String), 12 | sqlalchemy.Column("salary_from", sqlalchemy.Integer), 13 | sqlalchemy.Column("salary_to", sqlalchemy.Integer), 14 | sqlalchemy.Column("is_active", sqlalchemy.Boolean), 15 | sqlalchemy.Column("created_at", sqlalchemy.DateTime, default=datetime.datetime.utcnow), 16 | sqlalchemy.Column("updated_at", sqlalchemy.DateTime, default=datetime.datetime.utcnow) 17 | ) -------------------------------------------------------------------------------- /endpoints/depends.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, HTTPException, status 2 | from repositories.users import UserRepository 3 | from repositories.jobs import JobRepository 4 | from db.base import database 5 | from core.security import JWTBearer, decode_access_token 6 | from models.user import User 7 | 8 | def get_user_repository() -> UserRepository: 9 | return UserRepository(database) 10 | 11 | def get_job_repository() -> JobRepository: 12 | return JobRepository(database) 13 | 14 | async def get_current_user( 15 | users: UserRepository = Depends(get_user_repository), 16 | token: str = Depends(JWTBearer()), 17 | ) -> User: 18 | cred_exception = HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Credentials are not valid") 19 | payload = decode_access_token(token) 20 | if payload is None: 21 | raise cred_exception 22 | email: str = payload.get("sub") 23 | if email is None: 24 | raise cred_exception 25 | user = await users.get_by_email(email=email) 26 | if user is None: 27 | return cred_exception 28 | return user 29 | 30 | 31 | -------------------------------------------------------------------------------- /endpoints/users.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from fastapi import APIRouter, Depends, HTTPException, status 3 | from repositories.users import UserRepository 4 | from models.user import User, UserIn 5 | from .depends import get_user_repository, get_current_user 6 | 7 | 8 | router = APIRouter() 9 | 10 | @router.get("/", response_model=List[User]) 11 | async def read_users( 12 | users: UserRepository = Depends(get_user_repository), 13 | limit: int = 100, 14 | skip: int = 0): 15 | return await users.get_all(limit=limit, skip=0) 16 | 17 | @router.post("/", response_model=User) 18 | async def create_user( 19 | user: UserIn, 20 | users: UserRepository = Depends(get_user_repository)): 21 | return await users.create(u=user) 22 | 23 | @router.put("/", response_model=User) 24 | async def update_user( 25 | id: int, 26 | user: UserIn, 27 | users: UserRepository = Depends(get_user_repository), 28 | current_user: User = Depends(get_current_user)): 29 | old_user = await users.get_by_id(id=id) 30 | if old_user is None or old_user.email != current_user.email: 31 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found user") 32 | return await users.update(id=id, u=user) -------------------------------------------------------------------------------- /core/security.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from fastapi import Request, HTTPException, status 3 | from fastapi.security import HTTPBearer 4 | from passlib.context import CryptContext 5 | from jose import jwt 6 | from .config import ACCESS_TOKEN_EXPIRE_MINUTES, SECRET_KEY, ALGORITHM 7 | 8 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 9 | 10 | def hash_password(password: str) -> str: 11 | return pwd_context.hash(password) 12 | 13 | def verify_password(password: str, hash: str) -> bool: 14 | return pwd_context.verify(password, hash) 15 | 16 | def create_access_token(data: dict) -> str: 17 | to_encode = data.copy() 18 | to_encode.update({"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)}) 19 | return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) 20 | 21 | def decode_access_token(token: str): 22 | try: 23 | encoded_jwt = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 24 | except jwt.JWSError: 25 | return None 26 | return encoded_jwt 27 | 28 | class JWTBearer(HTTPBearer): 29 | def __init__(self, auto_error: bool = True): 30 | super(JWTBearer, self).__init__(auto_error=auto_error) 31 | 32 | async def __call__(self, request: Request): 33 | credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) 34 | exp = HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid auth token") 35 | if credentials: 36 | token = decode_access_token(credentials.credentials) 37 | if token is None: 38 | raise exp 39 | return credentials.credentials 40 | else: 41 | raise exp 42 | 43 | -------------------------------------------------------------------------------- /endpoints/jobs.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from models.jobs import Job, JobIn 3 | from models.user import User 4 | from repositories.jobs import JobRepository 5 | from fastapi import APIRouter, Depends, HTTPException, status 6 | from .depends import get_job_repository, get_current_user 7 | 8 | router = APIRouter() 9 | 10 | @router.get("/", response_model=List[Job]) 11 | async def read_jobs( 12 | limit: int = 100, 13 | skip: int = 0, 14 | jobs: JobRepository = Depends(get_job_repository)): 15 | return await jobs.get_all(limit=limit, skip=skip) 16 | 17 | @router.post("/", response_model=Job) 18 | async def create_job( 19 | j: JobIn, 20 | jobs: JobRepository = Depends(get_job_repository), 21 | current_user: User = Depends(get_current_user)): 22 | return await jobs.create(user_id=current_user.id, j=j) 23 | 24 | @router.put("/", response_model=Job) 25 | async def update_job( 26 | id: int, 27 | j: JobIn, 28 | jobs: JobRepository = Depends(get_job_repository), 29 | current_user: User = Depends(get_current_user)): 30 | job = await jobs.get_by_id(id=id) 31 | if job is None or job.user_id != current_user.id: 32 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Job not found") 33 | return await jobs.update(id=id, user_id=current_user.id, j=j) 34 | 35 | @router.delete("/") 36 | async def delete_job(id: int, 37 | jobs: JobRepository = Depends(get_job_repository), 38 | current_user: User = Depends(get_current_user)): 39 | job = await jobs.get_by_id(id=id) 40 | not_found_exception = HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Job not found") 41 | if job is None or job.user_id != current_user.id: 42 | raise not_found_exception 43 | result = await jobs.delete(id=id) 44 | return {"status": True} -------------------------------------------------------------------------------- /repositories/users.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List, Optional 3 | from db.users import users 4 | from models.user import User, UserIn 5 | from core.security import hash_password 6 | from .base import BaseRepository 7 | 8 | class UserRepository(BaseRepository): 9 | 10 | async def get_all(self, limit: int = 100, skip: int = 0) -> List[User]: 11 | query = users.select().limit(limit).offset(skip) 12 | return await self.database.fetch_all(query=query) 13 | 14 | async def get_by_id(self, id: int) -> Optional[User]: 15 | query = users.select().where(users.c.id==id) 16 | user = await self.database.fetch_one(query) 17 | if user is None: 18 | return None 19 | return User.parse_obj(user) 20 | 21 | async def create(self, u: UserIn) -> User: 22 | user = User( 23 | name=u.name, 24 | email=u.email, 25 | hashed_password=hash_password(u.password), 26 | is_company=u.is_company, 27 | created_at=datetime.datetime.utcnow(), 28 | updated_at=datetime.datetime.utcnow(), 29 | ) 30 | values = {**user.dict()} 31 | values.pop("id", None) 32 | query = users.insert().values(**values) 33 | user.id = await self.database.execute(query) 34 | return user 35 | 36 | async def update(self, id: int, u: UserIn) -> User: 37 | user = User( 38 | id=id, 39 | name=u.name, 40 | email=u.email, 41 | hashed_password=hash_password(u.password2), 42 | is_company=u.is_company, 43 | created_at=datetime.datetime.utcnow(), 44 | updated_at=datetime.datetime.utcnow(), 45 | ) 46 | values = {**user.dict()} 47 | values.pop("created_at", None) 48 | values.pop("id", None) 49 | query = users.update().where(users.c.id==id).values(**values) 50 | await self.database.execute(query) 51 | return user 52 | 53 | async def get_by_email(self, email: str) -> User: 54 | query = users.select().where(users.c.email==email) 55 | user = await self.database.fetch_one(query) 56 | if user is None: 57 | return None 58 | return User.parse_obj(user) 59 | -------------------------------------------------------------------------------- /repositories/jobs.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | import datetime 3 | from models.jobs import Job, JobIn 4 | from db.jobs import jobs 5 | from .base import BaseRepository 6 | 7 | class JobRepository(BaseRepository): 8 | 9 | async def create(self, user_id: int, j: JobIn) -> Job: 10 | job = Job( 11 | id=0, 12 | user_id=user_id, 13 | created_at=datetime.datetime.utcnow(), 14 | updated_at=datetime.datetime.utcnow(), 15 | title=j.title, 16 | description=j.description, 17 | salary_from=j.salary_from, 18 | salary_to=j.salary_to, 19 | is_active=j.is_active, 20 | ) 21 | values = {**job.dict()} 22 | values.pop("id", None) 23 | query = jobs.insert().values(**values) 24 | job.id = await self.database.execute(query=query) 25 | return job 26 | 27 | async def update(self, id: int, user_id: int, j: JobIn) -> Job: 28 | job = Job( 29 | id=id, 30 | user_id=user_id, 31 | created_at=datetime.datetime.utcnow(), 32 | updated_at=datetime.datetime.utcnow(), 33 | title=j.title, 34 | description=j.description, 35 | salary_from=j.salary_from, 36 | salary_to=j.salary_to, 37 | is_active=j.is_active, 38 | ) 39 | values = {**job.dict()} 40 | values.pop("id", None) 41 | values.pop("created_at", None) 42 | query = jobs.update().where(jobs.c.id==id).values(**values) 43 | await self.database.execute(query=query) 44 | return job 45 | 46 | async def get_all(self, limit: int = 100, skip: int = 0) -> List[Job]: 47 | query = jobs.select().limit(limit).offset(skip) 48 | return await self.database.fetch_all(query=query) 49 | 50 | async def delete(self, id: int): 51 | query = jobs.delete().where(jobs.c.id==id) 52 | return await self.database.execute(query=query) 53 | 54 | async def get_by_id(self, id: int) -> Optional[Job]: 55 | query = jobs.select().where(jobs.c.id==id) 56 | job = await self.database.fetch_one(query=query) 57 | if job is None: 58 | return None 59 | return Job.parse_obj(job) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | data 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "b3a0918e7e2776bfb740315b1474b1ba56ad30c841bbd91153a4ca88128823a2" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "asyncpg": { 20 | "hashes": [ 21 | "sha256:09badce47a4645cfe523cc8a182bd047d5d62af0caaea77935e6a3c9e77dc364", 22 | "sha256:22d161618b59e4b56fb2a5cc956aa9eeb336d07cae924a5b90c9aa1c2d137f15", 23 | "sha256:28584783dd0d21b2a0db3bfe54fb12f21425a4cc015e4419083ea99e6de0de9b", 24 | "sha256:308b8ba32c42ea1ed84c034320678ec307296bb4faf3fbbeb9f9e20b46db99a5", 25 | "sha256:3ade59cef35bffae6dbc6f5f3ef56e1d53c67f0a7adc3cc4c714f07568d2d717", 26 | "sha256:4421407b07b4e22291a226d9de0bf6f3ea8158aa1c12d83bfedbf5c22e13cd55", 27 | "sha256:53cb2a0eb326f61e34ef4da2db01d87ce9c0ebe396f65a295829df334e31863f", 28 | "sha256:615c7e3adb46e1f2e3aff45e4ee9401b4f24f9f7153e5530a0753369be72a5c6", 29 | "sha256:68f7981f65317a5d5f497ec76919b488dbe0e838f8b924e7517a680bdca0f308", 30 | "sha256:6b7807bfedd24dd15cfb2c17c60977ce01410615ecc285268b5144a944ec97ff", 31 | "sha256:7e51d1a012b779e0ebf0195f80d004f65d3c60cc06f0fa1cef9d3e536262abbd", 32 | "sha256:7ee29c4707eb8fb3d3a0348ac4495e06f4afaca3ee38c3bebedc9c8b239125ff", 33 | "sha256:823eca36108bd64a8600efe7bbf1230aa00f2defa3be42852f3b61ab40cf1226", 34 | "sha256:8587e206d78e739ca83a40c9982e03b28f8904c95a54dc782da99e86cf768f73", 35 | "sha256:888593b6688faa7ec1c97ff7f2ca3b5a5b8abb15478fe2a13c5012b607a28737", 36 | "sha256:915cebc8a7693c8a5e89804fa106678dbedcc50d0270ebab0b75f16e668bd59b", 37 | "sha256:a4c1feb285ec3807ecd5b54ab718a3d065bb55c93ebaf800670eadde31484be8", 38 | "sha256:aa2e0cb14c01a2f58caeeca7196681b30aa22dd22c82845560b401df5e98e171", 39 | "sha256:b1b10916c006e5c2c0dcd5dadeb38cbf61ecd20d66c50164e82f31c22c7e329d", 40 | "sha256:dddf4d4c5e781310a36529c3c87c1746837c2d2c7ec0f2ec4e4f06450d83c50a", 41 | "sha256:dfd491e9865e64a3e91f1587b1d88d71dde1cfb850429253a73d4d44b98c3a0f", 42 | "sha256:e7bfb9269aeb11d78d50accf1be46823683ced99209b7199e307cdf7da849522", 43 | "sha256:ea26604932719b3612541e606508d9d604211f56a65806ccf8c92c64104f4f8a", 44 | "sha256:ecd5232cf64f58caac3b85103f1223fdf20e9eb43bfa053c56ef9e5dd76ab099", 45 | "sha256:f2d1aa890ffd1ad062a38b7ff7488764b3da4b0a24e0c83d7bbb1d1a6609df15" 46 | ], 47 | "version": "==0.21.0" 48 | }, 49 | "bcrypt": { 50 | "hashes": [ 51 | "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", 52 | "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", 53 | "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", 54 | "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", 55 | "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", 56 | "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", 57 | "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" 58 | ], 59 | "index": "pypi", 60 | "version": "==3.2.0" 61 | }, 62 | "cffi": { 63 | "hashes": [ 64 | "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", 65 | "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", 66 | "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", 67 | "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", 68 | "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", 69 | "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", 70 | "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", 71 | "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", 72 | "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", 73 | "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", 74 | "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", 75 | "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", 76 | "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", 77 | "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", 78 | "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", 79 | "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", 80 | "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", 81 | "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", 82 | "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", 83 | "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", 84 | "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", 85 | "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", 86 | "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", 87 | "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", 88 | "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", 89 | "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", 90 | "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", 91 | "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", 92 | "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", 93 | "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", 94 | "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", 95 | "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", 96 | "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", 97 | "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", 98 | "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", 99 | "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" 100 | ], 101 | "version": "==1.14.4" 102 | }, 103 | "click": { 104 | "hashes": [ 105 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 106 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 107 | ], 108 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 109 | "version": "==7.1.2" 110 | }, 111 | "databases": { 112 | "extras": [ 113 | "postgresql" 114 | ], 115 | "hashes": [ 116 | "sha256:799febb8fc0ad1e9ac47b5510b91e971d35be205aa99b9a00b3811b4cb5e5254", 117 | "sha256:853c7fa9a0d9b8af8d58cfa15aae00ec0a4fa73b31df4331192308e00c5b6345" 118 | ], 119 | "index": "pypi", 120 | "version": "==0.4.1" 121 | }, 122 | "dnspython": { 123 | "hashes": [ 124 | "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", 125 | "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" 126 | ], 127 | "markers": "python_version >= '3.6'", 128 | "version": "==2.1.0" 129 | }, 130 | "ecdsa": { 131 | "hashes": [ 132 | "sha256:64c613005f13efec6541bb0a33290d0d03c27abab5f15fbab20fb0ee162bdd8e", 133 | "sha256:e108a5fe92c67639abae3260e43561af914e7fd0d27bae6d2ec1312ae7934dfe" 134 | ], 135 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 136 | "version": "==0.14.1" 137 | }, 138 | "email-validator": { 139 | "hashes": [ 140 | "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f", 141 | "sha256:1a13bd6050d1db4475f13e444e169b6fe872434922d38968c67cea9568cce2f0" 142 | ], 143 | "version": "==1.1.2" 144 | }, 145 | "fastapi": { 146 | "hashes": [ 147 | "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb", 148 | "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e" 149 | ], 150 | "index": "pypi", 151 | "version": "==0.63.0" 152 | }, 153 | "h11": { 154 | "hashes": [ 155 | "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", 156 | "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" 157 | ], 158 | "markers": "python_version >= '3.6'", 159 | "version": "==0.12.0" 160 | }, 161 | "idna": { 162 | "hashes": [ 163 | "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", 164 | "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" 165 | ], 166 | "markers": "python_version >= '3.4'", 167 | "version": "==3.1" 168 | }, 169 | "jose": { 170 | "hashes": [ 171 | "sha256:8436c3617cd94e1ba97828fbb1ce27c129f66c78fb855b4bb47e122b5f345fba" 172 | ], 173 | "index": "pypi", 174 | "version": "==1.0.0" 175 | }, 176 | "passlib": { 177 | "hashes": [ 178 | "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", 179 | "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04" 180 | ], 181 | "index": "pypi", 182 | "version": "==1.7.4" 183 | }, 184 | "psycopg2-binary": { 185 | "hashes": [ 186 | "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", 187 | "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", 188 | "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", 189 | "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", 190 | "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", 191 | "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", 192 | "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", 193 | "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", 194 | "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", 195 | "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", 196 | "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", 197 | "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", 198 | "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", 199 | "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", 200 | "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", 201 | "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", 202 | "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", 203 | "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", 204 | "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", 205 | "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", 206 | "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", 207 | "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", 208 | "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", 209 | "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", 210 | "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", 211 | "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", 212 | "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", 213 | "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", 214 | "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", 215 | "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", 216 | "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", 217 | "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", 218 | "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", 219 | "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", 220 | "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" 221 | ], 222 | "index": "pypi", 223 | "version": "==2.8.6" 224 | }, 225 | "pyasn1": { 226 | "hashes": [ 227 | "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", 228 | "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", 229 | "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", 230 | "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", 231 | "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", 232 | "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", 233 | "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", 234 | "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", 235 | "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", 236 | "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", 237 | "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", 238 | "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", 239 | "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" 240 | ], 241 | "version": "==0.4.8" 242 | }, 243 | "pycparser": { 244 | "hashes": [ 245 | "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", 246 | "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" 247 | ], 248 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 249 | "version": "==2.20" 250 | }, 251 | "pydantic": { 252 | "extras": [ 253 | "email" 254 | ], 255 | "hashes": [ 256 | "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f", 257 | "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef", 258 | "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9", 259 | "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec", 260 | "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229", 261 | "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af", 262 | "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e", 263 | "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f", 264 | "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1", 265 | "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2", 266 | "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b", 267 | "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009", 268 | "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c", 269 | "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd", 270 | "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e", 271 | "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127", 272 | "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23", 273 | "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef", 274 | "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730", 275 | "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608", 276 | "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e", 277 | "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95" 278 | ], 279 | "index": "pypi", 280 | "version": "==1.7.3" 281 | }, 282 | "python-jose": { 283 | "hashes": [ 284 | "sha256:4e4192402e100b5fb09de5a8ea6bcc39c36ad4526341c123d401e2561720335b", 285 | "sha256:67d7dfff599df676b04a996520d9be90d6cdb7e6dd10b4c7cacc0c3e2e92f2be" 286 | ], 287 | "index": "pypi", 288 | "version": "==3.2.0" 289 | }, 290 | "rsa": { 291 | "hashes": [ 292 | "sha256:69805d6b69f56eb05b62daea3a7dbd7aa44324ad1306445e05da8060232d00f4", 293 | "sha256:a8774e55b59fd9fc893b0d05e9bfc6f47081f46ff5b46f39ccf24631b7be356b" 294 | ], 295 | "markers": "python_version >= '3.5' and python_version < '4'", 296 | "version": "==4.7" 297 | }, 298 | "six": { 299 | "hashes": [ 300 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 301 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 302 | ], 303 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 304 | "version": "==1.15.0" 305 | }, 306 | "sqlalchemy": { 307 | "hashes": [ 308 | "sha256:04f995fcbf54e46cddeb4f75ce9dfc17075d6ae04ac23b2bacb44b3bc6f6bf11", 309 | "sha256:0c6406a78a714a540d980a680b86654feadb81c8d0eecb59f3d6c554a4c69f19", 310 | "sha256:0c72b90988be749e04eff0342dcc98c18a14461eb4b2ad59d611b57b31120f90", 311 | "sha256:108580808803c7732f34798eb4a329d45b04c562ed83ee90f09f6a184a42b766", 312 | "sha256:1418f5e71d6081aa1095a1d6b567a562d2761996710bdce9b6e6ba20a03d0864", 313 | "sha256:17610d573e698bf395afbbff946544fbce7c5f4ee77b5bcb1f821b36345fae7a", 314 | "sha256:216ba5b4299c95ed179b58f298bda885a476b16288ab7243e89f29f6aeced7e0", 315 | "sha256:2ff132a379838b1abf83c065be54cef32b47c987aedd06b82fc76476c85225eb", 316 | "sha256:314f5042c0b047438e19401d5f29757a511cfc2f0c40d28047ca0e4c95eabb5b", 317 | "sha256:318b5b727e00662e5fc4b4cd2bf58a5116d7c1b4dd56ffaa7d68f43458a8d1ed", 318 | "sha256:3ab5b44a07b8c562c6dcb7433c6a6c6e03266d19d64f87b3333eda34e3b9936b", 319 | "sha256:426ece890153ccc52cc5151a1a0ed540a5a7825414139bb4c95a868d8da54a52", 320 | "sha256:491fe48adc07d13e020a8b07ef82eefc227003a046809c121bea81d3dbf1832d", 321 | "sha256:4a84c7c7658dd22a33dab2e2aa2d17c18cb004a42388246f2e87cb4085ef2811", 322 | "sha256:54da615e5b92c339e339fe8536cce99fe823b6ed505d4ea344852aefa1c205fb", 323 | "sha256:5a7f224cdb7233182cec2a45d4c633951268d6a9bcedac37abbf79dd07012aea", 324 | "sha256:61628715931f4962e0cdb2a7c87ff39eea320d2aa96bd471a3c293d146f90394", 325 | "sha256:62285607a5264d1f91590abd874d6a498e229d5840669bd7d9f654cfaa599bd0", 326 | "sha256:62fb881ba51dbacba9af9b779211cf9acff3442d4f2993142015b22b3cd1f92a", 327 | "sha256:68428818cf80c60dc04aa0f38da20ad39b28aba4d4d199f949e7d6e04444ea86", 328 | "sha256:6aaa13ee40c4552d5f3a59f543f0db6e31712cc4009ec7385407be4627259d41", 329 | "sha256:70121f0ae48b25ef3e56e477b88cd0b0af0e1f3a53b5554071aa6a93ef378a03", 330 | "sha256:715b34578cc740b743361f7c3e5f584b04b0f1344f45afc4e87fbac4802eb0a0", 331 | "sha256:758fc8c4d6c0336e617f9f6919f9daea3ab6bb9b07005eda9a1a682e24a6cacc", 332 | "sha256:7d4b8de6bb0bc736161cb0bbd95366b11b3eb24dd6b814a143d8375e75af9990", 333 | "sha256:81d8d099a49f83111cce55ec03cc87eef45eec0d90f9842b4fc674f860b857b0", 334 | "sha256:888d5b4b5aeed0d3449de93ea80173653e939e916cc95fe8527079e50235c1d2", 335 | "sha256:95bde07d19c146d608bccb9b16e144ec8f139bcfe7fd72331858698a71c9b4f5", 336 | "sha256:9bf572e4f5aa23f88dd902f10bb103cb5979022a38eec684bfa6d61851173fec", 337 | "sha256:bab5a1e15b9466a25c96cda19139f3beb3e669794373b9ce28c4cf158c6e841d", 338 | "sha256:bd4b1af45fd322dcd1fb2a9195b4f93f570d1a5902a842e3e6051385fac88f9c", 339 | "sha256:bde677047305fe76c7ee3e4492b545e0018918e44141cc154fe39e124e433991", 340 | "sha256:c389d7cc2b821853fb018c85457da3e7941db64f4387720a329bc7ff06a27963", 341 | "sha256:d055ff750fcab69ca4e57b656d9c6ad33682e9b8d564f2fbe667ab95c63591b0", 342 | "sha256:d53f59744b01f1440a1b0973ed2c3a7de204135c593299ee997828aad5191693", 343 | "sha256:f115150cc4361dd46153302a640c7fa1804ac207f9cc356228248e351a8b4676", 344 | "sha256:f1e88b30da8163215eab643962ae9d9252e47b4ea53404f2c4f10f24e70ddc62", 345 | "sha256:f8191fef303025879e6c3548ecd8a95aafc0728c764ab72ec51a0bdf0c91a341" 346 | ], 347 | "index": "pypi", 348 | "version": "==1.3.22" 349 | }, 350 | "starlette": { 351 | "hashes": [ 352 | "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9", 353 | "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc" 354 | ], 355 | "markers": "python_version >= '3.6'", 356 | "version": "==0.13.6" 357 | }, 358 | "uvicorn": { 359 | "hashes": [ 360 | "sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c", 361 | "sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355" 362 | ], 363 | "index": "pypi", 364 | "version": "==0.13.3" 365 | } 366 | }, 367 | "develop": {} 368 | } 369 | --------------------------------------------------------------------------------