mirror of
https://gitflic.ru/project/maks1ms/ocab.git
synced 2025-01-11 17:28:12 +03:00
wip
This commit is contained in:
parent
e8b5f79d99
commit
d52864a231
377
poetry.lock
generated
377
poetry.lock
generated
@ -301,6 +301,28 @@ d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.8.2"
|
||||
description = "Fast, simple object-to-object and broadcast signaling"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"},
|
||||
{file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachelib"
|
||||
version = "0.9.0"
|
||||
description = "A collection of cache libraries in the same API interface."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cachelib-0.9.0-py3-none-any.whl", hash = "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3"},
|
||||
{file = "cachelib-0.9.0.tar.gz", hash = "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.7.4"
|
||||
@ -479,6 +501,128 @@ pyyaml = ">=5.3.1"
|
||||
requests = ">=2.23.0"
|
||||
rich = "*"
|
||||
|
||||
[[package]]
|
||||
name = "dash"
|
||||
version = "2.17.1"
|
||||
description = "A Python framework for building reactive web-apps. Developed by Plotly."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "dash-2.17.1-py3-none-any.whl", hash = "sha256:3eefc9ac67003f93a06bc3e500cae0a6787c48e6c81f6f61514239ae2da414e4"},
|
||||
{file = "dash-2.17.1.tar.gz", hash = "sha256:ee2d9c319de5dcc1314085710b72cd5fa63ff994d913bf72979b7130daeea28e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
dash-core-components = "2.0.0"
|
||||
dash-html-components = "2.0.0"
|
||||
dash-table = "5.0.0"
|
||||
Flask = ">=1.0.4,<3.1"
|
||||
importlib-metadata = "*"
|
||||
nest-asyncio = "*"
|
||||
plotly = ">=5.0.0"
|
||||
requests = "*"
|
||||
retrying = "*"
|
||||
setuptools = "*"
|
||||
typing-extensions = ">=4.1.1"
|
||||
Werkzeug = "<3.1"
|
||||
|
||||
[package.extras]
|
||||
celery = ["celery[redis] (>=5.1.2)", "redis (>=3.5.3)"]
|
||||
ci = ["black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==7.0.0)", "flaky (==3.8.1)", "flask-talisman (==1.0.0)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.26.3)", "openpyxl", "orjson (==3.10.3)", "pandas (>=1.4.0)", "pyarrow", "pylint (==3.0.3)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "pyzmq (==25.1.2)", "xlrd (>=2.0.1)"]
|
||||
compress = ["flask-compress"]
|
||||
dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"]
|
||||
diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"]
|
||||
testing = ["beautifulsoup4 (>=4.8.2)", "cryptography", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "dash-bootstrap-components"
|
||||
version = "1.6.0"
|
||||
description = "Bootstrap themed components for use in Plotly Dash"
|
||||
optional = false
|
||||
python-versions = "<4,>=3.8"
|
||||
files = [
|
||||
{file = "dash_bootstrap_components-1.6.0-py3-none-any.whl", hash = "sha256:97f0f47b38363f18863e1b247462229266ce12e1e171cfb34d3c9898e6e5cd1e"},
|
||||
{file = "dash_bootstrap_components-1.6.0.tar.gz", hash = "sha256:960a1ec9397574792f49a8241024fa3cecde0f5930c971a3fc81f016cbeb1095"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
dash = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
pandas = ["numpy", "pandas"]
|
||||
|
||||
[[package]]
|
||||
name = "dash-core-components"
|
||||
version = "2.0.0"
|
||||
description = "Core component suite for Dash"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"},
|
||||
{file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dash-extensions"
|
||||
version = "1.0.18"
|
||||
description = "Extensions for Plotly Dash."
|
||||
optional = false
|
||||
python-versions = "<4,>=3.9"
|
||||
files = [
|
||||
{file = "dash_extensions-1.0.18-py3-none-any.whl", hash = "sha256:17f4469670bd70ce12fac1a05baaae119fb65eee7b012af47aff7377d0399eeb"},
|
||||
{file = "dash_extensions-1.0.18.tar.gz", hash = "sha256:a6b6c0952b3af7ae84c418fea4b43cbd0bd4e82f20d91f1573380b8a3d90df0a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
dash = ">=2.17.0"
|
||||
dataclass-wizard = ">=0.22.2,<0.23.0"
|
||||
Flask-Caching = ">=2.1.0,<3.0.0"
|
||||
jsbeautifier = ">=1.14.3,<2.0.0"
|
||||
more-itertools = ">=10.2.0,<11.0.0"
|
||||
pydantic = ">=2.7.1,<3.0.0"
|
||||
ruff = ">=0.4.5,<0.5.0"
|
||||
|
||||
[package.extras]
|
||||
mantine = ["dash-mantine-components (>=0.14.3,<0.15.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "dash-html-components"
|
||||
version = "2.0.0"
|
||||
description = "Vanilla HTML components for Dash"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"},
|
||||
{file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dash-table"
|
||||
version = "5.0.0"
|
||||
description = "Dash table"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"},
|
||||
{file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dataclass-wizard"
|
||||
version = "0.22.3"
|
||||
description = "Marshal dataclasses to/from JSON. Use field properties with initial values. Construct a dataclass schema with JSON input."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "dataclass-wizard-0.22.3.tar.gz", hash = "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424"},
|
||||
{file = "dataclass_wizard-0.22.3-py2.py3-none-any.whl", hash = "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["Sphinx (==5.3.0)", "bump2version (==1.0.1)", "coverage (>=6.2)", "dataclass-factory (==2.12)", "dataclasses-json (==0.5.6)", "flake8 (>=3)", "jsons (==1.6.1)", "pip (>=21.3.1)", "pytest (==7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytimeparse (==1.1.8)", "sphinx-issues (==3.0.1)", "sphinx-issues (==4.0.0)", "tox (==3.24.5)", "twine (==3.8.0)", "watchdog[watchmedo] (==2.1.6)", "wheel (==0.37.1)", "wheel (==0.42.0)"]
|
||||
timedelta = ["pytimeparse (>=1.1.7)"]
|
||||
yaml = ["PyYAML (>=5.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "dataclasses-json"
|
||||
version = "0.6.7"
|
||||
@ -525,6 +669,16 @@ idna = ["idna (>=3.6)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
wmi = ["wmi (>=1.5.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "editorconfig"
|
||||
version = "0.12.4"
|
||||
description = "EditorConfig File Locator and Interpreter for Python"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.2.0"
|
||||
@ -614,6 +768,43 @@ mccabe = ">=0.7.0,<0.8.0"
|
||||
pycodestyle = ">=2.12.0,<2.13.0"
|
||||
pyflakes = ">=3.2.0,<3.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "3.0.3"
|
||||
description = "A simple framework for building complex web applications."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
|
||||
{file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
blinker = ">=1.6.2"
|
||||
click = ">=8.1.3"
|
||||
itsdangerous = ">=2.1.2"
|
||||
Jinja2 = ">=3.1.2"
|
||||
Werkzeug = ">=3.0.0"
|
||||
|
||||
[package.extras]
|
||||
async = ["asgiref (>=3.2)"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "flask-caching"
|
||||
version = "2.3.0"
|
||||
description = "Adds caching support to Flask applications."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Flask_Caching-2.3.0-py3-none-any.whl", hash = "sha256:51771c75682e5abc1483b78b96d9131d7941dc669b073852edfa319dd4e29b6e"},
|
||||
{file = "flask_caching-2.3.0.tar.gz", hash = "sha256:d7e4ca64a33b49feb339fcdd17e6ba25f5e01168cf885e53790e885f83a4d2cf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cachelib = ">=0.9.0,<0.10.0"
|
||||
Flask = "*"
|
||||
|
||||
[[package]]
|
||||
name = "flet"
|
||||
version = "0.23.2"
|
||||
@ -947,6 +1138,25 @@ files = [
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "8.0.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
|
||||
{file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.13.2"
|
||||
@ -961,6 +1171,17 @@ files = [
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
@ -978,6 +1199,20 @@ MarkupSafe = ">=2.0"
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "jsbeautifier"
|
||||
version = "1.15.1"
|
||||
description = "JavaScript unobfuscator and beautifier."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
editorconfig = ">=0.12.2"
|
||||
six = ">=1.13.0"
|
||||
|
||||
[[package]]
|
||||
name = "magic-filter"
|
||||
version = "1.0.12"
|
||||
@ -1126,6 +1361,17 @@ files = [
|
||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "10.3.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"},
|
||||
{file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.0.5"
|
||||
@ -1236,6 +1482,17 @@ files = [
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nest-asyncio"
|
||||
version = "1.6.0"
|
||||
description = "Patch asyncio to allow nested event loops"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"},
|
||||
{file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
@ -1322,6 +1579,21 @@ docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
|
||||
type = ["mypy (>=1.8)"]
|
||||
|
||||
[[package]]
|
||||
name = "plotly"
|
||||
version = "5.22.0"
|
||||
description = "An open-source, interactive data visualization library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "plotly-5.22.0-py3-none-any.whl", hash = "sha256:68fc1901f098daeb233cc3dd44ec9dc31fb3ca4f4e53189344199c43496ed006"},
|
||||
{file = "plotly-5.22.0.tar.gz", hash = "sha256:859fdadbd86b5770ae2466e542b761b247d1c6b49daed765b95bb8c7063e7469"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
packaging = "*"
|
||||
tenacity = ">=6.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "3.7.1"
|
||||
@ -1710,6 +1982,20 @@ files = [
|
||||
docs = ["Sphinx", "sphinx-rtd-theme"]
|
||||
test = ["pytest", "pytest-mock"]
|
||||
|
||||
[[package]]
|
||||
name = "retrying"
|
||||
version = "1.3.4"
|
||||
description = "Retrying"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"},
|
||||
{file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.7.1"
|
||||
@ -1728,6 +2014,32 @@ pygments = ">=2.13.0,<3.0.0"
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.4.10"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"},
|
||||
{file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"},
|
||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"},
|
||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"},
|
||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"},
|
||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"},
|
||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"},
|
||||
{file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"},
|
||||
{file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"},
|
||||
{file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"},
|
||||
{file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "3.0.2"
|
||||
@ -1739,6 +2051,22 @@ files = [
|
||||
{file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "71.0.1"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-71.0.1-py3-none-any.whl", hash = "sha256:1eb8ef012efae7f6acbc53ec0abde4bc6746c43087fd215ee09e1df48998711f"},
|
||||
{file = "setuptools-71.0.1.tar.gz", hash = "sha256:c51d7fd29843aa18dad362d4b4ecd917022131425438251f4e3d766c964dd1ad"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (<7.4)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
@ -1803,6 +2131,21 @@ files = [
|
||||
[package.dependencies]
|
||||
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "8.5.0"
|
||||
description = "Retry code until it succeeds"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"},
|
||||
{file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
doc = ["reno", "sphinx"]
|
||||
test = ["pytest", "tornado (>=4.5)", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "text-unidecode"
|
||||
version = "1.3"
|
||||
@ -2186,6 +2529,23 @@ files = [
|
||||
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.0.3"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"},
|
||||
{file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog (>=2.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.2.0"
|
||||
@ -2303,7 +2663,22 @@ files = [
|
||||
idna = ">=2.0"
|
||||
multidict = ">=4.0"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.19.2"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
|
||||
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.11.6,<3.13"
|
||||
content-hash = "3dfc8d87dd19fe6222428891e73a1bc61edf045596fdecbc7897ecd1356d8b3d"
|
||||
content-hash = "60d3a08ec1ea70b53fe4e2f3ccc112c30e88a1908acf4af93a24e186f3addea8"
|
||||
|
@ -38,6 +38,10 @@ semver = "^3.0.2"
|
||||
hypercorn = "^0.17.3"
|
||||
flet = "^0.23.2"
|
||||
fastapi = "^0.111.1"
|
||||
setuptools = "^71.0.1"
|
||||
dash = "^2.17.1"
|
||||
dash-extensions = "^1.0.18"
|
||||
dash-bootstrap-components = "^1.6.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
flake8 = "^7.1.0"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
# import time
|
||||
import traceback
|
||||
|
||||
|
||||
@ -8,13 +9,13 @@ def setup_logger():
|
||||
"""
|
||||
Настройка логирования
|
||||
"""
|
||||
current_date = time.strftime("%d-%m-%Y")
|
||||
# current_date = time.strftime("%d-%m-%Y")
|
||||
log_dir = os.path.join(os.path.dirname(__file__), "log")
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_file = os.path.join(log_dir, f"log-{current_date}.log")
|
||||
# log_file = os.path.join(log_dir, f"log-{current_date}.log")
|
||||
|
||||
logging.basicConfig(
|
||||
filename=log_file,
|
||||
# filename=log_file,
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
|
@ -11,6 +11,8 @@ from ocab_core.logger import log, setup_logger
|
||||
from ocab_core.modules_system import ModulesManager
|
||||
from ocab_core.modules_system.loaders import FSLoader, UnsafeFSLoader
|
||||
from ocab_core.singleton import Singleton
|
||||
|
||||
# TODO: заменить на get_module("standard.config")
|
||||
from ocab_modules.standard.config.config import get_telegram_token
|
||||
|
||||
ocab_modules_path = get_module_directory("ocab_modules")
|
||||
@ -26,16 +28,17 @@ def ocab_modules_loader(namespace: str, module_name: str, safe=True):
|
||||
bot_modules = [
|
||||
ocab_modules_loader("standard", "config", safe=False),
|
||||
ocab_modules_loader("standard", "database", safe=False),
|
||||
ocab_modules_loader("standard", "fsm_database_storage", safe=False),
|
||||
# ocab_modules_loader("standard", "fsm_database_storage", safe=False),
|
||||
ocab_modules_loader("standard", "roles", safe=False),
|
||||
ocab_modules_loader("external", "yandexgpt", safe=False),
|
||||
ocab_modules_loader("standard", "miniapp", safe=False),
|
||||
#
|
||||
ocab_modules_loader("standard", "command_helper"),
|
||||
ocab_modules_loader("standard", "info"),
|
||||
ocab_modules_loader("standard", "filters"),
|
||||
ocab_modules_loader("external", "create_report_apps"),
|
||||
ocab_modules_loader("standard", "admin"),
|
||||
# ocab_modules_loader("standard", "info"),
|
||||
# ocab_modules_loader("standard", "filters"),
|
||||
# ocab_modules_loader("external", "create_report_apps"),
|
||||
# ocab_modules_loader("standard", "admin"),
|
||||
ocab_modules_loader("standard", "message_processing"),
|
||||
ocab_modules_loader("standard", "miniapp", safe=False),
|
||||
]
|
||||
|
||||
|
||||
@ -48,6 +51,9 @@ async def long_polling_mode():
|
||||
async def webhook_mode():
|
||||
singleton = Singleton()
|
||||
app = FastAPI()
|
||||
|
||||
app.mount("/webapp", singleton.storage["webapp"])
|
||||
|
||||
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
||||
await singleton.bot.set_webhook(
|
||||
"https://mackerel-pumped-foal.ngrok-free.app/webhook"
|
||||
@ -71,7 +77,6 @@ async def init_app():
|
||||
await singleton.modules_manager.load(module_loader)
|
||||
|
||||
singleton.dp = Dispatcher(storage=singleton.storage["_fsm_storage"])
|
||||
|
||||
singleton.dp.include_routers(*singleton.storage["_routers"])
|
||||
|
||||
for middleware in singleton.storage["_outer_message_middlewares"]:
|
||||
|
@ -1,9 +1,27 @@
|
||||
import types
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from dataclasses_json import dataclass_json
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class DependencyInfo:
|
||||
version: str
|
||||
uses: Optional[List[str]] = None
|
||||
|
||||
|
||||
DependencyType = Union[str, DependencyInfo]
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class Dependencies:
|
||||
required: Optional[Dict[str, DependencyType]] = None
|
||||
optional: Optional[Dict[str, DependencyType]] = None
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ModuleInfo:
|
||||
@ -11,9 +29,10 @@ class ModuleInfo:
|
||||
name: str
|
||||
description: str
|
||||
version: str
|
||||
author: str | list[str]
|
||||
author: Union[str, List[str]]
|
||||
privileged: bool
|
||||
dependencies: dict
|
||||
dependencies: Dependencies
|
||||
pythonDependencies: Optional[Dependencies] = None
|
||||
|
||||
|
||||
class AbstractLoader:
|
||||
|
@ -3,6 +3,7 @@ from pathlib import Path
|
||||
|
||||
from RestrictedPython import compile_restricted_exec
|
||||
|
||||
# from ocab_core.logger import log
|
||||
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
|
||||
from ocab_core.modules_system.safe.policy import (
|
||||
ALLOWED_IMPORTS,
|
||||
@ -16,13 +17,30 @@ class FSLoader(UnsafeFSLoader):
|
||||
super().__init__(path)
|
||||
self.builtins = BUILTINS.copy()
|
||||
self.builtins["__import__"] = self._hook_import
|
||||
self.module_info = self.info()
|
||||
self.allowed_python_dependencies = self._get_allowed_python_dependencies()
|
||||
|
||||
def load(self):
|
||||
info = self.info()
|
||||
if info.privileged:
|
||||
if self.module_info.privileged:
|
||||
raise Exception("Only non privileged modules are allowed to be imported")
|
||||
self.module_id = self.module_info.id
|
||||
|
||||
return self._hook_import(".")
|
||||
|
||||
def _get_allowed_python_dependencies(self):
|
||||
allowed = {}
|
||||
|
||||
if self.module_info.pythonDependencies:
|
||||
if self.module_info.pythonDependencies.required:
|
||||
allowed.update(self.module_info.pythonDependencies.required)
|
||||
if self.module_info.pythonDependencies.optional:
|
||||
allowed.update(self.module_info.pythonDependencies.optional)
|
||||
|
||||
for allowed_module in ALLOWED_IMPORTS:
|
||||
allowed[allowed_module] = "*"
|
||||
|
||||
return allowed
|
||||
|
||||
def _resolve_module_from_path(self, module_name: str):
|
||||
path = Path(self.path)
|
||||
|
||||
@ -44,12 +62,14 @@ class FSLoader(UnsafeFSLoader):
|
||||
return file_path
|
||||
|
||||
def _hook_import(self, name: str, *args, **kwargs):
|
||||
for allowed in ALLOWED_IMPORTS:
|
||||
if name == allowed or name.startswith(f"{allowed}."):
|
||||
return __import__(name, *args, **kwargs)
|
||||
|
||||
if name == "ocab_core.modules_system.public_api":
|
||||
return __import__(name, *args, **kwargs)
|
||||
module = __import__(name, *args, **kwargs)
|
||||
module.__ocab_module_id__ = self.module_id
|
||||
return module
|
||||
|
||||
for key in self.allowed_python_dependencies.keys():
|
||||
if name == key or name.startswith(f"{key}."):
|
||||
return __import__(name, *args, **kwargs)
|
||||
|
||||
module_file_path = self._resolve_module_from_path(name)
|
||||
|
||||
@ -58,9 +78,7 @@ class FSLoader(UnsafeFSLoader):
|
||||
|
||||
module = types.ModuleType(name)
|
||||
module.__dict__.update(
|
||||
{
|
||||
"__builtins__": self.builtins,
|
||||
}
|
||||
{"__builtins__": self.builtins, "__ocab_module_id__": self.module_id}
|
||||
)
|
||||
result = compile_restricted_exec(src, "<string>", policy=RestrictedPythonPolicy)
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
import inspect
|
||||
|
||||
import pkg_resources
|
||||
import semver
|
||||
|
||||
from ocab_core.modules_system.loaders.base import AbstractLoader
|
||||
from ocab_core.modules_system.loaders.base import (
|
||||
AbstractLoader,
|
||||
DependencyInfo,
|
||||
ModuleInfo,
|
||||
)
|
||||
|
||||
|
||||
def is_version_compatible(version, requirement):
|
||||
@ -23,6 +28,46 @@ def is_version_compatible(version, requirement):
|
||||
return True
|
||||
|
||||
|
||||
def check_python_dependencies(info: ModuleInfo):
|
||||
if info.pythonDependencies and info.pythonDependencies.required:
|
||||
for dependency, req in info.pythonDependencies.required.items():
|
||||
try:
|
||||
installed_version = pkg_resources.get_distribution(dependency).version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
raise Exception(
|
||||
f"Module {info.id} requires {dependency},"
|
||||
f"but it is not installed"
|
||||
)
|
||||
|
||||
if isinstance(req, str):
|
||||
required_version = req
|
||||
elif isinstance(req, DependencyInfo):
|
||||
required_version = req.version
|
||||
else:
|
||||
raise ValueError(f"Invalid dependency specification for {dependency}")
|
||||
|
||||
if not is_version_compatible(installed_version, required_version):
|
||||
raise Exception(
|
||||
f"Module {info.id} depends on {dependency} {required_version}, "
|
||||
f"but version {installed_version} is installed"
|
||||
)
|
||||
|
||||
|
||||
def check_dependency_uses(
|
||||
loaded_dependency, required_uses, dependent_module_id, dependency_id
|
||||
):
|
||||
module = loaded_dependency.get("module")
|
||||
if not module:
|
||||
raise Exception(f"Module object not found for dependency {dependency_id}")
|
||||
|
||||
for required_attr in required_uses:
|
||||
if not hasattr(module, required_attr):
|
||||
raise Exception(
|
||||
f"Module {dependent_module_id} requires '{required_attr}' "
|
||||
f"from {dependency_id}, but it is not available"
|
||||
)
|
||||
|
||||
|
||||
async def await_if_async(module, method_name):
|
||||
if hasattr(module, method_name):
|
||||
method = getattr(module, method_name)
|
||||
@ -43,33 +88,54 @@ class ModulesManager:
|
||||
if any(mod["info"].id == info.id for mod in self.modules):
|
||||
return
|
||||
|
||||
# Check dependencies
|
||||
for dependency, version in info.dependencies.items():
|
||||
loaded_dependency = next(
|
||||
(mod for mod in self.modules if mod["info"].id == dependency), None
|
||||
)
|
||||
if not loaded_dependency:
|
||||
raise Exception(
|
||||
f"Module {info.id} depends on {dependency}, but it is not loaded"
|
||||
)
|
||||
loaded_dependency_info = loaded_dependency["info"]
|
||||
if not is_version_compatible(loaded_dependency_info.version, version):
|
||||
raise Exception(
|
||||
f"Module {info.id} depends on {dependency}, "
|
||||
f"but version {version} is not compatible"
|
||||
)
|
||||
self.check_module_dependencies(info)
|
||||
check_python_dependencies(info)
|
||||
|
||||
module_info = {
|
||||
"info": info,
|
||||
"module": None,
|
||||
}
|
||||
self.modules.append(module_info)
|
||||
module = loader.load()
|
||||
|
||||
self.modules.append(
|
||||
{
|
||||
"info": info,
|
||||
"module": module,
|
||||
}
|
||||
)
|
||||
module_info["module"] = module
|
||||
|
||||
await await_if_async(module, "module_init")
|
||||
|
||||
def check_module_dependencies(self, info: ModuleInfo):
|
||||
if info.dependencies.required:
|
||||
for dependency, req in info.dependencies.required.items():
|
||||
loaded_dependency = next(
|
||||
(mod for mod in self.modules if mod["info"].id == dependency), None
|
||||
)
|
||||
if not loaded_dependency:
|
||||
raise Exception(
|
||||
f"Module {info.id} depends on {dependency},"
|
||||
f"but it is not loaded"
|
||||
)
|
||||
|
||||
loaded_dependency_info = loaded_dependency["info"]
|
||||
|
||||
if isinstance(req, str):
|
||||
required_version = req
|
||||
elif isinstance(req, DependencyInfo):
|
||||
required_version = req.version
|
||||
if req.uses:
|
||||
check_dependency_uses(
|
||||
loaded_dependency, req.uses, info.id, dependency
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid dependency specification for {dependency}"
|
||||
)
|
||||
|
||||
if not is_version_compatible(
|
||||
loaded_dependency_info.version, required_version
|
||||
):
|
||||
raise Exception(
|
||||
f"Module {info.id} depends on {dependency} {required_version}, "
|
||||
f"but version {loaded_dependency_info.version} is loaded"
|
||||
)
|
||||
|
||||
async def late_init(self):
|
||||
for m in self.modules:
|
||||
module = m["module"]
|
||||
|
@ -6,6 +6,7 @@ from .public_api import (
|
||||
get_module,
|
||||
register_outer_message_middleware,
|
||||
register_router,
|
||||
set_chat_menu_button,
|
||||
set_my_commands,
|
||||
)
|
||||
from .utils import Utils
|
||||
|
@ -1,3 +1,4 @@
|
||||
import inspect
|
||||
import types
|
||||
from typing import Any, Tuple, Union
|
||||
|
||||
@ -5,9 +6,16 @@ from aiogram import BaseMiddleware, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.storage.base import StorageKey
|
||||
|
||||
# from ocab_core.logger import log
|
||||
from ocab_core.modules_system.loaders.base import DependencyInfo
|
||||
from ocab_core.singleton import Singleton
|
||||
|
||||
|
||||
async def set_chat_menu_button(menu_button):
|
||||
app = Singleton()
|
||||
await app.bot.set_chat_menu_button(menu_button=menu_button)
|
||||
|
||||
|
||||
def register_router(router: Router):
|
||||
app = Singleton()
|
||||
app.storage["_routers"].append(router)
|
||||
@ -45,10 +53,41 @@ def set_fsm(storage):
|
||||
def get_module(
|
||||
module_id: str, paths=None
|
||||
) -> Union[types.ModuleType, Union[Any, None], Tuple[Union[Any, None], ...]]:
|
||||
|
||||
caller_globals = inspect.currentframe().f_back.f_globals
|
||||
app = Singleton()
|
||||
|
||||
allowed_uses = None
|
||||
|
||||
if "__ocab_module_id__" in caller_globals:
|
||||
caller_module_id = caller_globals["__ocab_module_id__"]
|
||||
caller_module_info = app.modules_manager.get_info_by_id(caller_module_id)
|
||||
|
||||
if caller_module_info and caller_module_info.dependencies:
|
||||
dependency = None
|
||||
if caller_module_info.dependencies.required:
|
||||
dependency = caller_module_info.dependencies.required.get(module_id)
|
||||
if not dependency and caller_module_info.dependencies.optional:
|
||||
dependency = caller_module_info.dependencies.optional.get(module_id)
|
||||
|
||||
if (
|
||||
dependency
|
||||
and isinstance(dependency, DependencyInfo)
|
||||
and dependency.uses
|
||||
):
|
||||
allowed_uses = set(dependency.uses)
|
||||
|
||||
module = app.modules_manager.get_by_id(module_id)
|
||||
|
||||
if not module:
|
||||
raise ModuleNotFoundError(f"Module {module_id} not found")
|
||||
|
||||
if paths is None:
|
||||
if allowed_uses is not None:
|
||||
raise PermissionError(
|
||||
f"Direct access to module {module_id} is "
|
||||
f"not allowed for {caller_module_id}. Specify allowed attributes."
|
||||
)
|
||||
return module
|
||||
|
||||
if isinstance(paths, str):
|
||||
@ -61,9 +100,16 @@ def get_module(
|
||||
try:
|
||||
parts = path.split(".")
|
||||
for part in parts:
|
||||
if allowed_uses is not None and part not in allowed_uses:
|
||||
raise AttributeError(
|
||||
f"Access to '{part}' is not allowed "
|
||||
+ f"for module {caller_module_id}"
|
||||
)
|
||||
current_obj = getattr(current_obj, part)
|
||||
results.append(current_obj)
|
||||
except AttributeError:
|
||||
except AttributeError as e:
|
||||
if "is not allowed" in str(e):
|
||||
raise PermissionError(str(e))
|
||||
results.append(None)
|
||||
|
||||
if len(results) == 1:
|
||||
|
@ -1,6 +1,8 @@
|
||||
import types
|
||||
from _ast import AnnAssign
|
||||
from typing import Any
|
||||
|
||||
import flet as ft
|
||||
from aiogram import Bot
|
||||
from RestrictedPython import (
|
||||
RestrictingNodeTransformer,
|
||||
@ -9,8 +11,8 @@ from RestrictedPython import (
|
||||
utility_builtins,
|
||||
)
|
||||
from RestrictedPython.Eval import default_guarded_getitem, default_guarded_getiter
|
||||
from RestrictedPython.Guards import (
|
||||
full_write_guard,
|
||||
from RestrictedPython.Guards import ( # guarded_setattr,; full_write_guard,
|
||||
_write_wrapper,
|
||||
guarded_unpack_sequence,
|
||||
safer_getattr,
|
||||
)
|
||||
@ -95,6 +97,39 @@ def safes_getattr(object, name, default=None, getattr=safer_getattr):
|
||||
return getattr(object, name, default)
|
||||
|
||||
|
||||
trusted_settters_classes = [ft.Page, ft.View]
|
||||
|
||||
|
||||
def safes_setattr(self, key, value):
|
||||
if (
|
||||
isinstance(getattr(type(self), key, None), property)
|
||||
and getattr(type(self), key).fset is not None
|
||||
):
|
||||
getattr(type(self), key).fset(self, value)
|
||||
return
|
||||
|
||||
|
||||
def write_guard():
|
||||
# ed scope abuse!
|
||||
# safetypes and Wrapper variables are used by guard()
|
||||
safetypes = {dict, list}
|
||||
Wrapper = _write_wrapper()
|
||||
|
||||
def guard(ob):
|
||||
# Don't bother wrapping simple types, or objects that claim to
|
||||
# handle their own write security.
|
||||
if type(ob) in safetypes or hasattr(ob, "_guarded_writes"):
|
||||
return ob
|
||||
|
||||
if type(ob) in trusted_settters_classes:
|
||||
setattr(ob, "__guarded_setattr__", types.MethodType(safes_setattr, ob))
|
||||
|
||||
# Hand the object to the Wrapper instance, then return the instance.
|
||||
return Wrapper(ob)
|
||||
|
||||
return guard
|
||||
|
||||
|
||||
BUILTINS = safe_builtins.copy()
|
||||
BUILTINS.update(utility_builtins)
|
||||
BUILTINS.update(limited_builtins)
|
||||
@ -103,7 +138,7 @@ BUILTINS["__metaclass__"] = _metaclass
|
||||
BUILTINS["_getitem_"] = default_guarded_getitem
|
||||
BUILTINS["_getattr_"] = safes_getattr
|
||||
BUILTINS["_getiter_"] = default_guarded_getiter
|
||||
BUILTINS["_write_"] = full_write_guard
|
||||
BUILTINS["_write_"] = write_guard()
|
||||
BUILTINS["_unpack_sequence_"] = guarded_unpack_sequence
|
||||
BUILTINS["staticmethod"] = staticmethod
|
||||
BUILTINS["tuple"] = tuple
|
||||
|
@ -6,6 +6,8 @@
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"standard.filters": "^1.0.0"
|
||||
"required": {
|
||||
"standard.filters": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
{
|
||||
"id": "standard.command_helper",
|
||||
"name": "Command helper",
|
||||
"description": "Модуль для отображения команд при вводе '/'",
|
||||
"author": "OCAB Team",
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.database": "^1.0.0"
|
||||
"id": "standard.command_helper",
|
||||
"name": "Command helper",
|
||||
"description": "Модуль для отображения команд при вводе '/'",
|
||||
"author": "OCAB Team",
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"required": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.database": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,3 +5,4 @@ from .config import (
|
||||
get_yandexgpt_in_words,
|
||||
get_yandexgpt_start_words,
|
||||
)
|
||||
from .main import module_late_init
|
||||
|
@ -1,22 +1,67 @@
|
||||
# flake8: noqa
|
||||
|
||||
import yaml
|
||||
from .config_manager import ConfigManager
|
||||
|
||||
from src.service import paths
|
||||
config = ConfigManager(
|
||||
config_path="/home/maxim/dev/alt-gnome-infrastructure/ocab/src/ocab_core/config.yaml"
|
||||
)
|
||||
|
||||
|
||||
def get_config(is_test: bool = False) -> dict:
|
||||
if is_test:
|
||||
path = f"{paths.modules_standard}/config/tests"
|
||||
else:
|
||||
path = paths.core
|
||||
path = f"{path}/config.yaml"
|
||||
def register_settings(settings_manager: ConfigManager):
|
||||
# TELEGRAM settings
|
||||
settings_manager.register_setting(
|
||||
["TELEGRAM", "TOKEN"], "", "string", is_private=True
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["TELEGRAM", "APPROVED_CHAT_ID"],
|
||||
"-123456789 | -012345678",
|
||||
"string",
|
||||
pretty_name="ID разрешенных чатов",
|
||||
description='Чаты, в которых будет работать бот. "|" - разделитель',
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["TELEGRAM", "ADMINCHATID"],
|
||||
-12345678,
|
||||
"number",
|
||||
pretty_name="ID чата администраторов",
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["TELEGRAM", "DEFAULT_CHAT_TAG"],
|
||||
"@alt_gnome_chat",
|
||||
"string",
|
||||
pretty_name="Основной чат",
|
||||
)
|
||||
settings_manager.register_setting(["TELEGRAM", "CHECK_BOT"], True, "checkbox")
|
||||
|
||||
with open(path, "r") as file:
|
||||
return yaml.full_load(file)
|
||||
# YANDEXGPT settings
|
||||
settings_manager.register_setting(
|
||||
["YANDEXGPT", "TOKEN"], "", "string", is_private=True
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["YANDEXGPT", "TOKEN_FOR_REQUEST"], 8000, "number"
|
||||
)
|
||||
settings_manager.register_setting(["YANDEXGPT", "TOKEN_FOR_ANSWER"], 2000, "number")
|
||||
settings_manager.register_setting(
|
||||
["YANDEXGPT", "CATALOGID"], "", "string", is_private=True
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["YANDEXGPT", "PROMPT"], "Ты чат-бот ...", "string"
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["YANDEXGPT", "STARTWORD"], "Бот| Бот, | бот | бот,", "string"
|
||||
)
|
||||
settings_manager.register_setting(
|
||||
["YANDEXGPT", "INWORD"], "помогите | не работает", "string"
|
||||
)
|
||||
|
||||
# ROLES settings
|
||||
settings_manager.register_setting(["ROLES", "ADMIN"], 2, "number")
|
||||
settings_manager.register_setting(["ROLES", "MODERATOR"], 1, "number")
|
||||
settings_manager.register_setting(["ROLES", "USER"], 0, "number")
|
||||
settings_manager.register_setting(["ROLES", "BOT"], 3, "number")
|
||||
|
||||
|
||||
config = get_config()
|
||||
register_settings(config)
|
||||
|
||||
|
||||
def get_telegram_token() -> str:
|
||||
@ -76,4 +121,4 @@ def get_yandexgpt_token_for_answer() -> int:
|
||||
|
||||
|
||||
def get_access_rights() -> dict:
|
||||
return get_config()["ACCESS_RIGHTS"]
|
||||
return config["ACCESS_RIGHTS"]
|
||||
|
259
src/ocab_modules/standard/config/config_manager.py
Normal file
259
src/ocab_modules/standard/config/config_manager.py
Normal file
@ -0,0 +1,259 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import yaml
|
||||
|
||||
# from ocab_core.modules_system.public_api import get_module, log
|
||||
|
||||
try:
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash_extensions.enrich import Input, Output, dcc, html
|
||||
|
||||
DASH_AVAILABLE = True
|
||||
except ImportError:
|
||||
DASH_AVAILABLE = False
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(self, config_path: str):
|
||||
self._config_path = config_path
|
||||
self._config = self.load_config()
|
||||
self._registered_settings = dict()
|
||||
self._registered_settings_meta = dict()
|
||||
self._update_callbacks = []
|
||||
self._update_required = False
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return self._config
|
||||
|
||||
@config.setter
|
||||
def config(self, value):
|
||||
self._config = value
|
||||
|
||||
def load_config(self) -> Dict[str, Any]:
|
||||
with open(self._config_path, "r") as file:
|
||||
return yaml.safe_load(file)
|
||||
|
||||
def save_config(self):
|
||||
with open(self._config_path, "w") as file:
|
||||
yaml.dump(self._config, file)
|
||||
|
||||
def register_setting(
|
||||
self,
|
||||
key: Union[str, List[str]],
|
||||
default_value: Any,
|
||||
setting_type: str,
|
||||
is_private: bool = False,
|
||||
pretty_name: str = None,
|
||||
description: str = None,
|
||||
options: Optional[List[str]] = None,
|
||||
):
|
||||
if isinstance(key, str):
|
||||
key = [key]
|
||||
|
||||
current = self._registered_settings
|
||||
for k in key[:-1]:
|
||||
if k not in current:
|
||||
current[k] = {}
|
||||
current = current[k]
|
||||
|
||||
current[key[-1]] = self.get_nested_setting(self._config, key, default_value)
|
||||
|
||||
self.set_nested_setting(
|
||||
self._registered_settings_meta,
|
||||
key,
|
||||
{
|
||||
"type": setting_type,
|
||||
"is_private": is_private,
|
||||
"options": options,
|
||||
"pretty_name": pretty_name,
|
||||
"description": description,
|
||||
},
|
||||
)
|
||||
|
||||
def get_nested_setting(
|
||||
self, config: dict, keys: List[str], default: Any = None
|
||||
) -> Any:
|
||||
current = config
|
||||
for key in keys:
|
||||
if key in current:
|
||||
current = current[key]
|
||||
else:
|
||||
return default
|
||||
return current
|
||||
|
||||
def set_nested_setting(self, config: dict, keys: List[str], value: Any):
|
||||
current = config
|
||||
for key in keys[:-1]:
|
||||
if key not in current:
|
||||
current[key] = {}
|
||||
current = current[key]
|
||||
current[keys[-1]] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self._registered_settings:
|
||||
return self._registered_settings[key]
|
||||
raise KeyError(key)
|
||||
|
||||
def update_setting(self, key: Union[str, List[str]], value: Any):
|
||||
if isinstance(key, str):
|
||||
key = [key]
|
||||
self.set_nested_setting(self._registered_settings, key, value)
|
||||
self.set_nested_setting(self._config, key, value)
|
||||
self.save_config()
|
||||
|
||||
def get_settings_layout(self):
|
||||
from dash_extensions.enrich import DashBlueprint
|
||||
|
||||
bp = DashBlueprint()
|
||||
|
||||
def create_layout():
|
||||
def create_nested_layout(settings: dict, key_list=None):
|
||||
if key_list is None:
|
||||
key_list = []
|
||||
|
||||
components = []
|
||||
|
||||
for key, value in settings.items():
|
||||
current_key_list = key_list.copy()
|
||||
current_key_list.append(key)
|
||||
|
||||
if isinstance(value, dict):
|
||||
nested = create_nested_layout(value, current_key_list)
|
||||
if len(nested) > 0:
|
||||
components.append(
|
||||
dbc.Card(
|
||||
[
|
||||
dbc.CardHeader(html.H3(key, className="mb-0")),
|
||||
dbc.CardBody(nested),
|
||||
],
|
||||
className="mb-3",
|
||||
)
|
||||
)
|
||||
else:
|
||||
meta = self.get_nested_setting(
|
||||
self._registered_settings_meta, current_key_list
|
||||
)
|
||||
|
||||
if not meta.get("is_private"):
|
||||
row = []
|
||||
label_text = meta.get("pretty_name", key)
|
||||
|
||||
if meta.get("type") != "checkbox":
|
||||
row.append(dbc.Label(label_text))
|
||||
|
||||
component_id = {
|
||||
"type": "setting",
|
||||
"key": "-".join(current_key_list),
|
||||
}
|
||||
|
||||
if meta.get("type") == "string":
|
||||
component = dbc.Input(
|
||||
id=component_id, type="text", value=value
|
||||
)
|
||||
elif meta.get("type") == "number":
|
||||
component = dbc.Input(
|
||||
id=component_id, type="number", value=value
|
||||
)
|
||||
elif meta.get("type") == "checkbox":
|
||||
component = dbc.Col(
|
||||
[
|
||||
dbc.Checkbox(
|
||||
id=component_id,
|
||||
value=value,
|
||||
label=dbc.Label(
|
||||
label_text,
|
||||
style={"margin-right": "10px"},
|
||||
check=True,
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
elif meta.get("type") == "select":
|
||||
options = [
|
||||
{"label": opt, "value": opt}
|
||||
for opt in meta.get("options", [])
|
||||
]
|
||||
component = dcc.Dropdown(
|
||||
id=component_id, options=options, value=value
|
||||
)
|
||||
else:
|
||||
continue
|
||||
|
||||
row.append(component)
|
||||
|
||||
if meta.get("description"):
|
||||
row.append(dbc.FormText(meta.get("description")))
|
||||
|
||||
components.append(dbc.Row(row, className="mb-3"))
|
||||
|
||||
return components
|
||||
|
||||
settings_components = create_nested_layout(self._registered_settings)
|
||||
|
||||
layout = html.Div(
|
||||
[
|
||||
html.H1("Настройки"),
|
||||
dbc.Form(settings_components),
|
||||
dbc.Button(
|
||||
"Сохранить",
|
||||
id="save-settings",
|
||||
color="primary",
|
||||
className="mt-3",
|
||||
n_clicks=0,
|
||||
),
|
||||
html.Div(id="settings-update-trigger", style={"display": "none"}),
|
||||
html.Span(
|
||||
id="save-confirmation", style={"verticalAlign": "middle"}
|
||||
),
|
||||
dcc.Store(id="settings-store"),
|
||||
],
|
||||
style={
|
||||
"padding": "20px",
|
||||
},
|
||||
)
|
||||
|
||||
return layout
|
||||
|
||||
bp.layout = create_layout
|
||||
self.setup_callbacks(bp)
|
||||
|
||||
return bp
|
||||
|
||||
def setup_callbacks(self, app):
|
||||
# ws = WebSocket(app, url="/ws")
|
||||
|
||||
@app.callback(
|
||||
Output("save-confirmation", "children"),
|
||||
# Output("settings-store", "data"),
|
||||
Input("save-settings", "n_clicks"),
|
||||
# State({"type": "setting", "key": ALL}, "value"),
|
||||
# State({"type": "setting", "key": ALL}, "id"),
|
||||
running=[(Output("save-settings", "disabled"), True, False)],
|
||||
)
|
||||
def save_settings(n_clicks, values, ids):
|
||||
if n_clicks > 0:
|
||||
# updated_settings = {}
|
||||
# print(ids)
|
||||
# for value, id_dict in zip(values, ids):
|
||||
# key = id_dict["key"]
|
||||
# self.update_setting(key.split("-"), value)
|
||||
# updated_settings[key] = value
|
||||
|
||||
return "Настройки сохранены!" # , json.dumps(updated_settings)
|
||||
return "" # , None
|
||||
|
||||
# @app.callback(
|
||||
# Output({"type": "setting", "key": ALL}, "value"),
|
||||
# Input("settings-store", "data"),
|
||||
# )
|
||||
# def update_settings_from_store(data):
|
||||
# if data:
|
||||
# updated_settings = json.loads(data)
|
||||
# print(
|
||||
# [current_value for key, current_value in updated_settings.items()]
|
||||
# )
|
||||
# return [
|
||||
# current_value for key, current_value in updated_settings.items()
|
||||
# ]
|
||||
# raise dash.exceptions.PreventUpdate()
|
@ -5,5 +5,14 @@
|
||||
"author": "OCAB Team",
|
||||
"version": "1.0.0",
|
||||
"privileged": true,
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"optional": {
|
||||
"standard.miniapp": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"pythonDependencies": {
|
||||
"optional": {
|
||||
"flet": "^0.23.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
35
src/ocab_modules/standard/config/main.py
Normal file
35
src/ocab_modules/standard/config/main.py
Normal file
@ -0,0 +1,35 @@
|
||||
from ocab_core.modules_system.public_api import get_module, log
|
||||
|
||||
from .config import config
|
||||
|
||||
|
||||
def register_settings_page():
|
||||
try:
|
||||
register_page = get_module("standard.miniapp", "register_page")
|
||||
#
|
||||
# def setup_callbacks_wrapper(config_manager):
|
||||
# def setup(app):
|
||||
# config_manager.setup_callbacks(app)
|
||||
#
|
||||
# return setup
|
||||
#
|
||||
# register_page(
|
||||
# name="Настройки",
|
||||
# path="/settings",
|
||||
# layout=config.get_settings_layout(),
|
||||
# setup_callbacks=setup_callbacks_wrapper(config),
|
||||
# )
|
||||
register_page(
|
||||
name="Настройки", path="/settings", blueprint=config.get_settings_layout()
|
||||
)
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
log(str(e))
|
||||
pass
|
||||
|
||||
|
||||
def module_late_init():
|
||||
register_settings_page()
|
||||
|
||||
pass
|
@ -6,7 +6,9 @@
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.config": "^1.0.0"
|
||||
"required": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.config": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
{
|
||||
"id": "standard.fsm_database_storage",
|
||||
"name": "FSM Database Storage",
|
||||
"description": "Очень полезный модуль",
|
||||
"author": "OCAB Team",
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"standard.database": "^1.0.0"
|
||||
"id": "standard.fsm_database_storage",
|
||||
"name": "FSM Database Storage",
|
||||
"description": "Очень полезный модуль",
|
||||
"author": "OCAB Team",
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"required": {
|
||||
"standard.database": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,10 @@
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.database": "^1.0.0",
|
||||
"standard.command_helper": "^1.0.0"
|
||||
"required": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.database": "^1.0.0",
|
||||
"standard.command_helper": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,10 @@
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.database": "^1.0.0",
|
||||
"standard.command_helper": "^1.0.0"
|
||||
"required": {
|
||||
"standard.roles": "^1.0.0",
|
||||
"standard.database": "^1.0.0",
|
||||
"standard.command_helper": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
from .main import module_init
|
||||
from .lib import register_page
|
||||
from .main import module_init, module_late_init
|
||||
|
@ -5,5 +5,17 @@
|
||||
"author": "OCAB Team",
|
||||
"version": "1.0.0",
|
||||
"privileged": false,
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"required": {
|
||||
"standard.config": {
|
||||
"version": "^1.0.0",
|
||||
"uses": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"pythonDependencies": {
|
||||
"required": {
|
||||
"flet": "^0.23.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
172
src/ocab_modules/standard/miniapp/lib.py
Normal file
172
src/ocab_modules/standard/miniapp/lib.py
Normal file
@ -0,0 +1,172 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import dash
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
||||
from dash_extensions.pages import setup_page_components
|
||||
from flask import Flask
|
||||
|
||||
pages = OrderedDict()
|
||||
|
||||
|
||||
def register_page(name, path, blueprint):
|
||||
pages[path] = {
|
||||
"name": name,
|
||||
"blueprint": blueprint,
|
||||
}
|
||||
|
||||
|
||||
def register_home_page():
|
||||
page = DashBlueprint()
|
||||
page.layout = html.Div([html.H1("Главная")])
|
||||
register_page("Главная", path="/", blueprint=page)
|
||||
|
||||
|
||||
register_home_page()
|
||||
|
||||
|
||||
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
||||
server = Flask(__name__)
|
||||
app = DashProxy(
|
||||
pages_folder="",
|
||||
use_pages=True,
|
||||
suppress_callback_exceptions=True,
|
||||
external_stylesheets=[
|
||||
dbc.themes.BOOTSTRAP,
|
||||
dbc.icons.BOOTSTRAP,
|
||||
],
|
||||
external_scripts=[
|
||||
"https://telegram.org/js/telegram-web-app.js"
|
||||
], # Add Telegram Mini Apps script to <head>
|
||||
server=server,
|
||||
requests_pathname_prefix=requests_pathname_prefix,
|
||||
meta_tags=[
|
||||
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
|
||||
],
|
||||
)
|
||||
|
||||
app.enable_dev_tools(
|
||||
dev_tools_ui=True,
|
||||
dev_tools_serve_dev_bundles=True,
|
||||
)
|
||||
|
||||
# Register pages
|
||||
for path, page in pages.items():
|
||||
# dash.register_page(page["name"], path=path, layout=page["layout"])
|
||||
page["blueprint"].register(app, path, prefix="a")
|
||||
|
||||
# Create sidebar
|
||||
sidebar = dbc.Offcanvas(
|
||||
id="offcanvas",
|
||||
title="Меню",
|
||||
is_open=False,
|
||||
children=[
|
||||
dbc.Nav(
|
||||
[
|
||||
dbc.NavLink(
|
||||
page["name"],
|
||||
href=f"{requests_pathname_prefix}{path.lstrip('/')}",
|
||||
id={"type": "nav-link", "index": path},
|
||||
)
|
||||
for path, page in pages.items()
|
||||
],
|
||||
vertical=True,
|
||||
pills=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Create navbar
|
||||
navbar = dbc.Navbar(
|
||||
dbc.Container(
|
||||
[
|
||||
dbc.Button(
|
||||
html.I(className="bi bi-list"),
|
||||
id="open-offcanvas",
|
||||
color="light",
|
||||
className="me-2",
|
||||
),
|
||||
dbc.NavbarBrand("OCAB"),
|
||||
]
|
||||
),
|
||||
color="primary",
|
||||
dark=True,
|
||||
)
|
||||
|
||||
# Define app layout
|
||||
app.layout = html.Div(
|
||||
[
|
||||
dcc.Location(id="url", refresh=False),
|
||||
dcc.Store(id="user-data", storage_type="session"),
|
||||
dcc.Interval(
|
||||
id="init-telegram-interval",
|
||||
interval=100,
|
||||
n_intervals=0,
|
||||
max_intervals=1,
|
||||
),
|
||||
# WebSocket(url="/ws"),
|
||||
html.Div(id="telegram-login-info"),
|
||||
navbar,
|
||||
sidebar,
|
||||
dash.page_container,
|
||||
setup_page_components(),
|
||||
]
|
||||
)
|
||||
|
||||
# Clientside callback to initialize Telegram Mini Apps and get user data
|
||||
app.clientside_callback(
|
||||
"""
|
||||
function(n_intervals) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve("test");
|
||||
if (window.Telegram && window.Telegram.WebApp) {
|
||||
const webapp = window.Telegram.WebApp;
|
||||
webapp.ready();
|
||||
if (webapp.initDataUnsafe && webapp.initDataUnsafe.user) {
|
||||
resolve(webapp.initDataUnsafe.user);
|
||||
} else {
|
||||
reject("User not authorized");
|
||||
}
|
||||
} else {
|
||||
reject("Telegram Mini Apps not available");
|
||||
}
|
||||
});
|
||||
}
|
||||
""",
|
||||
Output("user-data", "data"),
|
||||
Input("init-telegram-interval", "n_intervals"),
|
||||
)
|
||||
|
||||
# Открытие на кнопку меню
|
||||
app.clientside_callback(
|
||||
"""
|
||||
function(n_clicks) {
|
||||
if (n_clicks == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
""",
|
||||
Output(
|
||||
"offcanvas",
|
||||
"is_open",
|
||||
),
|
||||
Input("open-offcanvas", "n_clicks"),
|
||||
)
|
||||
|
||||
# Закрываем offcanvas при клике на ссылку в меню
|
||||
app.clientside_callback(
|
||||
"""
|
||||
function(n_clicks) {
|
||||
if (n_clicks == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
""",
|
||||
Output("offcanvas", "is_open", allow_duplicate=True),
|
||||
Input({"type": "nav-link", "index": dash.dependencies.ALL}, "n_clicks"),
|
||||
prevent_initial_call="initial_duplicate",
|
||||
)
|
||||
|
||||
return app
|
@ -1,2 +1,31 @@
|
||||
from aiogram import types
|
||||
from fastapi.middleware.wsgi import WSGIMiddleware
|
||||
|
||||
from ocab_core.modules_system.public_api import Storage, set_chat_menu_button
|
||||
|
||||
|
||||
def get_link():
|
||||
pass
|
||||
|
||||
|
||||
def module_init():
|
||||
pass
|
||||
|
||||
|
||||
def register_page():
|
||||
pass
|
||||
|
||||
|
||||
async def module_late_init():
|
||||
from .lib import create_dash_app
|
||||
|
||||
dash_app = create_dash_app(requests_pathname_prefix="/webapp/")
|
||||
|
||||
Storage.set("webapp", WSGIMiddleware(dash_app.server))
|
||||
|
||||
web_app_info = types.WebAppInfo(
|
||||
url="https://mackerel-pumped-foal.ngrok-free.app/webapp"
|
||||
)
|
||||
menu_button = types.MenuButtonWebApp(text="Меню", web_app=web_app_info)
|
||||
|
||||
await set_chat_menu_button(menu_button)
|
||||
|
@ -6,7 +6,9 @@
|
||||
"version": "1.0.0",
|
||||
"privileged": true,
|
||||
"dependencies": {
|
||||
"standard.config": "^1.0.0",
|
||||
"standard.database": "^1.0.0"
|
||||
"required": {
|
||||
"standard.config": "^1.0.0",
|
||||
"standard.database": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user