Merged with experimental/redesign-db-block

This commit is contained in:
Maxim Slipenko 2024-08-21 11:19:05 +03:00
commit aab5fb4e39
93 changed files with 3684 additions and 1468 deletions

View File

@ -33,3 +33,41 @@ repos:
rev: 1.7.9 # sync:bandit:poetry.lock rev: 1.7.9 # sync:bandit:poetry.lock
hooks: hooks:
- id: bandit - id: bandit
- repo: https://github.com/python-poetry/poetry
rev: 1.8.3
hooks:
- name: Poetry Lock (root)
id: poetry-lock
args: ["--no-update"]
- name: Poetry Check (root)
id: poetry-check
- name: Poetry Lock (gnomik)
id: poetry-lock
args: ["-C", "./src/gnomik", "--no-update"]
- name: Poetry Check (gnomik)
id: poetry-check
args: ["-C", "./src/gnomik"]
- name: Poetry Lock (altlinux)
id: poetry-lock
args: ["-C", "./src/altlinux", "--no-update"]
- name: Poetry Check (altlinux)
id: poetry-check
args: ["-C", "./src/altlinux"]
- name: Poetry Lock (karkas_core)
id: poetry-lock
args: ["-C", "./src/karkas_core", "--no-update"]
- name: Poetry Check (karkas_core)
id: poetry-check
args: ["-C", "./src/karkas_core"]
- name: Poetry Lock (karkas_blocks)
id: poetry-lock
args: ["-C", "./src/karkas_blocks", "--no-update"]
- name: Poetry Check (karkas_blocks)
id: poetry-check
args: ["-C", "./src/karkas_blocks"]
- name: Poetry Lock (karkas_piccolo)
id: poetry-lock
args: ["-C", "./src/karkas_piccolo", "--no-update"]
- name: Poetry Check (karkas_piccolo)
id: poetry-check
args: ["-C", "./src/karkas_piccolo"]

View File

