Создание и первичная инициализация базы даных успешно завершена. Наполнение демо-данными прошло без ошибок
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
DB_HOST=10.0.13.3
|
||||
DB_PORT=5432
|
||||
DB_NAME=tools_stock
|
||||
DB_USER=tools_stock
|
||||
DB_NAME=toolbox
|
||||
DB_USER=toolbox
|
||||
DB_PASS=z7kWLkSKa6
|
||||
Binary file not shown.
+1
-1
@@ -108,7 +108,7 @@ class CRUD:
|
||||
item = await db.execute(query)
|
||||
await db.commit()
|
||||
logger.info("Запись обновлена")
|
||||
return item
|
||||
return await db.get(db_data, id)
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
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 utils import logger
|
||||
from db import CRUD
|
||||
from db.schemas import AccessLevel
|
||||
from db.handlers import ServiceRecordsHandler
|
||||
from db.schemas.access import AccessLevel
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
|
||||
|
||||
class AccessLevelHandler:
|
||||
async def add(**kwargs):
|
||||
title = kwargs.get("title", None)
|
||||
async def add(newData):
|
||||
title = newData.get("title", None)
|
||||
if not title:
|
||||
logger.error("Не указано название уровня доступа")
|
||||
return {}
|
||||
@@ -17,8 +17,8 @@ class AccessLevelHandler:
|
||||
return {}
|
||||
try:
|
||||
logger.info(f"Создание уровня доступа {title}")
|
||||
user_id = kwargs.pop("user_id", None)
|
||||
accessData = await AccessLevel(**kwargs).save()
|
||||
user_id = newData.pop("user_id", None)
|
||||
accessData = await AccessLevel(**newData).save()
|
||||
await ServiceRecordsHandler.add(
|
||||
user_id, {"Добавлен уровень доступа": accessData.toDict()}
|
||||
)
|
||||
@@ -85,7 +85,7 @@ class AccessLevelHandler:
|
||||
)
|
||||
return result
|
||||
|
||||
async def initialize(self):
|
||||
async def initialize():
|
||||
baseAcessLevels = {
|
||||
"admin": {
|
||||
"title": "Администратор",
|
||||
@@ -149,7 +149,7 @@ class AccessLevelHandler:
|
||||
logger.info("Инициализация уровней доступа")
|
||||
|
||||
for accessLevel in baseAcessLevels.values():
|
||||
await self.add(**accessLevel)
|
||||
await AccessLevelHandler.add(accessLevel)
|
||||
|
||||
logger.info("Уровни доступа успешно инициализированы")
|
||||
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
|
||||
|
||||
|
||||
@@ -32,7 +39,7 @@ class StocksActions:
|
||||
recorded = await StocksRecordsHandler.add(
|
||||
action="Оприходование",
|
||||
source_stock_id=None,
|
||||
target_stock_id=newStocks.id,
|
||||
target_stock_id=newStocks.get("id"),
|
||||
source_toolbox_id=None,
|
||||
target_toolbox_id=toolbox_id,
|
||||
toolkit_id=toolkit_id,
|
||||
@@ -46,7 +53,7 @@ class StocksActions:
|
||||
f"Оприходование инструмента {toolkit_id} на складе {toolbox_id} прошло {'успешно' if recorded else 'не успешно'}"
|
||||
)
|
||||
if recorded:
|
||||
accepted = await StocksRecordsHandler.accept(
|
||||
accepted = await StocksRecordsHandler.decide(
|
||||
recorded, user_id, None, quantity, price
|
||||
)
|
||||
if not accepted:
|
||||
@@ -84,7 +91,7 @@ class StocksActions:
|
||||
return False
|
||||
|
||||
totalTakeQuantity = 0
|
||||
writeDownList = []
|
||||
movementsList = []
|
||||
|
||||
for stock in availability:
|
||||
if quantity == totalTakeQuantity:
|
||||
@@ -108,14 +115,15 @@ class StocksActions:
|
||||
f"Списание инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} в количестве {takeQuantity} по цене {stock['price']} успешно завершена"
|
||||
)
|
||||
|
||||
if not target_toolbox_id:
|
||||
writeDownList.append(
|
||||
movementsList.append(
|
||||
{
|
||||
"id": sourceEdit["id"],
|
||||
"quantity": takeQuantity,
|
||||
"price": stock["price"],
|
||||
}
|
||||
)
|
||||
|
||||
if not target_toolbox_id:
|
||||
continue
|
||||
|
||||
existing = await StockHandler.getByToolboxIdAndToolkitIdAndQPrice(
|
||||
@@ -168,7 +176,6 @@ class StocksActions:
|
||||
reason=reason,
|
||||
quantity=quantity,
|
||||
price=stock["price"],
|
||||
target_placement=target_placement,
|
||||
)
|
||||
if not recorded:
|
||||
logger.error(
|
||||
@@ -179,7 +186,7 @@ class StocksActions:
|
||||
logger.info(
|
||||
f"{action} инструмента {toolkit_id} со склада {source_toolbox_id} на склад {target_toolbox_id} прошло успешно"
|
||||
)
|
||||
return True if target_toolbox_id else writeDownList
|
||||
return movementsList
|
||||
|
||||
async def movingRequest(
|
||||
action: str,
|
||||
@@ -213,29 +220,50 @@ class StocksActions:
|
||||
)
|
||||
return recorded
|
||||
|
||||
async def movingAcceptance(self, record_id: int, user_id: int):
|
||||
logger.info(f"Принятие записи о движении инструмента {record_id} ...")
|
||||
writeDownARecord = await StocksRecordsHandler.getById(record_id, True)
|
||||
if not writeDownARecord:
|
||||
async def movingDecision(record_id: int, user_id: int, accepted: bool = True):
|
||||
logger.info(
|
||||
f"{'Принятие' if accepted else 'Отклонение'} записи о движении инструмента {record_id} ..."
|
||||
)
|
||||
movingRecord = await StocksRecordsHandler.getById(record_id, True)
|
||||
if not movingRecord:
|
||||
logger.error(f"Запись {record_id} не найдена")
|
||||
return False
|
||||
if writeDownARecord.accepted_at is not None:
|
||||
logger.error(f"Запись {record_id} уже была принята")
|
||||
if movingRecord.accepted is not None:
|
||||
logger.error(
|
||||
f"Запись {record_id} уже была {'принята' if movingRecord.accepted else 'отклонена'}"
|
||||
)
|
||||
return False
|
||||
stocksMovements = await self.moving(
|
||||
action=writeDownARecord.action,
|
||||
source_toolbox_id=writeDownARecord.source_toolbox_id,
|
||||
target_toolbox_id=writeDownARecord.target_toolbox_id,
|
||||
toolkit_id=writeDownARecord.toolkit_id,
|
||||
quantity=writeDownARecord.quantity,
|
||||
if not accepted:
|
||||
return await StocksRecordsHandler.decide(
|
||||
record_id,
|
||||
user_id,
|
||||
movingRecord.source_stock_id,
|
||||
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,
|
||||
reason=writeDownARecord.reason,
|
||||
reason=movingRecord.reason,
|
||||
)
|
||||
if not stocksMovements:
|
||||
logger.error(f"Ошибка при {writeDownARecord.action} инструмента")
|
||||
logger.error(f"Ошибка при {movingRecord.action} инструмента")
|
||||
return False
|
||||
|
||||
accept = await StocksRecordsHandler.accept(
|
||||
accept = await StocksRecordsHandler.decide(
|
||||
record_id,
|
||||
user_id,
|
||||
stocksMovements[0].get("id"),
|
||||
@@ -250,23 +278,23 @@ class StocksActions:
|
||||
if len(stocksMovements) > 1:
|
||||
for stock in stocksMovements[1:]:
|
||||
recorded = await StocksRecordsHandler.add(
|
||||
action=writeDownARecord.action,
|
||||
action=movingRecord.action,
|
||||
source_stock_id=stock.get("id"),
|
||||
target_stock_id=None,
|
||||
source_toolbox_id=writeDownARecord.source_toolbox_id,
|
||||
target_toolbox_id=writeDownARecord.target_toolbox_id,
|
||||
toolkit_id=writeDownARecord.toolkit_id,
|
||||
init_user_id=writeDownARecord.init_user_id,
|
||||
reason=writeDownARecord.reason,
|
||||
source_toolbox_id=movingRecord.source_toolbox_id,
|
||||
target_toolbox_id=target_toolbox_id,
|
||||
toolkit_id=movingRecord.toolkit_id,
|
||||
init_user_id=movingRecord.init_user_id,
|
||||
reason=movingRecord.reason,
|
||||
quantity=stock.get("quantity"),
|
||||
price=stock.get("price"),
|
||||
return_record_id=True,
|
||||
)
|
||||
if not recorded:
|
||||
return False
|
||||
accept = await StocksRecordsHandler.accept(
|
||||
accept = await StocksRecordsHandler.decide(
|
||||
record_id=recorded,
|
||||
accept_user_id=user_id,
|
||||
decision_user_id=user_id,
|
||||
source_stock_id=stock.get("id"),
|
||||
quantity=stock.get("quantity"),
|
||||
price=stock.get("price"),
|
||||
@@ -276,12 +304,11 @@ class StocksActions:
|
||||
totalRecordsIds.append(recorded)
|
||||
|
||||
logger.info(
|
||||
f"Записи {', '.join(map(str, totalRecordsIds))} о {writeDownARecord.action} инструмента успешно приняты {user_id}"
|
||||
f"Записи {', '.join(map(str, totalRecordsIds))} о {movingRecord.action} инструмента успешно приняты {user_id}"
|
||||
)
|
||||
return True
|
||||
|
||||
async def takeToolkit(
|
||||
self,
|
||||
source_toolbox_id: int,
|
||||
target_toolbox_id: int,
|
||||
toolkit_id: int,
|
||||
@@ -293,7 +320,7 @@ class StocksActions:
|
||||
logger.info(
|
||||
f"Формирование запроса на получение инструмента {toolkit_id} на склад {target_toolbox_id} со склада {source_toolbox_id} в количестве {quantity} ..."
|
||||
)
|
||||
takeRequest = await self.movingRequest(
|
||||
takeRequest = await StocksActions.movingRequest(
|
||||
action="Получение",
|
||||
source_toolbox_id=source_toolbox_id,
|
||||
target_toolbox_id=target_toolbox_id,
|
||||
@@ -315,7 +342,7 @@ class StocksActions:
|
||||
logger.info(
|
||||
f"Принятие запроса {takeRequest} на получение инструмента {toolkit_id} ..."
|
||||
)
|
||||
accepted = await self.movingAcceptance(takeRequest, user_id)
|
||||
accepted = await StocksActions.movingDecision(takeRequest, user_id)
|
||||
if not accepted:
|
||||
logger.error(
|
||||
f"Принятие запроса {takeRequest} на получение инструмента {toolkit_id} не удалось"
|
||||
@@ -327,6 +354,226 @@ class StocksActions:
|
||||
return True
|
||||
|
||||
async def initialize():
|
||||
# TODO прописать наполнение общих складов, получение на личные, возвраты и списания.
|
||||
# Не все запросы на возвраты и списания нужно принять автоматически, нужно оставить несколько для демонстрации
|
||||
pass
|
||||
|
||||
toolboxes = await ToolboxHandler.getAll()
|
||||
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 utils import logger
|
||||
from db import CRUD
|
||||
from db.schemas import Category
|
||||
from db.handlers import ServiceRecordsHandler
|
||||
from db.schemas.categories import Category
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
|
||||
|
||||
class CategoryHandler:
|
||||
|
||||
+19
-11
@@ -2,7 +2,8 @@ from datetime import datetime, timedelta
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -44,28 +45,37 @@ class StocksRecordsHandler:
|
||||
logger.error(f"Ошибка создания записи: {str(e)}")
|
||||
return False
|
||||
|
||||
async def accept(
|
||||
async def decide(
|
||||
record_id: int,
|
||||
accept_user_id: int,
|
||||
decision_user_id: int,
|
||||
source_stock_id: int,
|
||||
quantity: int,
|
||||
price: float,
|
||||
accept: bool = True,
|
||||
):
|
||||
try:
|
||||
logger.info(f"Принятие записи {record_id} от {accept_user_id}")
|
||||
record = await StocksRecords.get(id=record_id)
|
||||
record.accept_user_id = accept_user_id
|
||||
record.accepted_at = datetime.now()
|
||||
logger.info(
|
||||
f"{'Принятие' if accept else 'Отклонение'} записи {record_id} от {decision_user_id}"
|
||||
)
|
||||
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.quantity = quantity
|
||||
record.price = price
|
||||
record.accepted = accept
|
||||
await record.save()
|
||||
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
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка принятия записи: {str(e)}")
|
||||
logger.error(
|
||||
f"Ошибка {'принятия' if accept else 'отклонения'} записи: {str(e)}"
|
||||
)
|
||||
return False
|
||||
|
||||
async def edit(record_id: int, edit_user_id: int, **kwargs):
|
||||
@@ -77,8 +87,6 @@ class StocksRecordsHandler:
|
||||
return recordDB
|
||||
|
||||
try:
|
||||
from db.handlers.stock import StockHandler
|
||||
|
||||
logger.info(f"Обновление записи {record_id} от {edit_user_id}")
|
||||
|
||||
record = await StocksRecords.get(id=record_id)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from sqlalchemy import select
|
||||
from db.schemas import Stock
|
||||
from db.schemas.stock import Stock
|
||||
from utils import logger
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class StockHandler:
|
||||
|
||||
stock = await CRUD.read(select(Stock).where(Stock.id == stockId))
|
||||
if not stock:
|
||||
logger.error("Запись об остатках не найдена")
|
||||
logger.error(f"Запись {stockId} об остатках не найдена")
|
||||
return {}
|
||||
return filterQuantity(stock, filtered) if not record else stock
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from utils import logger
|
||||
from db import CRUD
|
||||
from db.schemas import Toolbox
|
||||
from db.schemas.toolbox import Toolbox
|
||||
from sqlalchemy import or_, select
|
||||
from db.handlers import ServiceRecordsHandler
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
|
||||
|
||||
class ToolboxHandler:
|
||||
|
||||
+9
-11
@@ -1,8 +1,8 @@
|
||||
from utils import logger, saveImage, safeFilename, deleteImage
|
||||
from db import CRUD
|
||||
from db.schemas import Toolkit
|
||||
from db.schemas.toolkit import Toolkit
|
||||
from sqlalchemy import select
|
||||
from db.handlers import ServiceRecordsHandler
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
|
||||
|
||||
def handleToolkitImage(imageData, title: str):
|
||||
@@ -51,10 +51,8 @@ class ToolkitHandler:
|
||||
return {}
|
||||
|
||||
logger.info(f"Инструмент {newToolkit.title} успешно создан")
|
||||
await ServiceRecordsHandler.add(
|
||||
user_id, {"Добавлен инструмент": toolkitData.toDict()}
|
||||
)
|
||||
return newToolkit.toDict()
|
||||
await ServiceRecordsHandler.add(user_id, {"Добавлен инструмент": toolkitData})
|
||||
return newToolkit
|
||||
|
||||
async def edit(toolkitId: int, **kwargs):
|
||||
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
||||
@@ -155,7 +153,7 @@ class ToolkitHandler:
|
||||
)
|
||||
return result
|
||||
|
||||
async def initialize(self):
|
||||
async def initialize():
|
||||
from .categories import CategoryHandler
|
||||
|
||||
logger.info("Инициализация инструментов")
|
||||
@@ -203,7 +201,7 @@ class ToolkitHandler:
|
||||
"external_link": "https://nazv.ru",
|
||||
},
|
||||
{
|
||||
"title": "Псластина №1",
|
||||
"title": "Пластина №1",
|
||||
"description": "Пластина такая сякая этакая #1",
|
||||
"specifications": {
|
||||
"Размер": "10",
|
||||
@@ -216,7 +214,7 @@ class ToolkitHandler:
|
||||
"external_link": "https://nazv.ru",
|
||||
},
|
||||
{
|
||||
"title": "Псластина №2",
|
||||
"title": "Пластина №2",
|
||||
"description": "Пластина такая сякая этакая #2",
|
||||
"specifications": {
|
||||
"Размер": "10",
|
||||
@@ -229,7 +227,7 @@ class ToolkitHandler:
|
||||
"external_link": "https://nazv.ru",
|
||||
},
|
||||
{
|
||||
"title": "Псластина №3",
|
||||
"title": "Пластина №3",
|
||||
"description": "Пластина такая сякая этакая #3",
|
||||
"specifications": {
|
||||
"Размер": "10",
|
||||
@@ -283,7 +281,7 @@ class ToolkitHandler:
|
||||
]
|
||||
|
||||
for toolkit in baseToolkits:
|
||||
await self.add(toolkit)
|
||||
await ToolkitHandler.add(toolkit)
|
||||
|
||||
logger.info("Базовые инструменты успешно созданы")
|
||||
return
|
||||
|
||||
+26
-17
@@ -1,9 +1,10 @@
|
||||
from sqlalchemy import or_, select
|
||||
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 db.schemas import User
|
||||
from db.handlers import ServiceRecordsHandler
|
||||
from db.schemas.user import User
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
|
||||
|
||||
def handleUserPhoto(imageData, login: str):
|
||||
@@ -52,20 +53,26 @@ class UserHandler:
|
||||
logger.info(
|
||||
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 = {
|
||||
"title": f"Тулбокс {newUser.username}",
|
||||
"description": f"Оборудование, полученное сотрудником {newUser.username}, под личную материальную ответственность",
|
||||
"owner_id": newUser.id,
|
||||
}
|
||||
newToolbox = await addNewToolbox(newToolboxData)
|
||||
newToolbox = await ToolboxHandler.add(newToolboxData)
|
||||
logger.info(
|
||||
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {newUser.username}"
|
||||
)
|
||||
await ServiceRecordsHandler.add(
|
||||
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:
|
||||
id = userData.get("id", None)
|
||||
@@ -107,7 +114,7 @@ class UserHandler:
|
||||
"description": f"Оборудование, полученное сотрудником {editedUser.username}, под личную материальную ответственность",
|
||||
"owner_id": editedUser.id,
|
||||
}
|
||||
newToolbox = await addNewToolbox(newToolboxData)
|
||||
newToolbox = await ToolboxHandler.addNewToolbox(newToolboxData)
|
||||
logger.info(
|
||||
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {editedUser.username}"
|
||||
)
|
||||
@@ -182,7 +189,7 @@ class UserHandler:
|
||||
)
|
||||
return userData
|
||||
|
||||
async def initialize(self):
|
||||
async def initialize():
|
||||
from .access import AccessLevelHandler
|
||||
|
||||
logger.info("Инициализация пользователей")
|
||||
@@ -190,34 +197,36 @@ class UserHandler:
|
||||
acessLevels = {
|
||||
accessLevel["title"]: accessLevel["id"] for accessLevel in accessLevelsList
|
||||
}
|
||||
logger.info(acessLevels)
|
||||
password = "Alex0172"
|
||||
baseUsers = {
|
||||
"admin": {
|
||||
"login": "admin",
|
||||
"username": "Администратор",
|
||||
"password": "Alex0172",
|
||||
"username": "Администратор - Demo",
|
||||
"password": password,
|
||||
"access_level_id": acessLevels["Администратор"],
|
||||
},
|
||||
"manager": {
|
||||
"login": "manager",
|
||||
"username": "Менеджер",
|
||||
"password": "Alex0172",
|
||||
"username": "Менеджер - Demo",
|
||||
"password": password,
|
||||
"access_level_id": acessLevels["Менеджер"],
|
||||
},
|
||||
"storekeeper": {
|
||||
"login": "storekeeper",
|
||||
"username": "Кладовщик",
|
||||
"password": "Alex0172",
|
||||
"username": "Кладовщик - Demo",
|
||||
"password": password,
|
||||
"access_level_id": acessLevels["Кладовщик"],
|
||||
},
|
||||
"employee": {
|
||||
"login": "employee",
|
||||
"username": "Сотрудник",
|
||||
"password": "Alex0172",
|
||||
"username": "Сотрудник - Demo",
|
||||
"password": password,
|
||||
"access_level_id": acessLevels["Сотрудник"],
|
||||
},
|
||||
}
|
||||
for user in baseUsers.values():
|
||||
await self.add(user)
|
||||
await UserHandler.add(user)
|
||||
|
||||
logger.info("Инициализация пользователей завершена")
|
||||
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 .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__ = [
|
||||
"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 sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
|
||||
from db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -44,11 +44,7 @@ class AccessLevel(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(self, **kwargs):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.update(AccessLevel, self.id, **kwargs)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String, Text
|
||||
from db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -21,11 +21,7 @@ class Category(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(id: int, **kwargs):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.update(Category, id, **kwargs)
|
||||
|
||||
+14
-10
@@ -1,7 +1,16 @@
|
||||
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 db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -28,18 +37,19 @@ class StocksRecords(Base):
|
||||
init_user_id = Column(
|
||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
accept_user_id = Column(
|
||||
decision_user_id = Column(
|
||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
|
||||
)
|
||||
reason = Column(Text, nullable=False)
|
||||
quantity = Column(Integer, nullable=False)
|
||||
price = Column(Float, nullable=False)
|
||||
accepted = Column(Boolean, default=None, nullable=True)
|
||||
edit_user_id = Column(
|
||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
|
||||
)
|
||||
edited = Column(JSONB, nullable=True)
|
||||
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)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
@@ -51,13 +61,9 @@ class StocksRecords(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(self, **kwargs):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.update(StocksRecords, self.id, **kwargs)
|
||||
|
||||
|
||||
@@ -77,6 +83,4 @@ class ServicesRecords(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
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.orm import relationship
|
||||
|
||||
from db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -14,13 +14,21 @@ class Stock(Base):
|
||||
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
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(
|
||||
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
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)
|
||||
price = Column(Float, nullable=False)
|
||||
@@ -36,11 +44,7 @@ class Stock(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(self, **kwargs):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.update(Stock, self.id, **kwargs)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
|
||||
from db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -25,11 +25,7 @@ class Toolbox(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(id: int, **kwargs):
|
||||
from db import CRUD
|
||||
|
||||
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.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import relationship
|
||||
from db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ class Toolkit(Base):
|
||||
specifications = Column(JSONB, default={})
|
||||
category_id = Column(Integer, ForeignKey("categories.id", ondelete="CASCADE"))
|
||||
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)
|
||||
quantity_min = Column(Integer, nullable=True)
|
||||
@@ -34,11 +38,7 @@ class Toolkit(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(id: int, **kwargs):
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.update(Toolkit, id, **kwargs)
|
||||
|
||||
+1
-5
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
|
||||
from db import Base
|
||||
from db import Base, CRUD
|
||||
import utils
|
||||
|
||||
|
||||
@@ -25,11 +25,7 @@ class User(Base):
|
||||
return utils.toDict(self)
|
||||
|
||||
async def save(self) -> "User":
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.create(self, refresh=True)
|
||||
|
||||
async def edit(self, **kwargs) -> "User":
|
||||
from db import CRUD
|
||||
|
||||
return await CRUD.update(User, self.id, **kwargs)
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
from utils import logger, setLogLevel
|
||||
from utils import logger
|
||||
|
||||
|
||||
def main():
|
||||
setLogLevel("WARNING")
|
||||
logger.info("Приложение запущено")
|
||||
async def main():
|
||||
from db import DATABASE_URL
|
||||
from db.initialize import DatabaseInitializer
|
||||
|
||||
logger.info("Получение данных из базы...")
|
||||
logger.warning({"query": "SELECT * FROM tools", "status": "slow"})
|
||||
setLogLevel("INFO")
|
||||
logger.info("Пользователь открыл страницу настроек")
|
||||
logger.debug(["Ошибка загрузки интерфейса", 502, "Bad Gateway"])
|
||||
setLogLevel("DEBUG")
|
||||
test_data = {"a": 1, "b": 2, "c": [10, 20]}
|
||||
logger.info(test_data)
|
||||
|
||||
logger.debug("Приложение завершено")
|
||||
try:
|
||||
force = True
|
||||
reNewDB = True
|
||||
await DatabaseInitializer(DATABASE_URL).initialize(force, reNewDB)
|
||||
except Exception as e:
|
||||
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}", exc_info=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
import asyncio
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
+3
-1
@@ -5,8 +5,10 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"argon2-cffi>=25.1.0",
|
||||
"asyncpg>=0.31.0",
|
||||
"colorlog>=6.10.1",
|
||||
"passlib>=1.7.4",
|
||||
"greenlet>=3.2.4",
|
||||
"pillow>=12.0.0",
|
||||
"python-dotenv>=1.2.1",
|
||||
"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 dateToStr(date):
|
||||
if date is None:
|
||||
return None
|
||||
return date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
return {
|
||||
c.name: (
|
||||
(
|
||||
@@ -7,7 +12,7 @@ def toDict(data) -> dict:
|
||||
else getattr(data, c.name).toDict()
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
+27
-13
@@ -1,18 +1,32 @@
|
||||
from passlib.context import CryptContext
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import (
|
||||
VerifyMismatchError,
|
||||
VerificationError,
|
||||
InvalidHash,
|
||||
)
|
||||
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def pwd_hash(pwd_plant: str) -> str:
|
||||
return pwd_context.encrypt(pwd_plant)
|
||||
|
||||
|
||||
def pwd_verify(pwd_plant: str, pwd_hash: str) -> bool:
|
||||
from utils.loggers import logger
|
||||
|
||||
|
||||
pwd_hasher = PasswordHasher(
|
||||
time_cost=3,
|
||||
memory_cost=65536,
|
||||
parallelism=2,
|
||||
)
|
||||
|
||||
|
||||
def pwd_hash(pwd_plain: str) -> str:
|
||||
pwd_plain = str(pwd_plain)
|
||||
return pwd_hasher.hash(pwd_plain)
|
||||
|
||||
|
||||
def pwd_verify(pwd_plain: str, stored_hash: str) -> bool:
|
||||
pwd_plain = str(pwd_plain)
|
||||
|
||||
try:
|
||||
return pwd_context.verify(pwd_plant, pwd_hash)
|
||||
except Exception as e:
|
||||
logger.error(f"Password verification error: {str(e)}")
|
||||
valid = pwd_hasher.verify(stored_hash, pwd_plain)
|
||||
except (VerifyMismatchError, VerificationError, InvalidHash) as e:
|
||||
logger.warning(f"Password verification failed: {e.__class__.__name__}")
|
||||
return False
|
||||
|
||||
return valid
|
||||
|
||||
@@ -1,6 +1,130 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
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]]
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pillow"
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.1"
|
||||
@@ -153,8 +277,10 @@ name = "tools-stock"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "argon2-cffi" },
|
||||
{ name = "asyncpg" },
|
||||
{ name = "colorlog" },
|
||||
{ name = "passlib" },
|
||||
{ name = "greenlet" },
|
||||
{ name = "pillow" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "sqlalchemy" },
|
||||
@@ -162,8 +288,10 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "argon2-cffi", specifier = ">=25.1.0" },
|
||||
{ name = "asyncpg", specifier = ">=0.31.0" },
|
||||
{ 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 = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.44" },
|
||||
|
||||
Reference in New Issue
Block a user