From f07843de5a6683c74a28da0c441b5f3b64026b8b Mon Sep 17 00:00:00 2001 From: Macbook Date: Sat, 6 Dec 2025 12:58:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B2=D0=B8=D1=87=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B7=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D1=8B=D1=85=20=D1=83=D1=81=D0=BF=D0=B5=D1=88?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0.=20=D0=9D=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B4=D0=B5=D0=BC=D0=BE-=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=BC=D0=B8=20=D0=BF=D1=80=D0=BE=D1=88=D0=BB=D0=BE?= =?UTF-8?q?=20=D0=B1=D0=B5=D0=B7=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 +- config/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 666 bytes db/__init__.py | 2 +- db/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 8358 bytes db/__pycache__/initialize.cpython-313.pyc | Bin 0 -> 11608 bytes db/handlers/__init__.py | 45 --- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 560 bytes .../__pycache__/access.cpython-313.pyc | Bin 0 -> 8915 bytes .../__pycache__/actions.cpython-313.pyc | Bin 0 -> 24929 bytes .../__pycache__/categories.cpython-313.pyc | Bin 0 -> 6967 bytes .../__pycache__/records.cpython-313.pyc | Bin 0 -> 12175 bytes db/handlers/__pycache__/stock.cpython-313.pyc | Bin 0 -> 7572 bytes .../__pycache__/toolbox.cpython-313.pyc | Bin 0 -> 8226 bytes .../__pycache__/toolkit.cpython-313.pyc | Bin 0 -> 12369 bytes db/handlers/__pycache__/user.cpython-313.pyc | Bin 0 -> 14726 bytes db/handlers/access.py | 16 +- db/handlers/actions.py | 333 +++++++++++++++--- db/handlers/categories.py | 4 +- db/handlers/records.py | 30 +- db/handlers/stock.py | 4 +- db/handlers/toolbox.py | 4 +- db/handlers/toolkit.py | 20 +- db/handlers/user.py | 43 ++- db/initialize.py | 153 ++++++++ db/schemas/__init__.py | 130 +------ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 356 bytes db/schemas/__pycache__/access.cpython-313.pyc | Bin 0 -> 2922 bytes .../__pycache__/categories.cpython-313.pyc | Bin 0 -> 1955 bytes .../__pycache__/records.cpython-313.pyc | Bin 0 -> 3963 bytes db/schemas/__pycache__/stock.cpython-313.pyc | Bin 0 -> 2420 bytes .../__pycache__/toolbox.cpython-313.pyc | Bin 0 -> 2147 bytes .../__pycache__/toolkit.cpython-313.pyc | Bin 0 -> 2657 bytes db/schemas/__pycache__/user.cpython-313.pyc | Bin 0 -> 2211 bytes db/schemas/access.py | 6 +- db/schemas/categories.py | 6 +- db/schemas/records.py | 24 +- db/schemas/stock.py | 18 +- db/schemas/toolbox.py | 6 +- db/schemas/toolkit.py | 12 +- db/schemas/user.py | 6 +- main.py | 28 +- pyproject.toml | 4 +- utils/__pycache__/__init__.cpython-313.pyc | Bin 261 -> 261 bytes utils/__pycache__/for_DB.cpython-313.pyc | Bin 946 -> 1109 bytes utils/__pycache__/password.cpython-313.pyc | Bin 1013 -> 1329 bytes .../__pycache__/safe_filename.cpython-313.pyc | Bin 0 -> 2457 bytes utils/for_DB.py | 7 +- utils/password.py | 32 +- uv.lock | 150 +++++++- 49 files changed, 734 insertions(+), 353 deletions(-) create mode 100644 config/__pycache__/__init__.cpython-313.pyc create mode 100644 db/__pycache__/__init__.cpython-313.pyc create mode 100644 db/__pycache__/initialize.cpython-313.pyc delete mode 100644 db/handlers/__init__.py create mode 100644 db/handlers/__pycache__/__init__.cpython-313.pyc create mode 100644 db/handlers/__pycache__/access.cpython-313.pyc create mode 100644 db/handlers/__pycache__/actions.cpython-313.pyc create mode 100644 db/handlers/__pycache__/categories.cpython-313.pyc create mode 100644 db/handlers/__pycache__/records.cpython-313.pyc create mode 100644 db/handlers/__pycache__/stock.cpython-313.pyc create mode 100644 db/handlers/__pycache__/toolbox.cpython-313.pyc create mode 100644 db/handlers/__pycache__/toolkit.cpython-313.pyc create mode 100644 db/handlers/__pycache__/user.cpython-313.pyc create mode 100644 db/initialize.py create mode 100644 db/schemas/__pycache__/__init__.cpython-313.pyc create mode 100644 db/schemas/__pycache__/access.cpython-313.pyc create mode 100644 db/schemas/__pycache__/categories.cpython-313.pyc create mode 100644 db/schemas/__pycache__/records.cpython-313.pyc create mode 100644 db/schemas/__pycache__/stock.cpython-313.pyc create mode 100644 db/schemas/__pycache__/toolbox.cpython-313.pyc create mode 100644 db/schemas/__pycache__/toolkit.cpython-313.pyc create mode 100644 db/schemas/__pycache__/user.cpython-313.pyc create mode 100644 utils/__pycache__/safe_filename.cpython-313.pyc diff --git a/.env b/.env index 9684b92..c236b7b 100644 --- a/.env +++ b/.env @@ -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 \ No newline at end of file diff --git a/config/__pycache__/__init__.cpython-313.pyc b/config/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1054fe321e34da25dee07364d5d903d283a54df GIT binary patch literal 666 zcmey&%ge<81nci}5Glvt zU`?)DO!>vP*dSVqKyh-59cV~qQGVVn=JeE(A{KBcf(TIH7qNo~4j{3T;WLnAxTT{X zTAW%`te=>dQj}V$?~+=kUy`4nlaybfpPZkUmYJ>}AD@|*SrQ+wS5SG2!zMRBr8Fni zu1El=85BvyIY8nAGb1D8Z3fP}3~~<{xLbHXurYA+_G@)&_3L%&&5*pzrQE=NgNug> LB8}`td_W5UUqE$a literal 0 HcmV?d00001 diff --git a/db/__init__.py b/db/__init__.py index d1cb303..a292f80 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -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) diff --git a/db/__pycache__/__init__.cpython-313.pyc b/db/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cf4a0b0fd2ba6eab211b1745d6dfafba3027b6a GIT binary patch literal 8358 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 DGk+dh literal 0 HcmV?d00001 diff --git a/db/__pycache__/initialize.cpython-313.pyc b/db/__pycache__/initialize.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77f0e7bede578ca18c8f2160089c8b7781753fc5 GIT binary patch literal 11608 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 Z)qf`KSBT{bDZQocRWODd1Y_Ff{{wk0p%nlC literal 0 HcmV?d00001 diff --git a/db/handlers/__init__.py b/db/handlers/__init__.py deleted file mode 100644 index 9fd910d..0000000 --- a/db/handlers/__init__.py +++ /dev/null @@ -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", -] diff --git a/db/handlers/__pycache__/__init__.cpython-313.pyc b/db/handlers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e56e20a659e275f78fc3006b2e8c8aec1b482eb GIT binary patch literal 560 zcmY+BJxc>I7{`NV%_m_J#&b4(PSaIvtCcA$88Z>=o-o+)d4SyW!pkc&v_PC&(+nI_iJ`xm#b`bAoi zW!*)wVW=3NO5Hu3GQ~*Dp6Q4vGA5E<_$#_PGb8Cb$!5{6%Ju7JmY7Y7=P@Hz zyQ0wNlA^YYF^OZ0-@!8d8NG+nMM-2fCFlZ;=o}S=Q8Y$(`2r=vm>EqN?>;Vc}O#lD@ literal 0 HcmV?d00001 diff --git a/db/handlers/__pycache__/access.cpython-313.pyc b/db/handlers/__pycache__/access.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b70ed853bc7324d36672b4cdb60993d96aab1c93 GIT binary patch literal 8915 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 ezyAo-IVv?Lw<}6iTPb^uL{x`lqJoZ&9eJXS z@?H4S3R?w$w&2VMHC0kJ$IL%2@o(n3S$)i$A_hd1}8lseZYI2w6J6K`%_fP*(o3p!nTb&GhIyAzypcC=0$2ANmi+0=kfma_O|Vq|Ofk1NREJR1Ary5z3xs{D z;p5j7GkZg9!#pjpE%R*xyP0RV^5$&|26X}?$6;s1Y`-BWr~koJI{6!ZoN$?-6j^V71=eS%MJTB z%G!X?mB>7mgn5G0m&iOf%^#fa;&;KxrK9}t7(ezb?|3fG3h(Ap#k=UcS(J0*qKe9O z->Z^o3h0c?IB>FqmNHxJoKaJ`>%X|efMEH>9W@AscZ^il+AkuO&OheV-KhZq;!ZJT zZGjvxS>Mq|)9!2lj(+Dc8phyaF9QI65%_12nqMdwqv?N3kD8P})I#jF%F!(46_XmG zS1h*CJmr=3WpIBt-v)2qT}O}FmG7>{jPJHqjutB4E5-NkZJ|d?l<#fbiDi9YrAN!P z9~9^?Uf$LO!G}(I)UEum5WoAdeC=qJ@}n&L?xVHz=sM*`Rrvm+hRV?fP(ddUTWV)2z;Fc=!jXaI5|wwqpAKFt#Zm_{S^*-U|`(e=Mi* z?k4vt@}_~@H<346@%}m-FxCrZ7c{W$bkqCcxG{{nO%d(jpnvGNZ*VZ89~?YBF%kr$ z;9$f!I0z?xk4IB72M3=R;{uc8V}7510+JQ763G#baR(F?Q9cfOo1`bAR!Edb?yoT= zcMwiNh7)y%2Ztsnxv|HClfJ;126tz$^SO=a5jpUouf^xkoSG43W zC?LEEp$bCaG}*9k2xD3*CvQQAF+CiLvtYoOk%D{~7&B2>mV22Pdy1x9Ws6y7Xq8ih zdf$!~ zVOnLG1>bNC=Z2;5^8WkedEq%YO(lOZbFIoELc=%*j#MhFZ&Y>|Pl2lSum6nc?Rs|==#siYk0||&wmIORfRz$l= zqU$Z(EIa~XQ3138EhuQ3{(CCpQ>yY)s)qb3KBd-uN;Uj<%KEwbl$N&Kq3}+M`aeGE BL302A literal 0 HcmV?d00001 diff --git a/db/handlers/__pycache__/categories.cpython-313.pyc b/db/handlers/__pycache__/categories.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d826a6c17a1b668c592fcfe9a1888f4848c40ef7 GIT binary patch literal 6967 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 literal 0 HcmV?d00001 diff --git a/db/handlers/__pycache__/records.cpython-313.pyc b/db/handlers/__pycache__/records.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d09f9069deb0d09d8d83d11cbb23b7c8ca850997 GIT binary patch literal 12175 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 literal 0 HcmV?d00001 diff --git a/db/handlers/__pycache__/toolbox.cpython-313.pyc b/db/handlers/__pycache__/toolbox.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ee38f6fe8d9e9a606321a6ef5ecb42aeb31d656 GIT binary patch literal 8226 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 literal 0 HcmV?d00001 diff --git a/db/handlers/__pycache__/toolkit.cpython-313.pyc b/db/handlers/__pycache__/toolkit.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87a2ac1545426ca75450ec5141272752ab99bc91 GIT binary patch literal 12369 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 literal 0 HcmV?d00001 diff --git a/db/handlers/access.py b/db/handlers/access.py index 9744979..037354e 100644 --- a/db/handlers/access.py +++ b/db/handlers/access.py @@ -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 diff --git a/db/handlers/actions.py b/db/handlers/actions.py index 0bacf6e..7900fde 100644 --- a/db/handlers/actions.py +++ b/db/handlers/actions.py @@ -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']} успешно завершена" ) + movementsList.append( + { + "id": sourceEdit["id"], + "quantity": takeQuantity, + "price": stock["price"], + } + ) + if not target_toolbox_id: - writeDownList.append( - { - "id": sourceEdit["id"], - "quantity": takeQuantity, - "price": stock["price"], - } - ) 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 diff --git a/db/handlers/categories.py b/db/handlers/categories.py index ed6cb25..45d1c9d 100644 --- a/db/handlers/categories.py +++ b/db/handlers/categories.py @@ -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: diff --git a/db/handlers/records.py b/db/handlers/records.py index 1d5948f..d17ae2a 100644 --- a/db/handlers/records.py +++ b/db/handlers/records.py @@ -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) diff --git a/db/handlers/stock.py b/db/handlers/stock.py index 6c874a4..3463ccc 100644 --- a/db/handlers/stock.py +++ b/db/handlers/stock.py @@ -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 diff --git a/db/handlers/toolbox.py b/db/handlers/toolbox.py index c78e834..21b8d75 100644 --- a/db/handlers/toolbox.py +++ b/db/handlers/toolbox.py @@ -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: diff --git a/db/handlers/toolkit.py b/db/handlers/toolkit.py index d6a8549..bf550d5 100644 --- a/db/handlers/toolkit.py +++ b/db/handlers/toolkit.py @@ -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 diff --git a/db/handlers/user.py b/db/handlers/user.py index 4955875..ce2e82d 100644 --- a/db/handlers/user.py +++ b/db/handlers/user.py @@ -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 diff --git a/db/initialize.py b/db/initialize.py new file mode 100644 index 0000000..065ee9d --- /dev/null +++ b/db/initialize.py @@ -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 diff --git a/db/schemas/__init__.py b/db/schemas/__init__.py index 314ee01..adefaaf 100644 --- a/db/schemas/__init__.py +++ b/db/schemas/__init__.py @@ -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", @@ -141,4 +13,4 @@ __all__ = [ "Category", "Stock", "Toolkit", -] \ No newline at end of file +] diff --git a/db/schemas/__pycache__/__init__.cpython-313.pyc b/db/schemas/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..651e7d68437a099c7667323ef2a953206d448cc4 GIT binary patch literal 356 zcmX|+KT88K7>ASGzf$RXzo0GfC3b70xr;TORy)@Q`A%3 zQxXV+0zE*L&Z0d2hCVKb7M|p(jh{4EHoazyYi+YfDXXb`vx6II_)MzWCWVn~-cIJi z+~=8%S6hd6j`5D4*dc2j!F^CT3_*!*bwX%W5wZqN_>?`v;a!dCvQ)grxBL}%1}>*q zvuD1b8WSRx!Vq%$Qrp4hLYAwFU#YPV2w%`gzHK51p#ysk>^pGaz~Q!!f75ISy=Q9I GU-<*5SYvJg literal 0 HcmV?d00001 diff --git a/db/schemas/__pycache__/access.cpython-313.pyc b/db/schemas/__pycache__/access.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b44e0c4cab94c546ff52089c79663bbba2b8304 GIT binary patch literal 2922 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 literal 0 HcmV?d00001 diff --git a/db/schemas/__pycache__/records.cpython-313.pyc b/db/schemas/__pycache__/records.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdfd98ad7e6d7c7fbe6d37f2ee41b5360da07bbb GIT binary patch literal 3963 zcmcH*OKcm*b#}R2E-8{rO4QeqL(8%oCaP%1l2bQsTuZcKQ#Au`4-UYjE2 z*`;C`IS~CJNe@*E7qdR!O$95w;Bt8U$(Gfz z|F-)OnK8KCpcd~V?6}qra7gO_ctT46JgIfEuM{JxE*{bBO8%BLP4g92upYx7gD%^MS>S<*i`vA+%%x56*a{F!)TTHuS4$;h zp+qO|hHY@(g~}aj0sq}LT-`ElT>M+9T9n}|2TmC%&_$zKvRNP8WK&@M@JWfz2Srzx zF(C5bmwQ8oSsVNtIRN*_tT5e7_1MO|ZwI*yDpo~8mXFVQaY)E@an`pRl}U270|h2Y z5zfzn?taszOBN4U)HZCJZAx6SXlaqlx85>X(c(VGCM!m%N-YV_Ny6ai^ek$XHp&G? zSJN4~lJ+c+E-a+2{0+KfSZU9~Bg?B?(RH(I+PcoWTApNN7ymU7-8c%MMjrMhe?9#E z@UKSR&Av17DBQ7@xnKQj_{0+*Ig)CirY#0SHSymV0dSAZ0d&VSm|_1IB(r49GvE+m zNuZ+%s;kevp=6Rj6_nbF-F=vFoINKO3Y?f2fkpZSY= zY+E_Tz6#iun_@ElKJs4%a2%fCi`-TdH1V8x1=95m&~in%=gaxSWQZsvC%x)h6gGXi z8Ay8pMi&{iZfuGy3evfpXB1qCy#k9SP2g>f70TONRYBYZycoLUV^pQH_vdpzn_Ew8 z*!9lQkJQm^Wwfr2e%1&B!}odzg6iXN;edWWMlm-5d@=pz(4(9&OQzA)_l;5U z6u5jwI4c}(0p}G{lBc&DLM#S?xM&t2xbQLANdOi)MACgyF42N%?>7YE#*q{EG#TPI z16$G`1AhqAJI6mb3*aMld|Mf>tK*+pjzwNNBm7o4E&a}aM%W~&7t!0x#u5c@59#_+ zrBE&5Jgn=tt47JAsJgyrGRrQRWm>KPIil+}W)qjr&|6p#!8Z|L)#9pEsj@uPoz$d5 zW>Rg4QEKlY7`6gjHuAPvDf76;?u909h>DP}BO#R@cUS|CaHFbp#h zm%+6x5U+qXW!tn@Spd;~NTPYl@I|>|k%a7`oh7uDr!V zuFDp5EcJMDfjaY`%kH4v*F`jY33p>)vach!h;ll0u!Lcod0>FHV4-SLOV`;1GGI8m z(Tu*~I0(kA^+SmBACZTNq1x4d1l6^``pj0M9z0tc-|6aqsHi{fyFay+s>jbius`vM z-E!^CPh`?Pa2O?;Sesm*tw&z2P44s@b-8j|->Jthd^iOAd*yAZ69n|Wa44z0$0c3e zx>Aq7{$VHVYL{zS*p%pVDcP;wdi=Eq6etO~_AQ|7p5##qK`~TEmkLPOHCz9wt`9u-?gHL3^ zKlTj3gy6mqK29F~dtX5H?$BmEUMN9twc-<1B`1C zfbCk8brjW9jH?=DE8y&Vjm0EL;4ugr2fNo=FsQYJ9YN5Apc_FifJrt0uqAt9G<->g z-pP>c7#=-% z;BymwU^n492@|?T+kg(`dB$YdaOZUd`|{vC;_%F-g!f%sBEXZaZv()Dck)2OgZRGo z{>_cP56t?}%iG$udh~kjjUB+wLQ0Fi4D)?a|2&Q{gvZQ2pYs+wEmuyDEM<>>Q0BL&ka(L6NL%N5HmLW6U=#2k}|g#{MDE!gd_QwW^2 z?x@PmroFckIBHNVo(r2B>)--o+XHI`tPLoZzTLwenprN X)-q2CexKeHV#3&3;TeICqw0SET^EXH literal 0 HcmV?d00001 diff --git a/db/schemas/__pycache__/stock.cpython-313.pyc b/db/schemas/__pycache__/stock.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4eda2c89c6dd736eda2b743ecfa90d1c83eef88d GIT binary patch literal 2420 zcma)8-A^1<6uLbG=eR6S_^d#LtVqPH zM5L@tWzN-K|; zlIEyV)LrZvWt^6&JZ_tnvPG2)tWCihl`fdJ?$Xc&n_#14SumE;JQdIB4yNMx#s~izjo`|zjH=31I z0sh2MfdqVgRsjx@DyxA}URBf}Sc0=5SPQ9PH3BPw8Xc8Lcn`X1A3EZpWBbt2#$L=H zPntX}=6UByv!}(uFSE6HTI+0L^7vjV{#ax^`jlQ4ltxvcu-i6vQ{YM2xadRiF5rd6r)r%qDj61*TXP)70loOaoIse6>Y4&AyZm>9@(!f^JdyvTaznm=O=0U8ZX>M|_wE-Jx{I;TtafB*7LQ6d}jgm&_4Srg9Qs60c zsg*fQp%U8U@(?h|aBk=R)0+D$tFJ9G2$)SQ5$YtHEU`K>~QZQ)$;IxB@ zGpJidf>#DJczLivbFer!=oA)kS$77#tPCuzP({-W%WyS~HhEbac%mN#ht3Nis;J(X z`l9cXzRw5V&E6P)5NTb@+^qZb4ry{`B8oX`r87qTjthDpk;k(LkEjIj@!+gHg(an9zrNqyqZtg$4-Uoo!m7kB-467S&OruFw&`*tF{$(ia z40n=yHgi8~VuP=LQAiUwqP$UrII!fAUJwo|fRvv?D&nF6E!qzNo1b8aBOvw!;LGHO zcssBes3pg?PJ*}>8@sEF)na3fNE{#HzT!@ZUrR&WCd#J|v2wQbGKO*vXWl5?Zvkf781gGJ~_oi00a%OI|O}7Gm3oZ<%C>)eY**@wEHjRx8AJ9U%PYUxn62+TPtlywb(%Q;{8a=+Q_Yu zS|nXPe;?MDH^f?Ve{}**b5q$kcDrx0uNFU5&F;~^ZU4UOyRKS1U(N1_y!7IZ!b#mb zGAE5Z265VF&Pbl@hVqWGCs;^*VjK zP8-yN1rjQ7ARt8qfuI5o$%+06<-|1>5^aW772;G*Z45%(c(ZG(4XCJN?VI=J{bu&P z-+Mc5baccBj<@a~cN!8Qzj32Al%~>{M`fKD#1JM)kpRhXU7Qq(B8Wu^q@oORQ30i> zf?Cu-E9#(!7-_Pt7=u``9oj=%o{U4hk7S9V3=l(|i;dzt`GAfkIj243YEEFfZ3Q%N z$~31j?R2H$Qp;oNM8&O^J*F4XSwbg^ovi?Mmb^*2%Cz%dK$j>m)d;}xmY7_k*MgkL z(+u5c8y5}kYaf@PBlL9|aiar~iO}krOHL2A0(mlrh@SsLJY!!c zMwBuV0|e4W7s?lmB+6c+d#-OpG}caRaZ9S9M)$-lgy8RiA=C?AT13IAu(nDxBf& z3T|_0K&No4X46Hh>IRU)y=QX*^zt*raN@}7GB5UmfUB*cqSB5}V+zGOnGvR+_BK(Y z&AEr1OA=BQ30Xcg+w3MnE{Zc!tG6=bLJvPMLzc*vz++OJfR=rx`82SC0CF;ued;bU z_3~87_D6*j6r$6wx+$i7^I=eUO& zo*;i3!zk9tEXrt1BLV*&Su#ULo74>w7(hQyz8W~L&(xq&a2A3s0S@50&tVxjh!T#V zV4bayBUm5DAkP@^y+rEc>mOdAcq#|1r?o;^ zyp;sE_HoF56c2e4>Hg1VKb^gq+6?OHkvoZzZGEJk82Q`hr@q^t6}}KM@|Q|hV4`FH zC%caj)OZuc^JOv+_wpu58~1AZ?=e&C$|{GN@%ZbOYJffKkC z_`eQox2eAX&T`;XJP0o%y>iu~U^+H%&RDEm!7r%7FLdCy0`T5vl2^IPVo~L7(+XHD z;)v^zH1jerRi`o*dg_-|qWy{}21? z$*Ed#R~D7SySkuccU3_deT3rVn*SHUDT+Gmi_VCcn198!Tz(U+hWteZ2#MqAM5W+W zi0BvXdEobK*l2K=Yhn6(DPj8SQTgA1GYH`qg?|XeBS{d1`{cj_V%#A{ofr>D|1Tu| QhuSBcykY-Ia0(;*2SbbL+W-In literal 0 HcmV?d00001 diff --git a/db/schemas/__pycache__/toolkit.cpython-313.pyc b/db/schemas/__pycache__/toolkit.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0eeef132df50e106b188253e60e2d4cdea5e228 GIT binary patch literal 2657 zcma)7&2JmW6`x)1E?4{zC5qIS9fzisIBaCfN%Uc%MuAv#;7}EdYNaB81T0qE5w-Dh zmwvlcOv48v1&XveR48CnMS|3AbD$1^{U@ZSUQGn3rD=?yD0=XziIt-5t#5Y8L`IP` zL-5VKnKy6V$M3y8P9|dn$47sE#rlU#$lo~AAIJyeco(NTL?fE8Ov(gEj<3aKp)7(} zjzFX=fmD`3E-RpvqYw?wBg<-e00x4!v>YqPAs(#dMP zG<7pM7YUz*p_)<{c+8oCz*N)lsc+S3L1D_0Yd7l-Q%ktFhI=gbnhVsbIm@)gqOUtX ztx;f#=7Z(bn6yT3`lv!KYd@-7!9@b7ZTOb!cso|3ATsHS;ZY_nt*)0U)$s9NkGsDh?)QqO$2FE&XY|=i{MXMj6gKt)qt0g9|-su^2(wJ@qkNQL_d*BhIcmy z(Qj27&|-_?oEW|asm&qWjcW-lsSV=o)S?W-;q5uJgAYiD_j|fWp5ihA$L-+nW&>_? zb8Piuc=sd?Y?=5riEp?!g#Jx_+r2>ZssAf+S{t4u@Qjv5dPW;T`mC1O%*~2gc9Og) z!C7q-^0l$TIE${iu6^C|9}l1dO!Y&~Tf_Xt8(`H9XzAB6{j%kl^d{~qO~<;?q%eZ= z96(Ofwv8>D;yP;5ZKG-Xw~|#vP|XF4dPU1*(WO_lrB_Q=xwwmJZ0b{{E`>6!TWQ0# zr*q-<1s57ShQpLMUCW{7YXyl#tA&+wN+Atwa#7*PeMz3lJc|%-}^lKo7OIFoC5MT=TJs#m4ynqzAYBtz6ITU-2%V3<*;7?PT zU*7)3?fudxsk0B%v+yGBK4l|snLow<@iippFznyOPEF9nMe%iPsypc4u5c$(krQN! zsH7q-M7D)Oq_W0A3v?T(w}SvmqIRZKfg*fh2ALB+U_Omq$`XAiILCeSP9u5Doybo7 zdgE6ccQYNoo1J}_nth)v zhhv?5H@k3OLGm!Q@IYPYrWSfW=L*8_h4a!M)RH1zK!MYxOkk@b)WZ_@Eo|~5alGWn}14}>|4G~A?RRnlX_LK8vHv1 z{3FZ;y#}pX+ZOgD{%r!@3s}6@p<23S0`Cm0-?Lgeb`Jv!x^C1k-)NeH>RWo5*%x>b9_SK{G_6;wAev46f( z>W;kB{^8NYvv;HSr@9jh52Vqf%;f$&9%No>uN)1J?W>&^?oA&|cZV0+63htr>U+h8>-jb86F2NW zs=iWJbfF$%NHn*AmwQlWaDlIalsug#rd|%g{{vV@5#A@g=aGCK5d`58dFC@xen`rX c$i+wG{AXn1FC_7$a!$DPj`>f5Q{dTu0qB*8;{X5v literal 0 HcmV?d00001 diff --git a/db/schemas/__pycache__/user.cpython-313.pyc b/db/schemas/__pycache__/user.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a3ef5896289d2c59a70ac31de0333efea3e40af GIT binary patch literal 2211 zcma)8-D@0G6u&b&GyCl(%}4X4Zqqcl8ndnWC|k79WUK9lM93tzlHf4ez1d7V*-g)# zNt)7!MM0`Q7?fZ{(G&y|Q0$+ePx_dpVB2d;5uf@d-HQ0;xicF`DwWw{aw@1f z4YXVkf}TGx7RrSooQpujvz4(ZMB7L=(bW#3YqQ~@fWJS)ib^W@Ko}{BhYF@c9cz)M zG#(r&mu+g6xHek0-Nh0QWzji>P9Ax^4Ad%?#^@>!o-H}FNP%m*16HY+lDRTsGRl?F z$?5Dw-v5j*Lv^DTkwi|j1SFk+tV>6dE!3N*e|gDNB26(giwiy0+85gk_L;ux?&SnE5i;97G^YV0Hmvu=pjvtGXBq@?@X#%rSEr%{L9%R%p9S2ehR~WVD zxpr~Igd*bs4})dXb}7TFLXtRmUs}M%(q^dubTyr&%V`HmJ6FD(F3hD_et|BUJ~sVJ zt2|^FR>^VzyRoLV?1uYm?8w6a=}gr` zcKbjPsfnl7k7Aw7p!CONooKtZcz2U&vP;e$0&O@Y(smuoW?Xa1Su5{sOVEk)OkkUY z=TX8D6g=vk*N+hknW%k*+)XrpeD%Yt8`+zQLpz~E(2cYE4k&uad-&AOpg4?ZKg}U^ zNtZM7S?n?knB20o9+*&Lq=$sagmOGEFQoz#Q-WPU=Yg^XOr z<}&4BF9V@^33g%71Cc>X`)4zs%xpGqIn|cITZzG)&|oz&_z#O=z`O0;(ic)v`BLqc z{*%^67*M-_;_1?w5uEYHG?^4+erycNN0HQ7X;^x4;(pDPdb<84fjDl&WvhTNh|EA_ z#zgs(egU~c3zk#QU#Nm;OjLDG{@*ljI^V8bU#YfaZuFwKmB{RbGSx(;4#bNC(s$A^ z<$HBN+9u+*Xh0noO5WE~or2jVbCDvmBZje9F1WVPV}^0bHSM}5VHookFvqq^v{V+C zz%U$h&ZfQ}1Xn;bXh;hYM!{9PT(q$9yLeqh7+04T%1)Ul7EHE43&xVk*h(1+;5Dv& zZN{()ATZ})iy3C#v6d+a+(BFz9w?Ppc*JK+!7v>j@!=FYBt@7&2woE6C<@+U7z`VO zmB#@5nVEAP$_xWu5dnhs{$&xx@GueQVb@W-Pi{ANR>pr1C)R?SQ#YEc;o-{2o!Ei3 z;mw|{LNzu}IejP5vNrGe62p}tG(xL zww}kihSs%}Ev4Guu#)BK7trvdi+wQA29ApU{y7zsT>^AOmN y3J<;NQ{p6?#t_k4SQ5pb0ZEeXk!SCd@mpm4K574r#2#sF(ur$>e-V6nC;Jz(!TM(a literal 0 HcmV?d00001 diff --git a/db/schemas/access.py b/db/schemas/access.py index 7004bc8..a281ef6 100644 --- a/db/schemas/access.py +++ b/db/schemas/access.py @@ -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) diff --git a/db/schemas/categories.py b/db/schemas/categories.py index bef42b9..2c00b14 100644 --- a/db/schemas/categories.py +++ b/db/schemas/categories.py @@ -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) diff --git a/db/schemas/records.py b/db/schemas/records.py index b84b19b..d56cb6f 100644 --- a/db/schemas/records.py +++ b/db/schemas/records.py @@ -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) diff --git a/db/schemas/stock.py b/db/schemas/stock.py index 6ccdf81..cc980b1 100644 --- a/db/schemas/stock.py +++ b/db/schemas/stock.py @@ -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) diff --git a/db/schemas/toolbox.py b/db/schemas/toolbox.py index 14a1a17..9520d1b 100644 --- a/db/schemas/toolbox.py +++ b/db/schemas/toolbox.py @@ -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) diff --git a/db/schemas/toolkit.py b/db/schemas/toolkit.py index ebf4c22..7396fce 100644 --- a/db/schemas/toolkit.py +++ b/db/schemas/toolkit.py @@ -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) diff --git a/db/schemas/user.py b/db/schemas/user.py index ee7203b..d5c9f0f 100644 --- a/db/schemas/user.py +++ b/db/schemas/user.py @@ -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) diff --git a/main.py b/main.py index c771ae3..ea265b6 100644 --- a/main.py +++ b/main.py @@ -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() \ No newline at end of file + import asyncio + + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml index 31181b4..e2bf2b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/utils/__pycache__/__init__.cpython-313.pyc b/utils/__pycache__/__init__.cpython-313.pyc index 0d862dbe0b9e8d99444dbfa2814f1bdce2484218..7bbd2249a531d089b0b910a0c92045185a9052ea 100644 GIT binary patch delta 28 icmZo=YGva6%*)Hg00at$bTi8*^7b?5CFV|?p9TP9ln5jM delta 28 icmZo=YGva6%*)Hg00g4iI+dihs2ZuOnyagtqM+(wrRr;?8m!59 zi=()tD6J$jHx%vQa+7y6von$~d`zQG^LXO}ro~&S(l$3IZAsiXkO}fdMA1KG~d+j}fFrhR@$EniHyoAp#_1 z$*YVeA{)&MQ-Pp^nPE1>Gx@GzVBls@0JGq$sTTH)Ako0^L2PmwbH2hCHU=Td86xw`W|mzRRJ$&ye^F5Xf`Rj8L6=Yb z?3}KQlixB+`OS1PbmnF81Dl?iTmm*~CBtW+ISfVoK;jmMO>TZlX-=wLkuZ=8wnY%g Z_`uA_$oPSYhmmc9%U1>7%Q6n?X_UjMAUwVRrX2&L!4jkwSap&Nwl^Tu02?;4TM{Wgz3o~Q8sVX6!H1ECnX6C(l zZ@wAiav9+A&EZAwR|VjAF&T?Cat52+xeFFpWErXii79+#S*cQ_RTb500uvIW%UU&w zNfS26S+Ga4bDGz^$}dKv7+FRn-jua7RoF7Y96uF_ zuqK@FSX#XDC%zp}URP(RmG&kgu0Fg=PxbceU7KdXVSh$>{&e{ko&s-F6vx7#moniDr*=RWX-gT}CTYT3^b9mfnXZ01HKwGwEtohWo!N9e-)+2Meo)S!$chE7 zm?rmhQ5A@27>epJDb|uZ4hCM(Vrkp1`~0D7o26_!a5x|BvMS#hUlKbLlF5v?k%(h* z!3lv&c8oGiao{u`QJt&j-n6f(iQ++i@^QZOAYZyS^Dw{gS)!kTY+-lhuP159nU6{r zeq8S9?+ifUJ5$CT;|rtr^88QaXZ^nZ`0~nw%PYP4YrWH}k5bnUPMrFupWyrcjs%Im zEKb9>#l_d@{qu`6^p}|gcUQ|fra9OOg9|9=!3x94tb4oecEksbUT6o)NwUrkzi1@8 zBilD@q)5cnTb$P@iu@k1$jz_=sW;nUW6O8n#%Yd;PZ8bZs!s_azd`9wMJ37+aP>3` zq;#Z#@yfn&Vn1Kp&lir8s-f&=`i4SY+oOHp<{o__Oh1z*g}qn%z|D8>N%O literal 1013 zcmZ8fO=uHA6n?XR*<_n6rAd`)+pQLLZD|g+1&b|&+ENsXbcqNlNPZ@1B;5_Oo3`=P zqn?%fXVR0Wcf1! z_{J|i(Yi)wg&BL`fP)^w14NJo>*yLdQW80`quiB<|T zvJuf4)91bxok*sAO#EclEf8E!-p5x zVvHQ(Q3lwt3oT>t#27g;M)vE+hTT-`4ue5>rV{sNIlzvQML%KoRUk* znJ%It%*&%tM7Fw0_@ivLTE#1sFnK=wsM`-~3zKrO%*I&uTniK8kvVJG4$-LS+tiuA zPx=|3@XV+t7W9D&jg7g?DLLTOb9Q6MtJ+rf{@D$;8Fc$ zcI?8bGTv4cHPJGMPRy|*bF5(>o3l-Q_IJyOcR*4TU-j6lzL$N?OVb~n!~PGC4_2Cq z`7ioH%Z&eRtIXk#A7`On#E0Q^Bo&k24yVHMr?AS*RaTYB_n=bFO?B6a@G*+I$RM}) zLQo~gR!5}v9*D_dk+*(t(<{^}c!6AH7~ea8i5+bjA#@72erRA`X{DlCX$85h9t9_Sm7zP ztwn81yfE^ZzWJ#3X74lo3y3&_b9`~8eW){ih=YCbt-rHp1KRq~Gqd0G+25Y;{?2{b z<*EcMHrI~$V=KV#{H0v%CM?erfH|N*$x+ZmO*9e$Dp5H?sD)ZlZL|W_P93O~R6%u8 z7pj}CL9L?Is2=J?t)aE3Yw0@F_4EzYI=TUMBi)2rPv1oK(FW8;+7#h=n(1bI+d{XZ zZliwG?X(4T2W>?S&>(7v?nK>1+fdtS2WltnLfuXGpmrPCdJOJ0`n8wt!}xxBAVLli zO&XPkD#`<2tug9YeJfLk30^n_r-&|xK$pC5WGz5e0!Wi#7*fh@SL?ECm_(%G>j4z- zfi5RW5;|~xdGP@~IBKJPqqcSn?XQD#7OjGYS0aauh<$?SQrS5U{=-kXb&zLI%jte|_0oEP*JBNKwYW+W!)8%9)vir1Jll@h^knKb>L!0#BD7IdGHxS;PDnG*B^ zBkv3Pk&%R;pBR}G^fMz#LBB9!{`yx&(nhS9V?-15AtSn=i;QFheZ)vs(8r973;Ki+ z(W~MmM#e!5Jp$CSGQ;UJhSj#1s&ITJs?SCB1&-QGG>aHzmW+BK!)%(G z$!f_lb7lAxg9RTMpXFUBcpk{m%U?0Z3zh0%7`#wk?ef|-gI^xh>aoQu|HLx~ZTKu7 zLutYtFlV@r)Bz;P2*yMi;(7m7-mdQ8YmVhS%vpVKe52S?6VN4(6F(XBOU!w4=-?nd zHgGa>{NM>DPpBCTN`a8J0c|?B`+O`E9M)A$4@Q$QO`Qsc)$_qjDwQ~!x)97};t4&d zN5|F3cs!vdqmybNJ;fZo6KYbukk!6 zm+;BqC58)%cZd};t=vB&PVo(3XoboVfX0=5N^PyU7gzp{cbFUZ@-T5jn~Zz6VsN^0 zs2YjAIkdqnnk5P7cSYr zSvA{zx%+BwF8mx{+_RlmI`gdsZy~m*w7eYKJnx;ySksc?FDm}qO^Zsv^ylTgXJ*fm zQa^7q4AnI^!?y;m58OCfXfD=lS*+Tc8+hdQ%$&;a%%900T~K@r7N6FNUGY1#cC57B zTsEUHTQ(iTL%^(f8YXpS&8E{h4Kj}2m=|LtICM8R@Pls*IusIvw)ak^V%dbcPuq{5 z_@!UznNw9weoWA&hC-X5?SVV8pw905{RTZT99DEZ zK}W1)5IWig84e?6I5^?NS{<0BLU460<}tax5i^#x)ZxOt=6H9>u#lV4wfxd?|{SN|tU&ad2{9Ct9Upsx{ zOo3b 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 } diff --git a/utils/password.py b/utils/password.py index b8327c9..2fe84da 100644 --- a/utils/password.py +++ b/utils/password.py @@ -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: - return pwd_context.encrypt(pwd_plant) +def pwd_hash(pwd_plain: str) -> str: + pwd_plain = str(pwd_plain) + return pwd_hasher.hash(pwd_plain) -def pwd_verify(pwd_plant: str, pwd_hash: str) -> bool: - from utils.loggers import logger +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 diff --git a/uv.lock b/uv.lock index 3266a11..e1ce415 100644 --- a/uv.lock +++ b/uv.lock @@ -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" },