@ -12,6 +12,10 @@
"name": "Karkas Core", "name": "Karkas Core",
"path": "src/karkas_core" "path": "src/karkas_core"
}, },
{
"name": "Karkas Piccolo",
"path": "src/karkas_piccolo"
},
{ {
"name": "Gnomik", "name": "Gnomik",
"path": "src/gnomik" "path": "src/gnomik"

28
poetry.lock generated
View File

@ -61,6 +61,8 @@ mypy-extensions = ">=0.4.3"
packaging = ">=22.0" packaging = ">=22.0"
pathspec = ">=0.9.0" pathspec = ">=0.9.0"
platformdirs = ">=2" platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
colorama = ["colorama (>=0.4.3)"] colorama = ["colorama (>=0.4.3)"]
@ -449,6 +451,28 @@ files = [
[package.dependencies] [package.dependencies]
pbr = ">=2.0.0,<2.1.0 || >2.1.0" pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.26.3" version = "20.26.3"
@ -471,5 +495,5 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = ">=3.10,<3.13"
content-hash = "7db42302f410e90c105f188f32680864d9268137049af500f4221e69b4f9cc7f" content-hash = "71a956a903d10f5fffea9e7ff4528723cbbb873f38016373e7b817bdc72f36f9"

View File

@ -25,7 +25,7 @@ init = 'scripts.init:main'
module = 'scripts.module:main' module = 'scripts.module:main'
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<=3.12" python = ">=3.10,<3.13"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
flake8 = "^7.1.0" flake8 = "^7.1.0"

154
src/altlinux/poetry.lock generated
View File

@ -128,6 +128,7 @@ files = [
[package.dependencies] [package.dependencies]
aiosignal = ">=1.1.2" aiosignal = ">=1.1.2"
async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""}
attrs = ">=17.3.0" attrs = ">=17.3.0"
frozenlist = ">=1.1.1" frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0" multidict = ">=4.5,<7.0"
@ -173,14 +174,27 @@ files = [
] ]
[package.dependencies] [package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8" idna = ">=2.8"
sniffio = ">=1.1" sniffio = ">=1.1"
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"] trio = ["trio (>=0.23)"]
[[package]]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "23.2.0" version = "23.2.0"
@ -539,6 +553,20 @@ files = [
dnspython = ">=2.0.0" dnspython = ">=2.0.0"
idna = ">=2.0.0" idna = ">=2.0.0"
[[package]]
name = "exceptiongroup"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.111.1" version = "0.111.1"
@ -846,9 +874,13 @@ files = [
] ]
[package.dependencies] [package.dependencies]
exceptiongroup = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
h11 = "*" h11 = "*"
h2 = ">=3.1.0" h2 = ">=3.1.0"
priority = "*" priority = "*"
taskgroup = {version = "*", markers = "python_version < \"3.11\""}
tomli = {version = "*", markers = "python_version < \"3.11\""}
typing_extensions = {version = "*", markers = "python_version < \"3.11\""}
wsproto = ">=0.14.0" wsproto = ">=0.14.0"
[package.extras] [package.extras]
@ -940,6 +972,52 @@ files = [
editorconfig = ">=0.12.2" editorconfig = ">=0.12.2"
six = ">=1.13.0" six = ">=1.13.0"
[[package]]
name = "karkas-blocks"
version = "0.1.0"
description = ""
optional = false
python-versions = ">=3.10,<3.13"
files = []
develop = true
[package.dependencies]
dash = "^2.17.1"
dash-bootstrap-components = "^1.6.0"
dash-extensions = "^1.0.18"
karkas-core = {path = "../karkas_core", develop = true}
peewee = "^3.17.6"
pyyaml = "^6.0.1"
[package.source]
type = "directory"
url = "../karkas_blocks"
[[package]]
name = "karkas-core"
version = "0.1.0"
description = ""
optional = false
python-versions = ">=3.10,<3.13"
files = []
develop = true
[package.dependencies]
aiogram = "^3.10.0"
dataclasses-json = "^0.6.7"
fastapi = {version = "^0.111.1", optional = true}
hypercorn = {version = "^0.17.3", optional = true}
restrictedpython = "^7.1"
semver = "^3.0.2"
setuptools = "^71.0.1"
[package.extras]
webhook = ["fastapi (>=0.111.1,<0.112.0)", "hypercorn (>=0.17.3,<0.18.0)"]
[package.source]
type = "directory"
url = "../karkas_core"
[[package]] [[package]]
name = "magic-filter" name = "magic-filter"
version = "1.0.12" version = "1.0.12"
@ -1209,52 +1287,6 @@ files = [
{file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
] ]
[[package]]
name = "karkas-core"
version = "0.1.0"
description = ""
optional = false
python-versions = "~3.12"
files = []
develop = true
[package.dependencies]
aiogram = "^3.10.0"
dataclasses-json = "^0.6.7"
fastapi = {version = "^0.111.1", optional = true}
hypercorn = {version = "^0.17.3", optional = true}
restrictedpython = "^7.1"
semver = "^3.0.2"
setuptools = "^71.0.1"
[package.extras]
webhook = ["fastapi (>=0.111.1,<0.112.0)", "hypercorn (>=0.17.3,<0.18.0)"]
[package.source]
type = "directory"
url = "../karkas_core"
[[package]]
name = "karkas-blocks"
version = "0.1.0"
description = ""
optional = false
python-versions = "~3.12"
files = []
develop = true
[package.dependencies]
dash = "^2.17.1"
dash-bootstrap-components = "^1.6.0"
dash-extensions = "^1.0.18"
karkas-core = {path = "../karkas_core", develop = true}
peewee = "^3.17.6"
pyyaml = "^6.0.1"
[package.source]
type = "directory"
url = "../karkas_blocks"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "24.1"
@ -1695,6 +1727,20 @@ anyio = ">=3.4.0,<5"
[package.extras] [package.extras]
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
[[package]]
name = "taskgroup"
version = "0.0.0a4"
description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout"
optional = false
python-versions = "*"
files = [
{file = "taskgroup-0.0.0a4-py2.py3-none-any.whl", hash = "sha256:5c1bd0e4c06114e7a4128583ab75c987597d5378a33948a3b74c662b90f61277"},
{file = "taskgroup-0.0.0a4.tar.gz", hash = "sha256:eb08902d221e27661950f2a0320ddf3f939f579279996f81fe30779bca3a159c"},
]
[package.dependencies]
exceptiongroup = "*"
[[package]] [[package]]
name = "tenacity" name = "tenacity"
version = "8.5.0" version = "8.5.0"
@ -1710,6 +1756,17 @@ files = [
doc = ["reno", "sphinx"] doc = ["reno", "sphinx"]
test = ["pytest", "tornado (>=4.5)", "typeguard"] test = ["pytest", "tornado (>=4.5)", "typeguard"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]] [[package]]
name = "typer" name = "typer"
version = "0.12.3" version = "0.12.3"
@ -1788,6 +1845,7 @@ h11 = ">=0.8"
httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
@ -2158,5 +2216,5 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = ">=3.10,<3.13"
content-hash = "63f870b298f75049e8fb6fe1a5fa9482e4bfb2d624c8bc6bf3dfc6bafa1c05c3" content-hash = "d9a8013e760cfe29900565540b9638ba421dc0f63ae288e785f42d62de17e88c"

View File

@ -8,7 +8,7 @@ authors = [
readme = "README.md" readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<=3.12" python = ">=3.10,<3.13"
karkas-core = { extras=["webhook"], path = "../karkas_core", develop = true } karkas-core = { extras=["webhook"], path = "../karkas_core", develop = true }
karkas-blocks = { path = "../karkas_blocks", develop = true } karkas-blocks = { path = "../karkas_blocks", develop = true }

View File

@ -9,18 +9,22 @@ async def main():
await ocab.init_app( await ocab.init_app(
[ [
block_loader("standard", "config", safe=False), block_loader("standard", "config", safe=False),
block_loader("standard", "filters", safe=False),
block_loader("standard", "database", safe=False), block_loader("standard", "database", safe=False),
block_loader("standard", "fsm_database_storage", safe=False), block_loader("standard", "statistics", safe=False),
block_loader("standard", "roles", safe=False), block_loader("standard", "chats", safe=False),
block_loader("external", "yandexgpt", safe=False), block_loader("standard", "users", safe=False),
#
block_loader("standard", "command_helper"), block_loader("standard", "command_helper"),
block_loader("standard", "info"), block_loader("standard", "roles", safe=False),
block_loader("standard", "filters"), block_loader("standard", "fsm_database_storage", safe=False),
block_loader("external", "create_report_apps"), block_loader("external", "create_report_apps"),
block_loader("standard", "admin"), block_loader("standard", "info"),
block_loader("standard", "message_processing"), block_loader("standard", "help"),
block_loader("standard", "miniapp", safe=False), # block_loader("external", "yandexgpt", safe=False),
#
# block_loader("standard", "admin"),
# block_loader("standard", "message_processing"),
# block_loader("standard", "miniapp", safe=False),
] ]
) )
await ocab.start() await ocab.start()

931
src/gnomik/poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,10 @@ authors = ["Максим Слипенко <maxim@slipenko.com>"]
readme = "README.md" readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<=3.12" python = ">=3.10,<3.13"
karkas-core = { extras=["webhook"], path = "../karkas_core", develop = true } karkas-core = { extras=["webhook"], path = "../karkas_core", develop = true }
karkas-blocks = { path = "../karkas_blocks", develop = true } karkas-blocks = { path = "../karkas_blocks", develop = true }
karkas-piccolo = { path = "../karkas_piccolo", develop = true }
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]

View File

@ -0,0 +1,15 @@
from karkas_core.modules_system.public_api import (
get_module,
register_outer_message_middleware,
)
from .main import ChatsMiddleware
def module_init():
register_app_config = get_module("standard.database", "register_app_config")
from .db import APP_CONFIG
register_app_config(APP_CONFIG)
register_outer_message_middleware(ChatsMiddleware())

View File

@ -0,0 +1 @@
from .piccolo_app import APP_CONFIG

View File

@ -0,0 +1,15 @@
import os
from karkas_piccolo.conf.apps import AppConfig
from .tables import ChatInfo
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
APP_CONFIG = AppConfig(
app_name="standard.chats",
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
table_classes=[ChatInfo],
migration_dependencies=[],
commands=[],
)

View File

@ -0,0 +1,104 @@
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
from piccolo.columns.column_types import Date, Integer, Text
from piccolo.columns.defaults.date import DateNow
from piccolo.columns.indexes import IndexMethod
ID = "2024-08-20T17:25:21:296396"
VERSION = "1.16.0"
DESCRIPTION = ""
async def forwards():
manager = MigrationManager(
migration_id=ID, app_name="standard.chats", description=DESCRIPTION
)
manager.add_table(
class_name="ChatInfo", tablename="chat_info", schema=None, columns=None
)
manager.add_column(
table_class_name="ChatInfo",
tablename="chat_info",
column_name="chat_id",
db_column_name="chat_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": True,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="ChatInfo",
tablename="chat_info",
column_name="chat_name",
db_column_name="chat_name",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="ChatInfo",
tablename="chat_info",
column_name="chat_type",
db_column_name="chat_type",
column_class_name="Integer",
column_class=Integer,
params={
"default": 10,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="ChatInfo",
tablename="chat_info",
column_name="created_at",
db_column_name="created_at",
column_class_name="Date",
column_class=Date,
params={
"default": DateNow(),
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
return manager

View File

@ -0,0 +1,9 @@
from piccolo.columns import Date, Integer, Text
from piccolo.table import Table
class ChatInfo(Table):
chat_id = Integer(primary_key=True)
chat_name = Text()
chat_type = Integer(default=10)
created_at = Date()

View File

@ -0,0 +1,13 @@
{
"id": "standard.chats",
"name": "Чаты",
"description": "Очень полезный модуль",
"author": "Karkas Team",
"version": "1.0.0",
"privileged": true,
"dependencies": {
"required": {
"standard.database": "^1.0.0"
}
}
}

View File

@ -0,0 +1,34 @@
from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import Chat, TelegramObject
from .db.tables import ChatInfo
async def update_chat_info(chat: Chat):
chat_name = chat.title if chat.type != "private" else ""
await ChatInfo.insert(
ChatInfo(
chat_name=chat_name,
)
).on_conflict(
action="DO UPDATE",
values=[
ChatInfo.chat_name,
],
).run()
class ChatsMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
chat = event.chat
await update_chat_info(chat)
result = await handler(event, data)
return result

View File

@ -1,48 +0,0 @@
## Модуль DataBase
Модуль DataBase предназначен для ведения и работы с базами данных Karkas.
Модуль содержит в себе следующие таблицы:
* `Chats` - таблица для хранения информации о чатах.
* `Users` - таблица для хранения информации о пользователях.
* `Messages` - таблица для хранения информации о сообщениях.
* `ChatStats` - таблица для хранения статистики чатов по дням.
* `UserStats` - таблица для хранения статистики пользователей по дням.
руктура таблицы `Chats`:
* `chat_id` - идентификатор чата.
* `chat_name` - название чата.
* `chat_type` - тип чата. (0 - Чат администраторов, 1 - Пользовательский чат, 3 - Чат разрешённых личных запросов к боту
10 - Не инициализированный чат)
* `chat_stats` - количество всех отправленных сообщений в чате.
руктура таблицы `Users`:
* `user_id` - идентификатор пользователя telegram.
* `user_tag` - тег пользователя telegram.
* `user_name` - имя пользователя telegram.
* `user_role` - роль пользователя в чате. (0 - Администратор, 1 - Модератор, 2 - Пользователь)
* `user_stats` - количество всех отправленных сообщений пользователем.
* `user_rep` - репутация пользователя.
руктура таблицы `Messages`:
* `message_chat_id` - идентификатор чата в котором отправлено сообщение.
* `message_id` - идентификатор сообщения.
* `messag_sender_id` - идентификатор пользователя отправившего сообщение. Если сообщение отправил бот, то
`messag_sender_id` = 0.
* `answer_to_message_id` - идентификатор сообщения на которое дан ответ. Если ответа нет или ответ на служебное
сообщение о создании топика в чатах с форумным типом, то `answer_to_message_id` = 0.
* `message_ai_model` - идентификатор модели нейросети, которая использовалась для генерации ответа. Если ответ'
сгенерирован не был, то `message_ai_model` = null.
* `message_text` - текст сообщения.
руктура таблицы `ChatStats`:
* `chat_id` - идентификатор чата для которого собрана статистика.
* `date` - дата на которую собрана статистика.
* `messages_count` - количество сообщений отправленных в чат за день.
руктура таблицы `UserStats`:
* `chat_id` - идентификатор чата для которого собрана статистика.
* `user_id` - идентификатор пользователя для которого собрана статистика.
* `date` - дата на которую собрана статистика.
* `messages_count` - количество сообщений отправленных пользователем в чат за день.

View File

@ -1,5 +1 @@
from . import db_api, models, repositories from .main import module_init, module_late_init, register_app_config
async def module_init():
db_api.connect_database()

View File

@ -1,301 +0,0 @@
import peewee as pw
from aiogram.types import Message
from .exceptions import NotExpectedModuleName
from .models.chat_stats import ChatStats
from .models.chats import Chats
from .models.db import database_proxy
from .models.fsm_data import FSMData
from .models.messages import Messages
from .models.user_stats import UserStats
from .models.users import Users
def connect_database(is_test: bool = False, module: str | None = None):
if module:
raise NotExpectedModuleName()
db_path = "database"
database = pw.SqliteDatabase(f"{db_path}/Karkas.db")
database_proxy.initialize(database)
database.connect()
create_tables(database)
return database, f"{db_path}/Karkas.db"
def create_tables(db: pw.SqliteDatabase):
"""Создание таблиц"""
for table in Chats, Messages, Users, UserStats, ChatStats, FSMData:
if not table.table_exists():
db.create_tables([table])
def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0):
chat, created = Chats.get_or_create(
id=chat_id,
defaults={
"chat_name": chat_name,
"chat_type": chat_type,
"chat_all_stat": chat_stats,
},
)
if not created:
# Обновить существующий чат, если он уже существует
chat.chat_name = chat_name
chat.chat_type = chat_type
chat.chat_stats = chat_stats
chat.save()
def add_user(
user_id,
user_first_name,
user_last_name=None,
user_tag=None,
user_role=0,
user_stats=0,
user_rep=0,
):
if user_last_name is None:
user_name = user_first_name
else:
user_name = user_first_name + " " + user_last_name
user, created = Users.get_or_create(
id=user_id,
defaults={
"user_tag": user_tag,
"user_name": user_name,
"user_role": user_role,
"user_stats": user_stats,
"user_rep": user_rep,
},
)
if not created:
# Обновить существующего пользователя, если он уже существует
user.user_tag = user_tag
user.user_name = user_name
user.user_role = user_role
user.user_stats = user_stats
user.user_rep = user_rep
user.save()
def add_message(message: Message, message_ai_model=None):
if message.reply_to_message:
answer_to_message_id = message.reply_to_message.message_id
else:
answer_to_message_id = None
Messages.create(
message_chat_id=message.chat.id,
message_id=message.message_id,
message_sender_id=message.from_user.id,
answer_to_message_id=answer_to_message_id,
message_ai_model=message_ai_model,
message_text=message.text,
)
def add_chat_stats(chat_id, date, messages_count):
ChatStats.create(chat_id=chat_id, date=date, messages_count=messages_count)
def add_user_stats(chat_id, user_id, date, messages_count):
UserStats.create(
chat_id=chat_id, user_id=user_id, date=date, messages_count=messages_count
)
# Работа с таблицей чатов
def get_chat(chat_id):
return Chats.get_or_none(Chats.id == chat_id)
def change_chat_name(chat_id, new_chat_name):
query = Chats.update(chat_name=new_chat_name).where(Chats.id == chat_id)
query.execute()
def change_chat_type(chat_id, new_chat_type):
query = Chats.update(chat_type=new_chat_type).where(Chats.id == chat_id)
query.execute()
def get_chat_all_stat(chat_id):
chat = Chats.get_or_none(Chats.id == chat_id)
return chat.chat_all_stat if chat else None
# Работа с таблицей пользователей
def get_user(user_id) -> Users | None:
return Users.get_or_none(Users.id == user_id)
def get_user_tag(user_id):
user = Users.get_or_none(Users.id == user_id)
return user.user_tag if user else None
def get_user_id(user_tag):
user = Users.get_or_none(Users.user_tag == user_tag)
return user.id if user else None
def get_user_name(user_id):
user = Users.get_or_none(Users.id == user_id)
return user.user_name if user else None
def get_user_role(user_id: str):
user = Users.get_or_none(Users.id == user_id)
return user.user_role if user else None
def get_user_all_stats(user_id):
user = Users.get_or_none(Users.id == user_id)
return user.user_stats if user else None
def get_user_rep(user_id):
user = Users.get_or_none(Users.id == user_id)
return user.user_rep if user else None
def change_user_name(user_id, user_first_name, user_last_name=None):
if user_last_name is None:
new_user_name = user_first_name
else:
new_user_name = user_first_name + " " + user_last_name
query = Users.update(user_name=new_user_name).where(Users.id == user_id)
query.execute()
def change_user_tag(user_id, new_user_tag):
query = Users.update(user_tag=new_user_tag).where(Users.id == user_id)
query.execute()
def change_user_role(user_id, new_user_role):
query = Users.update(user_role=new_user_role).where(Users.id == user_id)
query.execute()
# Работа с таблицей сообщений
def get_message(message_chat_id, message_id):
return Messages.get_or_none(
Messages.message_chat_id == message_chat_id,
Messages.message_id == message_id,
)
def get_message_sender_id(message_chat_id, message_id):
message = Messages.get_or_none(
Messages.message_chat_id == message_chat_id, Messages.message_id == message_id
)
return message.message_sender_id if message else None
def get_message_text(message_chat_id, message_id):
message = Messages.get_or_none(
Messages.message_chat_id == message_chat_id, Messages.message_id == message_id
)
return message.message_text if message else None
def get_message_ai_model(message_chat_id, message_id):
message = Messages.get_or_none(
Messages.message_chat_id == message_chat_id, Messages.message_id == message_id
)
return message.message_ai_model if message else None
def get_answer_to_message_id(message_chat_id, message_id):
message = Messages.get_or_none(
Messages.message_chat_id == message_chat_id, Messages.message_id == message_id
)
return message.answer_to_message_id if message else None
# Работа с таблицей статистики чатов
def get_chat_stats(chat_id):
chat_stats = {}
for chat_stat in ChatStats.select().where(ChatStats.chat_id == chat_id):
chat_stats[chat_stat.date] = chat_stat.messages_count
return chat_stats
# Работа с таблицей статистики пользователей
def get_user_stats(user_id):
user_stats = {}
for user_stat in UserStats.select().where(UserStats.user_id == user_id):
user_stats[user_stat.date] = user_stat.messages_count
return user_stats
# Функции обновления
def update_chat_all_stat(chat_id):
query = Chats.update(chat_all_stat=Chats.chat_all_stat + 1).where(
Chats.id == chat_id
)
query.execute()
def update_chat_stats(chat_id, date):
chat_stats = ChatStats.get_or_none(
ChatStats.chat_id == chat_id, ChatStats.date == date
)
if chat_stats:
query = ChatStats.update(messages_count=ChatStats.messages_count + 1).where(
ChatStats.chat_id == chat_id, ChatStats.date == date
)
query.execute()
else:
ChatStats.create(chat_id=chat_id, date=date, messages_count=1)
def update_user_all_stat(user_id):
user = Users.get_or_none(Users.id == user_id)
if user:
query = Users.update(user_stats=Users.user_stats + 1).where(Users.id == user_id)
query.execute()
else:
Users.create(id=user_id, user_stats=1)
def update_user_rep(user_id):
user = Users.get_or_none(Users.id == user_id)
if user:
query = Users.update(user_rep=Users.user_rep + 1).where(Users.id == user_id)
query.execute()
else:
Users.create(id=user_id, user_rep=1)
def update_user_stats(chat_id, user_id, date):
user_stats = UserStats.get_or_none(
UserStats.chat_id == chat_id,
UserStats.user_id == user_id,
UserStats.date == date,
)
if user_stats:
query = UserStats.update(messages_count=UserStats.messages_count + 1).where(
UserStats.chat_id == chat_id,
UserStats.user_id == user_id,
UserStats.date == date,
)
query.execute()
else:
UserStats.create(chat_id=chat_id, user_id=user_id, date=date, messages_count=1)

View File

@ -1,12 +0,0 @@
class MissingModuleName(BaseException):
def __init__(self):
self.message = "Пропущено название директории модуля"
super().__init__(self.message)
class NotExpectedModuleName(BaseException):
def __init__(self):
self.message = "Не ожидалось название директории модуля"
super().__init__(self.message)

View File

@ -0,0 +1,40 @@
from karkas_piccolo.karkas import migrate_forward # isort:skip
from karkas_piccolo.conf.apps import AppConfig, AppRegistry # isort:skip
from piccolo.conf.apps import PiccoloConfModule
from piccolo.engine.sqlite import SQLiteEngine
from karkas_core.singleton import Singleton
# from karkas_piccolo.conf.apps import A
APPS_CONFIGS = dict()
def register_app_config(app_config: AppConfig):
APPS_CONFIGS[app_config.app_name] = app_config
def module_init():
singleton = Singleton()
singleton.storage["_database"] = dict()
pass
async def module_late_init():
singleton = Singleton()
DB = SQLiteEngine(path="./db.sqlite3")
APP_REGISTRY = AppRegistry(apps_configs=APPS_CONFIGS)
module = PiccoloConfModule(name="standard.database")
module.DB = DB
module.APP_REGISTRY = APP_REGISTRY
singleton.storage["_database"]["conf"] = module
await migrate_forward()
pass

View File

@ -1 +0,0 @@
from .fsm_data import FSMData

View File

@ -1,12 +0,0 @@
import peewee as pw
from .db import database_proxy
class ChatStats(pw.Model):
class Meta:
database = database_proxy
chat_id = pw.IntegerField(null=False)
date = pw.DateField(null=False)
messages_count = pw.IntegerField(null=False, default=0)

View File

@ -1,12 +0,0 @@
import peewee as pw
from .db import database_proxy
class Chats(pw.Model):
class Meta:
database = database_proxy
chat_name = pw.CharField(null=False)
chat_type = pw.IntegerField(null=False, default=10)
chat_all_stat = pw.IntegerField(null=False)

View File

@ -1,3 +0,0 @@
from peewee import DatabaseProxy
database_proxy = DatabaseProxy()

View File

@ -1,12 +0,0 @@
import peewee as pw
from .db import database_proxy
class FSMData(pw.Model):
class Meta:
database = database_proxy
key = pw.CharField(primary_key=True)
state = pw.CharField(null=True)
data = pw.CharField(null=True)

View File

@ -1,15 +0,0 @@
import peewee as pw
from .db import database_proxy
class Messages(pw.Model):
class Meta:
database = database_proxy
message_chat_id = pw.IntegerField(null=False)
message_id = pw.IntegerField(null=False)
message_sender_id = pw.IntegerField(null=False)
answer_to_message_id = pw.IntegerField(null=True)
message_ai_model = pw.TextField(null=True)
message_text = pw.TextField(null=False)

View File

@ -1,13 +0,0 @@
import peewee as pw
from .db import database_proxy
class UserStats(pw.Model):
class Meta:
database = database_proxy
chat_id = pw.IntegerField(null=False)
user_id = pw.IntegerField(null=False)
date = pw.DateField(null=False)
messages_count = pw.IntegerField(null=False, default=0)

View File

@ -1,14 +0,0 @@
import peewee as pw
from .db import database_proxy
class Users(pw.Model):
class Meta:
database = database_proxy
user_tag = pw.CharField(null=True)
user_name = pw.CharField(null=False) # до 255 символов
user_role = pw.IntegerField(null=True, default=3)
user_stats = pw.IntegerField(null=True, default=0)
user_rep = pw.IntegerField(null=True, default=0)

View File

@ -1 +0,0 @@
from .fsm_data import FSMDataRepository

View File

@ -1,32 +0,0 @@
from peewee import fn
from ..models import FSMData
class FSMDataRepository:
def get(self, key: str):
return FSMData.get_or_none(key=key)
def set_state(self, key: str, state: str):
FSMData.insert(
key=key,
state=state,
data=fn.COALESCE(
FSMData.select(FSMData.data).where(FSMData.key == key), None
),
).on_conflict(
conflict_target=[FSMData.key],
update={FSMData.state: state},
).execute()
def set_data(self, key: str, data: str):
FSMData.insert(
key=key,
data=data,
state=fn.COALESCE(
FSMData.select(FSMData.state).where(FSMData.key == key), None
),
).on_conflict(
conflict_target=[FSMData.key],
update={FSMData.data: data},
).execute()

View File

@ -1 +0,0 @@
Эта директория для тестовой БД

View File

@ -1,142 +0,0 @@
# flake8: noqa
import os
import unittest
from ...exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName
from ..db_api import *
class TestDatabaseAPI(unittest.TestCase):
database = None
path = None
@classmethod
def setUpClass(cls):
cls.database, cls.path = connect_database(is_test=True, module="database")
create_tables(cls.database)
def test_fail_connect(cls):
with cls.assertRaises(MissingModuleName):
cls.database, cls.path = connect_database(is_test=True)
with cls.assertRaises(NotExpectedModuleName):
cls.database, cls.path = connect_database(module="database")
def test_add_and_get_chat(self):
add_chat(chat_id=21, chat_role=0, chat_stats=0, chat_federation=0)
add_chat(chat_id=22, chat_role=1, chat_stats=100, chat_federation=1)
chat1 = get_chat(21)
self.assertIsNotNone(chat1)
self.assertEqual(chat1.id, 21)
self.assertEqual(chat1.chat_role, 0)
chat2 = get_chat(22)
self.assertIsNotNone(chat2)
self.assertEqual(chat2.id, 22)
self.assertEqual(chat2.chat_role, 1)
def test_add_and_get_message(self):
add_message(
message_id=1, message_text="Test Message 1", message_sender=1, answer_id=2
)
add_message(
message_id=2, message_text="Test Message 2", message_sender=2, answer_id=1
)
message1 = get_message(1)
self.assertIsNotNone(message1)
self.assertEqual(message1.id, 1)
self.assertEqual(message1.message_text, "Test Message 1")
message2 = get_message(2)
self.assertIsNotNone(message2)
self.assertEqual(message2.id, 2)
self.assertEqual(message2.message_text, "Test Message 2")
def test_add_and_get_user(self):
add_user(
user_id=100,
user_name="TestUser1",
user_tag="TestTag1",
user_role=0,
user_stats=10,
user_rep=5,
)
add_user(
user_id=101,
user_name="TestUser2",
user_tag="TestTag2",
user_role=1,
user_stats=20,
user_rep=10,
)
user1 = get_user(100)
self.assertIsNotNone(user1)
self.assertEqual(user1.id, 100)
self.assertEqual(user1.user_name, "TestUser1")
user2 = get_user(101)
self.assertIsNotNone(user2)
self.assertEqual(user2.id, 101)
self.assertEqual(user2.user_name, "TestUser2")
def test_get_user_role(self):
add_user(
user_id=102,
user_name="TestUser3",
user_tag="TestTag3",
user_role=0,
user_stats=30,
user_rep=15,
)
add_user(
user_id=103,
user_name="TestUser4",
user_tag="TestTag4",
user_role=1,
user_stats=40,
user_rep=20,
)
user_role1 = get_user_role(102)
self.assertEqual(user_role1, 0)
user_role2 = get_user_role(103)
self.assertEqual(user_role2, 1)
def test_change_user_name(self):
add_user(
user_id=104,
user_name="OldName1",
user_tag="TestTag5",
user_role=0,
user_stats=50,
user_rep=25,
)
change_user_name(104, "NewName1")
updated_user1 = get_user(104)
self.assertEqual(updated_user1.user_name, "NewName1")
add_user(
user_id=105,
user_name="OldName2",
user_tag="TestTag6",
user_role=1,
user_stats=60,
user_rep=30,
)
change_user_name(105, "NewName2")
updated_user2 = get_user(105)
self.assertEqual(updated_user2.user_name, "NewName2")
@classmethod
def tearDownClass(cls):
cls.database.close()
os.system(f"rm {cls.path}") # nosec
if __name__ == "__main__":
unittest.main()

View File

@ -1,11 +1,14 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
from aiogram import Bot from aiogram import BaseMiddleware, Bot
from aiogram.filters import BaseFilter from aiogram.filters import BaseFilter
from aiogram.types import Message from aiogram.types import Message, TelegramObject
from typing_extensions import deprecated from typing_extensions import deprecated
from karkas_core.modules_system.public_api import get_module from karkas_core.modules_system.public_api import (
get_module,
register_outer_message_middleware,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from karkas_blocks.standard.config import IConfig from karkas_blocks.standard.config import IConfig
@ -22,11 +25,47 @@ except Exception:
pass pass
class GlobalFilter(BaseMiddleware):
def __init__(self) -> None:
super().__init__()
self.filter = ChatIDFilter()
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
if not isinstance(event, Message):
return await handler(event, data)
if not config.get("filters::global::enabled"):
return await handler(event, data)
if await self.filter(event, None):
return await handler(event, data)
if event.chat.type == "private":
if config.get("filters::global::private_allowed"):
return await handler(event, data)
return await event.answer(config.get("filters::global::forbidden_message"))
def module_init(): def module_init():
config.register( config.register(
"filters::approved_chat_id", "int", multiple=True, shared=True, default_value=[] "filters::approved_chat_id", "int", multiple=True, shared=True, default_value=[]
) )
config.register("filters::default_chat_tag", "string", shared=True) config.register("filters::default_chat_tag", "string", shared=True)
config.register(
"filters::global::forbidden_message",
"string",
default_value="Вы не имеете доступа!",
)
config.register("filters::global::enabled", "boolean", default_value=False)
config.register("filters::global::private_allowed", "boolean", default_value=False)
register_outer_message_middleware(GlobalFilter())
def get_approved_chat_id() -> list: def get_approved_chat_id() -> list:

View File

@ -0,0 +1 @@
from .piccolo_app import APP_CONFIG

View File

@ -0,0 +1,15 @@
import os
from karkas_piccolo.conf.apps import AppConfig
from .tables import FSMData
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
APP_CONFIG = AppConfig(
app_name="standard.fsm_database_storage",
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
table_classes=[FSMData],
migration_dependencies=[],
commands=[],
)

View File

@ -0,0 +1,85 @@
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
from piccolo.columns.column_types import Text
from piccolo.columns.indexes import IndexMethod
ID = "2024-08-20T15:07:25:276545"
VERSION = "1.16.0"
DESCRIPTION = ""
async def forwards():
manager = MigrationManager(
migration_id=ID,
app_name="standard.fsm_database_storage",
description=DESCRIPTION,
)
manager.add_table(
class_name="FSMData", tablename="fsm_data", schema=None, columns=None
)
manager.add_column(
table_class_name="FSMData",
tablename="fsm_data",
column_name="key",
db_column_name="key",
column_class_name="Text",
column_class=Text,
params={
"primary": True,
"default": "",
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="FSMData",
tablename="fsm_data",
column_name="state",
db_column_name="state",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": True,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="FSMData",
tablename="fsm_data",
column_name="data",
db_column_name="data",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": True,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
return manager

View File

@ -0,0 +1,8 @@
from piccolo.columns import Text
from piccolo.table import Table
class FSMData(Table):
key = Text(primary=True)
state = Text(null=True)
data = Text(null=True)

View File

@ -1,5 +1,5 @@
import json import json
from typing import TYPE_CHECKING, Any, Dict, Optional from typing import Any, Dict, Optional
from aiogram.fsm.state import State from aiogram.fsm.state import State
from aiogram.fsm.storage.base import BaseStorage, StorageKey from aiogram.fsm.storage.base import BaseStorage, StorageKey
@ -7,14 +7,54 @@ from aiogram.fsm.storage.base import BaseStorage, StorageKey
from karkas_core.modules_system.public_api import get_module, log from karkas_core.modules_system.public_api import get_module, log
from karkas_core.modules_system.public_api.public_api import set_fsm from karkas_core.modules_system.public_api.public_api import set_fsm
if TYPE_CHECKING: from .db.tables import FSMData
from karkas_blocks.standard.database.repositories import (
FSMDataRepository as IFSMDataRepository,
class FSMDataRepository:
def get(self, key: str):
return FSMData.select().where(FSMData.key == key).first().run_sync()
def set_state(self, key: str, state: str):
returning = (
FSMData.update(
{
FSMData.state: state,
}
)
.where(FSMData.key == key)
.returning(FSMData.key)
.run_sync()
) )
FSMDataRepository: "type[IFSMDataRepository]" = get_module( if len(returning) == 0:
"standard.database", "repositories.FSMDataRepository" FSMData.insert(
FSMData(
key=key,
state=state,
data=None,
) )
).run_sync()
def set_data(self, key: str, data: str):
returning = (
FSMData.update(
{
FSMData.data: data,
}
)
.where(FSMData.key == key)
.returning(FSMData.key)
.run_sync()
)
if len(returning) == 0:
FSMData.insert(
FSMData(
key=key,
data=data,
state=None,
)
).run_sync()
def serialize_key(key: StorageKey) -> str: def serialize_key(key: StorageKey) -> str:
@ -68,7 +108,7 @@ class SQLStorage(BaseStorage):
try: try:
s_state = self.repo.get(s_key) s_state = self.repo.get(s_key)
return s_state.state if s_state else None return s_state["state"] if s_state else None
except Exception as e: except Exception as e:
log(f"FSM Storage database error: {e}") log(f"FSM Storage database error: {e}")
return None return None
@ -99,7 +139,7 @@ class SQLStorage(BaseStorage):
try: try:
s_data = self.repo.get(s_key) s_data = self.repo.get(s_key)
return deserialize_object(s_data.data) if s_data else None return deserialize_object(s_data["data"]) if s_data else None
except Exception as e: except Exception as e:
log(f"FSM Storage database error: {e}") log(f"FSM Storage database error: {e}")
return None return None
@ -126,4 +166,9 @@ class SQLStorage(BaseStorage):
async def module_init(): async def module_init():
register_app_config = get_module("standard.database", "register_app_config")
from .db import APP_CONFIG
register_app_config(APP_CONFIG)
set_fsm(SQLStorage()) set_fsm(SQLStorage())

View File

@ -1,2 +1,2 @@
from .handlers import get_chat_info, get_user_info from .handlers import get_user_info
from .main import module_init from .main import module_init

View File

@ -20,32 +20,10 @@ Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int): async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int):
ai_model = db_api.get_message_ai_model(message.chat.id, message.message_id)
if ai_model is not None:
await message.reply(
"Это сообщение было сгенерировано ботом используя модель: " + ai_model
)
return
if user_id == bot.id:
await message.reply("Это сообщение было отправлено ботом")
return
user = db_api.get_user(user_id)
if user is None:
await message.reply("Пользователь не найден")
log(f"Пользователь не найден: {user_id}, {user}")
return
roles = Roles() roles = Roles()
answer = ( role = roles.get_user_role(user_id)
f"Пользователь: {user.user_name}\n"
f"Роль: {await roles.get_role_name(role_id=user.user_role)}\n" answer = f"Имя: {user_id}\n" f"Роль: {role}\n"
f"Тег: @{user.user_tag}\n"
f"Кол-во сообщений: {user.user_stats}\n"
f"Репутация: {user.user_rep}"
)
await message.reply(answer) await message.reply(answer)
@ -54,14 +32,7 @@ async def get_user_info(message: Message, bot: Bot):
# Если сообщение отвечает на другое сообщение, то выводим информацию о пользователе, на чье сообщение был ответ # Если сообщение отвечает на другое сообщение, то выводим информацию о пользователе, на чье сообщение был ответ
# Если это бот то выводим информацию, что это бот и какая модель yandexgpt используется # Если это бот то выводим информацию, что это бот и какая модель yandexgpt используется
try: try:
if len(message.text.split()) > 1 and message.text.split()[1].startswith("@"): if message.reply_to_message:
user_tag = message.text.split()[1][1:]
user_id = db_api.get_user_id(user_tag)
if user_id:
await get_info_answer_by_id(message, bot, user_id)
else:
await message.reply(f"Пользователь с тегом @{user_tag} не найден")
elif message.reply_to_message:
user_id = message.reply_to_message.from_user.id user_id = message.reply_to_message.from_user.id
await get_info_answer_by_id(message, bot, user_id) await get_info_answer_by_id(message, bot, user_id)
else: else:

View File

@ -3,7 +3,7 @@ from aiogram.filters import Command
from karkas_core.modules_system.public_api import get_module, register_router from karkas_core.modules_system.public_api import get_module, register_router
from .handlers import get_chat_info, get_user_info from .handlers import get_user_info
register_command = get_module("standard.command_helper", "register_command") register_command = get_module("standard.command_helper", "register_command")
@ -12,9 +12,9 @@ async def module_init():
router = Router() router = Router()
router.message.register(get_user_info, Command("info")) router.message.register(get_user_info, Command("info"))
router.message.register(get_chat_info, Command("chatinfo")) # router.message.register(get_chat_info, Command("chatinfo"))
register_router(router) register_router(router)
register_command("info", "Информация о пользователе") register_command("info", "Информация о пользователе")
register_command("chatinfo", "Информация о чате") # register_command("chatinfo", "Информация о чате")

View File

@ -1,14 +0,0 @@
# Модуль Message Processing
Модуль `message_processing` обрабатывает все входящие сообщения.
## Функциональность
- Проверка чата и пользователя на наличие в базе данных.
- Обновление информации о чате и пользователе.
- Добавление статистики сообщений.
- Передача сообщения модулю `yandexgpt`, если оно соответствует условиям.
## Использование
Модуль автоматически обрабатывает все входящие сообщения.

View File

@ -1 +0,0 @@
from .message_api import module_init

View File

@ -1,15 +0,0 @@
{
"id": "standard.message_processing",
"name": "Info",
"description": "Модуль с информацией",
"author": "Karkas Team",
"version": "1.0.0",
"privileged": false,
"dependencies": {
"required": {
"standard.roles": "^1.0.0",
"standard.database": "^1.0.0",
"standard.command_helper": "^1.0.0"
}
}
}

View File

@ -1,155 +0,0 @@
# flake8: noqa
from typing import TYPE_CHECKING
from aiogram import Bot, F, Router, types
from karkas_core.modules_system.public_api import get_module, log, register_router
if TYPE_CHECKING:
from karkas_blocks.standard.config import IConfig
config: "IConfig" = get_module("standard.config", "config")
def get_yandexgpt_start_words():
return config.get("yandexgpt::startword").split(" | ")
def get_yandexgpt_in_words():
return config.get("yandexgpt::inword").split(" | ")
chat_not_in_approve = get_module("standard.filters", ["chat_not_in_approve"])
answer_to_message = get_module("external.yandexgpt", "answer_to_message")
(
get_chat,
add_chat,
get_user,
add_user,
get_user_name,
change_user_name,
get_user_tag,
change_user_tag,
update_chat_all_stat,
update_user_all_stat,
add_message,
) = get_module(
"standard.database",
[
"db_api.get_chat",
"db_api.add_chat",
"db_api.get_user",
"db_api.add_user",
"db_api.get_user_name",
"db_api.change_user_name",
"db_api.get_user_tag",
"db_api.change_user_tag",
"db_api.update_chat_all_stat",
"db_api.update_user_all_stat",
"db_api.add_message",
],
)
async def chat_check(message: types.Message):
# Проверка наличия id чата в базе данных чатов
# Если чата нет в базе данных, то проверяем его в наличии в конфиге и если он там есть то добавляем его в БД
# Если чат есть в базе данных, то pass
if get_chat(message.chat.id) is None:
if not chat_not_in_approve(message):
# print(f"Chat in approve list: {message.chat.id} {message.chat.title}")
log(f"Chat in approve list: {message.chat.id} {message.chat.title}")
add_chat(message.chat.id, message.chat.title)
# print(f"Chat added: {message.chat.id} {message.chat.title}")
log(f"Chat added: {message.chat.id} {message.chat.title}")
else:
# print(f"Chat not in approve list: {message.chat.id} {message.chat.title}")
log(f"Chat not in approve list: {message.chat.id} {message.chat.title}")
pass
else:
# Проверяем обновление названия чата
chat = get_chat(message.chat.id)
if chat.chat_name != message.chat.title:
chat.chat_name = message.chat.title
chat.save()
# print(f"Chat updated: {message.chat.id} {message.chat.title}")
log(f"Chat updated: {message.chat.id} {message.chat.title}")
else:
# print(f"Chat already exists: {message.chat.id} {message.chat.title}")
log(f"Chat already exists: {message.chat.id} {message.chat.title}")
pass
async def user_check(message: types.Message):
# Проверка наличия id пользователя в базе данных пользователей
# Если пользователя нет в базе данных, то добавляем его
# Если пользователь есть в базе данных, то pass
current_user_name = ""
if message.from_user.last_name is None:
current_user_name = message.from_user.first_name
else:
current_user_name = (
message.from_user.first_name + " " + message.from_user.last_name
)
if get_user(message.from_user.id) is None:
add_user(
message.from_user.id,
message.from_user.first_name,
message.from_user.last_name,
message.from_user.username,
)
log(f"User added: {message.from_user.id} {current_user_name}")
else:
log(
f"User already exists: {message.from_user.id} {current_user_name} {message.from_user.username}"
)
# Проверяем обновление имени пользователя
if get_user_name(message.from_user.id) != current_user_name:
change_user_name(message.from_user.id, current_user_name)
log(f"User name updated: {message.from_user.id} {current_user_name}")
# Проверяем обновление username пользователя
if get_user_tag(message.from_user.id) != message.from_user.username:
change_user_tag(message.from_user.id, message.from_user.username)
log(
f"User tag updated: {message.from_user.id} {message.from_user.username}"
)
pass
async def add_stats(message: types.Message):
# Добавляем пользователю и чату статистику
update_chat_all_stat(message.chat.id)
update_user_all_stat(message.from_user.id)
async def message_processing(message: types.Message, bot: Bot):
await chat_check(message)
await user_check(message)
await add_stats(message)
add_message(message)
# Если сообщение в начале содержит слово из списка или внутри сообщения содержится слово из списка или сообщение отвечает на сообщение бота
if (message.text.split(" ")[0] in get_yandexgpt_start_words()) or (
any(word in message.text for word in get_yandexgpt_in_words())
):
log("message_processing")
await answer_to_message(message, bot)
elif message.reply_to_message is not None:
if message.reply_to_message.from_user.is_bot:
log("message_processing")
await answer_to_message(message, bot)
router = Router()
# Если сообщение содержит текст то вызывается функция message_processing
router.message.register(message_processing, F.text)
async def module_init():
register_router(router)

View File

@ -0,0 +1 @@
from .piccolo_app import APP_CONFIG

View File

@ -0,0 +1,15 @@
import os
from karkas_piccolo.conf.apps import AppConfig
from .tables import Roles
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
APP_CONFIG = AppConfig(
app_name="standard.roles",
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
table_classes=[Roles],
migration_dependencies=[],
commands=[],
)

View File

@ -0,0 +1,59 @@
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
from piccolo.columns.column_types import Integer
from piccolo.columns.indexes import IndexMethod
ID = "2024-08-20T13:36:02:417372"
VERSION = "1.16.0"
DESCRIPTION = ""
async def forwards():
manager = MigrationManager(
migration_id=ID, app_name="standard.roles", description=DESCRIPTION
)
manager.add_table(class_name="Roles", tablename="roles", schema=None, columns=None)
manager.add_column(
table_class_name="Roles",
tablename="roles",
column_name="role_id",
db_column_name="role_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="Roles",
tablename="roles",
column_name="user_id",
db_column_name="user_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
return manager

View File

@ -0,0 +1,7 @@
from piccolo.columns import Integer
from piccolo.table import Table
class Roles(Table):
role_id = Integer()
user_id = Integer()

View File

@ -4,6 +4,9 @@ from karkas_core.modules_system.public_api import get_module
if TYPE_CHECKING: if TYPE_CHECKING:
from karkas_blocks.standard.config import IConfig from karkas_blocks.standard.config import IConfig
from karkas_blocks.standard.database import (
register_app_config as Iregister_app_config,
)
def module_init(): def module_init():
@ -34,4 +37,12 @@ def module_init():
visible=False, visible=False,
) )
register_app_config: "Iregister_app_config" = get_module(
"standard.database", "register_app_config"
)
from .db import APP_CONFIG
register_app_config(APP_CONFIG)
pass pass

View File

@ -2,13 +2,11 @@ from typing import TYPE_CHECKING
from karkas_core.modules_system.public_api import get_module from karkas_core.modules_system.public_api import get_module
from .db.tables import Roles as RolesTable
if TYPE_CHECKING: if TYPE_CHECKING:
from karkas_blocks.standard.config import IConfig from karkas_blocks.standard.config import IConfig
from karkas_blocks.standard.database.db_api import get_user_role as IGetUserRoleType
get_user_role: "IGetUserRoleType" = get_module(
"standard.database", "db_api.get_user_role"
)
config: "IConfig" = get_module("standard.config", "config") config: "IConfig" = get_module("standard.config", "config")
@ -21,6 +19,17 @@ class Roles:
def __init__(self): def __init__(self):
pass pass
def get_user_role(self, user_id):
row = (
RolesTable.select(RolesTable.role_id)
.where(RolesTable.user_id == user_id)
.first()
.run_sync()
)
if row is not None:
return row["role_id"]
return None
def update_roles(self): def update_roles(self):
self.user_role_id = config.get("roles::user") self.user_role_id = config.get("roles::user")
self.moderator_role_id = config.get("roles::moderator") self.moderator_role_id = config.get("roles::moderator")
@ -29,7 +38,7 @@ class Roles:
async def check_admin_permission(self, user_id): async def check_admin_permission(self, user_id):
self.update_roles() self.update_roles()
match get_user_role(user_id): match self.get_user_role(user_id):
case self.admin_role_id: case self.admin_role_id:
return True return True
case _: case _:
@ -37,7 +46,7 @@ class Roles:
async def check_moderator_permission(self, user_id): async def check_moderator_permission(self, user_id):
self.update_roles() self.update_roles()
match get_user_role(user_id): match self.get_user_role(user_id):
case self.moderator_role_id: case self.moderator_role_id:
return True return True
case _: case _:
@ -59,7 +68,7 @@ class Roles:
async def get_user_permission(self, user_id): async def get_user_permission(self, user_id):
self.update_roles() self.update_roles()
match get_user_role(user_id): match self.get_user_role(user_id):
case self.admin_role_id: case self.admin_role_id:
return self.admin return self.admin
case self.moderator_role_id: case self.moderator_role_id:

View File

@ -0,0 +1,15 @@
from karkas_core.modules_system.public_api import (
get_module,
register_outer_message_middleware,
)
from .main import StatisticsMiddleware
def module_init():
register_app_config = get_module("standard.database", "register_app_config")
from .db import APP_CONFIG
register_app_config(APP_CONFIG)
register_outer_message_middleware(StatisticsMiddleware())

View File

@ -0,0 +1 @@
from .piccolo_app import APP_CONFIG

View File

@ -0,0 +1,15 @@
import os
from karkas_piccolo.conf.apps import AppConfig
from .tables import ChatStats, Messages, UserStats
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
APP_CONFIG = AppConfig(
app_name="standard.statistics",
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
table_classes=[ChatStats, Messages, UserStats],
migration_dependencies=[],
commands=[],
)

View File

@ -0,0 +1,322 @@
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
from piccolo.columns.column_types import Date, Integer, Text
from piccolo.columns.defaults.date import DateNow
from piccolo.columns.indexes import IndexMethod
ID = "2024-08-20T16:28:38:371951"
VERSION = "1.16.0"
DESCRIPTION = ""
async def forwards():
manager = MigrationManager(
migration_id=ID, app_name="standard.statistics", description=DESCRIPTION
)
manager.add_table(
class_name="Messages", tablename="messages", schema=None, columns=None
)
manager.add_table(
class_name="ChatStats", tablename="chat_stats", schema=None, columns=None
)
manager.add_table(
class_name="UserStats", tablename="user_stats", schema=None, columns=None
)
manager.add_column(
table_class_name="Messages",
tablename="messages",
column_name="key",
db_column_name="key",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": False,
"primary_key": True,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="Messages",
tablename="messages",
column_name="chat_id",
db_column_name="chat_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="Messages",
tablename="messages",
column_name="message_id",
db_column_name="message_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="Messages",
tablename="messages",
column_name="sender_id",
db_column_name="sender_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="Messages",
tablename="messages",
column_name="answer_to_message_id",
db_column_name="answer_to_message_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": None,
"null": True,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="Messages",
tablename="messages",
column_name="message_text",
db_column_name="message_text",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="ChatStats",
tablename="chat_stats",
column_name="chat_id",
db_column_name="chat_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": True,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="ChatStats",
tablename="chat_stats",
column_name="date",
db_column_name="date",
column_class_name="Date",
column_class=Date,
params={
"default": DateNow(),
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="ChatStats",
tablename="chat_stats",
column_name="messages_count",
db_column_name="messages_count",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserStats",
tablename="user_stats",
column_name="key",
db_column_name="key",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": False,
"primary_key": True,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserStats",
tablename="user_stats",
column_name="chat_id",
db_column_name="chat_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserStats",
tablename="user_stats",
column_name="user_id",
db_column_name="user_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserStats",
tablename="user_stats",
column_name="date",
db_column_name="date",
column_class_name="Date",
column_class=Date,
params={
"default": DateNow(),
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserStats",
tablename="user_stats",
column_name="messages_count",
db_column_name="messages_count",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
return manager

View File

@ -0,0 +1,34 @@
from piccolo.columns import Date, Integer, Text
from piccolo.table import Table
class ChatStats(Table):
chat_id = Integer(primary_key=True)
date = Date()
messages_count = Integer(default=0)
class Messages(Table):
# Временная мера, пока не примут
# https://github.com/piccolo-orm/piccolo/pull/984
# {message_chat_id}-{message_id}
key = Text(primary_key=True)
chat_id = Integer()
message_id = Integer()
sender_id = Integer()
answer_to_message_id = Integer(null=True, default=None)
# message_ai_model = pw.TextField(null=True)
message_text = Text()
class UserStats(Table):
# Временная мера, пока не примут
# https://github.com/piccolo-orm/piccolo/pull/984
# {chat_id}-{user_id}
key = Text(primary_key=True)
chat_id = Integer()
user_id = Integer()
date = Date()
messages_count = Integer(default=0)

View File

@ -0,0 +1,18 @@
{
"id": "standard.statistics",
"name": "Статистика",
"description": "Очень полезный модуль",
"author": "Karkas Team",
"version": "1.0.0",
"privileged": true,
"dependencies": {
"required": {
"standard.database": "^1.0.0"
}
},
"pythonDependencies": {
"required": {
"piccolo": "*"
}
}
}

View File

@ -0,0 +1,68 @@
from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import Message, TelegramObject
from .db.tables import ChatStats, Messages, UserStats
async def update_chat_stats(event: Message):
await ChatStats.insert(
ChatStats(chat_id=event.chat.id, date=event.date, messages_count=1)
).on_conflict(
action="DO UPDATE",
values=[
ChatStats.date,
(ChatStats.messages_count, ChatStats.messages_count + 1),
],
).run()
async def update_user_stats(event: Message):
await UserStats.insert(
UserStats(
key=f"{event.chat.id}-{event.from_user.id}",
chat_id=event.chat.id,
user_id=event.from_user.id,
date=event.date,
messages_count=1,
)
).on_conflict(
action="DO UPDATE",
values=[
UserStats.date,
(UserStats.messages_count, UserStats.messages_count + 1),
],
).run()
async def save_messages(event: Message):
await Messages.insert(
Messages(
key=f"{event.chat.id}-{event.message_id}",
chat_id=event.chat.id,
message_id=event.message_id,
sender_id=event.from_user.id,
answer_to_message_id=(
event.reply_to_message.message_id if event.reply_to_message else None
),
message_text=event.text,
)
)
class StatisticsMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
await update_chat_stats(event)
await update_user_stats(event)
await save_messages(event)
result = await handler(event, data)
return result

View File

@ -0,0 +1,15 @@
from karkas_core.modules_system.public_api import (
get_module,
register_outer_message_middleware,
)
from .main import UsersMiddleware
def module_init():
register_app_config = get_module("standard.database", "register_app_config")
from .db import APP_CONFIG
register_app_config(APP_CONFIG)
register_outer_message_middleware(UsersMiddleware())

View File

@ -0,0 +1 @@
from .piccolo_app import APP_CONFIG

View File

@ -0,0 +1,15 @@
import os
from karkas_piccolo.conf.apps import AppConfig
from .tables import UserInfo
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
APP_CONFIG = AppConfig(
app_name="standard.users",
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
table_classes=[UserInfo],
migration_dependencies=[],
commands=[],
)

View File

@ -0,0 +1,104 @@
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
from piccolo.columns.column_types import Date, Integer, Text
from piccolo.columns.defaults.date import DateNow
from piccolo.columns.indexes import IndexMethod
ID = "2024-08-20T17:06:44:026509"
VERSION = "1.16.0"
DESCRIPTION = ""
async def forwards():
manager = MigrationManager(
migration_id=ID, app_name="standard.users", description=DESCRIPTION
)
manager.add_table(
class_name="UserInfo", tablename="user_info", schema=None, columns=None
)
manager.add_column(
table_class_name="UserInfo",
tablename="user_info",
column_name="user_id",
db_column_name="user_id",
column_class_name="Integer",
column_class=Integer,
params={
"default": 0,
"null": False,
"primary_key": True,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserInfo",
tablename="user_info",
column_name="name",
db_column_name="name",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserInfo",
tablename="user_info",
column_name="tag",
db_column_name="tag",
column_class_name="Text",
column_class=Text,
params={
"default": "",
"null": True,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
manager.add_column(
table_class_name="UserInfo",
tablename="user_info",
column_name="created_at",
db_column_name="created_at",
column_class_name="Date",
column_class=Date,
params={
"default": DateNow(),
"null": False,
"primary_key": False,
"unique": False,
"index": False,
"index_method": IndexMethod.btree,
"choices": None,
"db_column_name": None,
"secret": False,
},
schema=None,
)
return manager

View File

@ -0,0 +1,9 @@
from piccolo.columns import Date, Integer, Text
from piccolo.table import Table
class UserInfo(Table):
user_id = Integer(primary_key=True)
name = Text()
tag = Text(null=True)
created_at = Date()

View File

@ -0,0 +1,13 @@
{
"id": "standard.users",
"name": "Пользователи",
"description": "Очень полезный модуль",
"author": "Karkas Team",
"version": "1.0.0",
"privileged": true,
"dependencies": {
"required": {
"standard.database": "^1.0.0"
}
}
}

View File

@ -0,0 +1,36 @@
from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, User
from .db.tables import UserInfo
async def update_user_info(user: User):
if user.last_name is None:
user_name = user.first_name
else:
user_name = user.first_name + " " + user.last_name
await UserInfo.insert(
UserInfo(user_id=user.id, name=user_name, tag=user.username)
).on_conflict(
action="DO UPDATE",
values=[
UserInfo.name,
UserInfo.tag,
],
).run()
class UsersMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
user = event.from_user
await update_user_info(user)
result = await handler(event, data)
return result

View File

@ -13,18 +13,18 @@ files = [
[[package]] [[package]]
name = "aiogram" name = "aiogram"
version = "3.10.0" version = "3.12.0"
description = "Modern and fully asynchronous framework for Telegram Bot API" description = "Modern and fully asynchronous framework for Telegram Bot API"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "aiogram-3.10.0-py3-none-any.whl", hash = "sha256:dc43bfbe68c736cca48d91ffbc55a397df24b56c332206af850965619689beca"}, {file = "aiogram-3.12.0-py3-none-any.whl", hash = "sha256:4b9969d6751b810748835d341aafc0b44fe4675d741bff1b07c24daa47edf79f"},
{file = "aiogram-3.10.0.tar.gz", hash = "sha256:f500d4b309e3cc08a87ae5a053b229199034f382925de00aa2ed005d5e25d575"}, {file = "aiogram-3.12.0.tar.gz", hash = "sha256:582a5cf058fccc470ed08c9aa36fdef5b708d01965e51963c3969596c1d2ac19"},
] ]
[package.dependencies] [package.dependencies]
aiofiles = ">=23.2.1,<23.3.0" aiofiles = ">=23.2.1,<23.3.0"
aiohttp = ">=3.9.0,<3.10.0" aiohttp = ">=3.9.0,<3.11"
certifi = ">=2023.7.22" certifi = ">=2023.7.22"
magic-filter = ">=1.0.12,<1.1" magic-filter = ">=1.0.12,<1.1"
pydantic = ">=2.4.1,<2.9" pydantic = ">=2.4.1,<2.9"
@ -32,8 +32,8 @@ typing-extensions = ">=4.7.0,<=5.0"
[package.extras] [package.extras]
cli = ["aiogram-cli (>=1.0.3,<1.1.0)"] cli = ["aiogram-cli (>=1.0.3,<1.1.0)"]
dev = ["black (>=24.4.2,<24.5.0)", "isort (>=5.13.2,<5.14.0)", "motor-types (>=1.0.0b4,<1.1.0)", "mypy (>=1.10.0,<1.11.0)", "packaging (>=24.1,<25.0)", "pre-commit (>=3.5,<4.0)", "ruff (>=0.4.9,<0.5.0)", "toml (>=0.10.2,<0.11.0)"] dev = ["black (>=24.4.2,<24.5.0)", "isort (>=5.13.2,<5.14.0)", "motor-types (>=1.0.0b4,<1.1.0)", "mypy (>=1.10.0,<1.11.0)", "packaging (>=24.1,<25.0)", "pre-commit (>=3.5,<4.0)", "ruff (>=0.5.1,<0.6.0)", "toml (>=0.10.2,<0.11.0)"]
docs = ["furo (>=2023.9.10,<2023.10.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.16.1,<2.17.0)", "pymdown-extensions (>=10.3,<11.0)", "sphinx (>=7.2.6,<7.3.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.1.0,<2.2.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.3.2a0,<0.4.0)", "towncrier (>=23.6.0,<23.7.0)"] docs = ["furo (>=2023.9.10,<2023.10.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.16.1,<2.17.0)", "pymdown-extensions (>=10.3,<11.0)", "sphinx (>=7.2.6,<7.3.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.1.0,<2.2.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.4.0a0,<0.5.0)", "towncrier (>=24.7.1,<24.8.0)"]
fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)"] fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)"]
i18n = ["babel (>=2.13.0,<2.14.0)"] i18n = ["babel (>=2.13.0,<2.14.0)"]
mongo = ["motor (>=3.3.2,<3.4.0)"] mongo = ["motor (>=3.3.2,<3.4.0)"]
@ -41,100 +41,113 @@ proxy = ["aiohttp-socks (>=0.8.3,<0.9.0)"]
redis = ["redis[hiredis] (>=5.0.1,<5.1.0)"] redis = ["redis[hiredis] (>=5.0.1,<5.1.0)"]
test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.19.0,<3.20.0)", "pytest (>=7.4.2,<7.5.0)", "pytest-aiohttp (>=1.0.5,<1.1.0)", "pytest-asyncio (>=0.21.1,<0.22.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-html (>=4.0.2,<4.1.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.12.0,<3.13.0)", "pytest-mypy (>=0.10.3,<0.11.0)", "pytz (>=2023.3,<2024.0)"] test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.19.0,<3.20.0)", "pytest (>=7.4.2,<7.5.0)", "pytest-aiohttp (>=1.0.5,<1.1.0)", "pytest-asyncio (>=0.21.1,<0.22.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-html (>=4.0.2,<4.1.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.12.0,<3.13.0)", "pytest-mypy (>=0.10.3,<0.11.0)", "pytz (>=2023.3,<2024.0)"]
[[package]]
name = "aiohappyeyeballs"
version = "2.4.0"
description = "Happy Eyeballs for asyncio"
optional = false
python-versions = ">=3.8"
files = [
{file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"},
{file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"},
]
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
version = "3.9.5" version = "3.10.4"
description = "Async http client/server framework (asyncio)" description = "Async http client/server framework (asyncio)"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, {file = "aiohttp-3.10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81037ddda8cc0a95c6d8c1b9029d0b19a62db8770c0e239e3bea0109d294ab66"},
{file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, {file = "aiohttp-3.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71944d4f4090afc07ce96b7029d5a574240e2f39570450df4af0d5b93a5ee64a"},
{file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, {file = "aiohttp-3.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c774f08afecc0a617966f45a9c378456e713a999ee60654d9727617def3e4ee4"},
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc990e73613c78ab2930b60266135066f37fdfce6b32dd604f42c5c377ee880a"},
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6acd1a908740f708358d240f9a3243cec31a456e3ded65c2cb46f6043bc6735"},
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6075e27e7e54fbcd1c129c5699b2d251c885c9892e26d59a0fb7705141c2d14b"},
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc98d93d11d860ac823beb6131f292d82efb76f226b5e28a3eab1ec578dfd041"},
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:201ddf1471567568be381b6d4701e266a768f7eaa2f99ef753f2c9c5e1e3fb5c"},
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7d202ec55e61f06b1a1eaf317fba7546855cbf803c13ce7625d462fb8c88e238"},
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:96b2e7c110a941c8c1a692703b8ac1013e47f17ee03356c71d55c0a54de2ce38"},
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8ba0fbc56c44883bd757ece433f9caadbca67f565934afe9bc53ba3bd99cc368"},
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46cc9069da466652bb7b8b3fac1f8ce2e12a9dc0fb11551faa420c4cdbc60abf"},
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a19cd1e9dc703257fda78b8e889c3a08eabaa09f6ff0d867850b03964f80d1"},
{file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, {file = "aiohttp-3.10.4-cp310-cp310-win32.whl", hash = "sha256:8593040bcc8075fc0e817a602bc5d3d74c7bd717619ffc175a8ba0188edebadf"},
{file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, {file = "aiohttp-3.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:326fb5228aadfc395981d9b336d56a698da335897c4143105c73b583d7500839"},
{file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, {file = "aiohttp-3.10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dfe48f477e02ef5ab247c6ac431a6109c69b5c24cb3ccbcd3e27c4fb39691fe4"},
{file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, {file = "aiohttp-3.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6fe78b51852e25d4e20be51ef88c2a0bf31432b9f2223bdbd61c01a0f9253a7"},
{file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, {file = "aiohttp-3.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cc75ff5efbd92301e63a157fddb18a6964a3f40e31c77d57e97dbb9bb3373b4"},
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dca39391f45fbb28daa6412f98c625265bf6b512cc41382df61672d1b242f8f4"},
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8616dd5ed8b3b4029021b560305041c62e080bb28f238c27c2e150abe3539587"},
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d7958ba22854b3f00a7bbb66cde1dc759760ce8a3e6dfe9ea53f06bccaa9aa2"},
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a24ac7164a824ef2e8e4e9a9f6debb1f43c44ad7ad04efc6018a6610555666d"},
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:660ad010b8fd0b26e8edb8ae5c036db5b16baac4278198ad238b11956d920b3d"},
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:93ee83008d3e505db9846a5a1f48a002676d8dcc90ee431a9462541c9b81393c"},
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77071795efd6ba87f409001141fb05c94ee962b9fca6c8fa1f735c2718512de4"},
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ff371ae72a1816c3eeba5c9cff42cb739aaa293fec7d78f180d1c7ee342285b6"},
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c253e81f12da97f85d45441e8c6da0d9c12e07db4a7136b0a955df6fc5e4bf51"},
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ce101c447cf7ba4b6e5ab07bfa2c0da21cbab66922f78a601f0b84fd7710d72"},
{file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, {file = "aiohttp-3.10.4-cp311-cp311-win32.whl", hash = "sha256:705c311ecf2d30fbcf3570d1a037c657be99095694223488140c47dee4ef2460"},
{file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, {file = "aiohttp-3.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:ebddbfea8a8d6b97f717658fa85a96681a28990072710d3de3a4eba5d6804a37"},
{file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, {file = "aiohttp-3.10.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4d63f42d9c604521b208b754abfafe01218af4a8f6332b43196ee8fe88bbd5"},
{file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, {file = "aiohttp-3.10.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fef7b7bd3a6911b4d148332136d34d3c2aee3d54d354373b1da6d96bc08089a5"},
{file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, {file = "aiohttp-3.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff8606149098935188fe1e135f7e7991e6a36d6fe394fd15939fc57d0aff889"},
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb3df1aa83602be9a5e572c834d74c3c8e382208b59a873aabfe4c493c45ed0"},
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c4a71d4a5e0cbfd4bfadd13cb84fe2bc76c64d550dc4f22c22008c9354cffb3"},
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf61884a604c399458c4a42c8caea000fbcc44255ed89577ff50cb688a0fe8e2"},
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2015e4b40bd5dedc8155c2b2d24a2b07963ae02b5772373d0b599a68e38a316b"},
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b06e1a66bf0a1a2d0f12aef25843dfd2093df080d6c1acbc43914bb9c8f36ed3"},
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb898c9ad5a1228a669ebe2e2ba3d76aebe1f7c10b78f09a36000254f049fc2b"},
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2d64a5a7539320c3cecb4bca093ea825fcc906f8461cf8b42a7bf3c706ce1932"},
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:438c6e1492d060b21285f4b6675b941cf96dd9ef3dfdd59940561029b82e3e1f"},
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e99bf118afb2584848dba169a685fe092b338a4fe52ae08c7243d7bc4cc204fe"},
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dc26781fb95225c6170619dece8b5c6ca7cfb1b0be97b7ee719915773d0c2a9"},
{file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, {file = "aiohttp-3.10.4-cp312-cp312-win32.whl", hash = "sha256:45bb655cb8b3a61e19977183a4e0962051ae90f6d46588ed4addb8232128141c"},
{file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, {file = "aiohttp-3.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:347bbdc48411badc24fe3a13565820bc742db3aa2f9127cd5f48c256caf87e29"},
{file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, {file = "aiohttp-3.10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4ad284cee0fdcdc0216346b849fd53d201b510aff3c48aa3622daec9ada4bf80"},
{file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, {file = "aiohttp-3.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:58df59234be7d7e80548b9482ebfeafdda21948c25cb2873c7f23870c8053dfe"},
{file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, {file = "aiohttp-3.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5f52225af7f91f27b633f73473e9ef0aa8e2112d57b69eaf3aa4479e3ea3bc0e"},
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f1a0e12c321d923c024b56d7dcd8012e60bf30a4b3fb69a88be15dcb9ab80b"},
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9e9e9a51dd12f2f71fdbd7f7230dcb75ed8f77d8ac8e07c73b599b6d7027e5c"},
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38bb515f1affc36d3d97b02bf82099925a5785c4a96066ff4400a83ad09d3d5d"},
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e685afb0e3b7b861d89cb3690d89eeda221b43095352efddaaa735c6baf87f3"},
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd5673e3391564871ba6753cf674dcf2051ef19dc508998fe0758a6c7b429a0"},
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4b34e5086e1ead3baa740e32adf35cc5e42338e44c4b07f7b62b41ca6d6a5bfd"},
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c3fd3b8f0164fb2866400cd6eb9e884ab0dc95f882cf8b25e560ace7350c552d"},
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:b95e1694d234f27b4bbf5bdef56bb751974ac5dbe045b1e462bde1fe39421cbe"},
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:c031de4dfabe7bb6565743745ab43d20588944ddfc7233360169cab4008eee2f"},
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:03c5a3143d4a82c43a3d82ac77d9cdef527a72f1c04dcca7b14770879f33d196"},
{file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, {file = "aiohttp-3.10.4-cp38-cp38-win32.whl", hash = "sha256:b71722b527445e02168e2d1cf435772731874671a647fa159ad000feea7933b6"},
{file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, {file = "aiohttp-3.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fd1f57aac7d01c9c768675d531976d20d5b79d9da67fac87e55d41b4ade05f9"},
{file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, {file = "aiohttp-3.10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:15b36a644d1f44ea3d94a0bbb71e75d5f394a3135dc388a209466e22b711ce64"},
{file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, {file = "aiohttp-3.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:394ddf9d216cf0bd429b223239a0ab628f01a7a1799c93ce4685eedcdd51b9bc"},
{file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, {file = "aiohttp-3.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd33f4d571b4143fc9318c3d9256423579c7d183635acc458a6db81919ae5204"},
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5991b80886655e6c785aadf3114d4f86e6bec2da436e2bb62892b9f048450a4"},
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92021bf0a4b9ad16851a6c1ca3c86e5b09aecca4f7a2576430c6bbf3114922b1"},
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:938e37fd337343c67471098736deb33066d72cec7d8927b9c1b6b4ea807ade9e"},
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d697023b16c62f9aeb3ffdfb8ec4ac3afd477388993b9164b47dadbd60e7062"},
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2f9f07fe6d0d51bd2a788cbb339f1570fd691449c53b5dec83ff838f117703e"},
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:50ac670f3fc13ce95e4d6d5a299db9288cc84c663aa630142444ef504756fcf7"},
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9bcdd19398212785a9cb82a63a4b75a299998343f3f5732dfd37c1a4275463f9"},
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:122c26f0976225aba46f381e3cabb5ef89a08af6503fc30493fb732e578cfa55"},
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d0665e2a346b6b66959f831ffffd8aa71dd07dd2300017d478f5b47573e66cfe"},
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:625a4a9d4b9f80e7bbaaf2ace06341cf701b2fee54232843addf0bb7304597fb"},
{file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, {file = "aiohttp-3.10.4-cp39-cp39-win32.whl", hash = "sha256:5115490112f39f16ae87c1b34dff3e2c95306cf456b1d2af5974c4ac7d2d1ec7"},
{file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, {file = "aiohttp-3.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9b58b2ef7f28a2462ba86acbf3b20371bd80a1faa1cfd82f31968af4ac81ef25"},
{file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, {file = "aiohttp-3.10.4.tar.gz", hash = "sha256:23a5f97e7dd22e181967fb6cb6c3b11653b0fdbbc4bb7739d9b6052890ccab96"},
] ]
[package.dependencies] [package.dependencies]
aiohappyeyeballs = ">=2.3.0"
aiosignal = ">=1.1.2" aiosignal = ">=1.1.2"
async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""}
attrs = ">=17.3.0" attrs = ">=17.3.0"
frozenlist = ">=1.1.1" frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0" multidict = ">=4.5,<7.0"
yarl = ">=1.0,<2.0" yarl = ">=1.0,<2.0"
[package.extras] [package.extras]
speedups = ["Brotli", "aiodns", "brotlicffi"] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"]
[[package]] [[package]]
name = "aiosignal" name = "aiosignal"
@ -161,24 +174,35 @@ files = [
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
] ]
[[package]]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "23.2.0" version = "24.2.0"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
] ]
[package.extras] [package.extras]
cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
dev = ["attrs[tests]", "pre-commit"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
tests = ["attrs[tests-no-zope]", "zope-interface"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
[[package]] [[package]]
name = "blinker" name = "blinker"
@ -679,6 +703,29 @@ files = [
editorconfig = ">=0.12.2" editorconfig = ">=0.12.2"
six = ">=1.13.0" six = ">=1.13.0"
[[package]]
name = "karkas-core"
version = "0.1.0"
description = ""
optional = false
python-versions = ">=3.10,<3.13"
files = []
develop = true
[package.dependencies]
aiogram = "^3.10.0"
dataclasses-json = "^0.6.7"
restrictedpython = "^7.1"
semver = "^3.0.2"
setuptools = "^71.0.1"
[package.extras]
webhook = ["fastapi (>=0.111.1,<0.112.0)", "hypercorn (>=0.17.3,<0.18.0)"]
[package.source]
type = "directory"
url = "../karkas_core"
[[package]] [[package]]
name = "magic-filter" name = "magic-filter"
version = "1.0.12" version = "1.0.12"
@ -783,13 +830,13 @@ tests = ["pytest", "pytz", "simplejson"]
[[package]] [[package]]
name = "more-itertools" name = "more-itertools"
version = "10.3.0" version = "10.4.0"
description = "More routines for operating on iterables, beyond itertools" description = "More routines for operating on iterables, beyond itertools"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, {file = "more-itertools-10.4.0.tar.gz", hash = "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923"},
{file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, {file = "more_itertools-10.4.0-py3-none-any.whl", hash = "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27"},
] ]
[[package]] [[package]]
@ -913,29 +960,6 @@ files = [
{file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
] ]
[[package]]
name = "karkas-core"
version = "0.1.0"
description = ""
optional = false
python-versions = "~3.12"
files = []
develop = true
[package.dependencies]
aiogram = "^3.10.0"
dataclasses-json = "^0.6.7"
restrictedpython = "^7.1"
semver = "^3.0.2"
setuptools = "^71.0.1"
[package.extras]
webhook = ["fastapi (>=0.111.1,<0.112.0)", "hypercorn (>=0.17.3,<0.18.0)"]
[package.source]
type = "directory"
url = "../karkas_core"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "24.1"
@ -1094,62 +1118,64 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0.1" version = "6.0.2"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.8"
files = [ files = [
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
] ]
[[package]] [[package]]
@ -1175,13 +1201,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]] [[package]]
name = "restrictedpython" name = "restrictedpython"
version = "7.1" version = "7.2"
description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment." description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment."
optional = false optional = false
python-versions = ">=3.7, <3.13" python-versions = "<3.13,>=3.7"
files = [ files = [
{file = "RestrictedPython-7.1-py3-none-any.whl", hash = "sha256:56d0c73e5de1757702053383601b0fcd3fb2e428039ee1df860409ad67b17d2b"}, {file = "RestrictedPython-7.2-py3-none-any.whl", hash = "sha256:139cb41da6e57521745a566d05825f7a09e6a884f7fa922568cff0a70b84ce6b"},
{file = "RestrictedPython-7.1.tar.gz", hash = "sha256:875aeb51c139d78e34cef8605dc65309b449168060dd08551a1fe9edb47cb9a5"}, {file = "RestrictedPython-7.2.tar.gz", hash = "sha256:4d1d30f709a6621ca7c4236f08b67b732a651c8099145f137078c7dd4bed3d21"},
] ]
[package.extras] [package.extras]
@ -1268,13 +1294,13 @@ files = [
[[package]] [[package]]
name = "tenacity" name = "tenacity"
version = "8.5.0" version = "9.0.0"
description = "Retry code until it succeeds" description = "Retry code until it succeeds"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"},
{file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"},
] ]
[package.extras] [package.extras]
@ -1446,13 +1472,13 @@ multidict = ">=4.0"
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.19.2" version = "3.20.0"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"},
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"},
] ]
[package.extras] [package.extras]
@ -1461,5 +1487,5 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = ">=3.10,<3.13"
content-hash = "c7fd9c49b3d382a9bbd3c75c20d668574e671a7f1e6aca5ee1e1787059a9e1e7" content-hash = "fa282851d0e8d13e428df402edeffe56b4468295b93e492135a6d73760ffb9a5"

View File

@ -6,7 +6,7 @@ authors = ["Maxim Slipenko <maxim@slipenko.com>"]
readme = "README.md" readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<=3.12" python = ">=3.10,<3.13"
karkas-core = { path = "../karkas_core", develop = true } karkas-core = { path = "../karkas_core", develop = true }
peewee = "^3.17.6" peewee = "^3.17.6"

View File

@ -3,8 +3,7 @@ from typing import TYPE_CHECKING
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from karkas_core.lib import register_bot_webhook from karkas_core.logger import log, setup_logger
from karkas_core.logger import CustomLogger, log, setup_logger
from karkas_core.modules_system import ModulesManager from karkas_core.modules_system import ModulesManager
from karkas_core.modules_system.public_api import get_module from karkas_core.modules_system.public_api import get_module
from karkas_core.singleton import Singleton from karkas_core.singleton import Singleton
@ -70,6 +69,9 @@ class Karkas:
from fastapi import FastAPI from fastapi import FastAPI
from hypercorn.asyncio import serve from hypercorn.asyncio import serve
from hypercorn.config import Config as HyperConfig from hypercorn.config import Config as HyperConfig
from karkas_core.lib import register_bot_webhook
from karkas_core.logger import CustomLogger
except ImportError: except ImportError:
log( log(
"Error: FastAPI and Hypercorn are required" "Error: FastAPI and Hypercorn are required"

View File

@ -22,4 +22,5 @@ class Singleton(metaclass=SingletonMeta):
"_fsm_storage": MemoryStorage(), "_fsm_storage": MemoryStorage(),
"_routers": [], "_routers": [],
"_outer_message_middlewares": [], "_outer_message_middlewares": [],
"metainfo": {},
} }

View File

@ -128,6 +128,7 @@ files = [
[package.dependencies] [package.dependencies]
aiosignal = ">=1.1.2" aiosignal = ">=1.1.2"
async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""}
attrs = ">=17.3.0" attrs = ">=17.3.0"
frozenlist = ">=1.1.1" frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0" multidict = ">=4.5,<7.0"
@ -173,14 +174,27 @@ files = [
] ]
[package.dependencies] [package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8" idna = ">=2.8"
sniffio = ">=1.1" sniffio = ">=1.1"
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"] trio = ["trio (>=0.23)"]
[[package]]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "23.2.0" version = "23.2.0"
@ -539,6 +553,20 @@ files = [
dnspython = ">=2.0.0" dnspython = ">=2.0.0"
idna = ">=2.0.0" idna = ">=2.0.0"
[[package]]
name = "exceptiongroup"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = true
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.111.1" version = "0.111.1"
@ -846,9 +874,13 @@ files = [
] ]
[package.dependencies] [package.dependencies]
exceptiongroup = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
h11 = "*" h11 = "*"
h2 = ">=3.1.0" h2 = ">=3.1.0"
priority = "*" priority = "*"
taskgroup = {version = "*", markers = "python_version < \"3.11\""}
tomli = {version = "*", markers = "python_version < \"3.11\""}
typing_extensions = {version = "*", markers = "python_version < \"3.11\""}
wsproto = ">=0.14.0" wsproto = ">=0.14.0"
[package.extras] [package.extras]
@ -940,6 +972,27 @@ files = [
editorconfig = ">=0.12.2" editorconfig = ">=0.12.2"
six = ">=1.13.0" six = ">=1.13.0"
[[package]]
name = "karkas-blocks"
version = "0.1.0"
description = ""
optional = false
python-versions = ">=3.10,<3.13"
files = []
develop = true
[package.dependencies]
dash = "^2.17.1"
dash-bootstrap-components = "^1.6.0"
dash-extensions = "^1.0.18"
karkas-core = {path = "../karkas_core", develop = true}
peewee = "^3.17.6"
pyyaml = "^6.0.1"
[package.source]
type = "directory"
url = "../karkas_blocks"
[[package]] [[package]]
name = "magic-filter" name = "magic-filter"
version = "1.0.12" version = "1.0.12"
@ -1209,27 +1262,6 @@ files = [
{file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
] ]
[[package]]
name = "karkas-blocks"
version = "0.1.0"
description = ""
optional = false
python-versions = "~3.12"
files = []
develop = true
[package.dependencies]
dash = "^2.17.1"
dash-bootstrap-components = "^1.6.0"
dash-extensions = "^1.0.18"
karkas-core = {path = "../karkas_core", develop = true}
peewee = "^3.17.6"
pyyaml = "^6.0.1"
[package.source]
type = "directory"
url = "../karkas_blocks"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "24.1"
@ -1670,6 +1702,20 @@ anyio = ">=3.4.0,<5"
[package.extras] [package.extras]
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
[[package]]
name = "taskgroup"
version = "0.0.0a4"
description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout"
optional = true
python-versions = "*"
files = [
{file = "taskgroup-0.0.0a4-py2.py3-none-any.whl", hash = "sha256:5c1bd0e4c06114e7a4128583ab75c987597d5378a33948a3b74c662b90f61277"},
{file = "taskgroup-0.0.0a4.tar.gz", hash = "sha256:eb08902d221e27661950f2a0320ddf3f939f579279996f81fe30779bca3a159c"},
]
[package.dependencies]
exceptiongroup = "*"
[[package]] [[package]]
name = "tenacity" name = "tenacity"
version = "9.0.0" version = "9.0.0"
@ -1685,6 +1731,17 @@ files = [
doc = ["reno", "sphinx"] doc = ["reno", "sphinx"]
test = ["pytest", "tornado (>=4.5)", "typeguard"] test = ["pytest", "tornado (>=4.5)", "typeguard"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = true
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]] [[package]]
name = "typer" name = "typer"
version = "0.12.3" version = "0.12.3"
@ -1763,6 +1820,7 @@ h11 = ">=0.8"
httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
@ -2136,5 +2194,5 @@ webhook = ["fastapi", "hypercorn"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = ">=3.10,<3.13"
content-hash = "374abe60f0df5be4f88b1b0aeefe73b4205ff2a08625359e8191dae5795d5c8f" content-hash = "e7b4aaf181c6631a67107a6287162313137c3b564222e33ef5cd1f10052bc3b8"

View File

@ -6,7 +6,7 @@ authors = ["Максим Слипенко <maxim@slipenko.com>"]
readme = "README.md" readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<=3.12" python = ">=3.10,<3.13"
aiogram = "^3.10.0" aiogram = "^3.10.0"
setuptools = "^71.0.1" setuptools = "^71.0.1"
restrictedpython = "^7.1" restrictedpython = "^7.1"

View File

View File

@ -0,0 +1,3 @@
from .main import main
main()

View File

@ -0,0 +1,6 @@
from piccolo.conf.apps import AppConfig as OrigAppConfig
class AppConfig(OrigAppConfig):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -0,0 +1,6 @@
from piccolo.conf.apps import AppRegistry as OrigAppRegistry
class AppRegistry(OrigAppRegistry):
def __init__(self, apps_configs):
self.app_configs = apps_configs

View File

@ -0,0 +1,2 @@
from .AppConfig import AppConfig
from .AppRegistry import AppRegistry

View File

@ -0,0 +1,2 @@
from .get_info_path import get_info_path
from .migrate_forward import migrate_forward

View File

@ -0,0 +1,9 @@
import inspect
import os
def get_info_path():
caller_frame = inspect.currentframe()
caller_file = caller_frame.f_code.co_filename
caller_directory = os.path.dirname(os.path.abspath(caller_file))
return os.path.join(caller_directory, "info.json")

View File

@ -0,0 +1,7 @@
import karkas_piccolo.patches.app.finder # noqa: F401 isort:skip
from piccolo.apps.migrations.commands.forwards import run_forwards
async def migrate_forward():
await run_forwards("all")

View File

@ -0,0 +1,8 @@
import karkas_piccolo.patches.cli.finder # noqa: F401 isort:skip
import karkas_piccolo.patches.migrations # noqa: F401 isort:skip
def main():
from piccolo.main import main as piccolo_main
piccolo_main()

View File

@ -0,0 +1,28 @@
import typing as t
import piccolo.conf
import piccolo.conf.apps
from piccolo.conf.apps import PiccoloAppModule, PiccoloConfModule
from karkas_core.singleton import Singleton
class Finder(piccolo.conf.apps.Finder):
def get_app_modules(self):
apps = []
for k, m in self.get_app_registry().app_configs.items():
module = PiccoloAppModule(name=k)
module.APP_CONFIG = m
apps.append(module)
return apps
def get_piccolo_conf_module(
self, module_name: t.Optional[str] = None
) -> t.Optional[PiccoloConfModule]:
singleton = Singleton()
return singleton.storage["_database"]["conf"]
piccolo.conf.apps.Finder = Finder

View File

@ -0,0 +1,97 @@
import importlib
import os
import sys
import typing as t
from pathlib import Path
import piccolo.conf.apps
from piccolo.conf.apps import PiccoloAppModule, PiccoloConfModule
from piccolo.engine.sqlite import SQLiteEngine
from karkas_piccolo.conf.apps import AppRegistry
from karkas_piccolo.utils import get_info_json
class UnsafeFSLoader:
def __init__(self, path):
self.path = path
def _resolve_module_from_path(self, module_name: str):
path = Path(self.path)
if module_name != ".":
path = path.joinpath(module_name.replace(".", "/"))
if path.is_dir():
init_file_path = path / "__init__.py"
if not init_file_path.exists():
raise FileNotFoundError(f"File {init_file_path} does not exist.")
file_path = init_file_path
else:
path = path.with_suffix(".py")
if path.is_file():
file_path = path
else:
raise ValueError(f"Module not found: {module_name}")
return file_path
def load(self):
full_path = self._resolve_module_from_path("db")
if full_path.name == "__init__.py":
module_name = full_path.parent.name
path = full_path.parent.parent.absolute()
else:
module_name = full_path.stem
path = full_path.parent.absolute()
sys.path.insert(0, str(path))
# Загружаем спецификацию модуля
spec = importlib.util.spec_from_file_location(module_name, full_path)
# Создаем модуль
module = importlib.util.module_from_spec(spec)
# Выполняем модуль
spec.loader.exec_module(module)
return module
class Finder(piccolo.conf.apps.Finder):
def get_app_modules(self):
apps = []
for k, m in self.get_app_registry().app_configs.items():
module = PiccoloAppModule(name=k)
module.APP_CONFIG = m
apps.append(module)
return apps
def get_piccolo_conf_module(
self, module_name: t.Optional[str] = None
) -> t.Optional[PiccoloConfModule]:
info = get_info_json()
fallback_module = PiccoloConfModule(
name=info["id"],
)
module_dir = os.getcwd()
print(module_dir)
module = UnsafeFSLoader(module_dir).load()
fallback_module.DB = SQLiteEngine(config={})
fallback_module.APP_REGISTRY = AppRegistry(
apps_configs={info["id"]: module.APP_CONFIG}
)
return fallback_module
piccolo.conf.apps.Finder = Finder

View File

@ -0,0 +1,42 @@
import typing as t
import piccolo.apps.migrations.piccolo_app
from piccolo.apps.migrations.commands.backwards import backwards
from piccolo.apps.migrations.commands.check import check
from piccolo.apps.migrations.commands.clean import clean
from piccolo.apps.migrations.commands.forwards import forwards
from piccolo.apps.migrations.commands.new import new as orig_new
from piccolo.conf.apps import AppConfig, Command
from karkas_piccolo.utils import get_info_json
async def new(
auto: bool = False,
desc: str = "",
auto_input: t.Optional[str] = None,
):
info = get_info_json()
return await orig_new(
info["id"],
auto=auto,
desc=desc,
auto_input=auto_input,
)
APP_CONFIG = AppConfig(
app_name="migrations",
migrations_folder_path="",
commands=[
Command(callable=backwards, aliases=["b", "back", "backward"]),
Command(callable=check),
Command(callable=clean),
Command(callable=forwards, aliases=["f", "forward"]),
Command(callable=new, aliases=["n", "create"]),
],
)
piccolo.apps.migrations.piccolo_app.APP_CONFIG = APP_CONFIG

View File

@ -0,0 +1,8 @@
import json
def get_info_json(path="info.json"):
with open(path, "r", encoding="utf-8") as file:
data = json.load(file)
return data

1131
src/karkas_piccolo/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

View File

@ -0,0 +1,19 @@
[tool.poetry]
name = "karkas-piccolo"
version = "0.1.0"
description = ""
authors = ["Maxim Slipenko <maxim@slipenko.com>"]
readme = "README.md"
[tool.poetry.scripts]
karkas-piccolo = 'karkas_piccolo.main:main'
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
piccolo = { extras=["sqlite"], version = "^1.16.0" }
karkas-core = { path = "../karkas_core", develop = true }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"