Создание и первичная инициализация базы даных успешно завершена. Наполнение демо-данными прошло без ошибок
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
DB_HOST=10.0.13.3
|
DB_HOST=10.0.13.3
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_NAME=tools_stock
|
DB_NAME=toolbox
|
||||||
DB_USER=tools_stock
|
DB_USER=toolbox
|
||||||
DB_PASS=z7kWLkSKa6
|
DB_PASS=z7kWLkSKa6
|
||||||
Binary file not shown.
+1
-1
@@ -108,7 +108,7 @@ class CRUD:
|
|||||||
item = await db.execute(query)
|
item = await db.execute(query)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
logger.info("Запись обновлена")
|
logger.info("Запись обновлена")
|
||||||
return item
|
return await db.get(db_data, id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await db.rollback()
|
await db.rollback()
|
||||||
logger.error(f"Ошибка обновления: {str(e)}", exc_info=True)
|
logger.error(f"Ошибка обновления: {str(e)}", exc_info=True)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,45 +0,0 @@
|
|||||||
from .user import *
|
|
||||||
from .access import *
|
|
||||||
from .toolbox import *
|
|
||||||
from .categories import *
|
|
||||||
from .stock import *
|
|
||||||
from .toolkit import *
|
|
||||||
from .records import *
|
|
||||||
from .actions import *
|
|
||||||
|
|
||||||
|
|
||||||
class InitializeDatabase:
|
|
||||||
def __init__(self):
|
|
||||||
self.userHandler = UserHandler()
|
|
||||||
self.accessHandler = AccessLevelHandler()
|
|
||||||
self.toolboxHandler = ToolboxHandler()
|
|
||||||
self.categoryHandler = CategoryHandler()
|
|
||||||
self.stockHandler = StockHandler()
|
|
||||||
self.toolkitHandler = ToolkitHandler()
|
|
||||||
self.stocksRecordHandler = StocksRecordsHandler()
|
|
||||||
self.servicesRecordHandler = ServiceRecordsHandler()
|
|
||||||
self.actionsHandler = StocksActions()
|
|
||||||
|
|
||||||
async def initialize(self):
|
|
||||||
await self.accessHandler.initialize()
|
|
||||||
await self.userHandler.initialize()
|
|
||||||
await self.toolboxHandler.initialize()
|
|
||||||
await self.categoryHandler.initialize()
|
|
||||||
await self.toolkitHandler.initialize()
|
|
||||||
await self.actionsHandler.initialize()
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"UserHandler",
|
|
||||||
"AccessLevelHandler",
|
|
||||||
"ToolboxHandler",
|
|
||||||
"CategoryHandler",
|
|
||||||
"StockHandler",
|
|
||||||
"ToolkitHandler",
|
|
||||||
"StocksRecords",
|
|
||||||
"ServicesRecords",
|
|
||||||
"StocksRecordsHandler",
|
|
||||||
"ServiceRecordsHandler",
|
|
||||||
"StocksActions",
|
|
||||||
"InitializeDatabase",
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,13 +1,13 @@
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from utils import logger
|
from utils import logger
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
from db.schemas import AccessLevel
|
from db.schemas.access import AccessLevel
|
||||||
from db.handlers import ServiceRecordsHandler
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
|
|
||||||
|
|
||||||
class AccessLevelHandler:
|
class AccessLevelHandler:
|
||||||
async def add(**kwargs):
|
async def add(newData):
|
||||||
title = kwargs.get("title", None)
|
title = newData.get("title", None)
|
||||||
if not title:
|
if not title:
|
||||||
logger.error("Не указано название уровня доступа")
|
logger.error("Не указано название уровня доступа")
|
||||||
return {}
|
return {}
|
||||||
@@ -17,8 +17,8 @@ class AccessLevelHandler:
|
|||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
logger.info(f"Создание уровня доступа {title}")
|
logger.info(f"Создание уровня доступа {title}")
|
||||||
user_id = kwargs.pop("user_id", None)
|
user_id = newData.pop("user_id", None)
|
||||||
accessData = await AccessLevel(**kwargs).save()
|
accessData = await AccessLevel(**newData).save()
|
||||||
await ServiceRecordsHandler.add(
|
await ServiceRecordsHandler.add(
|
||||||
user_id, {"Добавлен уровень доступа": accessData.toDict()}
|
user_id, {"Добавлен уровень доступа": accessData.toDict()}
|
||||||
)
|
)
|
||||||
@@ -85,7 +85,7 @@ class AccessLevelHandler:
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize():
|
||||||
baseAcessLevels = {
|
baseAcessLevels = {
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Администратор",
|
"title": "Администратор",
|
||||||
@@ -149,7 +149,7 @@ class AccessLevelHandler:
|
|||||||
logger.info("Инициализация уровней доступа")
|
logger.info("Инициализация уровней доступа")
|
||||||
|
|
||||||
for accessLevel in baseAcessLevels.values():
|
for accessLevel in baseAcessLevels.values():
|
||||||
await self.add(**accessLevel)
|
await AccessLevelHandler.add(accessLevel)
|
||||||
|
|
||||||
logger.info("Уровни доступа успешно инициализированы")
|
logger.info("Уровни доступа успешно инициализированы")
|
||||||
return
|
return
|
||||||
|
|||||||
+285
-38
@@ -1,4 +1,11 @@
|
|||||||
from db.handlers import StockHandler, StocksRecordsHandler
|
import random
|
||||||
|
from db.handlers.stock import StockHandler
|
||||||
|
from db.handlers.toolbox import ToolboxHandler
|
||||||
|
from db.handlers.toolkit import ToolkitHandler
|
||||||
|
from db.handlers.user import UserHandler
|
||||||
|
from db.handlers.access import AccessLevelHandler
|
||||||
|
from db.handlers.records import StocksRecordsHandler
|
||||||
|
|
||||||
from utils import logger
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +39,7 @@ class StocksActions:
|
|||||||
recorded = await StocksRecordsHandler.add(
|
recorded = await StocksRecordsHandler.add(
|
||||||
action="Оприходование",
|
action="Оприходование",
|
||||||
source_stock_id=None,
|
source_stock_id=None,
|
||||||
target_stock_id=newStocks.id,
|
target_stock_id=newStocks.get("id"),
|
||||||
source_toolbox_id=None,
|
source_toolbox_id=None,
|
||||||
target_toolbox_id=toolbox_id,
|
target_toolbox_id=toolbox_id,
|
||||||
toolkit_id=toolkit_id,
|
toolkit_id=toolkit_id,
|
||||||
@@ -46,7 +53,7 @@ class StocksActions:
|
|||||||
f"Оприходование инструмента {toolkit_id} на складе {toolbox_id} прошло {'успешно' if recorded else 'не успешно'}"
|
f"Оприходование инструмента {toolkit_id} на складе {toolbox_id} прошло {'успешно' if recorded else 'не успешно'}"
|
||||||
)
|
)
|
||||||
if recorded:
|
if recorded:
|
||||||
accepted = await StocksRecordsHandler.accept(
|
accepted = await StocksRecordsHandler.decide(
|
||||||
recorded, user_id, None, quantity, price
|
recorded, user_id, None, quantity, price
|
||||||
)
|
)
|
||||||
if not accepted:
|
if not accepted:
|
||||||
@@ -84,7 +91,7 @@ class StocksActions:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
totalTakeQuantity = 0
|
totalTakeQuantity = 0
|
||||||
writeDownList = []
|
movementsList = []
|
||||||
|
|
||||||
for stock in availability:
|
for stock in availability:
|
||||||
if quantity == totalTakeQuantity:
|
if quantity == totalTakeQuantity:
|
||||||
@@ -108,14 +115,15 @@ class StocksActions:
|
|||||||
f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} успешно завершена"
|
f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} успешно завершена"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not target_toolbox_id:
|
movementsList.append(
|
||||||
writeDownList.append(
|
|
||||||
{
|
{
|
||||||
"id": sourceEdit["id"],
|
"id": sourceEdit["id"],
|
||||||
"quantity": takeQuantity,
|
"quantity": takeQuantity,
|
||||||
"price": stock["price"],
|
"price": stock["price"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not target_toolbox_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
existing = await StockHandler.getByToolboxIdAndToolkitIdAndQPrice(
|
existing = await StockHandler.getByToolboxIdAndToolkitIdAndQPrice(
|
||||||
@@ -168,7 +176,6 @@ class StocksActions:
|
|||||||
reason=reason,
|
reason=reason,
|
||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
price=stock["price"],
|
price=stock["price"],
|
||||||
target_placement=target_placement,
|
|
||||||
)
|
)
|
||||||
if not recorded:
|
if not recorded:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -179,7 +186,7 @@ class StocksActions:
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} прошло успешно"
|
f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} прошло успешно"
|
||||||
)
|
)
|
||||||
return True if target_toolbox_id else writeDownList
|
return movementsList
|
||||||
|
|
||||||
async def movingRequest(
|
async def movingRequest(
|
||||||
action: str,
|
action: str,
|
||||||
@@ -213,29 +220,50 @@ class StocksActions:
|
|||||||
)
|
)
|
||||||
return recorded
|
return recorded
|
||||||
|
|
||||||
async def movingAcceptance(self, record_id: int, user_id: int):
|
async def movingDecision(record_id: int, user_id: int, accepted: bool = True):
|
||||||
logger.info(f"Принятие записи о движении инструмента {record_id} ...")
|
logger.info(
|
||||||
writeDownARecord = await StocksRecordsHandler.getById(record_id, True)
|
f"{'Принятие' if accepted else 'Отклонение'} записи о движении инструмента {record_id} ..."
|
||||||
if not writeDownARecord:
|
)
|
||||||
|
movingRecord = await StocksRecordsHandler.getById(record_id, True)
|
||||||
|
if not movingRecord:
|
||||||
logger.error(f"Запись {record_id} не найдена")
|
logger.error(f"Запись {record_id} не найдена")
|
||||||
return False
|
return False
|
||||||
if writeDownARecord.accepted_at is not None:
|
if movingRecord.accepted is not None:
|
||||||
logger.error(f"Запись {record_id} уже была принята")
|
logger.error(
|
||||||
|
f"Запись {record_id} уже была {'принята' if movingRecord.accepted else 'отклонена'}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
stocksMovements = await self.moving(
|
if not accepted:
|
||||||
action=writeDownARecord.action,
|
return await StocksRecordsHandler.decide(
|
||||||
source_toolbox_id=writeDownARecord.source_toolbox_id,
|
record_id,
|
||||||
target_toolbox_id=writeDownARecord.target_toolbox_id,
|
user_id,
|
||||||
toolkit_id=writeDownARecord.toolkit_id,
|
movingRecord.source_stock_id,
|
||||||
quantity=writeDownARecord.quantity,
|
movingRecord.quantity,
|
||||||
|
movingRecord.price,
|
||||||
|
accepted,
|
||||||
|
)
|
||||||
|
|
||||||
|
target_toolbox_id = (
|
||||||
|
movingRecord.target_toolbox_id
|
||||||
|
if movingRecord.action != "Списание"
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
logger.warning(f"{target_toolbox_id = }, {movingRecord.action = }")
|
||||||
|
|
||||||
|
stocksMovements = await StocksActions.moving(
|
||||||
|
action=movingRecord.action,
|
||||||
|
source_toolbox_id=movingRecord.source_toolbox_id,
|
||||||
|
target_toolbox_id=target_toolbox_id,
|
||||||
|
toolkit_id=movingRecord.toolkit_id,
|
||||||
|
quantity=movingRecord.quantity,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
reason=writeDownARecord.reason,
|
reason=movingRecord.reason,
|
||||||
)
|
)
|
||||||
if not stocksMovements:
|
if not stocksMovements:
|
||||||
logger.error(f"Ошибка при {writeDownARecord.action} инструмента")
|
logger.error(f"Ошибка при {movingRecord.action} инструмента")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
accept = await StocksRecordsHandler.accept(
|
accept = await StocksRecordsHandler.decide(
|
||||||
record_id,
|
record_id,
|
||||||
user_id,
|
user_id,
|
||||||
stocksMovements[0].get("id"),
|
stocksMovements[0].get("id"),
|
||||||
@@ -250,23 +278,23 @@ class StocksActions:
|
|||||||
if len(stocksMovements) > 1:
|
if len(stocksMovements) > 1:
|
||||||
for stock in stocksMovements[1:]:
|
for stock in stocksMovements[1:]:
|
||||||
recorded = await StocksRecordsHandler.add(
|
recorded = await StocksRecordsHandler.add(
|
||||||
action=writeDownARecord.action,
|
action=movingRecord.action,
|
||||||
source_stock_id=stock.get("id"),
|
source_stock_id=stock.get("id"),
|
||||||
target_stock_id=None,
|
target_stock_id=None,
|
||||||
source_toolbox_id=writeDownARecord.source_toolbox_id,
|
source_toolbox_id=movingRecord.source_toolbox_id,
|
||||||
target_toolbox_id=writeDownARecord.target_toolbox_id,
|
target_toolbox_id=target_toolbox_id,
|
||||||
toolkit_id=writeDownARecord.toolkit_id,
|
toolkit_id=movingRecord.toolkit_id,
|
||||||
init_user_id=writeDownARecord.init_user_id,
|
init_user_id=movingRecord.init_user_id,
|
||||||
reason=writeDownARecord.reason,
|
reason=movingRecord.reason,
|
||||||
quantity=stock.get("quantity"),
|
quantity=stock.get("quantity"),
|
||||||
price=stock.get("price"),
|
price=stock.get("price"),
|
||||||
return_record_id=True,
|
return_record_id=True,
|
||||||
)
|
)
|
||||||
if not recorded:
|
if not recorded:
|
||||||
return False
|
return False
|
||||||
accept = await StocksRecordsHandler.accept(
|
accept = await StocksRecordsHandler.decide(
|
||||||
record_id=recorded,
|
record_id=recorded,
|
||||||
accept_user_id=user_id,
|
decision_user_id=user_id,
|
||||||
source_stock_id=stock.get("id"),
|
source_stock_id=stock.get("id"),
|
||||||
quantity=stock.get("quantity"),
|
quantity=stock.get("quantity"),
|
||||||
price=stock.get("price"),
|
price=stock.get("price"),
|
||||||
@@ -276,12 +304,11 @@ class StocksActions:
|
|||||||
totalRecordsIds.append(recorded)
|
totalRecordsIds.append(recorded)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Записи {', '.join(map(str, totalRecordsIds))} о {writeDownARecord.action} инструмента успешно приняты {user_id}"
|
f"Записи {', '.join(map(str, totalRecordsIds))} о {movingRecord.action} инструмента успешно приняты {user_id}"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def takeToolkit(
|
async def takeToolkit(
|
||||||
self,
|
|
||||||
source_toolbox_id: int,
|
source_toolbox_id: int,
|
||||||
target_toolbox_id: int,
|
target_toolbox_id: int,
|
||||||
toolkit_id: int,
|
toolkit_id: int,
|
||||||
@@ -293,7 +320,7 @@ class StocksActions:
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Формирование запроса на получение инструмента {toolkit_id} на склад {target_toolbox_id} со склада {source_toolbox_id} в количестве {quantity} ..."
|
f"Формирование запроса на получение инструмента {toolkit_id} на склад {target_toolbox_id} со склада {source_toolbox_id} в количестве {quantity} ..."
|
||||||
)
|
)
|
||||||
takeRequest = await self.movingRequest(
|
takeRequest = await StocksActions.movingRequest(
|
||||||
action="Получение",
|
action="Получение",
|
||||||
source_toolbox_id=source_toolbox_id,
|
source_toolbox_id=source_toolbox_id,
|
||||||
target_toolbox_id=target_toolbox_id,
|
target_toolbox_id=target_toolbox_id,
|
||||||
@@ -315,7 +342,7 @@ class StocksActions:
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Принятие запроса {takeRequest} на получение инструмента {toolkit_id} ..."
|
f"Принятие запроса {takeRequest} на получение инструмента {toolkit_id} ..."
|
||||||
)
|
)
|
||||||
accepted = await self.movingAcceptance(takeRequest, user_id)
|
accepted = await StocksActions.movingDecision(takeRequest, user_id)
|
||||||
if not accepted:
|
if not accepted:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Принятие запроса {takeRequest} на получение инструмента {toolkit_id} не удалось"
|
f"Принятие запроса {takeRequest} на получение инструмента {toolkit_id} не удалось"
|
||||||
@@ -327,6 +354,226 @@ class StocksActions:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def initialize():
|
async def initialize():
|
||||||
# TODO прописать наполнение общих складов, получение на личные, возвраты и списания.
|
|
||||||
# Не все запросы на возвраты и списания нужно принять автоматически, нужно оставить несколько для демонстрации
|
toolboxes = await ToolboxHandler.getAll()
|
||||||
pass
|
toolboxesDict = {
|
||||||
|
toolbox.get("title"): toolbox.get("id") for toolbox in toolboxes
|
||||||
|
}
|
||||||
|
toolboxesOwners = {
|
||||||
|
toolbox.get("owner_id"): toolbox.get("id")
|
||||||
|
for toolbox in toolboxes
|
||||||
|
if toolbox.get("owner_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
users = await UserHandler.getAll()
|
||||||
|
usersDict = {user.get("login"): user.get("id") for user in users}
|
||||||
|
|
||||||
|
toolkits = await ToolkitHandler.getAll()
|
||||||
|
|
||||||
|
logger.warning("Наполнение складов ...")
|
||||||
|
for toolkit in toolkits:
|
||||||
|
if "Сверло" in toolkit.get("title"):
|
||||||
|
toolboxId = toolboxesDict.get("Шкаф")
|
||||||
|
else:
|
||||||
|
toolboxId = toolboxesDict.get("Стеллаж")
|
||||||
|
|
||||||
|
placement = chr(65 + random.randint(0, 25)) + str(random.randint(1, 19))
|
||||||
|
|
||||||
|
registryCount = 5
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
f">> Наполнение склада {toolboxId} инструментом {toolkit.get('title')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(registryCount):
|
||||||
|
quantity = random.randint(20, 30)
|
||||||
|
price = round(300 + random.random() * 500, 2)
|
||||||
|
|
||||||
|
logger.warning(f">>>> Количество: {quantity}, Цена: {price}")
|
||||||
|
|
||||||
|
success = await StocksActions.registration(
|
||||||
|
toolkit_id=toolkit.get("id"),
|
||||||
|
toolbox_id=toolboxId,
|
||||||
|
user_id=usersDict.get("storekeeper"),
|
||||||
|
quantity=quantity,
|
||||||
|
price=price,
|
||||||
|
placement=placement,
|
||||||
|
reason=f"Приход инструмента {toolkit.get('title')}. Счёт-фактура № {random.randint(1000, 10000)}-{i+1}",
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Приход инструмента {toolkit.get('title')} не удался")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.warning("Наполнение складов завершено")
|
||||||
|
|
||||||
|
logger.warning("Получение инструментов из общих складов ...")
|
||||||
|
|
||||||
|
accessLevels = await AccessLevelHandler.getAll()
|
||||||
|
accessLevelsDict = {
|
||||||
|
accessLevel.get("id"): accessLevel.get("available_own_toolbox")
|
||||||
|
for accessLevel in accessLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
users = await UserHandler.getAll()
|
||||||
|
usersDict = {user.get("login"): user.get("id") for user in users}
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
userAccessLevelId = user.get("access_level_id")
|
||||||
|
if not accessLevelsDict.get(userAccessLevelId):
|
||||||
|
continue
|
||||||
|
|
||||||
|
own_toolbox_id = toolboxesOwners.get(user.get("id"))
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
f"Получение инструментов из общего склада пользователем {user.get('login')} ..."
|
||||||
|
)
|
||||||
|
|
||||||
|
for toolkit in toolkits:
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
f">> Выдача инструмента {toolkit.get('title')} пользователю {user.get('login')} ..."
|
||||||
|
)
|
||||||
|
|
||||||
|
if "Сверло" in toolkit.get("title"):
|
||||||
|
main_toolbox_id = toolboxesDict.get("Шкаф")
|
||||||
|
else:
|
||||||
|
main_toolbox_id = toolboxesDict.get("Стеллаж")
|
||||||
|
|
||||||
|
actionsCount = random.randint(3, 5)
|
||||||
|
|
||||||
|
for i in range(actionsCount):
|
||||||
|
success = await StocksActions.takeToolkit(
|
||||||
|
source_toolbox_id=main_toolbox_id,
|
||||||
|
target_toolbox_id=own_toolbox_id,
|
||||||
|
toolkit_id=toolkit.get("id"),
|
||||||
|
quantity=random.randint(10, 19),
|
||||||
|
reason=f"Получение инструмента {toolkit.get('title')}. Для выполнения заказа № {random.randint(1000, 10000)}",
|
||||||
|
user_id=user.get("id"),
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
logger.error(
|
||||||
|
f"Получение инструмента {toolkit.get('title')} пользователю {user.get('login')} не удалось"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
f">>>> Получение инструмента {toolkit.get('title')} пользователю {user.get('login')} успешно завершено"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.warning("Получение инструментов из общих складов завершено")
|
||||||
|
|
||||||
|
logger.warning("Направление запросов на возврат и списание инструментов ...")
|
||||||
|
|
||||||
|
requestsDict = {"Списание": [], "Возврат": []}
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
userAccessLevelId = user.get("access_level_id")
|
||||||
|
if not accessLevelsDict.get(userAccessLevelId):
|
||||||
|
continue
|
||||||
|
|
||||||
|
own_toolbox_id = toolboxesOwners.get(user.get("id"))
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
f"Направление запросов на возврат и списание инструмента от пользователя {user.get('login')} ..."
|
||||||
|
)
|
||||||
|
|
||||||
|
for action in requestsDict.keys():
|
||||||
|
for toolkit in toolkits:
|
||||||
|
if action == "Списание":
|
||||||
|
main_toolbox_id = None
|
||||||
|
else:
|
||||||
|
if "Сверло" in toolkit.get("title"):
|
||||||
|
main_toolbox_id = toolboxesDict.get("Шкаф")
|
||||||
|
else:
|
||||||
|
main_toolbox_id = toolboxesDict.get("Стеллаж")
|
||||||
|
|
||||||
|
actionsCount = random.randint(1, 3)
|
||||||
|
|
||||||
|
for i in range(actionsCount):
|
||||||
|
success = await StocksActions.movingRequest(
|
||||||
|
action=action,
|
||||||
|
toolkit_id=toolkit.get("id"),
|
||||||
|
source_toolbox_id=own_toolbox_id,
|
||||||
|
target_toolbox_id=main_toolbox_id,
|
||||||
|
quantity=random.randint(3, 5),
|
||||||
|
reason=f"{action} инструмента {toolkit.get('title')}. После выполнения заказа",
|
||||||
|
user_id=user.get("id"),
|
||||||
|
return_record_id=True,
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
logger.error(
|
||||||
|
f"{action} инструмента {toolkit.get('title')} пользователю {user.get('login')} не удалось"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
requestsDict.get(action).append(success)
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
f">>>> {action} инструмента пользователю {user.get('login')} успешно завершено"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"Направление запросов на возврат и списание инструментов завершено"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.warning("Обработка запросов на возврат и списание инструментов ...")
|
||||||
|
|
||||||
|
decisionsDict = {
|
||||||
|
"manager": {"accept": [], "reject": [], "ignore": []},
|
||||||
|
"storekeeper": {"accept": [], "reject": [], "ignore": []},
|
||||||
|
}
|
||||||
|
for recordsList in requestsDict.values():
|
||||||
|
managerList, storekeeperList = (
|
||||||
|
recordsList[: len(recordsList) // 2],
|
||||||
|
recordsList[len(recordsList) // 2 :],
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
decisionsDict["manager"]["accept"],
|
||||||
|
decisionsDict["manager"]["reject"],
|
||||||
|
decisionsDict["manager"]["ignore"],
|
||||||
|
) = (
|
||||||
|
managerList[: len(managerList) // 3],
|
||||||
|
managerList[len(managerList) // 3 : len(managerList) // 3 * 2],
|
||||||
|
managerList[len(managerList) // 3 * 2 :],
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
decisionsDict["storekeeper"]["accept"],
|
||||||
|
decisionsDict["storekeeper"]["reject"],
|
||||||
|
decisionsDict["storekeeper"]["ignore"],
|
||||||
|
) = (
|
||||||
|
storekeeperList[: len(storekeeperList) // 3],
|
||||||
|
storekeeperList[
|
||||||
|
len(storekeeperList) // 3 : len(storekeeperList) // 3 * 2
|
||||||
|
],
|
||||||
|
storekeeperList[len(storekeeperList) // 3 * 2 :],
|
||||||
|
)
|
||||||
|
|
||||||
|
for role in decisionsDict.keys():
|
||||||
|
user_id = usersDict.get(role)
|
||||||
|
for decision in decisionsDict.get(role).keys():
|
||||||
|
match decision:
|
||||||
|
case "accept":
|
||||||
|
accepted = True
|
||||||
|
case "reject":
|
||||||
|
accepted = False
|
||||||
|
case "ignore":
|
||||||
|
continue
|
||||||
|
for record_id in decisionsDict.get(role).get(decision):
|
||||||
|
success = await StocksActions.movingDecision(
|
||||||
|
record_id=record_id,
|
||||||
|
user_id=user_id,
|
||||||
|
accepted=accepted,
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
logger.error(
|
||||||
|
f"Принятие записи {record_id} пользователем {role} не удалось"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"Обработка запросов на возврат и списание инструментов завершено"
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from utils import logger
|
from utils import logger
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
from db.schemas import Category
|
from db.schemas.categories import Category
|
||||||
from db.handlers import ServiceRecordsHandler
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
|
|
||||||
|
|
||||||
class CategoryHandler:
|
class CategoryHandler:
|
||||||
|
|||||||
+19
-11
@@ -2,7 +2,8 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from db.schemas import StocksRecords, ServicesRecords
|
from db.handlers.stock import StockHandler
|
||||||
|
from db.schemas.records import StocksRecords, ServicesRecords
|
||||||
from utils import logger
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -44,28 +45,37 @@ class StocksRecordsHandler:
|
|||||||
logger.error(f"Ошибка создания записи: {str(e)}")
|
logger.error(f"Ошибка создания записи: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def accept(
|
async def decide(
|
||||||
record_id: int,
|
record_id: int,
|
||||||
accept_user_id: int,
|
decision_user_id: int,
|
||||||
source_stock_id: int,
|
source_stock_id: int,
|
||||||
quantity: int,
|
quantity: int,
|
||||||
price: float,
|
price: float,
|
||||||
|
accept: bool = True,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
logger.info(f"Принятие записи {record_id} от {accept_user_id}")
|
logger.info(
|
||||||
record = await StocksRecords.get(id=record_id)
|
f"{'Принятие' if accept else 'Отклонение'} записи {record_id} от {decision_user_id}"
|
||||||
record.accept_user_id = accept_user_id
|
)
|
||||||
record.accepted_at = datetime.now()
|
record = await StocksRecordsHandler.getById(record_id, record=True)
|
||||||
|
if not record:
|
||||||
|
logger.error(f"Запись {record_id} не найдена")
|
||||||
|
return False
|
||||||
|
record.decision_user_id = decision_user_id
|
||||||
|
record.decided_at = datetime.now()
|
||||||
record.source_stock_id = source_stock_id
|
record.source_stock_id = source_stock_id
|
||||||
record.quantity = quantity
|
record.quantity = quantity
|
||||||
record.price = price
|
record.price = price
|
||||||
|
record.accepted = accept
|
||||||
await record.save()
|
await record.save()
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Запись {record_id} успешно принята {accept_user_id} в {record.accepted_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
f"Запись {record_id} успешно {'принята' if accept else 'отклонена'} {decision_user_id} в {record.decided_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка принятия записи: {str(e)}")
|
logger.error(
|
||||||
|
f"Ошибка {'принятия' if accept else 'отклонения'} записи: {str(e)}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def edit(record_id: int, edit_user_id: int, **kwargs):
|
async def edit(record_id: int, edit_user_id: int, **kwargs):
|
||||||
@@ -77,8 +87,6 @@ class StocksRecordsHandler:
|
|||||||
return recordDB
|
return recordDB
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from db.handlers.stock import StockHandler
|
|
||||||
|
|
||||||
logger.info(f"Обновление записи {record_id} от {edit_user_id}")
|
logger.info(f"Обновление записи {record_id} от {edit_user_id}")
|
||||||
|
|
||||||
record = await StocksRecords.get(id=record_id)
|
record = await StocksRecords.get(id=record_id)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from db.schemas import Stock
|
from db.schemas.stock import Stock
|
||||||
from utils import logger
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class StockHandler:
|
|||||||
|
|
||||||
stock = await CRUD.read(select(Stock).where(Stock.id == stockId))
|
stock = await CRUD.read(select(Stock).where(Stock.id == stockId))
|
||||||
if not stock:
|
if not stock:
|
||||||
logger.error("Запись об остатках не найдена")
|
logger.error(f"Запись {stockId} об остатках не найдена")
|
||||||
return {}
|
return {}
|
||||||
return filterQuantity(stock, filtered) if not record else stock
|
return filterQuantity(stock, filtered) if not record else stock
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from utils import logger
|
from utils import logger
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
from db.schemas import Toolbox
|
from db.schemas.toolbox import Toolbox
|
||||||
from sqlalchemy import or_, select
|
from sqlalchemy import or_, select
|
||||||
from db.handlers import ServiceRecordsHandler
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
|
|
||||||
|
|
||||||
class ToolboxHandler:
|
class ToolboxHandler:
|
||||||
|
|||||||
+9
-11
@@ -1,8 +1,8 @@
|
|||||||
from utils import logger, saveImage, safeFilename, deleteImage
|
from utils import logger, saveImage, safeFilename, deleteImage
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
from db.schemas import Toolkit
|
from db.schemas.toolkit import Toolkit
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from db.handlers import ServiceRecordsHandler
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
|
|
||||||
|
|
||||||
def handleToolkitImage(imageData, title: str):
|
def handleToolkitImage(imageData, title: str):
|
||||||
@@ -51,10 +51,8 @@ class ToolkitHandler:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
logger.info(f"Инструмент {newToolkit.title} успешно создан")
|
logger.info(f"Инструмент {newToolkit.title} успешно создан")
|
||||||
await ServiceRecordsHandler.add(
|
await ServiceRecordsHandler.add(user_id, {"Добавлен инструмент": toolkitData})
|
||||||
user_id, {"Добавлен инструмент": toolkitData.toDict()}
|
return newToolkit
|
||||||
)
|
|
||||||
return newToolkit.toDict()
|
|
||||||
|
|
||||||
async def edit(toolkitId: int, **kwargs):
|
async def edit(toolkitId: int, **kwargs):
|
||||||
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
||||||
@@ -155,7 +153,7 @@ class ToolkitHandler:
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize():
|
||||||
from .categories import CategoryHandler
|
from .categories import CategoryHandler
|
||||||
|
|
||||||
logger.info("Инициализация инструментов")
|
logger.info("Инициализация инструментов")
|
||||||
@@ -203,7 +201,7 @@ class ToolkitHandler:
|
|||||||
"external_link": "https://nazv.ru",
|
"external_link": "https://nazv.ru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Псластина №1",
|
"title": "Пластина №1",
|
||||||
"description": "Пластина такая сякая этакая #1",
|
"description": "Пластина такая сякая этакая #1",
|
||||||
"specifications": {
|
"specifications": {
|
||||||
"Размер": "10",
|
"Размер": "10",
|
||||||
@@ -216,7 +214,7 @@ class ToolkitHandler:
|
|||||||
"external_link": "https://nazv.ru",
|
"external_link": "https://nazv.ru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Псластина №2",
|
"title": "Пластина №2",
|
||||||
"description": "Пластина такая сякая этакая #2",
|
"description": "Пластина такая сякая этакая #2",
|
||||||
"specifications": {
|
"specifications": {
|
||||||
"Размер": "10",
|
"Размер": "10",
|
||||||
@@ -229,7 +227,7 @@ class ToolkitHandler:
|
|||||||
"external_link": "https://nazv.ru",
|
"external_link": "https://nazv.ru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Псластина №3",
|
"title": "Пластина №3",
|
||||||
"description": "Пластина такая сякая этакая #3",
|
"description": "Пластина такая сякая этакая #3",
|
||||||
"specifications": {
|
"specifications": {
|
||||||
"Размер": "10",
|
"Размер": "10",
|
||||||
@@ -283,7 +281,7 @@ class ToolkitHandler:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for toolkit in baseToolkits:
|
for toolkit in baseToolkits:
|
||||||
await self.add(toolkit)
|
await ToolkitHandler.add(toolkit)
|
||||||
|
|
||||||
logger.info("Базовые инструменты успешно созданы")
|
logger.info("Базовые инструменты успешно созданы")
|
||||||
return
|
return
|
||||||
|
|||||||
+26
-17
@@ -1,9 +1,10 @@
|
|||||||
from sqlalchemy import or_, select
|
from sqlalchemy import or_, select
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
from db.handlers.toolbox import addNewToolbox
|
from db.handlers.access import AccessLevelHandler
|
||||||
|
from db.handlers.toolbox import ToolboxHandler
|
||||||
from utils import logger, pwd_hash, saveImage, safeFilename, deleteImage, pwd_verify
|
from utils import logger, pwd_hash, saveImage, safeFilename, deleteImage, pwd_verify
|
||||||
from db.schemas import User
|
from db.schemas.user import User
|
||||||
from db.handlers import ServiceRecordsHandler
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
|
|
||||||
|
|
||||||
def handleUserPhoto(imageData, login: str):
|
def handleUserPhoto(imageData, login: str):
|
||||||
@@ -52,20 +53,26 @@ class UserHandler:
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Пользователь {newUser.username} успешно добавлен, id: {newUser.id}"
|
f"Пользователь {newUser.username} успешно добавлен, id: {newUser.id}"
|
||||||
)
|
)
|
||||||
if newUser.available_own_toolbox:
|
userAccessLevel = await AccessLevelHandler.get(newUser.access_level_id)
|
||||||
|
if not userAccessLevel:
|
||||||
|
logger.error("Уровень доступа не найден")
|
||||||
|
return {}
|
||||||
|
if userAccessLevel.get("available_own_toolbox"):
|
||||||
newToolboxData = {
|
newToolboxData = {
|
||||||
"title": f"Тулбокс {newUser.username}",
|
"title": f"Тулбокс {newUser.username}",
|
||||||
"description": f"Оборудование, полученное сотрудником {newUser.username}, под личную материальную ответственность",
|
"description": f"Оборудование, полученное сотрудником {newUser.username}, под личную материальную ответственность",
|
||||||
"owner_id": newUser.id,
|
"owner_id": newUser.id,
|
||||||
}
|
}
|
||||||
newToolbox = await addNewToolbox(newToolboxData)
|
newToolbox = await ToolboxHandler.add(newToolboxData)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {newUser.username}"
|
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {newUser.username}"
|
||||||
)
|
)
|
||||||
await ServiceRecordsHandler.add(
|
await ServiceRecordsHandler.add(
|
||||||
user_id, {"Добавлен пользователь": newUser.toDict()}
|
user_id, {"Добавлен пользователь": newUser.toDict()}
|
||||||
)
|
)
|
||||||
return newUser.toDict()
|
newUserData = newUser.toDict()
|
||||||
|
newUserData["access_level_data"] = userAccessLevel
|
||||||
|
return newUserData
|
||||||
|
|
||||||
async def edit(userData: dict, user_id: int = None) -> dict:
|
async def edit(userData: dict, user_id: int = None) -> dict:
|
||||||
id = userData.get("id", None)
|
id = userData.get("id", None)
|
||||||
@@ -107,7 +114,7 @@ class UserHandler:
|
|||||||
"description": f"Оборудование, полученное сотрудником {editedUser.username}, под личную материальную ответственность",
|
"description": f"Оборудование, полученное сотрудником {editedUser.username}, под личную материальную ответственность",
|
||||||
"owner_id": editedUser.id,
|
"owner_id": editedUser.id,
|
||||||
}
|
}
|
||||||
newToolbox = await addNewToolbox(newToolboxData)
|
newToolbox = await ToolboxHandler.addNewToolbox(newToolboxData)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {editedUser.username}"
|
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {editedUser.username}"
|
||||||
)
|
)
|
||||||
@@ -182,7 +189,7 @@ class UserHandler:
|
|||||||
)
|
)
|
||||||
return userData
|
return userData
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize():
|
||||||
from .access import AccessLevelHandler
|
from .access import AccessLevelHandler
|
||||||
|
|
||||||
logger.info("Инициализация пользователей")
|
logger.info("Инициализация пользователей")
|
||||||
@@ -190,34 +197,36 @@ class UserHandler:
|
|||||||
acessLevels = {
|
acessLevels = {
|
||||||
accessLevel["title"]: accessLevel["id"] for accessLevel in accessLevelsList
|
accessLevel["title"]: accessLevel["id"] for accessLevel in accessLevelsList
|
||||||
}
|
}
|
||||||
|
logger.info(acessLevels)
|
||||||
|
password = "Alex0172"
|
||||||
baseUsers = {
|
baseUsers = {
|
||||||
"admin": {
|
"admin": {
|
||||||
"login": "admin",
|
"login": "admin",
|
||||||
"username": "Администратор",
|
"username": "Администратор - Demo",
|
||||||
"password": "Alex0172",
|
"password": password,
|
||||||
"access_level_id": acessLevels["Администратор"],
|
"access_level_id": acessLevels["Администратор"],
|
||||||
},
|
},
|
||||||
"manager": {
|
"manager": {
|
||||||
"login": "manager",
|
"login": "manager",
|
||||||
"username": "Менеджер",
|
"username": "Менеджер - Demo",
|
||||||
"password": "Alex0172",
|
"password": password,
|
||||||
"access_level_id": acessLevels["Менеджер"],
|
"access_level_id": acessLevels["Менеджер"],
|
||||||
},
|
},
|
||||||
"storekeeper": {
|
"storekeeper": {
|
||||||
"login": "storekeeper",
|
"login": "storekeeper",
|
||||||
"username": "Кладовщик",
|
"username": "Кладовщик - Demo",
|
||||||
"password": "Alex0172",
|
"password": password,
|
||||||
"access_level_id": acessLevels["Кладовщик"],
|
"access_level_id": acessLevels["Кладовщик"],
|
||||||
},
|
},
|
||||||
"employee": {
|
"employee": {
|
||||||
"login": "employee",
|
"login": "employee",
|
||||||
"username": "Сотрудник",
|
"username": "Сотрудник - Demo",
|
||||||
"password": "Alex0172",
|
"password": password,
|
||||||
"access_level_id": acessLevels["Сотрудник"],
|
"access_level_id": acessLevels["Сотрудник"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for user in baseUsers.values():
|
for user in baseUsers.values():
|
||||||
await self.add(user)
|
await UserHandler.add(user)
|
||||||
|
|
||||||
logger.info("Инициализация пользователей завершена")
|
logger.info("Инициализация пользователей завершена")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
from db.handlers.access import AccessLevelHandler
|
||||||
|
from db.handlers.user import UserHandler
|
||||||
|
from db.handlers.toolbox import ToolboxHandler
|
||||||
|
from db.handlers.categories import CategoryHandler
|
||||||
|
from db.handlers.toolkit import ToolkitHandler
|
||||||
|
from db.handlers.actions import StocksActions
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
from sqlalchemy import inspect, text
|
||||||
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
|
||||||
|
from sqlalchemy.schema import CreateTable
|
||||||
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseInitializer:
|
||||||
|
existing_tables: Optional[list[str]] = None
|
||||||
|
|
||||||
|
def __init__(self, database_url: str):
|
||||||
|
from db import Base
|
||||||
|
|
||||||
|
self.database_url = database_url
|
||||||
|
self.engine: Optional[AsyncEngine] = None
|
||||||
|
self.metadata = Base.metadata
|
||||||
|
|
||||||
|
async def initialize(self, force: bool = False, reNewDB: bool = False):
|
||||||
|
"""Main database initialization method"""
|
||||||
|
try:
|
||||||
|
self.engine = create_async_engine(self.database_url)
|
||||||
|
|
||||||
|
async with self.engine.begin() as conn:
|
||||||
|
if force:
|
||||||
|
logger.info("Принудительное удаление и создание баз...")
|
||||||
|
await self._drop_all()
|
||||||
|
await self._create_tables_directly()
|
||||||
|
await self._initialize_data(reNewDB)
|
||||||
|
elif not await self._check_tables_exist(conn):
|
||||||
|
logger.info("Не все необходимые таблицы существуют. Создаем...")
|
||||||
|
await self._create_tables_directly()
|
||||||
|
|
||||||
|
# Проверяем после создания
|
||||||
|
async with self.engine.begin() as conn:
|
||||||
|
if not await self._check_tables_exist(conn):
|
||||||
|
raise RuntimeError("Не все необходимые таблицы существуют!")
|
||||||
|
|
||||||
|
if reNewDB:
|
||||||
|
logger.info("Принудительная загрузка данных...")
|
||||||
|
await self._initialize_data(reNewDB)
|
||||||
|
else:
|
||||||
|
logger.info("Все необходимые таблицы существуют. Пропускаем...")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if self.engine:
|
||||||
|
await self.engine.dispose()
|
||||||
|
|
||||||
|
async def _check_tables_exist(self, conn) -> bool:
|
||||||
|
"""Check if all tables from metadata exist"""
|
||||||
|
try:
|
||||||
|
DatabaseInitializer.existing_tables = await conn.run_sync(
|
||||||
|
lambda sync_conn: inspect(sync_conn).get_table_names()
|
||||||
|
)
|
||||||
|
|
||||||
|
required_tables = set(self.metadata.tables.keys())
|
||||||
|
|
||||||
|
if not required_tables:
|
||||||
|
logger.error("Нет данных о таблицах в метаданных")
|
||||||
|
return False
|
||||||
|
|
||||||
|
missing_tables = required_tables - set(DatabaseInitializer.existing_tables)
|
||||||
|
if missing_tables:
|
||||||
|
logger.warning("Существующие таблицы:")
|
||||||
|
logger.info(DatabaseInitializer.existing_tables)
|
||||||
|
logger.warning("Необходимые таблицы:")
|
||||||
|
logger.info(required_tables)
|
||||||
|
logger.warning("Отсутствующие таблицы:")
|
||||||
|
logger.info(missing_tables)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Проверка таблиц завершилась ошибкой: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _create_tables_directly(self):
|
||||||
|
"""Create tables directly using SQLAlchemy (bypass Alembic)"""
|
||||||
|
async with self.engine.begin() as conn:
|
||||||
|
# Создаем все таблицы из метаданных
|
||||||
|
for table in self.metadata.sorted_tables:
|
||||||
|
try:
|
||||||
|
if (
|
||||||
|
DatabaseInitializer.existing_tables
|
||||||
|
and table.name in DatabaseInitializer.existing_tables
|
||||||
|
):
|
||||||
|
logger.debug(
|
||||||
|
f"Таблица {table.name} уже существует. Пропускаем..."
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
create_stmt = CreateTable(table)
|
||||||
|
logger.info(f"Создаем таблицу: {table.name}")
|
||||||
|
await conn.execute(create_stmt)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка создания таблицы: {str(e)}")
|
||||||
|
logger.warning("Все таблицы успешно созданы")
|
||||||
|
|
||||||
|
async def _drop_all(self):
|
||||||
|
"""Drop all tables"""
|
||||||
|
async with self.engine.begin() as conn:
|
||||||
|
# Получаем список всех таблиц
|
||||||
|
existing_tables = await conn.run_sync(
|
||||||
|
lambda sync_conn: inspect(sync_conn).get_table_names()
|
||||||
|
)
|
||||||
|
|
||||||
|
for table in existing_tables:
|
||||||
|
drop_stmt = text(
|
||||||
|
f'DROP TABLE "{table}" CASCADE'
|
||||||
|
) # Кавычки на случай спец. символов
|
||||||
|
logger.info(f"Удаляем таблицу: {table}")
|
||||||
|
await conn.execute(drop_stmt)
|
||||||
|
|
||||||
|
logger.warning("Все таблицы успешно удалены")
|
||||||
|
|
||||||
|
async def _initialize_data(self, waiting: bool = False):
|
||||||
|
"""Initialize required data"""
|
||||||
|
|
||||||
|
def waitForUser(waiting):
|
||||||
|
if waiting:
|
||||||
|
input("Для продолжения нажмите Enter...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info("Инициализация данных...")
|
||||||
|
logger.warning("Инициализация Прав доступа...")
|
||||||
|
await AccessLevelHandler.initialize()
|
||||||
|
# waitForUser(waiting)
|
||||||
|
logger.warning("Инициализация Пользователей...")
|
||||||
|
await UserHandler.initialize()
|
||||||
|
# waitForUser(waiting)
|
||||||
|
logger.warning("Инициализация Туллбоксов...")
|
||||||
|
await ToolboxHandler.initialize()
|
||||||
|
# waitForUser(waiting)
|
||||||
|
logger.warning("Инициализация Категорий...")
|
||||||
|
await CategoryHandler.initialize()
|
||||||
|
# waitForUser(waiting)
|
||||||
|
logger.warning("Инициализация Инструментов...")
|
||||||
|
await ToolkitHandler.initialize()
|
||||||
|
# waitForUser(waiting)
|
||||||
|
logger.warning("Инициализация Складов...")
|
||||||
|
await StocksActions.initialize()
|
||||||
|
logger.info("Данные успешно инициализированы")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Инициализация данных завершилась ошибкой: {str(e)}")
|
||||||
|
raise
|
||||||
@@ -5,134 +5,6 @@ from .categories import *
|
|||||||
from .toolbox import *
|
from .toolbox import *
|
||||||
from .stock import *
|
from .stock import *
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
from sqlalchemy import inspect, text
|
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
|
|
||||||
from sqlalchemy.schema import CreateTable
|
|
||||||
from utils import logger
|
|
||||||
|
|
||||||
# Импортируем все модели
|
|
||||||
from db import Base
|
|
||||||
from db.handlers import InitializeDatabase
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseInitializer:
|
|
||||||
existing_tables: Optional[list[str]] = None
|
|
||||||
|
|
||||||
def __init__(self, database_url: str):
|
|
||||||
self.database_url = database_url
|
|
||||||
self.engine: Optional[AsyncEngine] = None
|
|
||||||
self.metadata = Base.metadata
|
|
||||||
|
|
||||||
async def initialize(self, force: bool = False, reNewDB: bool = False):
|
|
||||||
"""Main database initialization method"""
|
|
||||||
try:
|
|
||||||
self.engine = create_async_engine(self.database_url)
|
|
||||||
|
|
||||||
async with self.engine.begin() as conn:
|
|
||||||
if force:
|
|
||||||
logger.info("Принудительное удаление и создание баз...")
|
|
||||||
await self._drop_all()
|
|
||||||
await self._create_tables_directly()
|
|
||||||
await self._initialize_data()
|
|
||||||
elif not await self._check_tables_exist(conn):
|
|
||||||
logger.info("Не все необходимые таблицы существуют. Создаем...")
|
|
||||||
await self._create_tables_directly()
|
|
||||||
|
|
||||||
# Проверяем после создания
|
|
||||||
async with self.engine.begin() as conn:
|
|
||||||
if not await self._check_tables_exist(conn):
|
|
||||||
raise RuntimeError("Не все необходимые таблицы существуют!")
|
|
||||||
|
|
||||||
if reNewDB:
|
|
||||||
logger.info("Принудительная загрузка данных...")
|
|
||||||
await self._initialize_data()
|
|
||||||
else:
|
|
||||||
logger.info("Все необходимые таблицы существуют. Пропускаем...")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}")
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
if self.engine:
|
|
||||||
await self.engine.dispose()
|
|
||||||
|
|
||||||
async def _check_tables_exist(self, conn) -> bool:
|
|
||||||
"""Check if all tables from metadata exist"""
|
|
||||||
try:
|
|
||||||
DatabaseInitializer.existing_tables = await conn.run_sync(
|
|
||||||
lambda sync_conn: inspect(sync_conn).get_table_names()
|
|
||||||
)
|
|
||||||
|
|
||||||
required_tables = set(self.metadata.tables.keys())
|
|
||||||
|
|
||||||
if not required_tables:
|
|
||||||
logger.error("Нет данных о таблицах в метаданных")
|
|
||||||
return False
|
|
||||||
|
|
||||||
missing_tables = required_tables - set(DatabaseInitializer.existing_tables)
|
|
||||||
if missing_tables:
|
|
||||||
logger.warning("Существующие таблицы:")
|
|
||||||
logger.info(DatabaseInitializer.existing_tables)
|
|
||||||
logger.warning("Необходимые таблицы:")
|
|
||||||
logger.info(required_tables)
|
|
||||||
logger.warning("Отсутствующие таблицы:")
|
|
||||||
logger.info(missing_tables)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Проверка таблиц завершилась ошибкой: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _create_tables_directly(self):
|
|
||||||
"""Create tables directly using SQLAlchemy (bypass Alembic)"""
|
|
||||||
async with self.engine.begin() as conn:
|
|
||||||
# Создаем все таблицы из метаданных
|
|
||||||
for table in self.metadata.sorted_tables:
|
|
||||||
try:
|
|
||||||
if (
|
|
||||||
DatabaseInitializer.existing_tables
|
|
||||||
and table.name in DatabaseInitializer.existing_tables
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
f"Таблица {table.name} уже существует. Пропускаем..."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
create_stmt = CreateTable(table)
|
|
||||||
logger.info(f"Создаем таблицу: {table.name}")
|
|
||||||
await conn.execute(create_stmt)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка создания таблицы: {str(e)}")
|
|
||||||
logger.warning("Все таблицы успешно созданы")
|
|
||||||
|
|
||||||
async def _drop_all(self):
|
|
||||||
"""Drop all tables"""
|
|
||||||
async with self.engine.begin() as conn:
|
|
||||||
# Получаем список всех таблиц
|
|
||||||
existing_tables = await conn.run_sync(
|
|
||||||
lambda sync_conn: inspect(sync_conn).get_table_names()
|
|
||||||
)
|
|
||||||
|
|
||||||
for table in existing_tables:
|
|
||||||
drop_stmt = text(
|
|
||||||
f'DROP TABLE "{table}" CASCADE'
|
|
||||||
) # Кавычки на случай спец. символов
|
|
||||||
logger.info(f"Удаляем таблицу: {table}")
|
|
||||||
await conn.execute(drop_stmt)
|
|
||||||
|
|
||||||
logger.warning("Все таблицы успешно удалены")
|
|
||||||
|
|
||||||
async def _initialize_data(self):
|
|
||||||
"""Initialize required data"""
|
|
||||||
try:
|
|
||||||
logger.info("Инициализация данных...")
|
|
||||||
await InitializeDatabase.initialize()
|
|
||||||
logger.info("Данные успешно инициализированы")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Инициализация данных завершилась ошибкой: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"User",
|
"User",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
|
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -44,11 +44,7 @@ class AccessLevel(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(self, **kwargs):
|
async def edit(self, **kwargs):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(AccessLevel, self.id, **kwargs)
|
return await CRUD.update(AccessLevel, self.id, **kwargs)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Column, DateTime, Integer, String, Text
|
from sqlalchemy import Column, DateTime, Integer, String, Text
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -21,11 +21,7 @@ class Category(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(id: int, **kwargs):
|
async def edit(id: int, **kwargs):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(Category, id, **kwargs)
|
return await CRUD.update(Category, id, **kwargs)
|
||||||
|
|||||||
+14
-10
@@ -1,7 +1,16 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String, Text
|
from sqlalchemy import (
|
||||||
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -28,18 +37,19 @@ class StocksRecords(Base):
|
|||||||
init_user_id = Column(
|
init_user_id = Column(
|
||||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||||||
)
|
)
|
||||||
accept_user_id = Column(
|
decision_user_id = Column(
|
||||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
|
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
|
||||||
)
|
)
|
||||||
reason = Column(Text, nullable=False)
|
reason = Column(Text, nullable=False)
|
||||||
quantity = Column(Integer, nullable=False)
|
quantity = Column(Integer, nullable=False)
|
||||||
price = Column(Float, nullable=False)
|
price = Column(Float, nullable=False)
|
||||||
|
accepted = Column(Boolean, default=None, nullable=True)
|
||||||
edit_user_id = Column(
|
edit_user_id = Column(
|
||||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
|
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
|
||||||
)
|
)
|
||||||
edited = Column(JSONB, nullable=True)
|
edited = Column(JSONB, nullable=True)
|
||||||
created_at = Column(DateTime, default=datetime.now)
|
created_at = Column(DateTime, default=datetime.now)
|
||||||
accepted_at = Column(DateTime, nullable=True)
|
decided_at = Column(DateTime, nullable=True)
|
||||||
edited_at = Column(DateTime, nullable=True)
|
edited_at = Column(DateTime, nullable=True)
|
||||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
@@ -51,13 +61,9 @@ class StocksRecords(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(self, **kwargs):
|
async def edit(self, **kwargs):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(StocksRecords, self.id, **kwargs)
|
return await CRUD.update(StocksRecords, self.id, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -77,6 +83,4 @@ class ServicesRecords(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|||||||
+11
-7
@@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String
|
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -14,13 +14,21 @@ class Stock(Base):
|
|||||||
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
|
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
|
||||||
)
|
)
|
||||||
toolkit_data = relationship(
|
toolkit_data = relationship(
|
||||||
"Toolkit", cascade="all, delete-orphan", lazy="joined", uselist=False
|
"Toolkit",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
lazy="joined",
|
||||||
|
uselist=False,
|
||||||
|
single_parent=True,
|
||||||
)
|
)
|
||||||
toolbox_id = Column(
|
toolbox_id = Column(
|
||||||
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False
|
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False
|
||||||
)
|
)
|
||||||
toolbox_data = relationship(
|
toolbox_data = relationship(
|
||||||
"Toolbox", cascade="all, delete-orphan", lazy="joined", uselist=False
|
"Toolbox",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
lazy="joined",
|
||||||
|
uselist=False,
|
||||||
|
single_parent=True,
|
||||||
)
|
)
|
||||||
quantity = Column(Integer, nullable=False)
|
quantity = Column(Integer, nullable=False)
|
||||||
price = Column(Float, nullable=False)
|
price = Column(Float, nullable=False)
|
||||||
@@ -36,11 +44,7 @@ class Stock(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(self, **kwargs):
|
async def edit(self, **kwargs):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(Stock, self.id, **kwargs)
|
return await CRUD.update(Stock, self.id, **kwargs)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -25,11 +25,7 @@ class Toolbox(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(id: int, **kwargs):
|
async def edit(id: int, **kwargs):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(Toolbox, id, **kwargs)
|
return await CRUD.update(Toolbox, id, **kwargs)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
|
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +15,11 @@ class Toolkit(Base):
|
|||||||
specifications = Column(JSONB, default={})
|
specifications = Column(JSONB, default={})
|
||||||
category_id = Column(Integer, ForeignKey("categories.id", ondelete="CASCADE"))
|
category_id = Column(Integer, ForeignKey("categories.id", ondelete="CASCADE"))
|
||||||
category_data = relationship(
|
category_data = relationship(
|
||||||
"Category", cascade="all, delete-orphan", lazy="joined", uselist=False
|
"Category",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
lazy="joined",
|
||||||
|
uselist=False,
|
||||||
|
single_parent=True,
|
||||||
)
|
)
|
||||||
image = Column(JSONB)
|
image = Column(JSONB)
|
||||||
quantity_min = Column(Integer, nullable=True)
|
quantity_min = Column(Integer, nullable=True)
|
||||||
@@ -34,11 +38,7 @@ class Toolkit(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(id: int, **kwargs):
|
async def edit(id: int, **kwargs):
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(Toolkit, id, **kwargs)
|
return await CRUD.update(Toolkit, id, **kwargs)
|
||||||
|
|||||||
+1
-5
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
|
||||||
from db import Base
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -25,11 +25,7 @@ class User(Base):
|
|||||||
return utils.toDict(self)
|
return utils.toDict(self)
|
||||||
|
|
||||||
async def save(self) -> "User":
|
async def save(self) -> "User":
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.create(self, refresh=True)
|
return await CRUD.create(self, refresh=True)
|
||||||
|
|
||||||
async def edit(self, **kwargs) -> "User":
|
async def edit(self, **kwargs) -> "User":
|
||||||
from db import CRUD
|
|
||||||
|
|
||||||
return await CRUD.update(User, self.id, **kwargs)
|
return await CRUD.update(User, self.id, **kwargs)
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
from utils import logger, setLogLevel
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
def main():
|
async def main():
|
||||||
setLogLevel("WARNING")
|
from db import DATABASE_URL
|
||||||
logger.info("Приложение запущено")
|
from db.initialize import DatabaseInitializer
|
||||||
|
|
||||||
logger.info("Получение данных из базы...")
|
try:
|
||||||
logger.warning({"query": "SELECT * FROM tools", "status": "slow"})
|
force = True
|
||||||
setLogLevel("INFO")
|
reNewDB = True
|
||||||
logger.info("Пользователь открыл страницу настроек")
|
await DatabaseInitializer(DATABASE_URL).initialize(force, reNewDB)
|
||||||
logger.debug(["Ошибка загрузки интерфейса", 502, "Bad Gateway"])
|
except Exception as e:
|
||||||
setLogLevel("DEBUG")
|
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}", exc_info=True)
|
||||||
test_data = {"a": 1, "b": 2, "c": [10, 20]}
|
|
||||||
logger.info(test_data)
|
|
||||||
|
|
||||||
logger.debug("Приложение завершено")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
import asyncio
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
|||||||
+3
-1
@@ -5,8 +5,10 @@ description = "Add your description here"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2-cffi>=25.1.0",
|
||||||
|
"asyncpg>=0.31.0",
|
||||||
"colorlog>=6.10.1",
|
"colorlog>=6.10.1",
|
||||||
"passlib>=1.7.4",
|
"greenlet>=3.2.4",
|
||||||
"pillow>=12.0.0",
|
"pillow>=12.0.0",
|
||||||
"python-dotenv>=1.2.1",
|
"python-dotenv>=1.2.1",
|
||||||
"sqlalchemy>=2.0.44",
|
"sqlalchemy>=2.0.44",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6
-1
@@ -1,4 +1,9 @@
|
|||||||
def toDict(data) -> dict:
|
def toDict(data) -> dict:
|
||||||
|
def dateToStr(date):
|
||||||
|
if date is None:
|
||||||
|
return None
|
||||||
|
return date.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
c.name: (
|
c.name: (
|
||||||
(
|
(
|
||||||
@@ -7,7 +12,7 @@ def toDict(data) -> dict:
|
|||||||
else getattr(data, c.name).toDict()
|
else getattr(data, c.name).toDict()
|
||||||
)
|
)
|
||||||
if not c.name.endswith("_at")
|
if not c.name.endswith("_at")
|
||||||
else getattr(data, c.name).strftime("%Y-%m-%d %H:%M:%S")
|
else dateToStr(getattr(data, c.name))
|
||||||
)
|
)
|
||||||
for c in data.__table__.columns
|
for c in data.__table__.columns
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-9
@@ -1,18 +1,32 @@
|
|||||||
from passlib.context import CryptContext
|
from argon2 import PasswordHasher
|
||||||
|
from argon2.exceptions import (
|
||||||
|
VerifyMismatchError,
|
||||||
|
VerificationError,
|
||||||
|
InvalidHash,
|
||||||
|
)
|
||||||
|
|
||||||
|
from utils.loggers import logger
|
||||||
|
|
||||||
|
|
||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
pwd_hasher = PasswordHasher(
|
||||||
|
time_cost=3,
|
||||||
|
memory_cost=65536,
|
||||||
|
parallelism=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def pwd_hash(pwd_plant: str) -> str:
|
def pwd_hash(pwd_plain: str) -> str:
|
||||||
return pwd_context.encrypt(pwd_plant)
|
pwd_plain = str(pwd_plain)
|
||||||
|
return pwd_hasher.hash(pwd_plain)
|
||||||
|
|
||||||
|
|
||||||
def pwd_verify(pwd_plant: str, pwd_hash: str) -> bool:
|
def pwd_verify(pwd_plain: str, stored_hash: str) -> bool:
|
||||||
from utils.loggers import logger
|
pwd_plain = str(pwd_plain)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return pwd_context.verify(pwd_plant, pwd_hash)
|
valid = pwd_hasher.verify(stored_hash, pwd_plain)
|
||||||
except Exception as e:
|
except (VerifyMismatchError, VerificationError, InvalidHash) as e:
|
||||||
logger.error(f"Password verification error: {str(e)}")
|
logger.warning(f"Password verification failed: {e.__class__.__name__}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|||||||
@@ -1,6 +1,130 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.14'",
|
||||||
|
"python_full_version < '3.14'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2-cffi"
|
||||||
|
version = "25.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "argon2-cffi-bindings" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2-cffi-bindings"
|
||||||
|
version = "25.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cffi" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asyncpg"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
@@ -51,15 +175,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
|
{ url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "passlib"
|
|
||||||
version = "1.7.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.0.0"
|
version = "12.0.0"
|
||||||
@@ -118,6 +233,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "2.23"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -153,8 +277,10 @@ name = "tools-stock"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "argon2-cffi" },
|
||||||
|
{ name = "asyncpg" },
|
||||||
{ name = "colorlog" },
|
{ name = "colorlog" },
|
||||||
{ name = "passlib" },
|
{ name = "greenlet" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "sqlalchemy" },
|
{ name = "sqlalchemy" },
|
||||||
@@ -162,8 +288,10 @@ dependencies = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "argon2-cffi", specifier = ">=25.1.0" },
|
||||||
|
{ name = "asyncpg", specifier = ">=0.31.0" },
|
||||||
{ name = "colorlog", specifier = ">=6.10.1" },
|
{ name = "colorlog", specifier = ">=6.10.1" },
|
||||||
{ name = "passlib", specifier = ">=1.7.4" },
|
{ name = "greenlet", specifier = ">=3.2.4" },
|
||||||
{ name = "pillow", specifier = ">=12.0.0" },
|
{ name = "pillow", specifier = ">=12.0.0" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||||
{ name = "sqlalchemy", specifier = ">=2.0.44" },
|
{ name = "sqlalchemy", specifier = ">=2.0.44" },
|
||||||
|
|||||||
Reference in New Issue
Block a user