Реализация crud и его добавление в RESTfull
This commit is contained in:
@ -0,0 +1,9 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from core.settings import settings
|
||||||
|
from .api_v1 import router as v1_router
|
||||||
|
router = APIRouter(
|
||||||
|
prefix= settings.api.prefix,
|
||||||
|
tags=["Основной роутер"]
|
||||||
|
)
|
||||||
|
|
||||||
|
router.include_router(v1_router)
|
@ -1,7 +0,0 @@
|
|||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
@router.get("/hello")
|
|
||||||
async def say_world():
|
|
||||||
return {"msg":"Hello WORLD!"}
|
|
13
backend-app/api/api_v1/__init__.py
Normal file
13
backend-app/api/api_v1/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from core.settings import settings
|
||||||
|
from .login import router as login_router
|
||||||
|
from .user import router as user_router
|
||||||
|
router = APIRouter(
|
||||||
|
prefix=settings.api.v1.prefix,
|
||||||
|
)
|
||||||
|
router.include_router(
|
||||||
|
user_router
|
||||||
|
)
|
||||||
|
router.include_router(
|
||||||
|
login_router
|
||||||
|
)
|
53
backend-app/api/api_v1/login.py
Normal file
53
backend-app/api/api_v1/login.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from core.settings import settings
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from typing import Annotated
|
||||||
|
from core.models import db_helper
|
||||||
|
from repo.schemas import LoginCreate,LoginRead, LoginUpdate
|
||||||
|
import repo.crud.login as crud
|
||||||
|
router = APIRouter(
|
||||||
|
prefix=settings.api.v1.login,
|
||||||
|
tags=["Login"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[LoginRead])
|
||||||
|
async def get_all_logins(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)]
|
||||||
|
):
|
||||||
|
logins = await crud.get_all_logins(session=session)
|
||||||
|
return logins
|
||||||
|
|
||||||
|
@router.get("/{login_id}", response_model=LoginRead)
|
||||||
|
async def get_login(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)],
|
||||||
|
login_id: int
|
||||||
|
):
|
||||||
|
login = await crud.get_login(
|
||||||
|
session=session,
|
||||||
|
login_id=login_id
|
||||||
|
)
|
||||||
|
return login
|
||||||
|
|
||||||
|
@router.post("/", response_model=LoginRead)
|
||||||
|
async def create_login(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)],
|
||||||
|
login_create: LoginCreate
|
||||||
|
):
|
||||||
|
login = await crud.create_login(
|
||||||
|
session=session,
|
||||||
|
login_create=login_create
|
||||||
|
)
|
||||||
|
return login
|
||||||
|
|
||||||
|
@router.patch("/{login_id}", response_model=LoginRead)
|
||||||
|
async def update_login(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)],
|
||||||
|
login_id: int,
|
||||||
|
login_update: LoginUpdate
|
||||||
|
):
|
||||||
|
login = await crud.update_login(
|
||||||
|
session=session,
|
||||||
|
login_id=login_id,
|
||||||
|
login_update=login_update
|
||||||
|
)
|
||||||
|
return login
|
57
backend-app/api/api_v1/user.py
Normal file
57
backend-app/api/api_v1/user.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from core.settings import settings
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from typing import Annotated
|
||||||
|
from repo.schemas import UserRead, UserCreate, UserUpdate
|
||||||
|
from core.models import db_helper
|
||||||
|
|
||||||
|
import repo.crud.user as crud
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix=settings.api.v1.user,
|
||||||
|
tags=["Users"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[UserRead])
|
||||||
|
async def get_all_users(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)]
|
||||||
|
):
|
||||||
|
users = await crud.get_all_users(session=session)
|
||||||
|
return users
|
||||||
|
|
||||||
|
@router.get("/{user_id}", response_model=UserRead)
|
||||||
|
async def get_user(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)],
|
||||||
|
user_id: int
|
||||||
|
):
|
||||||
|
users = await crud.get_user(
|
||||||
|
session=session,
|
||||||
|
user_id=user_id
|
||||||
|
)
|
||||||
|
return users
|
||||||
|
|
||||||
|
@router.post("/", response_model=UserRead)
|
||||||
|
async def create_user(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)],
|
||||||
|
user_create: UserCreate
|
||||||
|
):
|
||||||
|
users = await crud.create_user(
|
||||||
|
session=session,
|
||||||
|
user_create=user_create
|
||||||
|
)
|
||||||
|
return users
|
||||||
|
|
||||||
|
@router.patch("/{user_id}", response_model=UserRead)
|
||||||
|
async def update_user(
|
||||||
|
session: Annotated[AsyncSession, Depends(db_helper.session_getter)],
|
||||||
|
user_id: int,
|
||||||
|
user_update: UserUpdate
|
||||||
|
):
|
||||||
|
users = await crud.update_user(
|
||||||
|
session=session,
|
||||||
|
user_id=user_id,
|
||||||
|
user_update=user_update
|
||||||
|
)
|
||||||
|
return users
|
@ -1,7 +0,0 @@
|
|||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
@router.get("/hello")
|
|
||||||
async def say_hello():
|
|
||||||
return {"msg":"hello"}
|
|
@ -5,6 +5,15 @@ class RunSettings(BaseModel):
|
|||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
port: int = 8000
|
port: int = 8000
|
||||||
|
|
||||||
|
class APIV1Prefix(BaseModel):
|
||||||
|
prefix:str = "/v1"
|
||||||
|
user:str = "/user"
|
||||||
|
login:str = "/login"
|
||||||
|
|
||||||
|
class APIPrefix(BaseModel):
|
||||||
|
prefix:str = "/api"
|
||||||
|
v1: APIV1Prefix = APIV1Prefix()
|
||||||
|
|
||||||
class DatabaseConfig(BaseModel):
|
class DatabaseConfig(BaseModel):
|
||||||
url: str
|
url: str
|
||||||
echo:bool=False
|
echo:bool=False
|
||||||
@ -28,5 +37,6 @@ class Settings(BaseSettings):
|
|||||||
)
|
)
|
||||||
run: RunSettings = RunSettings()
|
run: RunSettings = RunSettings()
|
||||||
db: DatabaseConfig
|
db: DatabaseConfig
|
||||||
|
api: APIPrefix = APIPrefix()
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
@ -2,8 +2,7 @@ import uvicorn
|
|||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.responses import ORJSONResponse
|
from fastapi.responses import ORJSONResponse
|
||||||
from core.settings import settings
|
from core.settings import settings
|
||||||
from api.main_router import router as main_router
|
from api import router
|
||||||
from api.another_router import router as another_router
|
|
||||||
from core.models import db_helper
|
from core.models import db_helper
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
@ -21,15 +20,6 @@ main_app = FastAPI(
|
|||||||
|
|
||||||
|
|
||||||
#api/hello
|
#api/hello
|
||||||
main_app.include_router(
|
main_app.include_router(router)
|
||||||
router=main_router,
|
|
||||||
prefix="/api",
|
|
||||||
tags=["Основной роутер"]
|
|
||||||
)
|
|
||||||
main_app.include_router(
|
|
||||||
router=another_router,
|
|
||||||
prefix="/another",
|
|
||||||
tags=["Побочный роутер"]
|
|
||||||
)
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(main_app, host=settings.run.host, port=settings.run.port)
|
uvicorn.run(main_app, host=settings.run.host, port=settings.run.port)
|
58
backend-app/repo/crud/login.py
Normal file
58
backend-app/repo/crud/login.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from typing import Sequence
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select, delete, update
|
||||||
|
from ..schemas import LoginRead, LoginCreate, LoginUpdate
|
||||||
|
from ..models import LoginModel
|
||||||
|
|
||||||
|
from utils.hashing import get_password_hash
|
||||||
|
|
||||||
|
async def get_all_logins(
|
||||||
|
session: AsyncSession
|
||||||
|
) -> Sequence[LoginModel]:
|
||||||
|
stmt = select(LoginModel).order_by(LoginModel.id)
|
||||||
|
result = await session.scalars(stmt)
|
||||||
|
return result.all()
|
||||||
|
|
||||||
|
async def get_login(
|
||||||
|
session: AsyncSession,
|
||||||
|
login_id: int
|
||||||
|
) -> LoginModel:
|
||||||
|
db_login = await session.get(LoginModel,login_id)
|
||||||
|
if db_login is None:
|
||||||
|
return None
|
||||||
|
return db_login
|
||||||
|
|
||||||
|
async def create_login(
|
||||||
|
session:AsyncSession,
|
||||||
|
login_create: LoginCreate
|
||||||
|
) -> LoginModel:
|
||||||
|
login = LoginModel(**login_create.model_dump())
|
||||||
|
login.password = get_password_hash(login.password)
|
||||||
|
session.add(login)
|
||||||
|
await session.commit()
|
||||||
|
return login
|
||||||
|
|
||||||
|
async def delete_login(
|
||||||
|
session: AsyncSession,
|
||||||
|
login_id: int
|
||||||
|
) -> LoginModel:
|
||||||
|
db_login = await session.get(LoginModel,login_id)
|
||||||
|
if db_login is None:
|
||||||
|
return None
|
||||||
|
stmt = delete(LoginModel).filter(LoginModel.id == login_id)
|
||||||
|
await session.execute(stmt)
|
||||||
|
await session.commit()
|
||||||
|
return db_login.one_or_none()
|
||||||
|
|
||||||
|
async def update_login(
|
||||||
|
session:AsyncSession,
|
||||||
|
login_id: int,
|
||||||
|
login_update: LoginUpdate
|
||||||
|
) -> LoginModel:
|
||||||
|
db_login = await session.get(LoginModel,login_id)
|
||||||
|
if db_login is None:
|
||||||
|
return None
|
||||||
|
for var, value in vars(login_update).items():
|
||||||
|
setattr(db_login, var, value if var != "password" else get_password_hash(value)) if value else None
|
||||||
|
await session.commit()
|
||||||
|
return db_login
|
58
backend-app/repo/crud/user.py
Normal file
58
backend-app/repo/crud/user.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from typing import Sequence
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select, delete, update
|
||||||
|
from ..schemas import UserRead, UserCreate,UserUpdate
|
||||||
|
from ..models import UserModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_users(
|
||||||
|
session: AsyncSession
|
||||||
|
) -> Sequence[UserModel]:
|
||||||
|
stmt = select(UserModel).order_by(UserModel.id)
|
||||||
|
result = await session.scalars(stmt)
|
||||||
|
return result.all()
|
||||||
|
|
||||||
|
async def get_user(
|
||||||
|
session: AsyncSession,
|
||||||
|
user_id: int
|
||||||
|
) -> UserModel:
|
||||||
|
db_user = await session.get(UserModel, user_id)
|
||||||
|
if db_user is None:
|
||||||
|
return None
|
||||||
|
return db_user
|
||||||
|
|
||||||
|
async def create_user(
|
||||||
|
session:AsyncSession,
|
||||||
|
user_create: UserCreate
|
||||||
|
) -> UserModel:
|
||||||
|
user = UserModel(**user_create.model_dump())
|
||||||
|
session.add(user)
|
||||||
|
await session.commit()
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def delete_user(
|
||||||
|
session: AsyncSession,
|
||||||
|
user_id: int
|
||||||
|
) -> UserModel:
|
||||||
|
db_user = await session.get(UserModel, user_id)
|
||||||
|
if db_user is None:
|
||||||
|
return None
|
||||||
|
stmt = delete(UserModel).filter(UserModel.id == user_id)
|
||||||
|
await session.execute(stmt)
|
||||||
|
await session.commit()
|
||||||
|
return db_user.one_or_none()
|
||||||
|
|
||||||
|
async def update_user(
|
||||||
|
session:AsyncSession,
|
||||||
|
user_id: int,
|
||||||
|
user_update: UserUpdate
|
||||||
|
) -> UserModel:
|
||||||
|
db_user = await session.get(UserModel, user_id)
|
||||||
|
if db_user is None:
|
||||||
|
return None
|
||||||
|
for var, value in vars(user_update).items():
|
||||||
|
setattr(db_user, var, value) if value else None
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(db_user)
|
||||||
|
return db_user
|
@ -1,2 +1,33 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, EmailStr
|
||||||
|
from typing import Optional, Sequence
|
||||||
|
|
||||||
|
|
||||||
|
class LoginBase(BaseModel):
|
||||||
|
username:str
|
||||||
|
password:str
|
||||||
|
|
||||||
|
class LoginUpdate(BaseModel):
|
||||||
|
username:Optional[str] = None
|
||||||
|
password:Optional[str] = None
|
||||||
|
|
||||||
|
class LoginCreate(LoginBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class LoginRead(LoginBase):
|
||||||
|
id:int
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
firstname:Optional[str] = None
|
||||||
|
lastname:Optional[str] = None
|
||||||
|
age: Optional[int] = None
|
||||||
|
email: Optional[EmailStr] = None
|
||||||
|
|
||||||
|
class UserBase(UserUpdate):
|
||||||
|
login_id: int
|
||||||
|
|
||||||
|
class UserCreate(UserBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserRead(UserBase):
|
||||||
|
id:int
|
9
backend-app/utils/hashing.py
Normal file
9
backend-app/utils/hashing.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from passlib.context import CryptContext
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
def verify_password(plain_password, hashed_password):
|
||||||
|
return pwd_context.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
|
||||||
|
def get_password_hash(password):
|
||||||
|
return pwd_context.hash(password)
|
59
poetry.lock
generated
59
poetry.lock
generated
@ -64,6 +64,46 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin
|
|||||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||||
trio = ["trio (>=0.23)"]
|
trio = ["trio (>=0.23)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "4.2.0"
|
||||||
|
description = "Modern password hashing for your software and your servers"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"},
|
||||||
|
{file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"},
|
||||||
|
{file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"},
|
||||||
|
{file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"},
|
||||||
|
{file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"},
|
||||||
|
{file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"},
|
||||||
|
{file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"},
|
||||||
|
{file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||||
|
typecheck = ["mypy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "24.4.2"
|
version = "24.4.2"
|
||||||
@ -629,6 +669,23 @@ files = [
|
|||||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "passlib"
|
||||||
|
version = "1.7.4"
|
||||||
|
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||||
|
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||||
|
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||||
|
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
||||||
|
totp = ["cryptography"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@ -1353,4 +1410,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "c1080a2638fb671a736b742ed0b4f44863e6cd4d3f36153e4f5f20fed11cf559"
|
content-hash = "ad4b72b47db69c4361a78a797531725b35c4810580a1f160bcf8f57872f61ffd"
|
||||||
|
@ -10,3 +10,5 @@ aioodbc = "^0.5.0"
|
|||||||
orjson = "^3.10.6"
|
orjson = "^3.10.6"
|
||||||
alembic = "^1.13.2"
|
alembic = "^1.13.2"
|
||||||
black = "^24.4.2"
|
black = "^24.4.2"
|
||||||
|
passlib = "^1.7.4"
|
||||||
|
bcrypt = "^4.2.0"
|
||||||
|
Reference in New Issue
Block a user