diff --git a/README.md b/README.md index 2072957..f020bc3 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,11 @@ OCAB - это бот для Telegram, который призван помочь ## Дополнительные официальные модули -* `gpt` - модуль для генерации ответов на основе нейросети GPT-3.5. Позволяет боту отвечать на сообщения +* `yandexgpt` - модуль для генерации ответов на основе нейросети GPT-3.5. Позволяет боту отвечать на сообщения пользователей, используя нейросеть. Ключевой особенностью является построение линии контекста для нейросети, которая позволяет боту отвечать на вопросы, используя контекст предыдущих сообщений. Для этого используется модуль база данных хранящий историю сообщений. + Список модулей будет пополняться. Идеи для модулей можно оставлять в [issues](https://gitflic.ru/project/armatik/ocab/issue/create). diff --git a/init.py b/init.py deleted file mode 100644 index e4d52fa..0000000 --- a/init.py +++ /dev/null @@ -1,15 +0,0 @@ -from json import dumps -from pathlib import Path - -pwd = Path().cwd() -dir_core = pwd / "src" / "core" -dir_modules_standard = pwd / "src" / "modules" / "standard" -dir_modules_custom = pwd / "src" / "modules" / "custom" - -json = { - "core": str(dir_core), - "modules standard": str(dir_modules_standard), - "modules custom": str(dir_modules_custom), -} -with open("src/paths.json", "w", encoding="utf8") as f: - f.write(dumps(json, indent=4)) diff --git a/poetry.lock b/poetry.lock index 8b77791..0b6889e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "aiogram" -version = "3.5.0" +version = "3.10.0" description = "Modern and fully asynchronous framework for Telegram Bot API" optional = false python-versions = ">=3.8" files = [ - {file = "aiogram-3.5.0-py3-none-any.whl", hash = "sha256:70b5804671b87214768a2a63f19f1457684bd0c6cb6abd23e73bb16207fd7e58"}, - {file = "aiogram-3.5.0.tar.gz", hash = "sha256:1793deb24f36a6fc7b678c31d9a831cef7972765710a47a3e139645a99facba4"}, + {file = "aiogram-3.10.0-py3-none-any.whl", hash = "sha256:dc43bfbe68c736cca48d91ffbc55a397df24b56c332206af850965619689beca"}, + {file = "aiogram-3.10.0.tar.gz", hash = "sha256:f500d4b309e3cc08a87ae5a053b229199034f382925de00aa2ed005d5e25d575"}, ] [package.dependencies] @@ -27,15 +27,16 @@ aiofiles = ">=23.2.1,<23.3.0" aiohttp = ">=3.9.0,<3.10.0" certifi = ">=2023.7.22" magic-filter = ">=1.0.12,<1.1" -pydantic = ">=2.4.1,<2.8" +pydantic = ">=2.4.1,<2.9" typing-extensions = ">=4.7.0,<=5.0" [package.extras] cli = ["aiogram-cli (>=1.0.3,<1.1.0)"] -dev = ["black (>=23.10.0,<23.11.0)", "isort (>=5.12.0,<5.13.0)", "mypy (>=1.6.1,<1.7.0)", "packaging (>=23.1,<24.0)", "pre-commit (>=3.5.0,<3.6.0)", "ruff (>=0.1.1,<0.2.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.4.9,<0.5.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)"] fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)"] i18n = ["babel (>=2.13.0,<2.14.0)"] +mongo = ["motor (>=3.3.2,<3.4.0)"] proxy = ["aiohttp-socks (>=0.8.3,<0.9.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)"] @@ -151,13 +152,13 @@ frozenlist = ">=1.1.0" [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] @@ -249,13 +250,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -393,6 +394,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dataclasses-json" +version = "0.6.7" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, + {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + [[package]] name = "distlib" version = "0.3.8" @@ -524,13 +540,13 @@ files = [ [[package]] name = "identify" -version = "2.5.36" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -599,6 +615,25 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "marshmallow" +version = "3.21.3" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mccabe" version = "0.7.0" @@ -777,12 +812,12 @@ files = [ [[package]] name = "peewee" -version = "3.17.3" +version = "3.17.6" description = "a little orm" optional = false python-versions = "*" files = [ - {file = "peewee-3.17.3.tar.gz", hash = "sha256:ef15f90b628e41a584be8306cdc3243c51f73ce88b06154d9572f6d0284a0169"}, + {file = "peewee-3.17.6.tar.gz", hash = "sha256:cea5592c6f4da1592b7cff8eaf655be6648a1f5857469e30037bf920c03fb8fb"}, ] [[package]] @@ -832,109 +867,119 @@ files = [ [[package]] name = "pydantic" -version = "2.7.1" +version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, - {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.2" -typing-extensions = ">=4.6.1" +pydantic-core = "2.20.1" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.2" +version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, - {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, - {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, - {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, - {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, - {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, - {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, - {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, - {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, - {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, - {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, - {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, - {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, - {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] [package.dependencies] @@ -1027,13 +1072,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1046,6 +1091,21 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "restrictedpython" +version = "7.1" +description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment." +optional = false +python-versions = ">=3.7, <3.13" +files = [ + {file = "RestrictedPython-7.1-py3-none-any.whl", hash = "sha256:56d0c73e5de1757702053383601b0fcd3fb2e428039ee1df860409ad67b17d2b"}, + {file = "RestrictedPython-7.1.tar.gz", hash = "sha256:875aeb51c139d78e34cef8605dc65309b449168060dd08551a1fe9edb47cb9a5"}, +] + +[package.extras] +docs = ["Sphinx", "sphinx-rtd-theme"] +test = ["pytest", "pytest-mock"] + [[package]] name = "rich" version = "13.7.1" @@ -1080,24 +1140,39 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "typing-extensions" -version = "4.11.0" +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.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {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]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -1231,5 +1306,5 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" -python-versions = "^3.11.6" -content-hash = "5b43fa850045f857f8e7b84baa8a9d9b6c9e64758bf8525f4eb772574aafa5ca" +python-versions = ">=3.11.6,<3.13" +content-hash = "ea35c6a1b64b487fba434fe79cdbf6b78e21941e28d7cda433fd4093c3a0923d" diff --git a/pyproject.toml b/pyproject.toml index c9d3e5f..a71889f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,35 @@ [tool.poetry] +package-mode = false name = "ocab" -version = "0.1.0" -description = "" +version = "2.0.0" +description = "OCAB is a modular Telegram bot" license = "GPL-3.0-only" authors = ["Семён Фомченков "] maintainers = [ "Илья Женецкий ", "qualimock ", - "Кирилл Уницаев fiersik.kouji@yandex.ru", + "Кирилл Уницаев ", + "Максим Слипенко " ] readme = "README.md" repository = "https://gitflic.ru/project/armatik/ocab" +packages = [{include = "scripts"}] + +[tool.poetry.urls] +"Bug Tracker" = "https://gitflic.ru/project/armatik/ocab/issue?status=OPEN" + +[tool.poetry.scripts] +test = 'scripts.test:main' +init = 'scripts.init:main' [tool.poetry.dependencies] -python = "^3.11.6" -aiogram = "^3.2.0" -peewee = "^3.17.0" +python = ">=3.11.6,<3.13" +aiogram = "^3.10.0" +peewee = "^3.17.6" pyyaml = "^6.0.1" -requests = "^2.31.0" - +requests = "^2.32.3" +restrictedpython = "^7.1" +dataclasses-json = "^0.6.7" [tool.poetry.group.dev.dependencies] flake8 = "^7.1.0" diff --git a/run_tests b/run_tests deleted file mode 100644 index 6009a83..0000000 --- a/run_tests +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/sh -cd src -python -m unittest discover -v -cd .. diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/init.py b/scripts/init.py new file mode 100644 index 0000000..bd1a675 --- /dev/null +++ b/scripts/init.py @@ -0,0 +1,21 @@ +from json import dumps +from pathlib import Path + + +def main(): + pwd = Path().cwd() + dir_core = pwd / "src" / "core" + dir_modules_standard = pwd / "src" / "modules" / "standard" + dir_modules_custom = pwd / "src" / "modules" / "custom" + + json = { + "core": str(dir_core), + "modules standard": str(dir_modules_standard), + "modules custom": str(dir_modules_custom), + } + with open("src/paths.json", "w", encoding="utf8") as f: + f.write(dumps(json, indent=4)) + + +if __name__ == "__main__": + main() diff --git a/scripts/test.py b/scripts/test.py new file mode 100644 index 0000000..d03200a --- /dev/null +++ b/scripts/test.py @@ -0,0 +1,9 @@ +import subprocess # nosec + + +def main(): + subprocess.run(["python", "-u", "-m", "unittest", "discover"]) # nosec + + +if __name__ == "__main__": + main() diff --git a/src/core/logger.py b/src/core/logger.py index 4180a12..d12998c 100644 --- a/src/core/logger.py +++ b/src/core/logger.py @@ -1,6 +1,7 @@ import logging import os import time +import traceback def setup_logger(): @@ -26,4 +27,12 @@ async def log(message): Она асинхронная, хотя таковой на самом деле не является. """ + if isinstance(message, Exception): + error_message = f"Error: {str(message)}\n{traceback.format_exc()}" + logging.error(error_message) + else: + logging.info(message) + + +def log_new(message): logging.info(message) diff --git a/src/core/main.py b/src/core/main.py index da38dd2..2aa86df 100644 --- a/src/core/main.py +++ b/src/core/main.py @@ -1,11 +1,23 @@ import asyncio +import traceback from aiogram import Bot, Dispatcher -from routers import include_routers -from src.core.logger import log, setup_logger +from src.core.logger import setup_logger +from src.core.modules_system import ModulesManager +from src.core.modules_system.loaders import FSLoader +from src.core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader +from src.core.singleton import Singleton from src.modules.standard.config.config import get_telegram_token from src.modules.standard.database.db_api import connect_database, create_tables +from src.service import paths + +bot_modules = [ + UnsafeFSLoader(f"{paths.modules_standard}/config"), + UnsafeFSLoader(f"{paths.modules_standard}/database"), + UnsafeFSLoader(f"{paths.modules_standard}/roles"), + FSLoader(f"{paths.modules_standard}/info"), +] async def main(): @@ -13,17 +25,23 @@ async def main(): database = None setup_logger() + app = Singleton() + try: bot = Bot(token=get_telegram_token()) database, path = connect_database() database.connect() create_tables(database) - dp = Dispatcher() - await include_routers(dp) - await dp.start_polling(bot) - except Exception as e: - log(e) + app.dp = Dispatcher() + app.modules_manager = ModulesManager() + + for module_loader in bot_modules: + app.modules_manager.load(module_loader) + + await app.dp.start_polling(bot) + except Exception: + traceback.print_exc() finally: if bot is not None: await bot.session.close() diff --git a/src/core/modules_system/__init__.py b/src/core/modules_system/__init__.py new file mode 100644 index 0000000..a30b035 --- /dev/null +++ b/src/core/modules_system/__init__.py @@ -0,0 +1 @@ +from .modules_manager import ModulesManager diff --git a/src/core/modules_system/loaders/__init__.py b/src/core/modules_system/loaders/__init__.py new file mode 100644 index 0000000..e41ccbb --- /dev/null +++ b/src/core/modules_system/loaders/__init__.py @@ -0,0 +1 @@ +from .fs_loader import FSLoader diff --git a/src/core/modules_system/loaders/base.py b/src/core/modules_system/loaders/base.py new file mode 100644 index 0000000..1a67b97 --- /dev/null +++ b/src/core/modules_system/loaders/base.py @@ -0,0 +1,22 @@ +import types +from dataclasses import dataclass + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class ModuleInfo: + id: str + name: str + description: str + version: str + author: str + + +class AbstractLoader: + def info(self) -> ModuleInfo: + raise NotImplementedError + + def load(self) -> types.ModuleType: + raise NotImplementedError diff --git a/src/core/modules_system/loaders/fs_loader/FSLoader.py b/src/core/modules_system/loaders/fs_loader/FSLoader.py new file mode 100644 index 0000000..74bd930 --- /dev/null +++ b/src/core/modules_system/loaders/fs_loader/FSLoader.py @@ -0,0 +1,77 @@ +import types +from pathlib import Path + +from RestrictedPython import compile_restricted_exec + +from src.core.modules_system.loaders.fs_loader.policy import ( + ALLOWED_IMPORTS, + BUILTINS, + RestrictedPythonPolicy, +) +from src.core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader + + +class FSLoader(UnsafeFSLoader): + def __init__(self, path): + super().__init__(path) + self.builtins = BUILTINS.copy() + self.builtins["__import__"] = self._hook_import + + def load(self): + # TODO handle dependencies from info + + self.info() + + # with open(os.path.join(self.path, "__init__.py"), "r") as f: + # source = f.read() + + return self._hook_import(".") + + 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 _hook_import(self, name: str, *args, **kwargs): + if name in ALLOWED_IMPORTS: + return ALLOWED_IMPORTS[name] + + # TODO: allow only public api for modules + if name.startswith("src."): + return __import__(name, *args, **kwargs) + + module_file_path = self._resolve_module_from_path(name) + + with open(module_file_path, "r") as f: + src = f.read() + + module = types.ModuleType(name) + module.__dict__.update( + { + "__builtins__": self.builtins, + } + ) + result = compile_restricted_exec(src, "", policy=RestrictedPythonPolicy) + + if result.errors: + for error in result.errors: + print(error) + + exec(result.code, module.__dict__) # nosec + + return module diff --git a/src/core/modules_system/loaders/fs_loader/__init__.py b/src/core/modules_system/loaders/fs_loader/__init__.py new file mode 100644 index 0000000..c05452a --- /dev/null +++ b/src/core/modules_system/loaders/fs_loader/__init__.py @@ -0,0 +1 @@ +from .FSLoader import FSLoader diff --git a/src/core/modules_system/loaders/fs_loader/policy.py b/src/core/modules_system/loaders/fs_loader/policy.py new file mode 100644 index 0000000..0b86e6e --- /dev/null +++ b/src/core/modules_system/loaders/fs_loader/policy.py @@ -0,0 +1,101 @@ +import typing +import warnings +from _ast import AnnAssign +from typing import Any + +import aiogram +from RestrictedPython import ( + RestrictingNodeTransformer, + limited_builtins, + safe_builtins, + utility_builtins, +) +from RestrictedPython.Eval import default_guarded_getitem +from RestrictedPython.Guards import full_write_guard + + +class RestrictedPythonPolicy(RestrictingNodeTransformer): + def visit_AsyncFunctionDef(self, node): + return self.node_contents_visit(node) + + def visit_Await(self, node): + return self.node_contents_visit(node) + + def visit_AsyncFor(self, node): + return self.node_contents_visit(node) + + def visit_AsyncWith(self, node): + return self.node_contents_visit(node) + + """ + Не работает из-за getattr + + def visit_Match(self, node) -> Any: + return self.node_contents_visit(node) + + def visit_match_case(self, node) -> Any: + return self.node_contents_visit(node) + + def visit_MatchAs(self, node) -> Any: + return self.node_contents_visit(node) + + def visit_MatchValue(self, node) -> Any: + return self.node_contents_visit(node) + """ + + def visit_AnnAssign(self, node: AnnAssign) -> Any: + # missing in RestrictingNodeTransformer + # this doesn't need the logic that is in visit_Assign + # because it doesn't have a "targets" attribute, + # and node.target: Name | Attribute | Subscript + return self.node_contents_visit(node) + + # new Python 3.12 nodes + def visit_TypeAlias(self, node) -> Any: + # missing in RestrictingNodeTransformer + return self.node_contents_visit(node) + + def visit_TypeVar(self, node) -> Any: + # missing in RestrictingNodeTransformer + return self.node_contents_visit(node) + + def visit_TypeVarTuple(self, node) -> Any: + # missing in RestrictingNodeTransformer + return self.node_contents_visit(node) + + def visit_ParamSpec(self, node) -> Any: + # missing in RestrictingNodeTransformer + return self.node_contents_visit(node) + + +def _metaclass(name, bases, dict): + ob = type(name, bases, dict) + ob.__allow_access_to_unprotected_subobjects__ = 1 + ob._guarded_writes = 1 + return ob + + +ALLOWED_IMPORTS = { + "typing": typing, + "aiogram": aiogram, + "warnings": warnings, +} + +BUILTINS = safe_builtins.copy() +BUILTINS.update(utility_builtins) +BUILTINS.update(limited_builtins) +BUILTINS["__metaclass__"] = _metaclass +BUILTINS["_getitem_"] = default_guarded_getitem +BUILTINS["_write_"] = full_write_guard + + +class GuardedDictType: + def __call__(self, *args, **kwargs): + return dict(*args, **kwargs) + + @staticmethod + def fromkeys(iterable, value=None): + return dict.fromkeys(iterable, value) + + +BUILTINS["dict"] = GuardedDictType() diff --git a/src/core/modules_system/loaders/unsafe_fs_loader/UnsafeFSLoader.py b/src/core/modules_system/loaders/unsafe_fs_loader/UnsafeFSLoader.py new file mode 100644 index 0000000..aac9f58 --- /dev/null +++ b/src/core/modules_system/loaders/unsafe_fs_loader/UnsafeFSLoader.py @@ -0,0 +1,64 @@ +import importlib.util +import os +import sys +from pathlib import Path + +from src.core.modules_system.loaders.base import AbstractLoader, ModuleInfo + + +class UnsafeFSLoader(AbstractLoader): + def __init__(self, path): + self.path = path + + def info(self): + with open(os.path.join(self.path, "info.json"), "r") as f: + return ModuleInfo.from_json(f.read()) + + 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): + self.info() + + full_path = self._resolve_module_from_path(".") + + 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 + sys.path.insert(0, str(path)) + + print(full_path.parent.absolute()) + print(module_name) + + # Загружаем спецификацию модуля + 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 diff --git a/src/core/modules_system/loaders/unsafe_fs_loader/__init__.py b/src/core/modules_system/loaders/unsafe_fs_loader/__init__.py new file mode 100644 index 0000000..870053c --- /dev/null +++ b/src/core/modules_system/loaders/unsafe_fs_loader/__init__.py @@ -0,0 +1 @@ +from .UnsafeFSLoader import UnsafeFSLoader diff --git a/src/core/modules_system/modules_manager.py b/src/core/modules_system/modules_manager.py new file mode 100644 index 0000000..7ea479e --- /dev/null +++ b/src/core/modules_system/modules_manager.py @@ -0,0 +1,29 @@ +from src.core.modules_system.loaders.base import AbstractLoader + + +class ModulesManager: + def __init__(self): + self.modules = {} + + def load(self, loader: AbstractLoader): + info = loader.info() + module = loader.load() + + print(module) + + self.modules[info.id] = { + "info": info, + "module": module, + } + + def get_by_id(self, module_id: str): + if module_id not in self.modules: + raise Exception(f"Module with id {module_id} not loaded") + + return self.modules[module_id]["module"] + + def get_info_by_id(self, module_id: str): + if module_id not in self.modules: + raise Exception(f"Module with id {module_id} not loaded") + + return self.modules[module_id]["info"] diff --git a/src/core/modules_system/public_api/__init__.py b/src/core/modules_system/public_api/__init__.py new file mode 100644 index 0000000..448fd81 --- /dev/null +++ b/src/core/modules_system/public_api/__init__.py @@ -0,0 +1 @@ +from .public_api import get_module, register_router diff --git a/src/core/modules_system/public_api/public_api.py b/src/core/modules_system/public_api/public_api.py new file mode 100644 index 0000000..63a6dd4 --- /dev/null +++ b/src/core/modules_system/public_api/public_api.py @@ -0,0 +1,13 @@ +from aiogram import Router + +from src.core.singleton import Singleton + + +def register_router(router: Router): + app = Singleton() + app.dp.include_router(router) + + +def get_module(module_id: str): + app = Singleton() + return app.modules_manager.get_by_id(module_id) diff --git a/src/core/routers.py b/src/core/routers.py index f0c235b..e3bfcc8 100644 --- a/src/core/routers.py +++ b/src/core/routers.py @@ -1,7 +1,8 @@ from aiogram import Dispatcher from src.modules.standard.admin.routers import router as admin_router -from src.modules.standard.info.routers import router as info_router + +# from src.modules.standard.info.routers import router as info_router from src.modules.standard.message_processing.message_api import ( router as process_message, ) @@ -12,6 +13,6 @@ async def include_routers(dp: Dispatcher): Подключение роутеров в бота dp.include_router() """ - dp.include_router(info_router) + # dp.include_router(info_router) dp.include_router(admin_router) dp.include_router(process_message) diff --git a/src/core/singleton.py b/src/core/singleton.py new file mode 100644 index 0000000..45e56d4 --- /dev/null +++ b/src/core/singleton.py @@ -0,0 +1,18 @@ +from aiogram import Dispatcher + +from src.core.modules_system import ModulesManager + + +class SingletonMeta(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] + + +class Singleton(metaclass=SingletonMeta): + dp: Dispatcher = None + modules_manager: ModulesManager = None diff --git a/src/modules/standard/__init__.py b/src/modules/standard/__init__.py deleted file mode 100644 index 2cf9668..0000000 --- a/src/modules/standard/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import config, database, exceptions, roles diff --git a/src/modules/standard/config/__init__.py b/src/modules/standard/config/__init__.py index d63bc18..8dcce20 100644 --- a/src/modules/standard/config/__init__.py +++ b/src/modules/standard/config/__init__.py @@ -1 +1 @@ -from . import config +from .config import config diff --git a/src/modules/standard/config/config.py b/src/modules/standard/config/config.py index b47b307..815e349 100644 --- a/src/modules/standard/config/config.py +++ b/src/modules/standard/config/config.py @@ -2,7 +2,7 @@ import yaml -from ....service import paths +from src.service import paths def get_config(is_test: bool = False) -> dict: diff --git a/src/modules/standard/config/info.json b/src/modules/standard/config/info.json index c615aa5..632e4aa 100644 --- a/src/modules/standard/config/info.json +++ b/src/modules/standard/config/info.json @@ -1,6 +1,9 @@ { + "id": "standard.config", "name": "Config YAML", "description": "Модуль для работы с конфигурационным файлом бота (YAML)", "author": "OCAB Team", - "version": "1.0" + "version": "1.0", + "privileged": true, + "dependencies": {} } diff --git a/src/modules/standard/database/db_api.py b/src/modules/standard/database/db_api.py index e96d4ff..ab181d9 100644 --- a/src/modules/standard/database/db_api.py +++ b/src/modules/standard/database/db_api.py @@ -1,8 +1,9 @@ import peewee as pw from aiogram.types import Message -from ....service import paths -from ..exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName +from src.service import paths + +from .exceptions import MissingModuleName, NotExpectedModuleName from .models.chat_stats import ChatStats from .models.chats import Chats from .models.messages import Messages @@ -20,6 +21,7 @@ def connect_database(is_test: bool = False, module: str | None = None): raise NotExpectedModuleName() db_path = f"{paths.core}/database" + # WTF????? _database = pw.SqliteDatabase(f"{db_path}/OCAB.db") Chats._meta.database = _database Messages._meta.database = _database diff --git a/src/modules/standard/exceptions/module_exceptions.py b/src/modules/standard/database/exceptions.py similarity index 100% rename from src/modules/standard/exceptions/module_exceptions.py rename to src/modules/standard/database/exceptions.py diff --git a/src/modules/standard/database/info.json b/src/modules/standard/database/info.json index bfa88d4..e60fce2 100644 --- a/src/modules/standard/database/info.json +++ b/src/modules/standard/database/info.json @@ -1,6 +1,9 @@ { + "id": "standard.database", "name": "Database", "description": "Модуль для работы с БД", "author": "OCAB Team", - "version": "1.0" + "version": "1.0", + "privileged": true, + "dependencies": {} } diff --git a/src/modules/standard/exceptions/__init__.py b/src/modules/standard/exceptions/__init__.py deleted file mode 100644 index 6ceaccd..0000000 --- a/src/modules/standard/exceptions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import module_exceptions diff --git a/src/modules/standard/exceptions/info.json b/src/modules/standard/exceptions/info.json deleted file mode 100644 index 79618c6..0000000 --- a/src/modules/standard/exceptions/info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Exceptions", - "description": "Модуль с исключениями", - "author": "OCAB Team", - "version": "1.0" -} diff --git a/src/modules/standard/info/__init__.py b/src/modules/standard/info/__init__.py index e69de29..db0066a 100644 --- a/src/modules/standard/info/__init__.py +++ b/src/modules/standard/info/__init__.py @@ -0,0 +1,12 @@ +from aiogram import F, Router + +from src.core.modules_system.public_api import register_router + +from .handlers import get_chat_info, get_user_info + +router = Router() + +router.message.register(get_user_info, F.text.startswith("/info")) +router.message.register(get_chat_info, F.text.startswith("/chatinfo")) + +register_router(router) diff --git a/src/modules/standard/info/handlers.py b/src/modules/standard/info/handlers.py index d1b60ba..d76aa8f 100644 --- a/src/modules/standard/info/handlers.py +++ b/src/modules/standard/info/handlers.py @@ -1,12 +1,26 @@ # flake8: noqa +from typing import Type from aiogram import Bot -from aiogram.types import Message from src.core.logger import log -from src.modules.standard.config.config import get_user_role_name -from src.modules.standard.database.db_api import * -from src.modules.standard.roles.roles import Roles +from src.core.modules_system.public_api import get_module +from src.modules.standard.database.db_api import ( + Message, + get_chat_all_stat, + get_message_ai_model, + get_user, + get_user_all_stats, + get_user_id, + get_user_name, + get_user_rep, + get_user_role, + get_user_tag, +) + +from .interfaces import IRoles + +Roles: Type[IRoles] = get_module("standard.roles").Roles async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int): diff --git a/src/modules/standard/info/info.json b/src/modules/standard/info/info.json new file mode 100644 index 0000000..fe90d4c --- /dev/null +++ b/src/modules/standard/info/info.json @@ -0,0 +1,11 @@ +{ + "id": "standard.info", + "name": "Info", + "description": "Модуль с информацией", + "author": "OCAB Team", + "version": "1.0", + "privileged": false, + "dependencies": { + "standard.roles": "^1.0.0" + } +} diff --git a/src/modules/standard/info/interfaces.py b/src/modules/standard/info/interfaces.py new file mode 100644 index 0000000..f1bec6b --- /dev/null +++ b/src/modules/standard/info/interfaces.py @@ -0,0 +1,20 @@ +class IRoles: + user: str + moderator: str + admin: str + bot: str + + def __init__(self): + pass + + async def check_admin_permission(self, user_id: int) -> bool: + pass + + async def check_moderator_permission(self, user_id) -> bool: + pass + + async def get_role_name(self, role_id) -> str: + pass + + async def get_user_permission(self, user_id) -> str | None: + pass diff --git a/src/modules/standard/info/routers.py b/src/modules/standard/info/routers.py deleted file mode 100644 index 9594b91..0000000 --- a/src/modules/standard/info/routers.py +++ /dev/null @@ -1,10 +0,0 @@ -# flake8: noqa - -from aiogram import F, Router - -from src.modules.standard.info.handlers import get_chat_info, get_user_info - -router = Router() - -router.message.register(get_user_info, F.text.startswith("/info") == True) -router.message.register(get_chat_info, F.text.startswith("/chatinfo") == True) diff --git a/src/modules/standard/roles/__init__.py b/src/modules/standard/roles/__init__.py index e69de29..5cd56b0 100644 --- a/src/modules/standard/roles/__init__.py +++ b/src/modules/standard/roles/__init__.py @@ -0,0 +1 @@ +from .roles import Roles diff --git a/src/modules/standard/roles/info.json b/src/modules/standard/roles/info.json index 73dc7e6..80119dd 100644 --- a/src/modules/standard/roles/info.json +++ b/src/modules/standard/roles/info.json @@ -1,6 +1,12 @@ { + "id": "standard.roles", "name": "Roles", "description": "Модуль для работы с ролями", "author": "OCAB Team", - "version": "1.0" + "version": "1.0", + "privileged": true, + "dependencies": { + "standard.config": "^1.0.0", + "standard.database": "^1.0.0" + } } diff --git a/src/modules/standard/roles/roles.py b/src/modules/standard/roles/roles.py index 2bf3bb8..1f00e1d 100644 --- a/src/modules/standard/roles/roles.py +++ b/src/modules/standard/roles/roles.py @@ -1,7 +1,7 @@ -from ..config.config import get_config -from ..database.db_api import get_user_role +from src.core.modules_system.public_api import get_module -yaml_load = get_config() +get_user_role = get_module("standard.database").db_api.get_user_role +config = get_module("standard.config").config class Roles: @@ -9,7 +9,7 @@ class Roles: moderator = "MODERATOR" admin = "ADMIN" bot = "BOT" - __roles = yaml_load["ROLES"] + __roles = config["ROLES"] def __init__(self): self.user_role_id = self.__roles[self.user]