From 4a51ec89cc86239266a249ed1f9c9ca233caabfb Mon Sep 17 00:00:00 2001 From: Tordor <3262978839@qq.com> Date: Thu, 15 Jan 2026 18:08:08 +0800 Subject: [PATCH] format . --- config.py | 5 +- handler/exception.py | 3 +- lifespan.py | 12 +++-- main.py | 3 +- mcps/__init__.py | 7 +-- mcps/test/test.py | 2 +- model/__init__.py | 4 +- model/model.py | 81 +++++++++++++++++++++++++---- pyproject.toml | 5 +- router/auth.py | 20 +++---- scheduler/__init__.py | 6 ++- scheduler/scheduler.py | 9 ++-- service/__init__.py | 10 ++-- service/wecom/modules/base.py | 5 +- service/wecom/modules/department.py | 1 - service/wecom/modules/message.py | 1 - service/wecom/modules/users.py | 6 +-- utils/wxcom/__init__.py | 9 +--- utils/wxcom/wx_com.py | 11 ++-- utils/wxcom/wx_utils.py | 10 ++-- uv.lock | 46 +++++++++++++++- 21 files changed, 184 insertions(+), 72 deletions(-) diff --git a/config.py b/config.py index dfb5e06..fd1dd7d 100644 --- a/config.py +++ b/config.py @@ -1,12 +1,11 @@ -from pydantic_settings import BaseSettings,SettingsConfigDict +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): - model_config = SettingsConfigDict(env_file=".env" , env_prefix="WNZS_") + model_config = SettingsConfigDict(env_file=".env", env_prefix="WNZS_") PGSQL: str = "" WECOM_PROXY: str = "" WECOM_CORPID: str = "" WECOM_CORPSECRET: str = "" WECOM_APP_TOKEN: str = "" WECOM_APP_ENCODING_AES_KEY: str = "" - \ No newline at end of file diff --git a/handler/exception.py b/handler/exception.py index 0f59652..19c52f9 100644 --- a/handler/exception.py +++ b/handler/exception.py @@ -1,7 +1,8 @@ import http + from fastapi import FastAPI, HTTPException, Request -from pydantic import ValidationError from fastapi.responses import JSONResponse +from pydantic import ValidationError from uvicorn.server import logger exceptions = [Exception, HTTPException, ValidationError] diff --git a/lifespan.py b/lifespan.py index f0bd450..5481daf 100644 --- a/lifespan.py +++ b/lifespan.py @@ -3,6 +3,7 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from uvicorn.server import logger + def init_database(): from model import create_db_and_tables @@ -10,12 +11,14 @@ def init_database(): create_db_and_tables() logger.info("[数据库] 数据库初始化完成 ✅") -def init_scheduler(app : FastAPI): + +def init_scheduler(app: FastAPI): from scheduler import init_scheduler_router + logger.info("[定时任务] 初始化定时任务 📦") init_scheduler_router(app) logger.info("[定时任务] 定时任务初始化完成 ✅") - + def active_config(): logger.info(f"[激活配置] 加载配置 ⚙️") @@ -29,12 +32,15 @@ def import_router(app: FastAPI): app.include_router(router) logger.info(f"[导入路由] 路由导入完成 ✅") + async def import_mcp_server(app: FastAPI): logger.info(f"[导入MCP] 开始导入MCP 🛣️") from mcps import create_mcp_app - app.mount("/app" , await create_mcp_app()) + + app.mount("/app", await create_mcp_app()) logger.info(f"[导入MCP] MCP导入完成 ✅") + @asynccontextmanager async def lifespan(app: FastAPI): logger.info(f"[生命周期] 应用启动 🚀") diff --git a/main.py b/main.py index 6083faa..e17de56 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,6 @@ from handler.exception import install as exception_install from lifespan import lifespan from mcps import create_mcp_app - app = FastAPI(lifespan=lifespan) exception_install(app) @@ -12,4 +11,4 @@ exception_install(app) if __name__ == "__main__": import uvicorn - uvicorn.run(app=app, port=8000) \ No newline at end of file + uvicorn.run(app=app, port=8000) diff --git a/mcps/__init__.py b/mcps/__init__.py index 82a4231..e22ff70 100644 --- a/mcps/__init__.py +++ b/mcps/__init__.py @@ -1,8 +1,9 @@ from fastmcp import FastMCP + from mcps.test.test import weather_mcp - + async def create_mcp_app(): main_mcp = FastMCP("MCP 主服务") - await main_mcp.import_server(weather_mcp , prefix="test") - return main_mcp.http_app() \ No newline at end of file + await main_mcp.import_server(weather_mcp, prefix="test") + return main_mcp.http_app() diff --git a/mcps/test/test.py b/mcps/test/test.py index 2cbe9b2..dac0df1 100644 --- a/mcps/test/test.py +++ b/mcps/test/test.py @@ -1,3 +1,3 @@ from fastmcp import FastMCP -weather_mcp = FastMCP(name="WeatherService") \ No newline at end of file +weather_mcp = FastMCP(name="WeatherService") diff --git a/model/__init__.py b/model/__init__.py index 6509439..32a5c59 100644 --- a/model/__init__.py +++ b/model/__init__.py @@ -1,6 +1,7 @@ from sqlmodel import Session, SQLModel, create_engine -from config import Settings +from config import Settings +from model.model import Department, Employee, Tenant PGSQL = Settings().PGSQL @@ -17,4 +18,3 @@ def get_engine(): def get_session(): return Session(get_engine()) - diff --git a/model/model.py b/model/model.py index 31c79e0..2f525b2 100644 --- a/model/model.py +++ b/model/model.py @@ -1,22 +1,85 @@ -from sqlmodel import SQLModel, Field, Column, JSON +from datetime import datetime + +from sqlalchemy import JSON, Column, DateTime, func +from sqlmodel import JSON, Field, SQLModel -class Department(SQLModel, table = True): - did: int = Field(default=None, primary_key=True) +class TenantTimeMixin(SQLModel): + tenant_id: int = Field(index=True, description="租户ID") + + created_at: datetime = Field( + sa_column=Column( + DateTime(timezone=True), + server_default=func.now(), + nullable=False, + ) + ) + + updated_at: datetime = Field( + sa_column=Column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + ) + + +class Department(TenantTimeMixin, SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + dname: str = Field(max_length=100) name_en: str = Field(max_length=100) - department_leader: list[int] = Field(default=[], sa_column=Column(JSON)) - parent_id: int = Field(default=0) + + department_leader: list[int] = Field(default_factory=list, sa_column=Column(JSON)) + + parent_id: int = Field(default=0, index=True) order: int = Field(default=0) -class Employee(SQLModel, table = True): - userid: int = Field(default=None, primary_key=True) + +class Employee(TenantTimeMixin, SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + ename: str = Field(max_length=100) - dept_id: int = Field(foreign_key='Department.did') - open_userid: str = Field(max_length=100) + + dept_id: int = Field(foreign_key="department.id", index=True) + + open_userid: str = Field(max_length=100, index=True) +class Tenant(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + # ========== 基础信息 ========== + name: str = Field(max_length=100, index=True, description="租户名称 / 企业名称") + # ========== 企业微信配置 ========== + wecom_corp_id: str = Field(max_length=64, index=True, description="企业微信 CorpID") + wecom_corp_secret: str = Field(max_length=128, description="企业微信应用 Secret") + wecom_agent_id: int = Field(description="企业微信应用 AgentId") + + wecom_token: str = Field(max_length=64, description="企业微信回调 Token") + + wecom_encoding_aes_key: str = Field( + max_length=64, description="企业微信回调 EncodingAESKey" + ) + + # ========== 时间字段 ========== + created_at: datetime = Field( + sa_column=Column( + DateTime(timezone=True), + server_default=func.now(), + nullable=False, + ) + ) + + updated_at: datetime = Field( + sa_column=Column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + ) diff --git a/pyproject.toml b/pyproject.toml index cda8ddd..7b91de3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,4 +25,7 @@ dependencies = [ ] [dependency-groups] -dev = [] +dev = [ + "isort>=7.0.0", + "ruff>=0.14.11", +] diff --git a/router/auth.py b/router/auth.py index 6159c7a..bfe542f 100644 --- a/router/auth.py +++ b/router/auth.py @@ -1,15 +1,14 @@ import json + from fastapi import APIRouter, HTTPException, Request from fastapi.responses import PlainTextResponse from uvicorn.server import logger -from utils.wxcom import wxcpt -from utils.wxcom import ( - decrypt_message, - get_request_params -) + +from utils.wxcom import decrypt_message, get_request_params, wxcpt router = APIRouter() + @router.get("/callback") async def verify_url(msg_signature: str, timestamp: str, nonce: str, echostr: str): """验证URL有效性""" @@ -29,20 +28,21 @@ async def verify_url(msg_signature: str, timestamp: str, nonce: str, echostr: st logger.error(f"验证过程发生错误: {str(e)}") raise HTTPException(status_code=500, detail="服务器内部错误") + @router.post("/callback") async def receive_message(request: Request): """接收并处理企业微信消息""" try: - # 获取请求参数并验证,返回请求体、消息签名、时间戳和随机数 body, msg_signature, timestamp, nonce = await get_request_params(request) # 对请求体进行解密,得到解密后的消息字典 - xml_dict : dict= decrypt_message(body, msg_signature, timestamp, nonce) - logger.info(f"解密后的消息字典: \n {json.dumps(xml_dict.get("xml") , ensure_ascii=False , indent=2)}") + xml_dict: dict = decrypt_message(body, msg_signature, timestamp, nonce) + logger.info( + f"解密后的消息字典: \n {json.dumps(xml_dict.get('xml'), ensure_ascii=False, indent=2)}" + ) # 处理消息 # subscription(xml_dict) - except Exception as e: logger.error(f"处理消息时发生错误: {str(e)}") - raise HTTPException(status_code=500, detail="服务器内部错误") \ No newline at end of file + raise HTTPException(status_code=500, detail="服务器内部错误") diff --git a/scheduler/__init__.py b/scheduler/__init__.py index aa03b39..d0cad88 100644 --- a/scheduler/__init__.py +++ b/scheduler/__init__.py @@ -1,7 +1,9 @@ from fastapi import FastAPI from fastscheduler.fastapi_integration import create_scheduler_routes + from scheduler.scheduler import scheduler -def init_scheduler_router(app : FastAPI): + +def init_scheduler_router(app: FastAPI): app.include_router(create_scheduler_routes(scheduler)) - scheduler.start() \ No newline at end of file + scheduler.start() diff --git a/scheduler/scheduler.py b/scheduler/scheduler.py index 6134f52..4539802 100644 --- a/scheduler/scheduler.py +++ b/scheduler/scheduler.py @@ -1,9 +1,12 @@ from fastscheduler import FastScheduler +from service import get_wecom scheduler = FastScheduler(quiet=True) -@scheduler.every(10).seconds -def background_task(): - print("Background work") \ No newline at end of file +@scheduler.every(4).hours +async def background_task(): + wecom = get_wecom() + + await wecom.get_departments() diff --git a/service/__init__.py b/service/__init__.py index 2499f88..93f5c45 100644 --- a/service/__init__.py +++ b/service/__init__.py @@ -1,15 +1,15 @@ -from service.wecom import Wecom from config import Settings +from service.wecom import Wecom from utils.sing import SingletonProvider + # 获取单例函数 def get_wecom_single() -> Wecom: - wecom = Wecom( - Settings().WECOM_CORPID,Settings().WECOM_CORPSECRET - ) + wecom = Wecom(Settings().WECOM_CORPID, Settings().WECOM_CORPSECRET) WECOM_PROXY = Settings().WECOM_PROXY if WECOM_PROXY and WECOM_PROXY != "": wecom.BASE_URL = WECOM_PROXY return wecom -get_wecom = SingletonProvider(get_wecom_single) \ No newline at end of file + +get_wecom = SingletonProvider(get_wecom_single) diff --git a/service/wecom/modules/base.py b/service/wecom/modules/base.py index 506e527..bb03283 100644 --- a/service/wecom/modules/base.py +++ b/service/wecom/modules/base.py @@ -1,10 +1,7 @@ from datetime import datetime, timedelta from wecom.exceptions.general import SDKException -from wecom.schemas.token import ( - AccessTokenInfo, - AccessTokenParams, -) +from wecom.schemas.token import AccessTokenInfo, AccessTokenParams from wecom.utils.requests import HttpxRequest BASE_URL: str = "https://qyapi.weixin.qq.com/cgi-bin" diff --git a/service/wecom/modules/department.py b/service/wecom/modules/department.py index a43b20c..4691e10 100644 --- a/service/wecom/modules/department.py +++ b/service/wecom/modules/department.py @@ -11,7 +11,6 @@ from wecom.utils.requests import HttpxRequest class WecomDepartmentClient(WecomBaseClient): - async def create_departments(self, data: CreateDepartmentParams) -> int: """ 创建部门 diff --git a/service/wecom/modules/message.py b/service/wecom/modules/message.py index 487dd28..670fcc6 100644 --- a/service/wecom/modules/message.py +++ b/service/wecom/modules/message.py @@ -12,7 +12,6 @@ from wecom.utils.requests import HttpxRequest class WecomMessageClient(WecomBaseClient): - async def send_message( self, data: MessageParams, diff --git a/service/wecom/modules/users.py b/service/wecom/modules/users.py index 335c292..cc0e341 100644 --- a/service/wecom/modules/users.py +++ b/service/wecom/modules/users.py @@ -1,11 +1,7 @@ from wecom.exceptions.general import SDKException from wecom.modules.base import WecomBaseClient from wecom.schemas.departments import DepartmentInfo -from wecom.schemas.users import ( - DepartmentUserDetailInfo, - DepartmentUserInfo, - UserInfo, -) +from wecom.schemas.users import DepartmentUserDetailInfo, DepartmentUserInfo, UserInfo from wecom.utils.requests import HttpxRequest diff --git a/utils/wxcom/__init__.py b/utils/wxcom/__init__.py index 666963f..8997b43 100644 --- a/utils/wxcom/__init__.py +++ b/utils/wxcom/__init__.py @@ -1,9 +1,4 @@ from .wx_com import wxcpt -from .wx_utils import get_request_params,decrypt_message,extract_message_content +from .wx_utils import decrypt_message, extract_message_content, get_request_params -__all__ = [ - "wxcpt", - "get_request_params", - "decrypt_message", - "extract_message_content" -] \ No newline at end of file +__all__ = ["wxcpt", "get_request_params", "decrypt_message", "extract_message_content"] diff --git a/utils/wxcom/wx_com.py b/utils/wxcom/wx_com.py index 2918f65..9561a78 100644 --- a/utils/wxcom/wx_com.py +++ b/utils/wxcom/wx_com.py @@ -1,6 +1,7 @@ from uvicorn.server import logger + from config import Settings -from utils.wxcom.WXBizMsgCrypt3 import WXBizMsgCrypt +from utils.wxcom.WXBizMsgCrypt3 import WXBizMsgCrypt def get_wxcpt(): @@ -15,20 +16,20 @@ def get_wxcpt(): required_configs = [ Settings().WECOM_APP_TOKEN, Settings().WECOM_APP_ENCODING_AES_KEY, - Settings().WECOM_CORPID + Settings().WECOM_CORPID, ] - if not all(required_configs): raise ValueError("企业微信配置不完整") - + return WXBizMsgCrypt( Settings().WECOM_APP_TOKEN, # 设置的Token Settings().WECOM_APP_ENCODING_AES_KEY, # 设置密钥 - Settings().WECOM_CORPID # 企业ID + Settings().WECOM_CORPID, # 企业ID ) except Exception as e: logger.error(f"初始化WXBizMsgCrypt失败: {str(e)}") raise + wxcpt = get_wxcpt() diff --git a/utils/wxcom/wx_utils.py b/utils/wxcom/wx_utils.py index 5549541..f2e8d6e 100644 --- a/utils/wxcom/wx_utils.py +++ b/utils/wxcom/wx_utils.py @@ -4,6 +4,7 @@ from typing import Dict, Tuple, Union import xmltodict from fastapi import HTTPException, Request from uvicorn.server import logger + from .wx_com import wxcpt @@ -23,7 +24,10 @@ async def get_request_params(request: Request) -> Tuple[bytes, str, str, str]: return body, msg_signature, timestamp, nonce -def decrypt_message(body: bytes, msg_signature: str, timestamp: str, nonce: str) -> dict: + +def decrypt_message( + body: bytes, msg_signature: str, timestamp: str, nonce: str +) -> dict: """解密消息""" ret, sMsg = wxcpt.DecryptMsg(body, msg_signature, timestamp, nonce) if ret != 0: @@ -35,6 +39,7 @@ def decrypt_message(body: bytes, msg_signature: str, timestamp: str, nonce: str) return xml_dict + def extract_message_content( xml_dict: Dict, ) -> Tuple[str, str, str, str, Union[Dict[str, Union[str, None]], str, None], str, str]: @@ -82,7 +87,6 @@ def extract_message_content( message_data = xml_content.get("Content") logger.info(f"收到未知类型消息: {message_data}") - return { "ToUserName": to_user_name, "FromUserName": from_user_name, @@ -91,4 +95,4 @@ def extract_message_content( "MsgId": msg_id, "AgentID": agent_id, **message_data, - } \ No newline at end of file + } diff --git a/uv.lock b/uv.lock index 9b41966..2e8aa94 100644 --- a/uv.lock +++ b/uv.lock @@ -651,6 +651,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] +[[package]] +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, +] + [[package]] name = "jaraco-classes" version = "3.4.0" @@ -1659,6 +1668,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, ] +[[package]] +name = "ruff" +version = "0.14.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/77/9a7fe084d268f8855d493e5031ea03fa0af8cc05887f638bf1c4e3363eb8/ruff-0.14.11.tar.gz", hash = "sha256:f6dc463bfa5c07a59b1ff2c3b9767373e541346ea105503b4c0369c520a66958", size = 5993417, upload-time = "2026-01-08T19:11:58.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/a6/a4c40a5aaa7e331f245d2dc1ac8ece306681f52b636b40ef87c88b9f7afd/ruff-0.14.11-py3-none-linux_armv6l.whl", hash = "sha256:f6ff2d95cbd335841a7217bdfd9c1d2e44eac2c584197ab1385579d55ff8830e", size = 12951208, upload-time = "2026-01-08T19:12:09.218Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/360a35cb7204b328b685d3129c08aca24765ff92b5a7efedbdd6c150d555/ruff-0.14.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f6eb5c1c8033680f4172ea9c8d3706c156223010b8b97b05e82c59bdc774ee6", size = 13330075, upload-time = "2026-01-08T19:12:02.549Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9e/0cc2f1be7a7d33cae541824cf3f95b4ff40d03557b575912b5b70273c9ec/ruff-0.14.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2fc34cc896f90080fca01259f96c566f74069a04b25b6205d55379d12a6855e", size = 12257809, upload-time = "2026-01-08T19:12:00.366Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e5/5faab97c15bb75228d9f74637e775d26ac703cc2b4898564c01ab3637c02/ruff-0.14.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53386375001773ae812b43205d6064dae49ff0968774e6befe16a994fc233caa", size = 12678447, upload-time = "2026-01-08T19:12:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/e9767f60a2bef779fb5855cab0af76c488e0ce90f7bb7b8a45c8a2ba4178/ruff-0.14.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a697737dce1ca97a0a55b5ff0434ee7205943d4874d638fe3ae66166ff46edbe", size = 12758560, upload-time = "2026-01-08T19:11:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/eb/84/4c6cf627a21462bb5102f7be2a320b084228ff26e105510cd2255ea868e5/ruff-0.14.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6845ca1da8ab81ab1dce755a32ad13f1db72e7fba27c486d5d90d65e04d17b8f", size = 13599296, upload-time = "2026-01-08T19:11:30.371Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/92b5ed7ea66d849f6157e695dc23d5d6d982bd6aa8d077895652c38a7cae/ruff-0.14.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e36ce2fd31b54065ec6f76cb08d60159e1b32bdf08507862e32f47e6dde8bcbf", size = 15048981, upload-time = "2026-01-08T19:12:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/61/df/c1bd30992615ac17c2fb64b8a7376ca22c04a70555b5d05b8f717163cf9f/ruff-0.14.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590bcc0e2097ecf74e62a5c10a6b71f008ad82eb97b0a0079e85defe19fe74d9", size = 14633183, upload-time = "2026-01-08T19:11:40.069Z" }, + { url = "https://files.pythonhosted.org/packages/04/e9/fe552902f25013dd28a5428a42347d9ad20c4b534834a325a28305747d64/ruff-0.14.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53fe71125fc158210d57fe4da26e622c9c294022988d08d9347ec1cf782adafe", size = 14050453, upload-time = "2026-01-08T19:11:37.555Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/f36d89fa021543187f98991609ce6e47e24f35f008dfe1af01379d248a41/ruff-0.14.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35c9da08562f1598ded8470fcfef2afb5cf881996e6c0a502ceb61f4bc9c8a3", size = 13757889, upload-time = "2026-01-08T19:12:07.094Z" }, + { url = "https://files.pythonhosted.org/packages/b7/9f/c7fb6ecf554f28709a6a1f2a7f74750d400979e8cd47ed29feeaa1bd4db8/ruff-0.14.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0f3727189a52179393ecf92ec7057c2210203e6af2676f08d92140d3e1ee72c1", size = 13955832, upload-time = "2026-01-08T19:11:55.064Z" }, + { url = "https://files.pythonhosted.org/packages/db/a0/153315310f250f76900a98278cf878c64dfb6d044e184491dd3289796734/ruff-0.14.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eb09f849bd37147a789b85995ff734a6c4a095bed5fd1608c4f56afc3634cde2", size = 12586522, upload-time = "2026-01-08T19:11:35.356Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2b/a73a2b6e6d2df1d74bf2b78098be1572191e54bec0e59e29382d13c3adc5/ruff-0.14.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c61782543c1231bf71041461c1f28c64b961d457d0f238ac388e2ab173d7ecb7", size = 12724637, upload-time = "2026-01-08T19:11:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/f0/41/09100590320394401cd3c48fc718a8ba71c7ddb1ffd07e0ad6576b3a3df2/ruff-0.14.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82ff352ea68fb6766140381748e1f67f83c39860b6446966cff48a315c3e2491", size = 13145837, upload-time = "2026-01-08T19:11:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d8/e035db859d1d3edf909381eb8ff3e89a672d6572e9454093538fe6f164b0/ruff-0.14.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:728e56879df4ca5b62a9dde2dd0eb0edda2a55160c0ea28c4025f18c03f86984", size = 13850469, upload-time = "2026-01-08T19:12:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/4e/02/bb3ff8b6e6d02ce9e3740f4c17dfbbfb55f34c789c139e9cd91985f356c7/ruff-0.14.11-py3-none-win32.whl", hash = "sha256:337c5dd11f16ee52ae217757d9b82a26400be7efac883e9e852646f1557ed841", size = 12851094, upload-time = "2026-01-08T19:11:45.163Z" }, + { url = "https://files.pythonhosted.org/packages/58/f1/90ddc533918d3a2ad628bc3044cdfc094949e6d4b929220c3f0eb8a1c998/ruff-0.14.11-py3-none-win_amd64.whl", hash = "sha256:f981cea63d08456b2c070e64b79cb62f951aa1305282974d4d5216e6e0178ae6", size = 14001379, upload-time = "2026-01-08T19:11:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/1dbe51782c0e1e9cfce1d1004752672d2d4629ea46945d19d731ad772b3b/ruff-0.14.11-py3-none-win_arm64.whl", hash = "sha256:649fb6c9edd7f751db276ef42df1f3df41c38d67d199570ae2a7bd6cbc3590f0", size = 12938644, upload-time = "2026-01-08T19:11:50.027Z" }, +] + [[package]] name = "secretstorage" version = "3.5.0" @@ -1981,6 +2016,12 @@ dependencies = [ { name = "xmltodict" }, ] +[package.dev-dependencies] +dev = [ + { name = "isort" }, + { name = "ruff" }, +] + [package.metadata] requires-dist = [ { name = "apscheduler", specifier = ">=3.11.0" }, @@ -2003,7 +2044,10 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [] +dev = [ + { name = "isort", specifier = ">=7.0.0" }, + { name = "ruff", specifier = ">=0.14.11" }, +] [[package]] name = "wrapt"