начало БД

This commit is contained in:
2025-11-29 14:51:45 +03:00
parent c48d1ee383
commit c59db5819f
23 changed files with 713 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
DB_HOST=10.0.13.3
DB_PORT=5432
DB_NAME=tools_stock
DB_USER=tools_stock
DB_PASS=z7kWLkSKa6
+1
View File
@@ -0,0 +1 @@
3.13
+1
View File
@@ -1,3 +1,4 @@
# tool_stock # tool_stock
Учет хранения и перемещения инвентаря Учет хранения и перемещения инвентаря
+11
View File
@@ -0,0 +1,11 @@
import os
from dotenv import load_dotenv
load_dotenv()
# DB
DB_HOST = os.environ.get("DB_HOST")
DB_PORT = os.environ.get("DB_PORT")
DB_NAME = os.environ.get("DB_NAME")
DB_USER = os.environ.get("DB_USER")
DB_PASS = os.environ.get("DB_PASS")
+24
View File
@@ -0,0 +1,24 @@
[loggers]
keys=root
[handlers]
keys=logconsole
[formatters]
keys=formatter
encoding=utf-8
[logger_root]
level=INFO
handlers=logconsole
[formatter_formatter]
class=colorlog.ColoredFormatter
format=%(log_color)s%(asctime)s: [%(levelname)s] %(message)s [%(module)s.%(funcName)s():%(lineno)d]
datefmt=%Y-%m-%d %H:%M:%S
[handler_logconsole]
class=colorlog.StreamHandler
level=INFO
args=(sys.stdout,)
formatter=formatter
+115
View File
@@ -0,0 +1,115 @@
from sqlalchemy import delete, update
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.pool import NullPool
import config
from utils import loggerDB
DATABASE_URL = f"postgresql+asyncpg://{config.DB_USER}:{config.DB_PASS}@{config.DB_HOST}:{config.DB_PORT}/{config.DB_NAME}"
engine = create_async_engine(DATABASE_URL, poolclass=NullPool)
SessionLocal = async_sessionmaker(engine)
Base = declarative_base()
class CRUD:
async def create(db_data, refresh: bool = False):
try:
is_lst = isinstance(db_data, list)
async with SessionLocal() as db:
if is_lst:
loggerDB.info(f"Создаю {len(db_data)} записей")
try:
db.add_all(db_data)
except InvalidRequestError:
for data in db_data:
await db.merge(data)
else:
loggerDB.info("Создаю запись")
db.add(db_data)
await db.commit()
if refresh:
if is_lst:
loggerDB.info(f"Обновляю {len(db_data)} записей")
for data in db_data:
await db.refresh(data)
else:
loggerDB.info("Обновляю запись")
await db.refresh(db_data)
loggerDB.info("Запись создана")
return db_data if refresh else None
except Exception as e:
loggerDB.error(f"Ошибка создания: {str(e)}", exc_info=True)
return None
async def read(query, all: bool = False):
try:
async with SessionLocal() as db:
loggerDB.info(f"Чтение записей. Все: {all}")
results = await db.execute(query)
loggerDB.info(f"Чтение завершено")
return (
results.unique().scalars().all()
if all
else results.unique().scalars().first()
)
except Exception as e:
loggerDB.error(f"Ошибка чтения: {str(e)}", exc_info=True)
return None
async def delete(db_data) -> bool:
def itemInfo(instance):
from sqlalchemy import inspect
state = inspect(instance)
if state.identity is None:
pKey = None
pValue = None
else:
mapper = state.mapper
pKey = mapper.primary_key[0].name
pValue = getattr(instance, pKey)
return {"key": pKey, "value": pValue, "class": instance.__class__}
async def deleteFromDB(data, db):
itemData = itemInfo(data)
query = delete(itemData["class"]).where(
getattr(itemData["class"], itemData["key"]) == itemData["value"]
)
await db.execute(query)
async with SessionLocal() as db:
try:
if isinstance(db_data, list):
loggerDB.info(f"Удаляю записей: {len(db_data)}")
for data in db_data:
await deleteFromDB(data, db)
else:
loggerDB.info("Удаляю запись")
await deleteFromDB(db_data, db)
await db.commit()
loggerDB.info("Запись удалена")
return True
except Exception as e:
await db.rollback()
loggerDB.error(f"Ошибка удаления: {str(e)}", exc_info=True)
return False
async def update(db_data, id, **kwargs):
async with SessionLocal() as db:
try:
query = update(db_data).where(db_data.id == id).values(**kwargs)
item = await db.execute(query)
await db.commit()
loggerDB.info("Запись обновлена")
return item
except Exception as e:
await db.rollback()
loggerDB.error(f"Ошибка обновления: {str(e)}", exc_info=True)
return None
+26
View File
@@ -0,0 +1,26 @@
from sqlalchemy import select
from utils import logger
from db import CRUD
from db.schemas import AccessLevel
async def getAccessdata(accessId: int) -> dict:
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return {}
return accessData.toDict()
async def editAccessData(accessId: int, **kwargs):
query = select(AccessLevel).where(AccessLevel.id == accessId)
accessData = await CRUD.read(query)
if not accessData:
logger.error("Уровень доступа не найден")
return {}
editedAccessData = await accessData.edit(**kwargs)
logger.info(
f"Уровень доступа {editedAccessData.title} успешно обновлен, изменены данные: {kwargs.keys()}"
)
return editedAccessData.toDict()
+19
View File
@@ -0,0 +1,19 @@
from utils import logger
from db import CRUD
from db.schemas import Toolbox
from sqlalchemy import select
async def addNewToolbox(toolboxData: dict):
title = toolboxData.get("title", None)
if not title:
logger.error("Не указано Назавание тулбокса")
return {}
query = select(Toolbox).where(Toolbox.title == title)
toolbox = await CRUD.read(query)
if toolbox:
logger.error("Тулбокс с таким названием уже существует")
return {}
newToolbox = await Toolbox(**toolboxData).save()
logger.info(f"Тулбокс {newToolbox.title} успешно создан")
return newToolbox.toDict()
+81
View File
@@ -0,0 +1,81 @@
from sqlalchemy import or_, select
from db import CRUD
from db.handlers.toolbox import addNewToolbox
from db.schemas import User
from utils import logger, pwd_hash
async def addNewUser(userData: dict) -> dict:
login = userData.get("login", None)
if not login:
logger.error("Не указан логин")
return {}
userName = userData.get("username")
if not userName:
logger.error("Не указано имя пользователя")
return {}
query = select(User).where(or_(User.login == login, User.username == userName))
user = await CRUD.read(query)
if user:
logger.error("Пользователь с таким логином или именем уже существует")
return {}
if "access_level_id" not in userData:
logger.error("Не указан уровень доступа")
return {}
if "password" not in userData:
logger.error("Не указан пароль")
return {}
userData["hashed_password"] = pwd_hash(userData.pop("password"))
newUser = await User(**userData).save()
if not newUser:
logger.error("Ошибка сохранения пользователя")
return {}
logger.info(f"Пользователь {newUser.username} успешно добавлен, id: {newUser.id}")
if newUser.available_own_toolbox:
newToolboxData = {
"title": f"Тулбокс {newUser.username}",
"description": f"Оборудование, полученное сотрудником {newUser.username}, под личную материальную ответственность",
"owner_id": newUser.id,
}
newToolbox = await addNewToolbox(newToolboxData)
logger.info(
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {newUser.username}"
)
return newUser.toDict()
async def editUser(userData: dict) -> dict:
id = userData.get("id", None)
if not id:
logger.error("Не указан id пользователя")
return {}
query = select(User).where(User.id == id)
user = await CRUD.read(query)
if not user:
logger.error("Пользователь с таким id не найден")
return {}
changedUserData = userData.get("changedUserData", {})
if len(changedUserData.keys()) == 0:
logger.error("Не указаны изменяемые данные")
return {}
if "password" in changedUserData:
userData["hashed_password"] = pwd_hash(changedUserData.pop("password"))
editedUser = await user.edit(**changedUserData)
if not editedUser:
logger.error("Ошибка обновления пользователя")
return {}
logger.info(
f"Пользователь {editedUser.username} успешно обновлен, изменены данные: {changedUserData.keys()}"
)
if not user.available_own_toolbox:
if editedUser.available_own_toolbox:
newToolboxData = {
"title": f"Тулбокс {editedUser.username}",
"description": f"Оборудование, полученное сотрудником {editedUser.username}, под личную материальную ответственность",
"owner_id": editedUser.id,
}
newToolbox = await addNewToolbox(newToolboxData)
logger.info(
f"Тулбокс {newToolbox['title']} успешно создан и закреплен за пользователем {editedUser.username}"
)
return editedUser.toDict()
+6
View File
@@ -0,0 +1,6 @@
from user import *
from access import *
from toolkit import *
from categories import *
from db.schemas.toolbox import *
from stock import *
+53
View File
@@ -0,0 +1,53 @@
from datetime import datetime
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
from db import Base
import utils
class AccessLevel(Base):
__tablename__ = "access_level"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
# permissions
receiving_edit = Column(Boolean, default=False)
refund_request_edit = Column(Boolean, default=False)
refund_request_confirm = Column(Boolean, default=False)
debit_request_edit = Column(Boolean, default=False)
debit_request_confirm = Column(Boolean, default=False)
tools_creation = Column(Boolean, default=False)
tools_registration = Column(Boolean, default=False)
tools_registration_edit = Column(Boolean, default=False)
tools_edit = Column(Boolean, default=False)
tools_achievement = Column(Boolean, default=False)
users_creation = Column(Boolean, default=False)
users_edit = Column(Boolean, default=False)
users_disabling = Column(Boolean, default=False)
users_view = Column(Boolean, default=False)
available_own_toolbox = Column(Boolean, default=False)
view_all_toolboxes = Column(Boolean, default=False)
view_requests = Column(Boolean, default=False)
access_level_view = Column(Boolean, default=False)
access_level_edit = Column(Boolean, default=False)
manage_toolboxes = Column(Boolean, default=False)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
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)
+31
View File
@@ -0,0 +1,31 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, Integer, String, Text
from db import Base
import utils
class Category(Base):
__tablename__ = "categories"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
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)
+45
View File
@@ -0,0 +1,45 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer
from sqlalchemy.orm import relationship
from db import Base
import utils
class Stock(Base):
__tablename__ = "stocks"
id = Column(Integer, primary_key=True, index=True)
toolkit_id = Column(
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
)
toolkit_data = relationship(
"Toolkit", cascade="all, delete-orphan", lazy="joined", uselist=False
)
toolbox_id = Column(
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False
)
toolbox_data = relationship(
"Toolbox", cascade="all, delete-orphan", lazy="joined", uselist=False
)
quantity = Column(Integer, nullable=False)
price = Column(Float, nullable=False)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
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)
+34
View File
@@ -0,0 +1,34 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from db import Base
import utils
class Toolbox(Base):
__tablename__ = "toolboxes"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
owner_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
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)
+45
View File
@@ -0,0 +1,45 @@
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
import utils
class Toolkit(Base):
__tablename__ = "toolkits"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, unique=True, index=True)
description = Column(Text)
category_id = Column(Integer, ForeignKey("categories.id", ondelete="CASCADE"))
category_data = relationship(
"Category", cascade="all, delete-orphan", lazy="joined", uselist=False
)
image = Column(
JSONB, default={"main": "images/tools/default.png", "additional": []}
)
quantity_min = Column(Integer, nullable=True)
quantity_min_extra = Column(Integer, nullable=True)
external_link = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
refilled_at = Column(DateTime, default=datetime.now)
moved_at = Column(DateTime, default=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
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)
+35
View File
@@ -0,0 +1,35 @@
from datetime import datetime
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
from db import Base
import utils
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
login = Column(String, unique=True, index=True)
username = Column(String, unique=True, index=True)
photo = Column(String, default="images/users/default.png")
hashed_password = Column(String)
access_level_id = Column(Integer, ForeignKey("access_level.id", ondelete="CASCADE"))
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def toDict(self):
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)
+6
View File
@@ -0,0 +1,6 @@
def main():
print("Hello from tools-stock!")
if __name__ == "__main__":
main()
+12
View File
@@ -0,0 +1,12 @@
[project]
name = "tools-stock"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"colorlog>=6.10.1",
"passlib>=1.7.4",
"python-dotenv>=1.2.1",
"sqlalchemy>=2.0.44",
]
+3
View File
@@ -0,0 +1,3 @@
from my_loggers import *
from for_DB import *
from password import *
+13
View File
@@ -0,0 +1,13 @@
def toDict(data) -> dict:
return {
c.name: (
(
getattr(data, c.name)
if not c.name.endswith("_data")
else getattr(data, c.name).toDict()
)
if not c.name.endswith("_at")
else getattr(data, c.name).strftime("%Y-%m-%d %H:%M:%S")
)
for c in data.__table__.columns
}
+9
View File
@@ -0,0 +1,9 @@
import logging
import logging.config
logging.config.fileConfig("config/log.ini")
# loggers
logger = logging.getLogger("Tools Stock")
loggerDB = logging.getLogger("DB operations")
logger = logging.getLogger("UI operations")
+18
View File
@@ -0,0 +1,18 @@
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def pwd_hash(pwd_plant: str) -> str:
return pwd_context.encrypt(pwd_plant)
def pwd_verify(pwd_plant: str, pwd_hash: str) -> bool:
from my_loggers import logger
try:
return pwd_context.verify(pwd_plant, pwd_hash)
except Exception as e:
logger.error(f"Password verification error: {str(e)}")
return False
Generated
+119
View File
@@ -0,0 +1,119 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "colorlog"
version = "6.10.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" },
]
[[package]]
name = "greenlet"
version = "3.2.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" },
{ url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" },
{ url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" },
{ url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" },
{ url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" },
{ url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
{ url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
{ url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" },
{ url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" },
{ url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
{ url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
{ url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
{ url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" },
{ url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
{ url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
{ url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
{ url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" },
{ url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" },
{ 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 = "python-dotenv"
version = "1.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
]
[[package]]
name = "sqlalchemy"
version = "2.0.44"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" },
{ url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" },
{ url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" },
{ url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" },
{ url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" },
{ url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" },
{ url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" },
{ url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" },
{ url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" },
]
[[package]]
name = "tools-stock"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "colorlog" },
{ name = "passlib" },
{ name = "python-dotenv" },
{ name = "sqlalchemy" },
]
[package.metadata]
requires-dist = [
{ name = "colorlog", specifier = ">=6.10.1" },
{ name = "passlib", specifier = ">=1.7.4" },
{ name = "python-dotenv", specifier = ">=1.2.1" },
{ name = "sqlalchemy", specifier = ">=2.0.44" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]