From ada56a238211a045cf1ad303c94d6f5ac6fa222e Mon Sep 17 00:00:00 2001 From: Chuangbo Li Date: Wed, 12 Oct 2022 21:39:47 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E6=A8=A1=E6=9D=BF=E9=A2=84=E8=A7=88=E5=92=8C=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E7=9A=84=20web=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 8 +- core/base/webserver.py | 65 +++++ core/config.py | 16 ++ core/service.py | 4 +- core/template/README.md | 11 + core/template/__init__.py | 10 +- core/template/cache.py | 28 ++ core/template/services.py | 121 ++++++--- plugins/genshin/abyss.py | 10 +- plugins/genshin/abyss_team.py | 3 +- plugins/genshin/daily/material.py | 7 +- plugins/genshin/daily_note.py | 2 +- plugins/genshin/gacha/gacha.py | 2 +- plugins/genshin/gacha/gacha_log.py | 4 +- plugins/genshin/help.py | 2 +- plugins/genshin/ledger.py | 50 +--- plugins/genshin/player_cards.py | 5 +- plugins/genshin/userstats.py | 5 +- plugins/genshin/weapon.py | 2 +- poetry.lock | 249 +++++++++++++++++- pyproject.toml | 2 + resources/genshin/ledger/ledger.html | 77 +++--- .../genshin/player_card/player_card.html | 14 +- 23 files changed, 536 insertions(+), 161 deletions(-) create mode 100644 core/base/webserver.py create mode 100644 core/template/README.md create mode 100644 core/template/cache.py diff --git a/.env.example b/.env.example index 87fe375..da3b674 100644 --- a/.env.example +++ b/.env.example @@ -40,4 +40,10 @@ API_ID=12345 API_HASH="abcdefg" # ENKA_NETWORK_API 可选配置项 -ENKA_NETWORK_API_AGENT="" \ No newline at end of file +ENKA_NETWORK_API_AGENT="" + +# Web Server +# 目前只用于预览模板,仅开发环境启动 +# WEB_URL=http://localhost:8080/ +# WEB_HOST=localhost +# WEB_PORT=8080 diff --git a/core/base/webserver.py b/core/base/webserver.py new file mode 100644 index 0000000..28a3840 --- /dev/null +++ b/core/base/webserver.py @@ -0,0 +1,65 @@ +import asyncio +import uvicorn +from fastapi import FastAPI + +from core.config import BotConfig, config as botConfig +from core.service import Service + +__all__ = ["webapp", "WebServer"] + +webapp = FastAPI(debug=botConfig.debug) + + +@webapp.get("/") +def index(): + return {"Hello": "Paimon"} + + +class WebServer(Service): + debug: bool + + host: str + port: int + + server: uvicorn.Server + + _server_task: asyncio.Task + + @classmethod + def from_config(cls, config: BotConfig) -> Service: + return cls(debug=config.debug, **config.webserver.dict()) + + def __init__(self, debug: bool, host: str, port: int): + self.debug = debug + self.host = host + self.port = port + + self.server = uvicorn.Server( + uvicorn.Config( + app=webapp, + port=port, + use_colors=False, + host=host, + ) + ) + + async def start(self): + """启动 service""" + + # 暂时只在开发环境启动 webserver 用于开发调试 + if not self.debug: + return + + # 防止 uvicorn server 拦截 signals + self.server.install_signal_handlers = lambda: None + self._server_task = asyncio.create_task(self.server.serve()) + + async def stop(self): + """关闭 service""" + if not self.debug: + return + + self.server.should_exit = True + + # 等待 task 结束 + await self._server_task diff --git a/core/config.py b/core/config.py index d0c6ec9..64d25f6 100644 --- a/core/config.py +++ b/core/config.py @@ -52,6 +52,10 @@ class BotConfig(BaseSettings): pass_challenge_api: str = "" pass_challenge_app_key: str = "" + web_url: str = "http://localhost:8080/" + web_host: str = "localhost" + web_port: int = 8080 + class Config: case_sensitive = False json_loads = json.loads @@ -92,6 +96,13 @@ class BotConfig(BaseSettings): api_hash=self.api_hash, ) + @property + def webserver(self) -> "WebServerConfig": + return WebServerConfig( + host=self.web_host, + port=self.web_port, + ) + class ConfigChannel(BaseModel): name: str @@ -130,5 +141,10 @@ class MTProtoConfig(BaseModel): api_hash: Optional[str] +class WebServerConfig(BaseModel): + host: Optional[str] + port: Optional[int] + + BotConfig.update_forward_refs() config = BotConfig() diff --git a/core/service.py b/core/service.py index a7e02a4..a68b5ee 100644 --- a/core/service.py +++ b/core/service.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from types import FunctionType +from typing import Callable from utils.log import logger @@ -18,7 +18,7 @@ class Service(ABC): """关闭 service""" -def init_service(func: FunctionType): +def init_service(func: Callable): from core.bot import bot if bot.is_running: diff --git a/core/template/README.md b/core/template/README.md new file mode 100644 index 0000000..b8a9bb2 --- /dev/null +++ b/core/template/README.md @@ -0,0 +1,11 @@ +# TemplateService + +使用 jinja2 渲染 html 为图片的服务。 + +## 预览模板 + +为了方便调试 html,在开发环境中,我们会启动 web server 用于预览模板。(可以在 .env 里调整端口等参数,参数均为 `web_` 开头) + +在派蒙收到指令开始渲染某个模板的时候,控制台会输出一个预览链接,类似 `http://localhost:8080/preview/genshin/stats/stats.html?id=45f7d86a-058e-4f64-bdeb-42903d8415b2`,有效时间 8 小时。 + +如果是无需数据的模板,永久有效,比如 `http://localhost:8080/preview/bot/help/help.html` diff --git a/core/template/__init__.py b/core/template/__init__.py index c2dca0a..91b5ec4 100644 --- a/core/template/__init__.py +++ b/core/template/__init__.py @@ -1,9 +1,11 @@ from core.base.aiobrowser import AioBrowser from core.service import init_service -from .services import TemplateService - +from core.base.redisdb import RedisDB +from core.template.services import TemplateService +from core.template.cache import TemplatePreviewCache @init_service -def create_template_service(browser: AioBrowser): - _service = TemplateService(browser) +def create_template_service(browser: AioBrowser, redis: RedisDB): + _cache = TemplatePreviewCache(redis) + _service = TemplateService(browser, _cache) return _service diff --git a/core/template/cache.py b/core/template/cache.py new file mode 100644 index 0000000..4f2cf01 --- /dev/null +++ b/core/template/cache.py @@ -0,0 +1,28 @@ +from typing import Any +import pickle # nosec B403 +import gzip + +from core.base.redisdb import RedisDB + + +class TemplatePreviewCache: + '''暂存渲染模板的数据用于预览''' + + def __init__(self, redis: RedisDB): + self.client = redis.client + self.qname = "bot:template:preview" + + async def get_data(self, key: str) -> Any: + data = await self.client.get(self.cache_key(key)) + if data: + # skipcq: BAN-B301 + return pickle.loads(gzip.decompress(data)) # nosec B301 + + async def set_data(self, key: str, data: Any, ttl: int = 8 * 60 * 60): + ck = self.cache_key(key) + await self.client.set(ck, gzip.compress(pickle.dumps(data))) + if ttl != -1: + await self.client.expire(ck, ttl) + + def cache_key(self, key: str) -> str: + return f"{self.qname}:{key}" diff --git a/core/template/services.py b/core/template/services.py index 9dc3bbd..57ab006 100644 --- a/core/template/services.py +++ b/core/template/services.py @@ -1,13 +1,21 @@ -import os import time -from typing import Dict, Optional +from typing import Optional +from urllib.parse import urlencode, urljoin, urlsplit -from jinja2 import Environment, PackageLoader, Template +from jinja2 import Environment, FileSystemLoader, Template from playwright.async_api import ViewportSize +from uuid import uuid4 + +from fastapi import HTTPException +from fastapi.responses import FileResponse, HTMLResponse +from fastapi.staticfiles import StaticFiles from core.base.aiobrowser import AioBrowser from core.bot import bot +from core.base.webserver import webapp +from utils.const import PROJECT_ROOT from utils.log import logger +from core.template.cache import TemplatePreviewCache class _QuerySelectorNotFound(Exception): @@ -15,51 +23,35 @@ class _QuerySelectorNotFound(Exception): class TemplateService: - def __init__(self, browser: AioBrowser, template_package_name: str = "resources", cache_dir_name: str = "cache"): + def __init__(self, browser: AioBrowser, preview_cache: TemplatePreviewCache, template_dir: str = "resources"): self._browser = browser - self._template_package_name = template_package_name - self._current_dir = os.getcwd() - self._output_dir = os.path.join(self._current_dir, cache_dir_name) - if not os.path.exists(self._output_dir): - os.mkdir(self._output_dir) - self._jinja2_env: Dict[str, Environment] = {} - self._jinja2_template: Dict[str, Template] = {} + self.template_dir = PROJECT_ROOT / template_dir - def get_template(self, package_path: str, template_name: str) -> Template: - if bot.config.debug: - # DEBUG下 禁止复用 方便查看和修改模板 - loader = PackageLoader(self._template_package_name, package_path) - jinja2_env = Environment(loader=loader, enable_async=True, autoescape=True) - jinja2_template = jinja2_env.get_template(template_name) - else: - jinja2_env = self._jinja2_env.get(package_path) - jinja2_template = self._jinja2_template.get(package_path + template_name) - if jinja2_env is None: - loader = PackageLoader(self._template_package_name, package_path) - jinja2_env = Environment(loader=loader, enable_async=True, autoescape=True) - jinja2_template = jinja2_env.get_template(template_name) - self._jinja2_env[package_path] = jinja2_env - self._jinja2_template[package_path + template_name] = jinja2_template - elif jinja2_template is None: - jinja2_template = jinja2_env.get_template(template_name) - self._jinja2_template[package_path + template_name] = jinja2_template - return jinja2_template + self._jinja2_env = Environment( + loader=FileSystemLoader(template_dir), + enable_async=True, + autoescape=True, + auto_reload=bot.config.debug, + ) - async def render_async(self, template_path: str, template_name: str, template_data: dict): + self.previewer = TemplatePreviewer(self, preview_cache) + + def get_template(self, template_name: str) -> Template: + return self._jinja2_env.get_template(template_name) + + async def render_async(self, template_name: str, template_data: dict): """模板渲染 - :param template_path: 模板目录 :param template_name: 模板文件名 :param template_data: 模板数据 """ start_time = time.time() - template = self.get_template(template_path, template_name) + template = self.get_template(template_name) html = await template.render_async(**template_data) logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}") return html async def render( self, - template_path: str, template_name: str, template_data: dict, viewport: ViewportSize = None, @@ -78,14 +70,20 @@ class TemplateService: :return: """ start_time = time.time() - template = self.get_template(template_path, template_name) - template_data["res_path"] = f"file://{self._current_dir}" + template = self.get_template(template_name) + + if bot.config.debug: + preview_url = await self.previewer.get_preview_url(template_name, template_data) + logger.debug(f"调试模板 URL: {preview_url}") + html = await template.render_async(**template_data) logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}") + browser = await self._browser.get_browser() start_time = time.time() page = await browser.new_page(viewport=viewport) - await page.goto(f"file://{template.filename}") + uri = (PROJECT_ROOT / template.filename).as_uri() + await page.goto(uri) await page.set_content(html, wait_until="networkidle") if evaluate: await page.evaluate(evaluate) @@ -104,3 +102,52 @@ class TemplateService: await page.close() logger.debug(f"{template_name} 图片渲染使用了 {str(time.time() - start_time)}") return png_data + + +class TemplatePreviewer: + def __init__(self, template_service: TemplateService, cache: TemplatePreviewCache): + self.template_service = template_service + self.cache = cache + self.register_routes() + + async def get_preview_url(self, template: str, data: dict): + """获取预览 URL""" + components = urlsplit(bot.config.web_url) + path = urljoin("/preview/", template) + query = {} + + # 如果有数据,暂存在 redis 中 + if data: + key = str(uuid4()) + await self.cache.set_data(key, data) + query["key"] = key + + return components._replace(path=path, query=urlencode(query)).geturl() + + def register_routes(self): + """注册预览用到的路由""" + + @webapp.get("/preview/{path:path}") + async def preview_template(path: str, key: Optional[str] = None): # pylint: disable=W0612 + # 如果是 /preview/ 开头的静态文件,直接返回内容。比如使用相对链接 ../ 引入的静态资源 + if not path.endswith(".html"): + full_path = self.template_service.template_dir / path + if not full_path.is_file(): + raise HTTPException(status_code=404, detail=f"Template '{path}' not found") + return FileResponse(full_path) + + # 取回暂存的渲染数据 + data = await self.cache.get_data(key) if key else {} + if key and data is None: + raise HTTPException(status_code=404, detail=f"Template data {key} not found") + + # 渲染 jinja2 模板 + html = await self.template_service.render_async(path, data) + # 将本地 URL file:// 修改为 HTTP url,因为浏览器内不允许加载本地文件 + # file:///project_dir/cache/image.jpg => /cache/image.jpg + html = html.replace(PROJECT_ROOT.as_uri(), "") + return HTMLResponse(html) + + # 其他静态资源 + for name in ["cache", "resources"]: + webapp.mount(f"/{name}", StaticFiles(directory=PROJECT_ROOT / name), name=name) diff --git a/plugins/genshin/abyss.py b/plugins/genshin/abyss.py index e8e06a3..fd732b2 100644 --- a/plugins/genshin/abyss.py +++ b/plugins/genshin/abyss.py @@ -258,7 +258,7 @@ class Abyss(Plugin, BasePlugin): [ -1, await self.template_service.render( - "genshin/abyss", "overview.html", render_data, viewport={"width": 750, "height": 580} + "genshin/abyss/overview.html", render_data, viewport={"width": 750, "height": 580} ), ] ) @@ -269,8 +269,7 @@ class Abyss(Plugin, BasePlugin): [ floor_d["floor"], await self.template_service.render( - "genshin/abyss", - "floor.html", + "genshin/abyss/floor.html", { **render_data, "floor": floor_d, @@ -293,8 +292,7 @@ class Abyss(Plugin, BasePlugin): render_data["data"] = json.loads(result) return [ await self.template_service.render( - "genshin/abyss", - "overview.html", + "genshin/abyss/overview.html", render_data, viewport={"width": 750, "height": 580}, ) @@ -325,6 +323,6 @@ class Abyss(Plugin, BasePlugin): render_data["total_stars"] = f"{floor_data[0]['stars']}/{floor_data[0]['max_stars']}" return [ await self.template_service.render( - "genshin/abyss", "floor.html", render_data, viewport={"width": 690, "height": 500}, full_page=True + "genshin/abyss/floor.html", render_data, viewport={"width": 690, "height": 500}, full_page=True ) ] diff --git a/plugins/genshin/abyss_team.py b/plugins/genshin/abyss_team.py index 9840020..b1d2f4a 100644 --- a/plugins/genshin/abyss_team.py +++ b/plugins/genshin/abyss_team.py @@ -83,8 +83,7 @@ class AbyssTeam(Plugin, BasePlugin): await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) png_data = await self.template_service.render( - "genshin/abyss_team", - "abyss_team.html", + "genshin/abyss_team/abyss_team.html", abyss_teams_data, {"width": 785, "height": 800}, full_page=True, diff --git a/plugins/genshin/daily/material.py b/plugins/genshin/daily/material.py index 7a28d76..16a0a30 100644 --- a/plugins/genshin/daily/material.py +++ b/plugins/genshin/daily/material.py @@ -258,7 +258,8 @@ class DailyMaterial(Plugin, BasePlugin): AreaData( name=area_data["name"], materials=materials, - items=sort_item(items), + # template previewer pickle cannot serialize generator + items=list(sort_item(items)), material_name=get_material_serial_name(map(lambda x: x.name, materials)), ) ) @@ -268,12 +269,12 @@ class DailyMaterial(Plugin, BasePlugin): render_tasks = [ asyncio.create_task( self.template_service.render( # 渲染角色素材页 - "genshin/daily_material", "character.html", {"data": render_data}, {"width": 1164, "height": 500} + "genshin/daily_material/character.html", {"data": render_data}, {"width": 1164, "height": 500} ) ), asyncio.create_task( self.template_service.render( # 渲染武器素材页 - "genshin/daily_material", "weapon.html", {"data": render_data}, {"width": 1164, "height": 500} + "genshin/daily_material/weapon.html", {"data": render_data}, {"width": 1164, "height": 500} ) ), ] diff --git a/plugins/genshin/daily_note.py b/plugins/genshin/daily_note.py index 84a5441..9058cab 100644 --- a/plugins/genshin/daily_note.py +++ b/plugins/genshin/daily_note.py @@ -86,7 +86,7 @@ class DailyNote(Plugin, BasePlugin): "transformer_recovery_time": transformer_recovery_time, } png_data = await self.template_service.render( - "genshin/daily_note", "daily_note.html", daily_data, {"width": 600, "height": 548}, full_page=False + "genshin/daily_note/daily_note.html", daily_data, {"width": 600, "height": 548}, full_page=False ) return png_data diff --git a/plugins/genshin/gacha/gacha.py b/plugins/genshin/gacha/gacha.py index 42d18b9..40221c4 100644 --- a/plugins/genshin/gacha/gacha.py +++ b/plugins/genshin/gacha/gacha.py @@ -126,7 +126,7 @@ class Gacha(Plugin, BasePlugin): await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) # 因为 gacha_info["title"] 返回的是 HTML 标签 尝试关闭自动转义 png_data = await self.template_service.render( - "genshin/gacha", "gacha.html", data, {"width": 1157, "height": 603}, False + "genshin/gacha/gacha.html", data, {"width": 1157, "height": 603}, False ) reply_message = await message.reply_photo(png_data) diff --git a/plugins/genshin/gacha/gacha_log.py b/plugins/genshin/gacha/gacha_log.py index 4bd1537..9a409e6 100644 --- a/plugins/genshin/gacha/gacha_log.py +++ b/plugins/genshin/gacha/gacha_log.py @@ -290,7 +290,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation): else: await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) png_data = await self.template_service.render( - "genshin/gacha_log", "gacha_log.html", data, full_page=True, query_selector=".body_box" + "genshin/gacha_log/gacha_log.html", data, full_page=True, query_selector=".body_box" ) await message.reply_photo(png_data) except UserNotFoundError: @@ -336,7 +336,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation): document = True data["hasMore"] = False png_data = await self.template_service.render( - "genshin/gacha_count", "gacha_count.html", data, full_page=True, query_selector=".body_box" + "genshin/gacha_count/gacha_count.html", data, full_page=True, query_selector=".body_box" ) if document: await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT) diff --git a/plugins/genshin/help.py b/plugins/genshin/help.py index 1989718..d601100 100644 --- a/plugins/genshin/help.py +++ b/plugins/genshin/help.py @@ -28,7 +28,7 @@ class HelpPlugin(Plugin): logger.info(f"用户 {user.full_name}[{user.id}] 发出help命令") if self.file_id is None or bot.config.debug: await message.reply_chat_action(ChatAction.TYPING) - help_png = await self.template_service.render("bot/help", "help.html", {}, {"width": 1280, "height": 900}) + help_png = await self.template_service.render("bot/help/help.html", {}, {"width": 1280, "height": 900}) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) reply_photo = await message.reply_photo(help_png, filename="help.png", allow_sending_without_reply=True) photo = reply_photo.photo[0] diff --git a/plugins/genshin/ledger.py b/plugins/genshin/ledger.py index 5305357..24ecaff 100644 --- a/plugins/genshin/ledger.py +++ b/plugins/genshin/ledger.py @@ -1,4 +1,3 @@ -import json import os import re from datetime import datetime, timedelta @@ -89,52 +88,6 @@ class Ledger(Plugin, BasePlugin): def format_amount(amount: int) -> str: return f"{round(amount / 10000, 2)}w" if amount >= 10000 else amount - evaluate = ( - """const { Pie } = G2Plot; - const data = JSON.parse(`""" - + json.dumps(categories) - + """`); - const piePlot = new Pie("chartContainer", { - renderer: "svg", - animation: false, - data: data, - appendPadding: 10, - angleField: "amount", - colorField: "name", - radius: 1, - innerRadius: 0.7, - color: JSON.parse(`""" - + json.dumps(color) - + """`), - meta: {}, - label: { - type: "inner", - offset: "-50%", - autoRotate: false, - style: { - textAlign: "center", - fontFamily: "tttgbnumber", - }, - formatter: ({ percentage }) => { - return percentage > 2 ? `${percentage}%` : ""; - }, - }, - statistic: { - title: { - offsetY: -18, - content: "总计", - }, - content: { - offsetY: -10, - style: { - fontFamily: "tttgbnumber", - }, - }, - }, - legend:false, - }); - piePlot.render();""" - ) ledger_data = { "uid": client.uid, "day": diary_info.month, @@ -145,9 +98,10 @@ class Ledger(Plugin, BasePlugin): "last_gacha": int(diary_info.month_data.last_primogems / 160), "last_mora": format_amount(diary_info.month_data.last_mora), "categories": categories, + "color": color, } png_data = await self.template_service.render( - "genshin/ledger", "ledger.html", ledger_data, {"width": 580, "height": 610}, evaluate=evaluate + "genshin/ledger/ledger.html", ledger_data, {"width": 580, "height": 610} ) return png_data diff --git a/plugins/genshin/player_cards.py b/plugins/genshin/player_cards.py index bdbbc7c..aa272b7 100644 --- a/plugins/genshin/player_cards.py +++ b/plugins/genshin/player_cards.py @@ -271,13 +271,12 @@ class RenderTemplate: } # html = await self.template_service.render_async( - # "genshin/player_card", "player_card.html", data + # "genshin/player_card/player_card.html", data # ) # logger.debug(html) return await self.template_service.render( - "genshin/player_card", - "player_card.html", + "genshin/player_card/player_card.html", data, {"width": 950, "height": 1080}, full_page=True, diff --git a/plugins/genshin/userstats.py b/plugins/genshin/userstats.py index 9af6b82..d525b44 100644 --- a/plugins/genshin/userstats.py +++ b/plugins/genshin/userstats.py @@ -112,15 +112,14 @@ class UserStatsPlugins(Plugin, BasePlugin): } # html = await self.template_service.render_async( - # "genshin/stats", "stats.html", data + # "genshin/stats/stats.html", data # ) # logger.debug(html) await self.cache_images(user_info) return await self.template_service.render( - "genshin/stats", - "stats.html", + "genshin/stats/stats.html", data, {"width": 650, "height": 800}, full_page=True, diff --git a/plugins/genshin/weapon.py b/plugins/genshin/weapon.py index 7a846bd..7d8489d 100644 --- a/plugins/genshin/weapon.py +++ b/plugins/genshin/weapon.py @@ -111,7 +111,7 @@ class WeaponPlugin(Plugin, BasePlugin): template_data = await input_template_data(weapon_data) png_data = await self.template_service.render( - "genshin/weapon", "weapon.html", template_data, {"width": 540, "height": 540} + "genshin/weapon/weapon.html", template_data, {"width": 540, "height": 540} ) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_photo( diff --git a/poetry.lock b/poetry.lock index 4042c65..98fbe28 100644 --- a/poetry.lock +++ b/poetry.lock @@ -329,6 +329,24 @@ sortedcontainers = ">=2.4.0,<3.0.0" aioredis = ["aioredis (>=2.0.1,<3.0.0)"] lua = ["lupa (>=1.13,<2.0)"] +[[package]] +name = "fastapi" +version = "0.85.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.20.4" + +[package.extras] +all = ["email-validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"] +dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.7.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-orjson (==3.6.2)", "types-ujson (==5.4.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + [[package]] name = "flaky" version = "3.7.0" @@ -407,6 +425,17 @@ sniffio = ">=1.0.0,<2.0.0" http2 = ["h2 (>=3,<5)"] socks = ["socksio (>=1.0.0,<2.0.0)"] +[[package]] +name = "httptools" +version = "0.5.0" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + [[package]] name = "httpx" version = "0.23.0" @@ -815,6 +844,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" "backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} tzdata = {version = "*", markers = "python_version >= \"3.6\""} +[[package]] +name = "PyYAML" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "redis" version = "4.3.4" @@ -963,6 +1000,21 @@ pydantic = ">=1.8.2,<2.0.0" SQLAlchemy = ">=1.4.17,<=1.4.41" sqlalchemy2-stubs = "*" +[[package]] +name = "starlette" +version = "0.20.4" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + [[package]] name = "TgCrypto" version = "1.2.4" @@ -1058,6 +1110,52 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uvicorn" +version = "0.18.3" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.4.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] + +[[package]] +name = "uvloop" +version = "0.17.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "watchfiles" +version = "0.17.0" +description = "Simple, modern and high performance file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0.0,<4" + [[package]] name = "websockets" version = "10.1" @@ -1106,7 +1204,7 @@ test = ["pytest", "pytest-asyncio", "flaky"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "5108671687c709ce2ec1deae867f16271447adfc995ae6d043093e3f4d8c64fd" +content-hash = "7ba810263a9ddf763ea7b44030edf84a31f6137a8201835a695c76afe20d1b25" [metadata.files] aiofiles = [ @@ -1335,6 +1433,10 @@ fakeredis = [ {file = "fakeredis-1.9.3-py3-none-any.whl", hash = "sha256:74a2f1e5e8781014418fe734b156808d5d1a2d15edec982fada3d6e7603f8536"}, {file = "fakeredis-1.9.3.tar.gz", hash = "sha256:ea7e4ed076def2eea36188662586a9f2271946ae56ebc2de6a998c82b33df776"}, ] +fastapi = [ + {file = "fastapi-0.85.0-py3-none-any.whl", hash = "sha256:1803d962f169dc9f8dde54a64b22eb16f6d81573f54401971f90f0a67234a8b4"}, + {file = "fastapi-0.85.0.tar.gz", hash = "sha256:bb219cfafd0d2ccf8f32310c9a257a06b0210bd8e2a03706a6f5a9f9f1416878"}, +] flaky = [ {file = "flaky-3.7.0-py2.py3-none-any.whl", hash = "sha256:d6eda73cab5ae7364504b7c44670f70abed9e75f77dd116352f662817592ec9c"}, {file = "flaky-3.7.0.tar.gz", hash = "sha256:3ad100780721a1911f57a165809b7ea265a7863305acb66708220820caf8aa0d"}, @@ -1465,6 +1567,49 @@ httpcore = [ {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, ] +httptools = [ + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, + {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, + {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, + {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, + {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, + {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, + {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, + {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, + {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, + {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, +] httpx = [ {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, @@ -1885,6 +2030,48 @@ pytz-deprecation-shim = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] +PyYAML = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] redis = [ {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, @@ -1968,6 +2155,10 @@ sqlmodel = [ {file = "sqlmodel-0.0.8-py3-none-any.whl", hash = "sha256:0fd805719e0c5d4f22be32eb3ffc856eca3f7f20e8c7aa3e117ad91684b518ee"}, {file = "sqlmodel-0.0.8.tar.gz", hash = "sha256:3371b4d1ad59d2ffd0c530582c2140b6c06b090b32af9b9c6412986d7b117036"}, ] +starlette = [ + {file = "starlette-0.20.4-py3-none-any.whl", hash = "sha256:c0414d5a56297d37f3db96a84034d61ce29889b9eaccf65eb98a0b39441fcaa3"}, + {file = "starlette-0.20.4.tar.gz", hash = "sha256:42fcf3122f998fefce3e2c5ad7e5edbf0f02cf685d646a83a08d404726af5084"}, +] TgCrypto = [ {file = "TgCrypto-1.2.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b51c00f85d0ef762e1b5fa11e7d26ad84fa3dcd4d92e7a3e3e2f104ac5cbd59d"}, {file = "TgCrypto-1.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e8e7d5aeaff1a8b1694d0647a60ba539b9e3b5360b115d7df4f526b85a04f3f0"}, @@ -2117,6 +2308,62 @@ urllib3 = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] +uvicorn = [ + {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, + {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, +] +uvloop = [ + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, +] +watchfiles = [ + {file = "watchfiles-0.17.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:c7e1ffbd03cbcb46d1b7833e10e7d6b678ab083b4e4b80db06cfff5baca3c93f"}, + {file = "watchfiles-0.17.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:539bcdb55a487126776c9d8c011094214d1df3f9a2321a6c0b1583197309405a"}, + {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:00e5f307a58752ec1478eeb738863544bde21cc7a2728bd1c216060406bde9c1"}, + {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:92675f379a9d5adbc6a52179f3e39aa56944c6eecb80384608fff2ed2619103a"}, + {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dd1e3181ad5d83ca35e9147c72e24f39437fcdf570c9cdc532016399fb62957"}, + {file = "watchfiles-0.17.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:204950f1d6083539af5c8b7d4f5f8039c3ce36fa692da12d9743448f3199cb15"}, + {file = "watchfiles-0.17.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:4056398d8f6d4972fe0918707b59d4cb84470c91d3c37f0e11e5a66c2a598760"}, + {file = "watchfiles-0.17.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ffff3418dc753a2aed2d00200a4daeaac295c40458f8012836a65555f288be8b"}, + {file = "watchfiles-0.17.0-cp37-abi3-win32.whl", hash = "sha256:b5c334cd3bc88aa4a8a1e08ec9f702b63c947211275defdc2dd79dc037fcb500"}, + {file = "watchfiles-0.17.0-cp37-abi3-win_amd64.whl", hash = "sha256:53a2faeb121bc51bb6b960984f46901227e2e2475acc5a8d4c905a600436752d"}, + {file = "watchfiles-0.17.0-cp37-abi3-win_arm64.whl", hash = "sha256:58dc3140dcf02a8aa76464a77a093016f10e89306fec21a4814922a64f3e8b9f"}, + {file = "watchfiles-0.17.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:adcf15ecc2182ea9d2358c1a8c2b53203c3909484918776929b7bbe205522c0e"}, + {file = "watchfiles-0.17.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:afd35a1bd3b9e68efe384ae7538481ae725597feb66f56f4bd23ecdbda726da0"}, + {file = "watchfiles-0.17.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2bdcae4c0f07ca6c090f5a2c30188cc6edba011b45e7c96eb1896648092367"}, + {file = "watchfiles-0.17.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:a53cb6c06e5c1f216c792fbb432ce315239d432cb8b68d508547100939ec0399"}, + {file = "watchfiles-0.17.0-pp39-pypy39_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a3d6c699f3ce238dfa90bcef501f331a69b0d9b076f14459ed8eab26ba2f4cf"}, + {file = "watchfiles-0.17.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f4271af86569bdbf131dd5c7c121c45d0ed194f3c88b88326e48a3b6a2db12"}, + {file = "watchfiles-0.17.0.tar.gz", hash = "sha256:ae7c57ef920589a40270d5ef3216d693f4e6f8864d8fc8b6cb7885ca98ad2a61"}, +] websockets = [ {file = "websockets-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc"}, {file = "websockets-10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60"}, diff --git a/pyproject.toml b/pyproject.toml index 5dbc384..f9bfe5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,8 @@ pytest-asyncio = { version = "^0.19.0", optional = true } flaky = { version = "^3.7.0", optional = true } lxml = "^4.9.1" arko-wrapper = "^0.2.3" +fastapi = "^0.85.0" +uvicorn = {extras = ["standard"], version = "^0.18.3"} [tool.poetry.extras] pyro = ["Pyrogram", "TgCrypto"] diff --git a/resources/genshin/ledger/ledger.html b/resources/genshin/ledger/ledger.html index 927d174..ae2147a 100644 --- a/resources/genshin/ledger/ledger.html +++ b/resources/genshin/ledger/ledger.html @@ -58,47 +58,48 @@ - + statistic: { + title: { + offsetY: -18, + content: "总计", + }, + content: { + offsetY: -10, + style: { + fontFamily: "tttgbnumber", + }, + }, + }, + legend:false, + }); + piePlot.render(); + diff --git a/resources/genshin/player_card/player_card.html b/resources/genshin/player_card/player_card.html index 37dcc68..03bb1d3 100644 --- a/resources/genshin/player_card/player_card.html +++ b/resources/genshin/player_card/player_card.html @@ -4,7 +4,7 @@ Title - +