From 507a2522cde71ab0ba8cd161ba05657613666ef9 Mon Sep 17 00:00:00 2001 From: JiaoTianBo Date: Tue, 24 Mar 2026 11:29:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E4=B8=BA=E7=AE=80=E5=8E=86=E6=99=BA=E8=83=BD=E4=BD=93?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=9F=BA=E7=A1=80=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重命名项目及包结构为ylhp-hr-2-0,支持多平台简历爬取与AI分析 - 移除旧的main.py,新增统一主应用入口及初始化流程 - 实现配置模块,支持数据库、LLM、通知和爬虫多种配置项及环境变量加载 - 构建领域模型,包括候选人、简历、职位、评价等实体与枚举定义 - 设计评价方案服务,提供默认评价模板及方案管理接口 - 开发分析服务,整合LLM客户端实现基于AI的简历分析功能 - 实现多种通知渠道支持,包括企业微信、钉钉、邮件 - 引入爬虫工厂及Boss爬虫模块支持候选人数据抓取 - 统一入库服务,完成数据归一化、验证及去重功能 - 添加异步任务协调流程,支持爬取后自动分析及通知 - 配置项目依赖管理,支持选装LLM和开发工具插件 - 初步搭建代码目录结构,划分配置、领域、服务、映射、控制器层等模块 --- main.py | 23 - pyproject.toml | 29 +- src/main/python/cn/__init__.py | 1 + src/main/python/cn/yinlihupo/__init__.py | 1 + .../cn/yinlihupo/ylhp_hr_2.0/__init__.py | 3 + .../yinlihupo/ylhp_hr_2.0/common/__init__.py | 1 + .../yinlihupo/ylhp_hr_2.0/config/__init__.py | 5 + .../yinlihupo/ylhp_hr_2.0/config/settings.py | 95 ++ .../ylhp_hr_2.0/controller/__init__.py | 1 + .../yinlihupo/ylhp_hr_2.0/domain/__init__.py | 24 + .../yinlihupo/ylhp_hr_2.0/domain/candidate.py | 113 +++ .../cn/yinlihupo/ylhp_hr_2.0/domain/enums.py | 43 + .../ylhp_hr_2.0/domain/evaluation.py | 131 +++ .../cn/yinlihupo/ylhp_hr_2.0/domain/job.py | 61 ++ .../cn/yinlihupo/ylhp_hr_2.0/domain/resume.py | 69 ++ .../python/cn/yinlihupo/ylhp_hr_2.0/main.py | 293 ++++++ .../yinlihupo/ylhp_hr_2.0/mapper/__init__.py | 1 + .../yinlihupo/ylhp_hr_2.0/service/__init__.py | 1 + .../ylhp_hr_2.0/service/analysis/__init__.py | 16 + .../service/analysis/evaluation_schema.py | 282 ++++++ .../service/analysis/llm_client.py | 170 ++++ .../service/analysis/prompt_builder.py | 221 +++++ .../service/analysis/resume_analyzer.py | 251 +++++ .../service/analysis/scoring_engine.py | 167 ++++ .../ylhp_hr_2.0/service/crawler/__init__.py | 7 + .../service/crawler/base_crawler.py | 95 ++ .../service/crawler/boss_crawler.py | 207 ++++ .../service/crawler/crawler_factory.py | 113 +++ .../ylhp_hr_2.0/service/ingestion/__init__.py | 17 + .../service/ingestion/data_normalizer.py | 272 +++++ .../service/ingestion/data_validator.py | 157 +++ .../ingestion/deduplication_service.py | 266 +++++ .../ingestion/unified_ingestion_service.py | 234 +++++ .../service/notification/__init__.py | 21 + .../service/notification/channels/__init__.py | 15 + .../notification/channels/base_channel.py | 89 ++ .../notification/channels/dingtalk_channel.py | 183 ++++ .../notification/channels/email_channel.py | 226 +++++ .../channels/wechat_work_channel.py | 167 ++++ .../service/notification/message_template.py | 245 +++++ .../notification/notification_service.py | 211 ++++ uv.lock | 935 +++++++++++++++++- 42 files changed, 5424 insertions(+), 38 deletions(-) delete mode 100644 main.py create mode 100644 src/main/python/cn/__init__.py create mode 100644 src/main/python/cn/yinlihupo/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/common/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/settings.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/controller/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/candidate.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/enums.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/evaluation.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/job.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/resume.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/main.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/mapper/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/evaluation_schema.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/llm_client.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/prompt_builder.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/resume_analyzer.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/scoring_engine.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/base_crawler.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/boss_crawler.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/crawler_factory.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_normalizer.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_validator.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/deduplication_service.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/unified_ingestion_service.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/__init__.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/base_channel.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/dingtalk_channel.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/email_channel.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/wechat_work_channel.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/message_template.py create mode 100644 src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/notification_service.py diff --git a/main.py b/main.py deleted file mode 100644 index f010397..0000000 --- a/main.py +++ /dev/null @@ -1,23 +0,0 @@ -from boss import Boss - -wt_token = "Dfely9R4Oa1u3LP8pR1m7rYTLld0Vp4XEJlmLe4e5KSEbb36J17dHYjS72TjKLLz39Y9a7Of7MGYljpTzYNQ5Kw~~" - -client = Boss(wt=wt_token) - -# 1. 获取职位列表 -jobs = client.get_jobs() -first_job = jobs[0] - -# 2. 获取该职位下的推荐候选人 -geeks = client.geek_info(jobid=first_job.encryptJobId, page=1) -first_geek = geeks[0] - -# 3. 获取候选人详情 -detail = client.get_detail(first_geek) - -# 4. 解密简历正文 -resume_text = client.get_detail_text(detail) -print(resume_text) - -if __name__ == "__main__": - print(jobs) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b6fe9ed..1968fcc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,34 @@ [project] -name = "boss-hr-2-0" +name = "ylhp-hr-2-0" version = "0.1.0" -description = "Add your description here" +description = "简历智能体系统 - 多平台简历爬取、AI分析、多渠道通知" readme = "README.md" -requires-python = ">=3.14" +requires-python = ">=3.12" dependencies = [ - "ylhp-boss-hr>=1.37" + "ylhp-boss-hr>=1.37", + "pydantic>=2.0", + "pydantic-settings>=2.0", + "aiohttp>=3.8", +] + +[project.optional-dependencies] +llm = [ + "openai>=1.0", + "anthropic>=0.20", +] +dev = [ + "pytest>=7.0", + "pytest-asyncio>=0.21", + "black>=23.0", + "ruff>=0.1", ] [tool.setuptools.packages.find] -where = ["."] -include = ["boss*"] +where = ["src/main/python"] +include = ["cn*"] [tool.setuptools.package-dir] -"" = "." +"" = "src/main/python" [[tool.uv.index]] url = "http://mirrors.aliyun.com/pypi/simple" diff --git a/src/main/python/cn/__init__.py b/src/main/python/cn/__init__.py new file mode 100644 index 0000000..27b02ab --- /dev/null +++ b/src/main/python/cn/__init__.py @@ -0,0 +1 @@ +"""cn package""" diff --git a/src/main/python/cn/yinlihupo/__init__.py b/src/main/python/cn/yinlihupo/__init__.py new file mode 100644 index 0000000..9a4a48e --- /dev/null +++ b/src/main/python/cn/yinlihupo/__init__.py @@ -0,0 +1 @@ +"""yinlihupo package""" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/__init__.py new file mode 100644 index 0000000..3b43b15 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/__init__.py @@ -0,0 +1,3 @@ +"""ylhp_hr_2.0 - Resume Intelligence Agent""" + +__version__ = "0.1.0" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/common/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/common/__init__.py new file mode 100644 index 0000000..d85bd72 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/common/__init__.py @@ -0,0 +1 @@ +"""Common utilities""" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/__init__.py new file mode 100644 index 0000000..50ee236 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/__init__.py @@ -0,0 +1,5 @@ +"""Configuration module""" + +from .settings import Settings, get_settings + +__all__ = ["Settings", "get_settings"] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/settings.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/settings.py new file mode 100644 index 0000000..78da9ec --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/settings.py @@ -0,0 +1,95 @@ +"""Application settings""" +from typing import Optional +from pydantic_settings import BaseSettings +from pydantic import Field + + +class DatabaseSettings(BaseSettings): + """数据库配置""" + url: str = Field(default="sqlite:///./hr_agent.db", description="数据库连接URL") + echo: bool = Field(default=False, description="是否打印SQL语句") + + class Config: + env_prefix = "DB_" + + +class LLMSettings(BaseSettings): + """LLM 配置""" + provider: str = Field(default="openai", description="LLM提供商: openai, claude, mock") + api_key: Optional[str] = Field(default=None, description="API密钥") + base_url: Optional[str] = Field(default=None, description="自定义API地址") + model: str = Field(default="gpt-4", description="模型名称") + temperature: float = Field(default=0.7, description="温度参数") + max_tokens: int = Field(default=2000, description="最大token数") + + class Config: + env_prefix = "LLM_" + + +class NotificationSettings(BaseSettings): + """通知配置""" + # 企业微信 + wechat_work_webhook: Optional[str] = Field(default=None, description="企业微信Webhook") + wechat_work_mentioned: Optional[str] = Field(default=None, description="@提醒列表,逗号分隔") + + # 钉钉 + dingtalk_webhook: Optional[str] = Field(default=None, description="钉钉Webhook") + dingtalk_secret: Optional[str] = Field(default=None, description="钉钉加签密钥") + dingtalk_at_mobiles: Optional[str] = Field(default=None, description="@手机号列表,逗号分隔") + + # 邮件 + email_smtp_host: Optional[str] = Field(default=None, description="SMTP服务器") + email_smtp_port: int = Field(default=587, description="SMTP端口") + email_username: Optional[str] = Field(default=None, description="邮箱用户名") + email_password: Optional[str] = Field(default=None, description="邮箱密码") + email_from: Optional[str] = Field(default=None, description="发件人地址") + email_to: Optional[str] = Field(default=None, description="收件人地址,逗号分隔") + + class Config: + env_prefix = "NOTIFY_" + + +class CrawlerSettings(BaseSettings): + """爬虫配置""" + boss_wt_token: Optional[str] = Field(default=None, description="Boss直聘WT Token") + + class Config: + env_prefix = "CRAWLER_" + + +class Settings(BaseSettings): + """应用配置""" + + # 应用信息 + app_name: str = Field(default="ylhp_hr_2.0", description="应用名称") + app_version: str = Field(default="0.1.0", description="应用版本") + debug: bool = Field(default=False, description="调试模式") + + # 子配置 + database: DatabaseSettings = Field(default_factory=DatabaseSettings) + llm: LLMSettings = Field(default_factory=LLMSettings) + notification: NotificationSettings = Field(default_factory=NotificationSettings) + crawler: CrawlerSettings = Field(default_factory=CrawlerSettings) + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + + +# 全局配置实例 +_settings: Optional[Settings] = None + + +def get_settings() -> Settings: + """获取配置实例(单例)""" + global _settings + if _settings is None: + _settings = Settings() + return _settings + + +def reload_settings() -> Settings: + """重新加载配置""" + global _settings + _settings = Settings() + return _settings diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/controller/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/controller/__init__.py new file mode 100644 index 0000000..b6834b7 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/controller/__init__.py @@ -0,0 +1 @@ +"""Controller layer - API endpoints""" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/__init__.py new file mode 100644 index 0000000..7c5b4e1 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/__init__.py @@ -0,0 +1,24 @@ +"""Domain layer - Entity definitions""" + +from .candidate import Candidate, CandidateSource, CandidateStatus +from .resume import Resume, ResumeParsed +from .job import Job, JobStatus +from .evaluation import Evaluation, EvaluationSchema, Dimension, DimensionScore +from .enums import Gender, Education, Recommendation + +__all__ = [ + "Candidate", + "CandidateSource", + "CandidateStatus", + "Resume", + "ResumeParsed", + "Job", + "JobStatus", + "Evaluation", + "EvaluationSchema", + "Dimension", + "DimensionScore", + "Gender", + "Education", + "Recommendation", +] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/candidate.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/candidate.py new file mode 100644 index 0000000..6b76bc2 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/candidate.py @@ -0,0 +1,113 @@ +"""Candidate entity definitions""" +from dataclasses import dataclass, field +from datetime import datetime +from decimal import Decimal +from typing import Optional, List +from enum import Enum + +from .enums import Gender + + +class CandidateSource(Enum): + """候选人来源渠道""" + BOSS = "boss" + LIEPIN = "liepin" + ZHILIAN = "zhilian" + OTHER = "other" + + +class CandidateStatus(Enum): + """候选人状态""" + NEW = "new" # 新入库 + ANALYZED = "analyzed" # 已分析 + PUSHED = "pushed" # 已推送 + CONTACTED = "contacted" # 已联系 + INTERVIEWED = "interviewed" # 已面试 + HIRED = "hired" # 已录用 + REJECTED = "rejected" # 已拒绝 + + +@dataclass +class SalaryRange: + """薪资范围""" + min_salary: Optional[int] = None + max_salary: Optional[int] = None + + def __str__(self) -> str: + if self.min_salary and self.max_salary: + return f"{self.min_salary}-{self.max_salary}K" + elif self.min_salary: + return f"{self.min_salary}K+" + elif self.max_salary: + return f"0-{self.max_salary}K" + return "面议" + + +@dataclass +class Candidate: + """候选人实体""" + # 主键信息 + id: Optional[str] = None + source: CandidateSource = CandidateSource.BOSS + source_id: str = "" + + # 基本信息 + name: str = "" + phone: Optional[str] = None + email: Optional[str] = None + wechat: Optional[str] = None + gender: Gender = Gender.UNKNOWN + age: Optional[int] = None + location: Optional[str] = None + + # 职业信息 + current_company: Optional[str] = None + current_position: Optional[str] = None + work_years: Optional[Decimal] = None + education: Optional[str] = None + school: Optional[str] = None + salary_expectation: Optional[SalaryRange] = None + + # 状态管理 + status: CandidateStatus = CandidateStatus.NEW + + # 元数据 + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + def __post_init__(self): + if self.created_at is None: + self.created_at = datetime.now() + if self.updated_at is None: + self.updated_at = datetime.now() + + +@dataclass +class WorkExperience: + """工作经历""" + company: str = "" + position: str = "" + start_date: Optional[str] = None + end_date: Optional[str] = None + description: Optional[str] = None + is_current: bool = False + + +@dataclass +class ProjectExperience: + """项目经历""" + name: str = "" + role: Optional[str] = None + start_date: Optional[str] = None + end_date: Optional[str] = None + description: Optional[str] = None + + +@dataclass +class EducationExperience: + """教育经历""" + school: str = "" + major: Optional[str] = None + degree: Optional[str] = None + start_date: Optional[str] = None + end_date: Optional[str] = None diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/enums.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/enums.py new file mode 100644 index 0000000..48809b8 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/enums.py @@ -0,0 +1,43 @@ +"""Enum definitions for domain models""" +from enum import Enum, auto + + +class Gender(Enum): + """性别枚举""" + UNKNOWN = 0 + MALE = 1 + FEMALE = 2 + + +class Education(Enum): + """学历枚举""" + UNKNOWN = "unknown" + HIGH_SCHOOL = "high_school" + ASSOCIATE = "associate" + BACHELOR = "bachelor" + MASTER = "master" + PHD = "phd" + POSTDOC = "postdoc" + + +class Recommendation(Enum): + """推荐意见枚举""" + STRONG_RECOMMEND = "strong_recommend" + RECOMMEND = "recommend" + CONSIDER = "consider" + NOT_RECOMMEND = "not_recommend" + + +class ChannelType(Enum): + """通知渠道类型""" + WECHAT_WORK = "wechat_work" + DINGTALK = "dingtalk" + EMAIL = "email" + WEBHOOK = "webhook" + + +class NotificationStatus(Enum): + """通知状态""" + PENDING = "pending" + SENT = "sent" + FAILED = "failed" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/evaluation.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/evaluation.py new file mode 100644 index 0000000..1ad2395 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/evaluation.py @@ -0,0 +1,131 @@ +"""Evaluation entity definitions""" +from dataclasses import dataclass, field +from datetime import datetime +from typing import Optional, List, Dict, Tuple, Any + +from .enums import Recommendation + + +@dataclass +class Dimension: + """评价维度定义""" + id: str = "" + name: str = "" # 维度名称,如"技术能力" + description: str = "" # 维度描述 + criteria: List[str] = field(default_factory=list) # 评价标准 + score_range: Tuple[int, int] = (0, 100) # 分数范围 + + +@dataclass +class DimensionScore: + """维度评分结果""" + dimension_id: str = "" + dimension_name: str = "" + score: float = 0.0 # 分数 + weight: float = 1.0 # 权重 + comment: Optional[str] = None # 评价说明 + + +@dataclass +class EvaluationSchema: + """评价方案 - 可配置的多维度评价模板""" + id: Optional[str] = None + name: str = "" # 方案名称,如"Java后端评价方案" + description: Optional[str] = None + + # 评价维度配置 + dimensions: List[Dimension] = field(default_factory=list) + + # 维度权重 + weights: Dict[str, float] = field(default_factory=dict) + + # AI提示词模板 + prompt_template: Optional[str] = None + + # 是否为默认方案 + is_default: bool = False + + # 元数据 + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + def __post_init__(self): + if self.created_at is None: + self.created_at = datetime.now() + if self.updated_at is None: + self.updated_at = datetime.now() + + def get_weight(self, dimension_id: str) -> float: + """获取维度权重""" + return self.weights.get(dimension_id, 1.0) + + def calculate_overall_score(self, dimension_scores: List[DimensionScore]) -> float: + """计算综合评分""" + if not dimension_scores: + return 0.0 + + weighted_sum = 0.0 + total_weight = 0.0 + + for ds in dimension_scores: + weight = self.get_weight(ds.dimension_id) + weighted_sum += ds.score * weight + total_weight += weight + + return weighted_sum / total_weight if total_weight > 0 else 0.0 + + +@dataclass +class Evaluation: + """评价记录实体""" + id: Optional[str] = None + candidate_id: Optional[str] = None + schema_id: Optional[str] = None + job_id: Optional[str] = None + + # 评分结果 + overall_score: float = 0.0 # 综合评分 + dimension_scores: List[DimensionScore] = field(default_factory=list) + + # AI分析结果 + tags: List[str] = field(default_factory=list) # AI标签 + summary: Optional[str] = None # 评价摘要 + strengths: List[str] = field(default_factory=list) # 优势 + weaknesses: List[str] = field(default_factory=list) # 不足 + recommendation: Optional[Recommendation] = None # 推荐意见 + + # 原始响应 + raw_response: Optional[str] = None # LLM原始响应 + + # 元数据 + created_at: Optional[datetime] = None + + def __post_init__(self): + if self.created_at is None: + self.created_at = datetime.now() + + def to_dict(self) -> Dict[str, Any]: + """转换为字典""" + return { + "id": self.id, + "candidate_id": self.candidate_id, + "schema_id": self.schema_id, + "job_id": self.job_id, + "overall_score": self.overall_score, + "dimension_scores": [ + { + "dimension_id": ds.dimension_id, + "dimension_name": ds.dimension_name, + "score": ds.score, + "weight": ds.weight, + "comment": ds.comment + } + for ds in self.dimension_scores + ], + "tags": self.tags, + "summary": self.summary, + "strengths": self.strengths, + "weaknesses": self.weaknesses, + "recommendation": self.recommendation.value if self.recommendation else None, + "created_at": self.created_at.isoformat() if self.created_at else None + } diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/job.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/job.py new file mode 100644 index 0000000..9be8db7 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/job.py @@ -0,0 +1,61 @@ +"""Job entity definitions""" +from dataclasses import dataclass +from datetime import datetime +from typing import Optional, List +from enum import Enum + +from .candidate import CandidateSource + + +class JobStatus(Enum): + """职位状态""" + ACTIVE = "active" + PAUSED = "paused" + CLOSED = "closed" + ARCHIVED = "archived" + + +@dataclass +class JobRequirement: + """职位要求""" + min_work_years: Optional[int] = None + max_work_years: Optional[int] = None + education: Optional[str] = None + skills: Optional[List[str]] = None + description: Optional[str] = None + + +@dataclass +class Job: + """职位实体""" + id: Optional[str] = None + source: CandidateSource = CandidateSource.BOSS + source_id: str = "" + + # 职位信息 + title: str = "" + department: Optional[str] = None + location: Optional[str] = None + + # 薪资范围 + salary_min: Optional[int] = None + salary_max: Optional[int] = None + + # 职位要求 + requirements: Optional[JobRequirement] = None + description: Optional[str] = None + + # 状态 + status: JobStatus = JobStatus.ACTIVE + + # 元数据 + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + def __post_init__(self): + if self.created_at is None: + self.created_at = datetime.now() + if self.updated_at is None: + self.updated_at = datetime.now() + if self.requirements is None: + self.requirements = JobRequirement() diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/resume.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/resume.py new file mode 100644 index 0000000..1b58b54 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/resume.py @@ -0,0 +1,69 @@ +"""Resume entity definitions""" +from dataclasses import dataclass, field +from datetime import datetime +from typing import Optional, List, Dict, Any + +from .candidate import WorkExperience, ProjectExperience, EducationExperience + + +@dataclass +class ResumeParsed: + """结构化解析后的简历内容""" + # 基本信息 + name: Optional[str] = None + phone: Optional[str] = None + email: Optional[str] = None + gender: Optional[str] = None + age: Optional[int] = None + location: Optional[str] = None + + # 职业信息 + current_company: Optional[str] = None + current_position: Optional[str] = None + work_years: Optional[float] = None + education: Optional[str] = None + school: Optional[str] = None + + # 详细经历 + work_experiences: List[WorkExperience] = field(default_factory=list) + project_experiences: List[ProjectExperience] = field(default_factory=list) + education_experiences: List[EducationExperience] = field(default_factory=list) + + # 技能标签 + skills: List[str] = field(default_factory=list) + + # 自我评价 + self_evaluation: Optional[str] = None + + # 原始解析数据 + raw_data: Optional[Dict[str, Any]] = None + + +@dataclass +class Resume: + """简历实体""" + id: Optional[str] = None + candidate_id: Optional[str] = None + + # 简历内容 + raw_content: str = "" # 原始简历文本 + parsed_content: Optional[ResumeParsed] = None # 结构化解析内容 + + # 附件 + attachment_url: Optional[str] = None # 附件URL + attachment_type: Optional[str] = None # 附件类型 (pdf, doc, etc.) + + # 版本控制 + version: int = 1 + + # 元数据 + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + def __post_init__(self): + if self.created_at is None: + self.created_at = datetime.now() + if self.updated_at is None: + self.updated_at = datetime.now() + if self.parsed_content is None: + self.parsed_content = ResumeParsed() diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/main.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/main.py new file mode 100644 index 0000000..dbaa0ce --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/main.py @@ -0,0 +1,293 @@ +"""Application entry point""" +import asyncio +from typing import Optional + +from .config.settings import get_settings +from .domain.candidate import CandidateSource +from .service.crawler import CrawlerFactory, BossCrawler +from .service.ingestion import ( + UnifiedIngestionService, + DataNormalizer, + DataValidator, + DeduplicationService +) +from .service.analysis import ( + ResumeAnalyzer, + EvaluationSchemaService, + LLMClient, + OpenAIClient, + MockLLMClient +) +from .service.notification import ( + NotificationService, + WeChatWorkChannel, + DingTalkChannel, + EmailChannel +) + + +class HRAgentApplication: + """ + HR Agent 应用主类 + + 整合所有服务组件,提供统一的操作接口 + """ + + def __init__(self): + self.settings = get_settings() + self.crawler_factory = CrawlerFactory() + self.ingestion_service: Optional[UnifiedIngestionService] = None + self.analyzer: Optional[ResumeAnalyzer] = None + self.notification_service: Optional[NotificationService] = None + + self._initialized = False + + def initialize(self): + """初始化应用""" + if self._initialized: + return + + # 1. 初始化爬虫 + self._init_crawlers() + + # 2. 初始化入库服务 + self._init_ingestion_service() + + # 3. 初始化分析器 + self._init_analyzer() + + # 4. 初始化通知服务 + self._init_notification_service() + + self._initialized = True + print(f"HR Agent {self.settings.app_version} initialized successfully") + + def _init_crawlers(self): + """初始化爬虫""" + # Boss 爬虫 + if self.settings.crawler.boss_wt_token: + boss_crawler = BossCrawler(wt_token=self.settings.crawler.boss_wt_token) + self.crawler_factory.register(CandidateSource.BOSS, boss_crawler) + print("Boss crawler registered") + + def _init_ingestion_service(self): + """初始化入库服务""" + self.ingestion_service = UnifiedIngestionService( + normalizer=DataNormalizer(), + validator=DataValidator(), + deduplicator=DeduplicationService(), + on_analysis_triggered=self._on_analysis_triggered + ) + + def _init_analyzer(self): + """初始化分析器""" + # 根据配置选择 LLM 客户端 + llm_client = self._create_llm_client() + + self.analyzer = ResumeAnalyzer( + llm_client=llm_client, + schema_service=EvaluationSchemaService() + ) + + def _create_llm_client(self) -> LLMClient: + """创建 LLM 客户端""" + provider = self.settings.llm.provider.lower() + + if provider == "openai": + if self.settings.llm.api_key: + return OpenAIClient( + api_key=self.settings.llm.api_key, + model=self.settings.llm.model, + base_url=self.settings.llm.base_url, + temperature=self.settings.llm.temperature, + max_tokens=self.settings.llm.max_tokens + ) + else: + print("Warning: OpenAI API key not configured, using mock client") + return MockLLMClient() + + elif provider == "mock": + return MockLLMClient() + + else: + print(f"Warning: Unknown LLM provider '{provider}', using mock client") + return MockLLMClient() + + def _init_notification_service(self): + """初始化通知服务""" + notification_service = NotificationService() + + # 企业微信 + if self.settings.notification.wechat_work_webhook: + mentioned_list = None + if self.settings.notification.wechat_work_mentioned: + mentioned_list = [ + m.strip() + for m in self.settings.notification.wechat_work_mentioned.split(",") + ] + + channel = WeChatWorkChannel( + webhook_url=self.settings.notification.wechat_work_webhook, + mentioned_list=mentioned_list + ) + notification_service.register_channel(channel) + print("WeChat Work channel registered") + + # 钉钉 + if self.settings.notification.dingtalk_webhook: + at_mobiles = None + if self.settings.notification.dingtalk_at_mobiles: + at_mobiles = [ + m.strip() + for m in self.settings.notification.dingtalk_at_mobiles.split(",") + ] + + channel = DingTalkChannel( + webhook_url=self.settings.notification.dingtalk_webhook, + secret=self.settings.notification.dingtalk_secret, + at_mobiles=at_mobiles + ) + notification_service.register_channel(channel) + print("DingTalk channel registered") + + # 邮件 + if (self.settings.notification.email_smtp_host and + self.settings.notification.email_username): + to_addrs = [] + if self.settings.notification.email_to: + to_addrs = [ + addr.strip() + for addr in self.settings.notification.email_to.split(",") + ] + + if to_addrs: + channel = EmailChannel( + smtp_host=self.settings.notification.email_smtp_host, + smtp_port=self.settings.notification.email_smtp_port, + username=self.settings.notification.email_username, + password=self.settings.notification.email_password or "", + from_addr=self.settings.notification.email_from or self.settings.notification.email_username, + to_addrs=to_addrs + ) + notification_service.register_channel(channel) + print("Email channel registered") + + self.notification_service = notification_service + + def _on_analysis_triggered(self, candidate_id: str): + """分析触发回调""" + # 可以在这里触发异步分析任务 + print(f"Analysis triggered for candidate: {candidate_id}") + + async def crawl_and_ingest( + self, + source: CandidateSource, + job_id: str, + page: int = 1 + ): + """ + 爬取并入库候选人 + + Args: + source: 数据来源 + job_id: 职位ID + page: 页码 + """ + crawler = self.crawler_factory.get_crawler(source) + if not crawler: + print(f"No crawler registered for source: {source}") + return + + # 获取候选人列表 + candidates = crawler.get_candidates(job_id, page=page) + print(f"Found {len(candidates)} candidates from {source.value}") + + for candidate in candidates: + # 获取简历详情 + resume = crawler.get_resume_detail(candidate) + if not resume: + print(f"Failed to get resume for {candidate.name}") + continue + + # 构建原始数据 + raw_data = { + "geekId": candidate.source_id, + "name": candidate.name, + "phone": candidate.phone, + "email": candidate.email, + "age": candidate.age, + "gender": candidate.gender, + "company": candidate.current_company, + "position": candidate.current_position, + "workYears": candidate.work_years, + "education": candidate.education, + "school": candidate.school, + "resumeText": resume.raw_content, + } + + # 入库 + result = self.ingestion_service.ingest(source, raw_data) + print(f"Ingestion result for {candidate.name}: {result.message}") + + if result.success and result.candidate_id: + # 触发分析 + await self._analyze_and_notify(result.candidate_id, resume) + + async def _analyze_and_notify(self, candidate_id: str, resume): + """分析并通知""" + try: + # 分析简历 + evaluation = await self.analyzer.analyze( + candidate_id=candidate_id, + resume=resume + ) + print(f"Analysis completed for {candidate_id}, score: {evaluation.overall_score}") + + # 发送通知 + if self.notification_service: + # 获取候选人信息(这里简化处理) + from .domain.candidate import Candidate + candidate = Candidate( + id=candidate_id, + name="候选人", # 实际应该从仓库获取 + source=CandidateSource.BOSS + ) + + result = await self.notification_service.notify( + candidate=candidate, + evaluation=evaluation + ) + print(f"Notification result: {result.message}") + + except Exception as e: + print(f"Failed to analyze and notify: {e}") + + +# 全局应用实例 +_app: Optional[HRAgentApplication] = None + + +def get_app() -> HRAgentApplication: + """获取应用实例(单例)""" + global _app + if _app is None: + _app = HRAgentApplication() + _app.initialize() + return _app + + +async def main(): + """主函数""" + app = get_app() + + # 示例:爬取并入库 + # await app.crawl_and_ingest( + # source=CandidateSource.BOSS, + # job_id="your_job_id" + # ) + + print("HR Agent is running...") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/mapper/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/mapper/__init__.py new file mode 100644 index 0000000..80dba04 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/mapper/__init__.py @@ -0,0 +1 @@ +"""Mapper layer - Data access""" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/__init__.py new file mode 100644 index 0000000..30c71ab --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/__init__.py @@ -0,0 +1 @@ +"""Service layer - Business logic""" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/__init__.py new file mode 100644 index 0000000..82429a1 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/__init__.py @@ -0,0 +1,16 @@ +"""Analysis service layer - AI-powered resume analysis""" + +from .evaluation_schema import EvaluationSchemaService +from .resume_analyzer import ResumeAnalyzer +from .scoring_engine import ScoringEngine +from .prompt_builder import PromptBuilder +from .llm_client import LLMClient, OpenAIClient + +__all__ = [ + "EvaluationSchemaService", + "ResumeAnalyzer", + "ScoringEngine", + "PromptBuilder", + "LLMClient", + "OpenAIClient", +] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/evaluation_schema.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/evaluation_schema.py new file mode 100644 index 0000000..eb8fdc0 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/evaluation_schema.py @@ -0,0 +1,282 @@ +"""Evaluation schema service - Manage evaluation schemas""" +from typing import List, Optional, Dict, Any +from dataclasses import dataclass + +from ...domain.evaluation import EvaluationSchema, Dimension +from ...domain.enums import Recommendation + + +@dataclass +class DefaultSchemas: + """默认评价方案""" + + @staticmethod + def java_backend() -> EvaluationSchema: + """Java后端工程师评价方案""" + return EvaluationSchema( + id="java_backend", + name="Java后端工程师评价方案", + description="针对Java后端开发岗位的综合评价方案", + dimensions=[ + Dimension( + id="tech_capability", + name="技术能力", + description="Java技术栈掌握程度", + criteria=[ + "Java基础扎实程度", + "Spring生态熟悉度", + "数据库设计与优化", + "分布式系统经验" + ] + ), + Dimension( + id="project_exp", + name="项目经验", + description="项目经历的丰富度和质量", + criteria=[ + "项目复杂度", + "承担角色重要性", + "技术挑战解决能力" + ] + ), + Dimension( + id="learning_ability", + name="学习能力", + description="学习新技术和适应新环境的能力", + criteria=[ + "技术广度", + "新技术掌握速度", + "自我驱动学习" + ] + ), + Dimension( + id="communication", + name="沟通协作", + description="团队协作和沟通能力", + criteria=[ + "跨团队协作经验", + "技术文档能力", + "问题表达能力" + ] + ), + Dimension( + id="stability", + name="稳定性", + description="职业稳定性和忠诚度", + criteria=[ + "平均在职时长", + "跳槽频率", + "职业发展规划清晰度" + ] + ) + ], + weights={ + "tech_capability": 0.35, + "project_exp": 0.25, + "learning_ability": 0.15, + "communication": 0.15, + "stability": 0.10 + }, + is_default=True + ) + + @staticmethod + def frontend() -> EvaluationSchema: + """前端工程师评价方案""" + return EvaluationSchema( + id="frontend", + name="前端工程师评价方案", + description="针对前端开发岗位的综合评价方案", + dimensions=[ + Dimension( + id="tech_capability", + name="技术能力", + description="前端技术栈掌握程度", + criteria=[ + "JavaScript/TypeScript熟练度", + "主流框架掌握(Vue/React/Angular)", + "前端工程化经验", + "性能优化能力" + ] + ), + Dimension( + id="ui_ux_sense", + name="UI/UX感知", + description="对界面设计和用户体验的理解", + criteria=[ + "设计还原度", + "交互体验意识", + "响应式设计经验" + ] + ), + Dimension( + id="project_exp", + name="项目经验", + description="项目经历的丰富度和质量" + ), + Dimension( + id="learning_ability", + name="学习能力", + description="学习新技术的能力" + ), + Dimension( + id="communication", + name="沟通协作", + description="团队协作能力" + ) + ], + weights={ + "tech_capability": 0.30, + "ui_ux_sense": 0.20, + "project_exp": 0.25, + "learning_ability": 0.15, + "communication": 0.10 + } + ) + + @staticmethod + def general() -> EvaluationSchema: + """通用评价方案""" + return EvaluationSchema( + id="general", + name="通用评价方案", + description="适用于各类岗位的通用评价方案", + dimensions=[ + Dimension( + id="professional", + name="专业能力", + description="岗位相关专业技能水平" + ), + Dimension( + id="experience", + name="工作经验", + description="相关工作经验丰富度" + ), + Dimension( + id="education", + name="教育背景", + description="学历和专业匹配度" + ), + Dimension( + id="potential", + name="发展潜力", + description="未来成长空间" + ), + Dimension( + id="culture_fit", + name="文化匹配", + description="与企业文化的匹配度" + ) + ], + weights={ + "professional": 0.30, + "experience": 0.25, + "education": 0.15, + "potential": 0.15, + "culture_fit": 0.15 + }, + is_default=True + ) + + +class EvaluationSchemaService: + """ + 评价方案服务 + + 管理评价方案的 CRUD 操作 + """ + + def __init__(self, repository=None): + """ + 初始化评价方案服务 + + Args: + repository: 评价方案数据访问接口 + """ + self.repository = repository + self._default_schemas: Dict[str, EvaluationSchema] = {} + self._init_default_schemas() + + def _init_default_schemas(self): + """初始化默认评价方案""" + defaults = [ + DefaultSchemas.general(), + DefaultSchemas.java_backend(), + DefaultSchemas.frontend(), + ] + for schema in defaults: + self._default_schemas[schema.id] = schema + + def get_schema(self, schema_id: str) -> Optional[EvaluationSchema]: + """ + 获取评价方案 + + Args: + schema_id: 方案ID + + Returns: + 评价方案,如果不存在返回 None + """ + # 先查默认方案 + if schema_id in self._default_schemas: + return self._default_schemas[schema_id] + + # 再查数据库 + if self.repository: + return self.repository.get_by_id(schema_id) + + return None + + def get_default_schema(self) -> EvaluationSchema: + """获取默认评价方案""" + for schema in self._default_schemas.values(): + if schema.is_default: + return schema + + # 如果没有标记为默认的,返回第一个 + return next(iter(self._default_schemas.values())) + + def list_schemas(self) -> List[EvaluationSchema]: + """获取所有评价方案""" + schemas = list(self._default_schemas.values()) + + if self.repository: + db_schemas = self.repository.list_all() + # 合并,数据库中的覆盖默认的 + schema_dict = {s.id: s for s in schemas} + for s in db_schemas: + schema_dict[s.id] = s + schemas = list(schema_dict.values()) + + return schemas + + def create_schema(self, schema: EvaluationSchema) -> EvaluationSchema: + """创建评价方案""" + if self.repository: + return self.repository.save(schema) + + # 如果没有仓库,保存到内存 + self._default_schemas[schema.id] = schema + return schema + + def update_schema(self, schema: EvaluationSchema) -> Optional[EvaluationSchema]: + """更新评价方案""" + if self.repository: + return self.repository.update(schema) + + if schema.id in self._default_schemas: + self._default_schemas[schema.id] = schema + return schema + + return None + + def delete_schema(self, schema_id: str) -> bool: + """删除评价方案""" + # 不能删除默认方案 + if schema_id in self._default_schemas: + return False + + if self.repository: + return self.repository.delete(schema_id) + + return False diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/llm_client.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/llm_client.py new file mode 100644 index 0000000..b37ba19 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/llm_client.py @@ -0,0 +1,170 @@ +"""LLM client for resume analysis""" +from abc import ABC, abstractmethod +from typing import Optional, Dict, Any, List +import json + + +class LLMClient(ABC): + """LLM 客户端抽象基类""" + + @abstractmethod + async def analyze(self, prompt: str, **kwargs) -> str: + """ + 发送分析请求 + + Args: + prompt: 提示词 + **kwargs: 额外参数 + + Returns: + LLM 响应文本 + """ + pass + + @abstractmethod + def is_available(self) -> bool: + """检查客户端是否可用""" + pass + + +class OpenAIClient(LLMClient): + """OpenAI API 客户端""" + + def __init__( + self, + api_key: str, + model: str = "gpt-4", + base_url: Optional[str] = None, + temperature: float = 0.7, + max_tokens: int = 2000 + ): + """ + 初始化 OpenAI 客户端 + + Args: + api_key: API 密钥 + model: 模型名称 + base_url: 自定义 API 地址(用于兼容其他服务) + temperature: 温度参数 + max_tokens: 最大 token 数 + """ + self.api_key = api_key + self.model = model + self.base_url = base_url + self.temperature = temperature + self.max_tokens = max_tokens + self._client = None + + def _get_client(self): + """获取或创建客户端实例""" + if self._client is None: + try: + from openai import AsyncOpenAI + self._client = AsyncOpenAI( + api_key=self.api_key, + base_url=self.base_url + ) + except ImportError: + raise ImportError("openai package is required. Install with: pip install openai") + return self._client + + async def analyze(self, prompt: str, **kwargs) -> str: + """发送分析请求""" + client = self._get_client() + + messages = [ + {"role": "system", "content": "你是一个专业的简历分析专家,擅长评估候选人的能力和潜力。"}, + {"role": "user", "content": prompt} + ] + + response = await client.chat.completions.create( + model=kwargs.get("model", self.model), + messages=messages, + temperature=kwargs.get("temperature", self.temperature), + max_tokens=kwargs.get("max_tokens", self.max_tokens) + ) + + return response.choices[0].message.content + + def is_available(self) -> bool: + """检查客户端是否可用""" + try: + from openai import AsyncOpenAI + return bool(self.api_key) + except ImportError: + return False + + +class ClaudeClient(LLMClient): + """Claude API 客户端""" + + def __init__( + self, + api_key: str, + model: str = "claude-3-sonnet-20240229", + max_tokens: int = 2000 + ): + self.api_key = api_key + self.model = model + self.max_tokens = max_tokens + self._client = None + + def _get_client(self): + """获取或创建客户端实例""" + if self._client is None: + try: + import anthropic + self._client = anthropic.AsyncAnthropic(api_key=self.api_key) + except ImportError: + raise ImportError("anthropic package is required. Install with: pip install anthropic") + return self._client + + async def analyze(self, prompt: str, **kwargs) -> str: + """发送分析请求""" + client = self._get_client() + + response = await client.messages.create( + model=kwargs.get("model", self.model), + max_tokens=kwargs.get("max_tokens", self.max_tokens), + messages=[{"role": "user", "content": prompt}] + ) + + return response.content[0].text + + def is_available(self) -> bool: + """检查客户端是否可用""" + try: + import anthropic + return bool(self.api_key) + except ImportError: + return False + + +class MockLLMClient(LLMClient): + """模拟 LLM 客户端(用于测试)""" + + def __init__(self, response_template: Optional[str] = None): + self.response_template = response_template or self._default_response() + + async def analyze(self, prompt: str, **kwargs) -> str: + """返回模拟响应""" + return self.response_template + + def is_available(self) -> bool: + return True + + def _default_response(self) -> str: + """默认模拟响应""" + return json.dumps({ + "overall_score": 85, + "dimension_scores": [ + {"dimension_id": "tech", "score": 90, "comment": "技术能力优秀"}, + {"dimension_id": "experience", "score": 80, "comment": "经验丰富"}, + {"dimension_id": "education", "score": 85, "comment": "学历良好"} + ], + "tags": ["Java", "Spring", "微服务", "5年经验"], + "summary": "该候选人技术能力优秀,经验丰富,是一个不错的候选人。", + "strengths": ["技术扎实", "项目经验丰富", "学习能力强"], + "weaknesses": ["管理经验较少"], + "recommendation": "recommend" + }, ensure_ascii=False) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/prompt_builder.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/prompt_builder.py new file mode 100644 index 0000000..78d74c1 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/prompt_builder.py @@ -0,0 +1,221 @@ +"""Prompt builder for resume analysis""" +from typing import Optional, List, Dict, Any + +from ...domain.resume import ResumeParsed +from ...domain.evaluation import EvaluationSchema, Dimension +from ...domain.job import Job + + +class PromptBuilder: + """ + 提示词构建器 + + 根据简历内容、评价方案和职位要求构建 LLM 提示词 + """ + + DEFAULT_TEMPLATE = """ +你是一位专业的简历分析专家。请根据以下信息对候选人进行全面评估。 + +## 评价方案 +方案名称:{schema_name} +方案描述:{schema_description} + +## 评价维度 +{dimensions} + +## 职位要求 +{job_requirements} + +## 候选人简历 +{resume_content} + +## 分析要求 +请按照以下 JSON 格式输出分析结果: +```json +{{ + "overall_score": <综合评分 0-100>, + "dimension_scores": [ + {{ + "dimension_id": "<维度ID>", + "score": <该维度评分 0-100>, + "comment": "<该维度评价说明>" + }} + ], + "tags": ["<标签1>", "<标签2>", ...], + "summary": "<综合评价摘要,100字以内>", + "strengths": ["<优势1>", "<优势2>", ...], + "weaknesses": ["<不足1>", "<不足2>", ...], + "recommendation": "<推荐意见: strong_recommend/recommend/consider/not_recommend>" +}} +``` + +注意: +1. 评分要客观公正,基于简历实际内容 +2. 标签要简洁准确,体现候选人核心特点 +3. 优势和不足要具体,避免空泛描述 +4. 推荐意见要综合考虑各维度评分 +""" + + def __init__(self, template: Optional[str] = None): + """ + 初始化提示词构建器 + + Args: + template: 自定义提示词模板 + """ + self.template = template or self.DEFAULT_TEMPLATE + + def build( + self, + resume: ResumeParsed, + schema: EvaluationSchema, + job: Optional[Job] = None + ) -> str: + """ + 构建提示词 + + Args: + resume: 解析后的简历内容 + schema: 评价方案 + job: 关联职位(可选) + + Returns: + 完整的提示词 + """ + # 构建维度描述 + dimensions_text = self._build_dimensions(schema.dimensions) + + # 构建职位要求描述 + job_requirements_text = self._build_job_requirements(job) + + # 构建简历内容描述 + resume_content_text = self._build_resume_content(resume) + + # 填充模板 + return self.template.format( + schema_name=schema.name, + schema_description=schema.description or "无", + dimensions=dimensions_text, + job_requirements=job_requirements_text, + resume_content=resume_content_text + ) + + def _build_dimensions(self, dimensions: List[Dimension]) -> str: + """构建维度描述""" + if not dimensions: + return "使用默认维度进行评估" + + lines = [] + for dim in dimensions: + line = f"- {dim.name}(ID: {dim.id})" + if dim.description: + line += f":{dim.description}" + if dim.criteria: + line += f"\n 评价标准:{', '.join(dim.criteria)}" + lines.append(line) + + return "\n".join(lines) + + def _build_job_requirements(self, job: Optional[Job]) -> str: + """构建职位要求描述""" + if not job: + return "无特定职位要求" + + lines = [f"职位:{job.title}"] + + if job.location: + lines.append(f"地点:{job.location}") + + if job.salary_min or job.salary_max: + salary = f"{job.salary_min or '?'}-{job.salary_max or '?'}K" + lines.append(f"薪资范围:{salary}") + + if job.requirements: + if job.requirements.min_work_years: + lines.append(f"最低工作年限:{job.requirements.min_work_years}年") + if job.requirements.education: + lines.append(f"学历要求:{job.requirements.education}") + if job.requirements.skills: + lines.append(f"技能要求:{', '.join(job.requirements.skills)}") + if job.requirements.description: + lines.append(f"其他要求:{job.requirements.description}") + + if job.description: + lines.append(f"\n职位描述:\n{job.description}") + + return "\n".join(lines) + + def _build_resume_content(self, resume: ResumeParsed) -> str: + """构建简历内容描述""" + lines = [] + + # 基本信息 + lines.append("### 基本信息") + if resume.name: + lines.append(f"姓名:{resume.name}") + if resume.gender: + lines.append(f"性别:{resume.gender}") + if resume.age: + lines.append(f"年龄:{resume.age}") + if resume.location: + lines.append(f"所在地:{resume.location}") + if resume.current_company: + lines.append(f"当前公司:{resume.current_company}") + if resume.current_position: + lines.append(f"当前职位:{resume.current_position}") + if resume.work_years: + lines.append(f"工作年限:{resume.work_years}年") + if resume.education: + lines.append(f"学历:{resume.education}") + if resume.school: + lines.append(f"毕业院校:{resume.school}") + + # 工作经历 + if resume.work_experiences: + lines.append("\n### 工作经历") + for exp in resume.work_experiences: + line = f"- {exp.company} | {exp.position}" + if exp.start_date: + line += f" ({exp.start_date}" + if exp.end_date: + line += f" - {exp.end_date}" + line += ")" + lines.append(line) + if exp.description: + lines.append(f" {exp.description}") + + # 项目经历 + if resume.project_experiences: + lines.append("\n### 项目经历") + for exp in resume.project_experiences: + line = f"- {exp.name}" + if exp.role: + line += f" | {exp.role}" + lines.append(line) + if exp.description: + lines.append(f" {exp.description}") + + # 教育经历 + if resume.education_experiences: + lines.append("\n### 教育经历") + for exp in resume.education_experiences: + line = f"- {exp.school}" + if exp.major: + line += f" | {exp.major}" + if exp.degree: + line += f" | {exp.degree}" + lines.append(line) + + # 技能 + if resume.skills: + lines.append(f"\n### 技能\n{', '.join(resume.skills)}") + + # 自我评价 + if resume.self_evaluation: + lines.append(f"\n### 自我评价\n{resume.self_evaluation}") + + # 原始数据 + if resume.raw_data and "full_text" in resume.raw_data: + lines.append(f"\n### 完整简历文本\n{resume.raw_data['full_text']}") + + return "\n".join(lines) if lines else "(简历内容为空)" diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/resume_analyzer.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/resume_analyzer.py new file mode 100644 index 0000000..6cb8e3f --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/resume_analyzer.py @@ -0,0 +1,251 @@ +"""Resume analyzer - AI-powered resume analysis""" +import json +import re +from typing import Optional, List, Dict, Any +from datetime import datetime + +from .llm_client import LLMClient +from .prompt_builder import PromptBuilder +from .scoring_engine import ScoringEngine +from .evaluation_schema import EvaluationSchemaService +from ...domain.evaluation import ( + Evaluation, EvaluationSchema, DimensionScore +) +from ...domain.enums import Recommendation +from ...domain.resume import Resume + + +class ResumeAnalyzer: + """ + 简历分析器 + + 基于 LLM 对简历进行智能分析,生成评价结果 + """ + + def __init__( + self, + llm_client: LLMClient, + schema_service: Optional[EvaluationSchemaService] = None, + prompt_builder: Optional[PromptBuilder] = None, + scoring_engine: Optional[ScoringEngine] = None, + evaluation_repo=None + ): + """ + 初始化简历分析器 + + Args: + llm_client: LLM 客户端 + schema_service: 评价方案服务 + prompt_builder: 提示词构建器 + scoring_engine: 评分引擎 + evaluation_repo: 评价记录仓库 + """ + self.llm = llm_client + self.schema_service = schema_service or EvaluationSchemaService() + self.prompt_builder = prompt_builder or PromptBuilder() + self.scoring_engine = scoring_engine or ScoringEngine() + self.evaluation_repo = evaluation_repo + + async def analyze( + self, + candidate_id: str, + resume: Resume, + schema_id: Optional[str] = None, + job_id: Optional[str] = None + ) -> Evaluation: + """ + 分析候选人简历 + + Args: + candidate_id: 候选人ID + resume: 简历对象 + schema_id: 评价方案ID,不传使用默认方案 + job_id: 关联职位ID + + Returns: + 评价结果 + """ + # 1. 获取评价方案 + schema = self._get_schema(schema_id) + + # 2. 构建提示词 + prompt = self.prompt_builder.build( + resume=resume.parsed_content, + schema=schema, + job=None # TODO: 获取职位信息 + ) + + # 3. 调用 LLM 分析 + try: + response = await self.llm.analyze(prompt) + except Exception as e: + print(f"LLM analysis failed: {e}") + # 返回空评价 + return self._create_empty_evaluation(candidate_id, schema_id, job_id) + + # 4. 解析响应 + evaluation_data = self._parse_response(response) + + # 5. 构建评价对象 + evaluation = self._build_evaluation( + candidate_id=candidate_id, + schema_id=schema_id or schema.id, + job_id=job_id, + evaluation_data=evaluation_data, + raw_response=response + ) + + # 6. 保存评价 + if self.evaluation_repo: + evaluation = self.evaluation_repo.save(evaluation) + + return evaluation + + def _get_schema(self, schema_id: Optional[str]) -> EvaluationSchema: + """获取评价方案""" + if schema_id: + schema = self.schema_service.get_schema(schema_id) + if schema: + return schema + + return self.schema_service.get_default_schema() + + def _parse_response(self, response: str) -> Dict[str, Any]: + """解析 LLM 响应""" + try: + # 尝试直接解析 JSON + return json.loads(response) + except json.JSONDecodeError: + pass + + # 尝试从 markdown 代码块中提取 JSON + json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', response, re.DOTALL) + if json_match: + try: + return json.loads(json_match.group(1)) + except json.JSONDecodeError: + pass + + # 尝试从文本中提取 JSON + json_match = re.search(r'\{.*\}', response, re.DOTALL) + if json_match: + try: + return json.loads(json_match.group()) + except json.JSONDecodeError: + pass + + # 解析失败,返回空结构 + print(f"Failed to parse LLM response: {response[:200]}...") + return {} + + def _build_evaluation( + self, + candidate_id: str, + schema_id: str, + job_id: Optional[str], + evaluation_data: Dict[str, Any], + raw_response: str + ) -> Evaluation: + """构建评价对象""" + # 解析维度评分 + dimension_scores = [] + for ds_data in evaluation_data.get("dimension_scores", []): + dimension_scores.append(DimensionScore( + dimension_id=ds_data.get("dimension_id", ""), + dimension_name=ds_data.get("dimension_name", ""), + score=float(ds_data.get("score", 0)), + weight=float(ds_data.get("weight", 1.0)), + comment=ds_data.get("comment") + )) + + # 解析推荐意见 + recommendation_str = evaluation_data.get("recommendation", "") + recommendation = self._parse_recommendation(recommendation_str) + + # 获取综合评分 + overall_score = float(evaluation_data.get("overall_score", 0)) + + # 如果 LLM 没有返回综合评分,计算加权得分 + if overall_score == 0 and dimension_scores: + score_result = self.scoring_engine.calculate(dimension_scores) + overall_score = score_result.overall_score + + return Evaluation( + candidate_id=candidate_id, + schema_id=schema_id, + job_id=job_id, + overall_score=overall_score, + dimension_scores=dimension_scores, + tags=evaluation_data.get("tags", []), + summary=evaluation_data.get("summary"), + strengths=evaluation_data.get("strengths", []), + weaknesses=evaluation_data.get("weaknesses", []), + recommendation=recommendation, + raw_response=raw_response + ) + + def _parse_recommendation(self, value: str) -> Optional[Recommendation]: + """解析推荐意见""" + if not value: + return None + + value = value.lower().strip() + + mapping = { + "strong_recommend": Recommendation.STRONG_RECOMMEND, + "strong recommend": Recommendation.STRONG_RECOMMEND, + "strong": Recommendation.STRONG_RECOMMEND, + "recommend": Recommendation.RECOMMEND, + "consider": Recommendation.CONSIDER, + "not_recommend": Recommendation.NOT_RECOMMEND, + "not recommend": Recommendation.NOT_RECOMMEND, + "not": Recommendation.NOT_RECOMMEND, + } + + return mapping.get(value) + + def _create_empty_evaluation( + self, + candidate_id: str, + schema_id: Optional[str], + job_id: Optional[str] + ) -> Evaluation: + """创建空评价(分析失败时使用)""" + return Evaluation( + candidate_id=candidate_id, + schema_id=schema_id or "", + job_id=job_id, + overall_score=0, + summary="分析失败,无法生成评价", + recommendation=None + ) + + async def batch_analyze( + self, + items: List[tuple] + ) -> List[Evaluation]: + """ + 批量分析 + + Args: + items: [(candidate_id, resume, schema_id, job_id), ...] + + Returns: + 评价结果列表 + """ + results = [] + for item in items: + candidate_id, resume, schema_id, job_id = item + try: + evaluation = await self.analyze( + candidate_id=candidate_id, + resume=resume, + schema_id=schema_id, + job_id=job_id + ) + results.append(evaluation) + except Exception as e: + print(f"Failed to analyze candidate {candidate_id}: {e}") + results.append(self._create_empty_evaluation(candidate_id, schema_id, job_id)) + + return results diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/scoring_engine.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/scoring_engine.py new file mode 100644 index 0000000..00ce854 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/analysis/scoring_engine.py @@ -0,0 +1,167 @@ +"""Scoring engine for evaluation calculation""" +from typing import List, Dict, Optional +from dataclasses import dataclass + +from ...domain.evaluation import DimensionScore + + +@dataclass +class ScoreResult: + """评分结果""" + overall_score: float + dimension_scores: List[DimensionScore] + weighted_score: float + confidence: float # 置信度 0-1 + + +class ScoringEngine: + """ + 评分计算引擎 + + 负责: + 1. 计算各维度加权得分 + 2. 计算综合评分 + 3. 计算置信度 + """ + + def __init__(self): + pass + + def calculate( + self, + dimension_scores: List[DimensionScore], + weights: Optional[Dict[str, float]] = None + ) -> ScoreResult: + """ + 计算综合评分 + + Args: + dimension_scores: 各维度评分列表 + weights: 维度权重,如果不提供则使用评分中的权重 + + Returns: + 评分结果 + """ + if not dimension_scores: + return ScoreResult( + overall_score=0.0, + dimension_scores=[], + weighted_score=0.0, + confidence=0.0 + ) + + # 计算加权得分 + weighted_sum = 0.0 + total_weight = 0.0 + + for ds in dimension_scores: + weight = weights.get(ds.dimension_id, ds.weight) if weights else ds.weight + weighted_sum += ds.score * weight + total_weight += weight + + weighted_score = weighted_sum / total_weight if total_weight > 0 else 0.0 + + # 计算简单平均分 + simple_average = sum(ds.score for ds in dimension_scores) / len(dimension_scores) + + # 综合评分(加权分和简单平均的加权组合) + overall_score = weighted_score * 0.7 + simple_average * 0.3 + + # 计算置信度 + confidence = self._calculate_confidence(dimension_scores) + + return ScoreResult( + overall_score=round(overall_score, 1), + dimension_scores=dimension_scores, + weighted_score=round(weighted_score, 1), + confidence=round(confidence, 2) + ) + + def _calculate_confidence(self, dimension_scores: List[DimensionScore]) -> float: + """ + 计算置信度 + + 基于以下因素: + 1. 评分数量(维度越多置信度越高) + 2. 评分一致性(分数方差越小置信度越高) + """ + if not dimension_scores: + return 0.0 + + # 基于评分数量的基础置信度 + count_factor = min(len(dimension_scores) / 5, 1.0) # 5个维度为满分 + + # 基于评分一致性的置信度 + scores = [ds.score for ds in dimension_scores] + if len(scores) > 1: + import statistics + try: + variance = statistics.variance(scores) + # 方差越小,一致性越高 + consistency_factor = max(0, 1 - variance / 1000) + except statistics.StatisticsError: + consistency_factor = 1.0 + else: + consistency_factor = 0.5 # 只有一个维度时置信度较低 + + # 综合置信度 + confidence = count_factor * 0.4 + consistency_factor * 0.6 + + return min(max(confidence, 0.0), 1.0) + + def normalize_scores( + self, + dimension_scores: List[DimensionScore], + target_min: float = 0, + target_max: float = 100 + ) -> List[DimensionScore]: + """ + 归一化评分到目标范围 + + Args: + dimension_scores: 原始评分 + target_min: 目标最小值 + target_max: 目标最大值 + + Returns: + 归一化后的评分 + """ + if not dimension_scores: + return [] + + scores = [ds.score for ds in dimension_scores] + current_min = min(scores) + current_max = max(scores) + + if current_max == current_min: + # 所有分数相同,直接返回 + return dimension_scores + + normalized = [] + for ds in dimension_scores: + # 线性归一化 + normalized_score = target_min + (ds.score - current_min) / (current_max - current_min) * (target_max - target_min) + normalized.append(DimensionScore( + dimension_id=ds.dimension_id, + dimension_name=ds.dimension_name, + score=round(normalized_score, 1), + weight=ds.weight, + comment=ds.comment + )) + + return normalized + + def rank_candidates( + self, + candidate_scores: List[tuple] + ) -> List[tuple]: + """ + 对候选人进行排序 + + Args: + candidate_scores: [(candidate_id, score), ...] + + Returns: + 排序后的列表(按分数降序) + """ + return sorted(candidate_scores, key=lambda x: x[1], reverse=True) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/__init__.py new file mode 100644 index 0000000..2474932 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/__init__.py @@ -0,0 +1,7 @@ +"""Crawler service layer""" + +from .base_crawler import BaseCrawler +from .boss_crawler import BossCrawler +from .crawler_factory import CrawlerFactory + +__all__ = ["BaseCrawler", "BossCrawler", "CrawlerFactory"] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/base_crawler.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/base_crawler.py new file mode 100644 index 0000000..d8a95a0 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/base_crawler.py @@ -0,0 +1,95 @@ +"""Base crawler abstract class""" +from abc import ABC, abstractmethod +from typing import List, Optional, Dict, Any + +from ...domain.candidate import Candidate, CandidateSource +from ...domain.resume import Resume +from ...domain.job import Job + + +class BaseCrawler(ABC): + """ + 所有渠道爬虫的抽象基类 + + 子类需要实现: + 1. source_type - 返回渠道类型 + 2. get_jobs - 获取职位列表 + 3. get_candidates - 获取候选人列表 + 4. get_resume_detail - 获取候选人简历详情 + """ + + @property + @abstractmethod + def source_type(self) -> CandidateSource: + """返回爬虫对应的渠道类型""" + pass + + @abstractmethod + def get_jobs(self, status: Optional[str] = None) -> List[Job]: + """ + 获取职位列表 + + Args: + status: 职位状态过滤,如 "active" + + Returns: + 职位列表 + """ + pass + + @abstractmethod + def get_candidates( + self, + job_id: str, + page: int = 1, + page_size: int = 20 + ) -> List[Candidate]: + """ + 获取指定职位下的候选人列表 + + Args: + job_id: 职位ID + page: 页码,从1开始 + page_size: 每页数量 + + Returns: + 候选人列表 + """ + pass + + @abstractmethod + def get_resume_detail(self, candidate: Candidate) -> Optional[Resume]: + """ + 获取候选人简历详情 + + Args: + candidate: 候选人对象 + + Returns: + 简历对象,如果获取失败返回 None + """ + pass + + def get_candidate_by_id(self, source_id: str) -> Optional[Candidate]: + """ + 根据来源ID获取候选人(可选实现) + + Args: + source_id: 来源平台ID + + Returns: + 候选人对象,如果不存在返回 None + """ + return None + + def parse_raw_data(self, raw_data: Dict[str, Any]) -> Candidate: + """ + 解析原始数据为候选人对象(可选实现) + + Args: + raw_data: 原始API返回数据 + + Returns: + 候选人对象 + """ + raise NotImplementedError("parse_raw_data must be implemented") diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/boss_crawler.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/boss_crawler.py new file mode 100644 index 0000000..5448fd9 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/boss_crawler.py @@ -0,0 +1,207 @@ +"""Boss crawler implementation using ylhp-boss-hr SDK""" +from decimal import Decimal +from typing import List, Optional, Dict, Any +import re + +try: + from boss import Boss +except ImportError: + Boss = None + +from .base_crawler import BaseCrawler +from ...domain.candidate import ( + Candidate, CandidateSource, CandidateStatus, + SalaryRange, WorkExperience, ProjectExperience, EducationExperience +) +from ...domain.resume import Resume, ResumeParsed +from ...domain.job import Job, JobStatus, JobRequirement +from ...domain.enums import Gender + + +class BossCrawler(BaseCrawler): + """ + Boss直聘爬虫实现 + + 基于 ylhp-boss-hr SDK 封装,提供统一的候选人数据获取接口 + """ + + def __init__(self, wt_token: str): + """ + 初始化 Boss 爬虫 + + Args: + wt_token: Boss 平台的 wt token + """ + if Boss is None: + raise ImportError("ylhp-boss-hr SDK is not installed") + self.client = Boss(wt=wt_token) + + @property + def source_type(self) -> CandidateSource: + return CandidateSource.BOSS + + def get_jobs(self, status: Optional[str] = None) -> List[Job]: + """获取职位列表""" + try: + jobs_data = self.client.get_jobs() + return [self._parse_job(job_data) for job_data in jobs_data] + except Exception as e: + print(f"Failed to get jobs from Boss: {e}") + return [] + + def get_candidates( + self, + job_id: str, + page: int = 1, + page_size: int = 20 + ) -> List[Candidate]: + """获取指定职位下的候选人列表""" + try: + geeks_data = self.client.geek_info(jobid=job_id, page=page) + return [self._parse_candidate(geek_data) for geek_data in geeks_data] + except Exception as e: + print(f"Failed to get candidates from Boss: {e}") + return [] + + def get_resume_detail(self, candidate: Candidate) -> Optional[Resume]: + """获取候选人简历详情""" + try: + # 获取候选人详情 + detail = self.client.get_detail(candidate) + + # 解密简历正文 + resume_text = self.client.get_detail_text(detail) + + # 解析简历 + parsed_content = self._parse_resume_text(resume_text) + + return Resume( + candidate_id=candidate.id, + raw_content=resume_text, + parsed_content=parsed_content, + version=1 + ) + except Exception as e: + print(f"Failed to get resume detail from Boss: {e}") + return None + + def _parse_job(self, job_data: Any) -> Job: + """解析职位数据""" + # 从 SDK 返回的数据中提取职位信息 + # 注意:具体字段名需要根据 SDK 实际返回调整 + return Job( + source=CandidateSource.BOSS, + source_id=getattr(job_data, 'encryptJobId', ''), + title=getattr(job_data, 'jobName', ''), + location=getattr(job_data, 'cityName', ''), + salary_min=self._parse_salary_min(getattr(job_data, 'salary', '')), + salary_max=self._parse_salary_max(getattr(job_data, 'salary', '')), + status=JobStatus.ACTIVE + ) + + def _parse_candidate(self, geek_data: Any) -> Candidate: + """解析候选人数据""" + # 从 SDK 返回的数据中提取候选人信息 + source_id = getattr(geek_data, 'geekId', '') or getattr(geek_data, 'encryptGeekId', '') + + # 解析薪资期望 + salary_str = getattr(geek_data, 'salary', '') + salary_range = self._parse_salary_range(salary_str) + + # 解析性别 + gender = self._parse_gender(getattr(geek_data, 'gender', '')) + + # 解析工作年限 + work_years = self._parse_work_years(getattr(geek_data, 'workYears', '')) + + return Candidate( + source=CandidateSource.BOSS, + source_id=str(source_id), + name=getattr(geek_data, 'name', ''), + gender=gender, + age=getattr(geek_data, 'age', None), + location=getattr(geek_data, 'location', None), + current_company=getattr(geek_data, 'company', None), + current_position=getattr(geek_data, 'position', None), + work_years=work_years, + education=getattr(geek_data, 'education', None), + school=getattr(geek_data, 'school', None), + salary_expectation=salary_range, + status=CandidateStatus.NEW + ) + + def _parse_resume_text(self, resume_text: str) -> ResumeParsed: + """解析简历文本为结构化数据""" + parsed = ResumeParsed() + + # 这里可以实现更复杂的简历解析逻辑 + # 目前为基础实现,可根据需要扩展 + + # 提取基本信息 + lines = resume_text.split('\n') + for line in lines: + line = line.strip() + if not line: + continue + + # 提取手机号 + if not parsed.phone: + phone_match = re.search(r'1[3-9]\d{9}', line) + if phone_match: + parsed.phone = phone_match.group() + + # 提取邮箱 + if not parsed.email: + email_match = re.search(r'[\w.-]+@[\w.-]+\.\w+', line) + if email_match: + parsed.email = email_match.group() + + parsed.raw_data = {"full_text": resume_text} + return parsed + + def _parse_salary_range(self, salary_str: str) -> Optional[SalaryRange]: + """解析薪资范围字符串""" + if not salary_str: + return None + + # 匹配 "15-25K" 或 "15K-25K" 格式 + match = re.search(r'(\d+)[\s-]*K?[\s-]*(\d+)?', salary_str, re.IGNORECASE) + if match: + min_sal = int(match.group(1)) + max_sal = int(match.group(2)) if match.group(2) else None + return SalaryRange(min_salary=min_sal, max_salary=max_sal) + + return None + + def _parse_salary_min(self, salary_str: str) -> Optional[int]: + """解析最低薪资""" + salary_range = self._parse_salary_range(salary_str) + return salary_range.min_salary if salary_range else None + + def _parse_salary_max(self, salary_str: str) -> Optional[int]: + """解析最高薪资""" + salary_range = self._parse_salary_range(salary_str) + return salary_range.max_salary if salary_range else None + + def _parse_gender(self, gender_str: str) -> Gender: + """解析性别""" + if not gender_str: + return Gender.UNKNOWN + + gender_str = str(gender_str).lower() + if gender_str in ['男', 'male', 'm', '1']: + return Gender.MALE + elif gender_str in ['女', 'female', 'f', '2']: + return Gender.FEMALE + return Gender.UNKNOWN + + def _parse_work_years(self, work_years_str: str) -> Optional[Decimal]: + """解析工作年限""" + if not work_years_str: + return None + + # 提取数字 + match = re.search(r'(\d+(?:\.\d+)?)', str(work_years_str)) + if match: + return Decimal(match.group(1)) + return None diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/crawler_factory.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/crawler_factory.py new file mode 100644 index 0000000..d969f50 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/crawler/crawler_factory.py @@ -0,0 +1,113 @@ +"""Crawler factory for managing crawler instances""" +from typing import Dict, Optional, Type + +from .base_crawler import BaseCrawler +from .boss_crawler import BossCrawler +from ...domain.candidate import CandidateSource + + +class CrawlerFactory: + """ + 爬虫工厂类 + + 管理所有爬虫实例的注册和获取,支持动态扩展新渠道 + + Usage: + # 注册爬虫 + factory = CrawlerFactory() + factory.register(CandidateSource.BOSS, BossCrawler(wt_token="xxx")) + + # 获取爬虫 + boss_crawler = factory.get_crawler(CandidateSource.BOSS) + """ + + def __init__(self): + self._crawlers: Dict[CandidateSource, BaseCrawler] = {} + + def register(self, source: CandidateSource, crawler: BaseCrawler) -> None: + """ + 注册爬虫实例 + + Args: + source: 渠道类型 + crawler: 爬虫实例 + """ + if not isinstance(crawler, BaseCrawler): + raise ValueError(f"Crawler must be instance of BaseCrawler, got {type(crawler)}") + + if crawler.source_type != source: + raise ValueError( + f"Crawler source type mismatch: " + f"expected {source}, got {crawler.source_type}" + ) + + self._crawlers[source] = crawler + + def get_crawler(self, source: CandidateSource) -> Optional[BaseCrawler]: + """ + 获取指定渠道的爬虫实例 + + Args: + source: 渠道类型 + + Returns: + 爬虫实例,如果未注册返回 None + """ + return self._crawlers.get(source) + + def has_crawler(self, source: CandidateSource) -> bool: + """ + 检查是否已注册指定渠道的爬虫 + + Args: + source: 渠道类型 + + Returns: + 是否已注册 + """ + return source in self._crawlers + + def unregister(self, source: CandidateSource) -> None: + """ + 注销指定渠道的爬虫 + + Args: + source: 渠道类型 + """ + if source in self._crawlers: + del self._crawlers[source] + + def get_all_crawlers(self) -> Dict[CandidateSource, BaseCrawler]: + """ + 获取所有已注册的爬虫 + + Returns: + 渠道类型到爬虫实例的映射字典 + """ + return self._crawlers.copy() + + def get_registered_sources(self) -> list: + """ + 获取所有已注册的渠道类型 + + Returns: + 渠道类型列表 + """ + return list(self._crawlers.keys()) + + +# 全局爬虫工厂实例 +crawler_factory = CrawlerFactory() + + +def create_boss_crawler(wt_token: str) -> BossCrawler: + """ + 创建 Boss 爬虫实例的便捷函数 + + Args: + wt_token: Boss 平台的 wt token + + Returns: + BossCrawler 实例 + """ + return BossCrawler(wt_token=wt_token) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/__init__.py new file mode 100644 index 0000000..9462c46 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/__init__.py @@ -0,0 +1,17 @@ +"""Ingestion service layer - Unified data ingestion""" + +from .unified_ingestion_service import UnifiedIngestionService, IngestionResult +from .data_normalizer import DataNormalizer, NormalizedData +from .data_validator import DataValidator, ValidationResult +from .deduplication_service import DeduplicationService, DuplicateCheckResult + +__all__ = [ + "UnifiedIngestionService", + "IngestionResult", + "DataNormalizer", + "NormalizedData", + "DataValidator", + "ValidationResult", + "DeduplicationService", + "DuplicateCheckResult", +] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_normalizer.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_normalizer.py new file mode 100644 index 0000000..3a7d9d5 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_normalizer.py @@ -0,0 +1,272 @@ +"""Data normalizer - Convert different source data to unified format""" +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Dict, Any, Optional +from decimal import Decimal + +from ...domain.candidate import ( + Candidate, CandidateSource, CandidateStatus, + SalaryRange, Gender +) +from ...domain.resume import Resume, ResumeParsed + + +@dataclass +class NormalizedData: + """标准化后的数据""" + candidate: Candidate + resume: Resume + raw_source_data: Optional[Dict[str, Any]] = None + + +class SourceNormalizer(ABC): + """数据源标准化器基类""" + + @property + @abstractmethod + def source_type(self) -> CandidateSource: + """返回处理的数据源类型""" + pass + + @abstractmethod + def normalize(self, raw_data: Dict[str, Any]) -> NormalizedData: + """ + 将原始数据标准化为统一格式 + + Args: + raw_data: 原始API返回数据 + + Returns: + 标准化后的数据 + """ + pass + + +class BossNormalizer(SourceNormalizer): + """Boss直聘数据标准化器""" + + @property + def source_type(self) -> CandidateSource: + return CandidateSource.BOSS + + def normalize(self, raw_data: Dict[str, Any]) -> NormalizedData: + """将Boss数据标准化""" + # 解析候选人信息 + candidate = self._parse_candidate(raw_data) + + # 解析简历信息 + resume = self._parse_resume(raw_data) + + return NormalizedData( + candidate=candidate, + resume=resume, + raw_source_data=raw_data + ) + + def _parse_candidate(self, data: Dict[str, Any]) -> Candidate: + """解析候选人信息""" + # 提取基本信息 + source_id = data.get('geekId') or data.get('encryptGeekId') or data.get('id', '') + name = data.get('name', '') + + # 解析性别 + gender = self._parse_gender(data.get('gender')) + + # 解析薪资期望 + salary_range = self._parse_salary(data.get('salary')) + + # 解析工作年限 + work_years = self._parse_work_years(data.get('workYears')) + + return Candidate( + source=CandidateSource.BOSS, + source_id=str(source_id), + name=name, + phone=data.get('phone'), + email=data.get('email'), + wechat=data.get('wechat'), + gender=gender, + age=data.get('age'), + location=data.get('location') or data.get('cityName'), + current_company=data.get('company') or data.get('currentCompany'), + current_position=data.get('position') or data.get('currentPosition'), + work_years=work_years, + education=data.get('education'), + school=data.get('school'), + salary_expectation=salary_range, + status=CandidateStatus.NEW + ) + + def _parse_resume(self, data: Dict[str, Any]) -> Resume: + """解析简历信息""" + # 获取简历文本 + resume_text = data.get('resumeText', '') or data.get('resumeContent', '') + + # 解析结构化内容 + parsed_content = self._parse_resume_content(data) + + return Resume( + raw_content=resume_text, + parsed_content=parsed_content, + version=1 + ) + + def _parse_resume_content(self, data: Dict[str, Any]) -> ResumeParsed: + """解析简历内容为结构化数据""" + parsed = ResumeParsed() + + # 填充基本信息 + parsed.name = data.get('name') + parsed.phone = data.get('phone') + parsed.email = data.get('email') + parsed.gender = data.get('gender') + parsed.age = data.get('age') + parsed.location = data.get('location') or data.get('cityName') + parsed.current_company = data.get('company') or data.get('currentCompany') + parsed.current_position = data.get('position') or data.get('currentPosition') + parsed.work_years = self._parse_work_years_to_float(data.get('workYears')) + parsed.education = data.get('education') + parsed.school = data.get('school') + + # 提取技能标签 + skills = data.get('skills', []) + if isinstance(skills, str): + skills = [s.strip() for s in skills.split(',') if s.strip()] + parsed.skills = skills or [] + + # 自我评价 + parsed.self_evaluation = data.get('selfEvaluation') or data.get('selfDescription') + + # 保存原始数据 + parsed.raw_data = data + + return parsed + + def _parse_gender(self, gender_value: Any) -> Gender: + """解析性别""" + if gender_value is None: + return Gender.UNKNOWN + + gender_str = str(gender_value).lower().strip() + + if gender_str in ('男', 'male', 'm', '1'): + return Gender.MALE + elif gender_str in ('女', 'female', 'f', '2'): + return Gender.FEMALE + + return Gender.UNKNOWN + + def _parse_salary(self, salary_value: Any) -> Optional[SalaryRange]: + """解析薪资范围""" + if not salary_value: + return None + + salary_str = str(salary_value) + + # 尝试匹配 "15-25K" 或 "15K-25K" 格式 + import re + match = re.search(r'(\d+)\s*[Kk]?\s*[-~~]\s*(\d+)\s*[Kk]?', salary_str) + if match: + return SalaryRange( + min_salary=int(match.group(1)), + max_salary=int(match.group(2)) + ) + + # 尝试匹配单个数字 + match = re.search(r'(\d+)\s*[Kk]', salary_str) + if match: + return SalaryRange(min_salary=int(match.group(1))) + + return None + + def _parse_work_years(self, work_years_value: Any) -> Optional[Decimal]: + """解析工作年限为Decimal""" + if work_years_value is None: + return None + + try: + # 如果是数字 + if isinstance(work_years_value, (int, float)): + return Decimal(str(work_years_value)) + + # 如果是字符串,提取数字 + import re + match = re.search(r'(\d+(?:\.\d+)?)', str(work_years_value)) + if match: + return Decimal(match.group(1)) + except (ValueError, TypeError): + pass + + return None + + def _parse_work_years_to_float(self, work_years_value: Any) -> Optional[float]: + """解析工作年限为float""" + decimal_val = self._parse_work_years(work_years_value) + return float(decimal_val) if decimal_val else None + + +class LiepinNormalizer(SourceNormalizer): + """猎聘数据标准化器(预留)""" + + @property + def source_type(self) -> CandidateSource: + return CandidateSource.LIEPIN + + def normalize(self, raw_data: Dict[str, Any]) -> NormalizedData: + # TODO: 实现猎聘数据标准化 + raise NotImplementedError("Liepin normalizer not implemented yet") + + +class DataNormalizer: + """ + 数据标准化器 + + 将不同渠道的数据统一转换为标准格式 + """ + + def __init__(self): + self._normalizers: Dict[CandidateSource, SourceNormalizer] = {} + self._register_default_normalizers() + + def _register_default_normalizers(self): + """注册默认的标准化器""" + self.register(BossNormalizer()) + # 猎聘标准化器暂不注册 + # self.register(LiepinNormalizer()) + + def register(self, normalizer: SourceNormalizer) -> None: + """ + 注册数据源标准化器 + + Args: + normalizer: 标准化器实例 + """ + self._normalizers[normalizer.source_type] = normalizer + + def normalize( + self, + source: CandidateSource, + raw_data: Dict[str, Any] + ) -> NormalizedData: + """ + 标准化数据 + + Args: + source: 数据来源 + raw_data: 原始数据 + + Returns: + 标准化后的数据 + + Raises: + ValueError: 如果没有对应的标准化器 + """ + normalizer = self._normalizers.get(source) + if not normalizer: + raise ValueError(f"No normalizer registered for source: {source}") + + return normalizer.normalize(raw_data) + + def has_normalizer(self, source: CandidateSource) -> bool: + """检查是否有对应的标准化器""" + return source in self._normalizers diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_validator.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_validator.py new file mode 100644 index 0000000..a8f156e --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/data_validator.py @@ -0,0 +1,157 @@ +"""Data validator - Validate normalized data before ingestion""" +from dataclasses import dataclass, field +from typing import List, Optional, Dict, Any + +from ...domain.candidate import Candidate +from ...domain.resume import Resume +from .data_normalizer import NormalizedData + + +@dataclass +class ValidationError: + """验证错误""" + field: str + message: str + code: str = "invalid" + + +@dataclass +class ValidationResult: + """验证结果""" + is_valid: bool + errors: List[ValidationError] = field(default_factory=list) + warnings: List[ValidationError] = field(default_factory=list) + + @property + def error_messages(self) -> List[str]: + """获取错误消息列表""" + return [f"{e.field}: {e.message}" for e in self.errors] + + @property + def warning_messages(self) -> List[str]: + """获取警告消息列表""" + return [f"{w.field}: {w.message}" for w in self.warnings] + + +class DataValidator: + """ + 数据验证器 + + 验证标准化后的数据是否符合入库要求 + """ + + def __init__(self): + self._rules: List[callable] = [] + self._register_default_rules() + + def _register_default_rules(self): + """注册默认验证规则""" + self._rules = [ + self._validate_candidate_name, + self._validate_candidate_source, + self._validate_candidate_source_id, + self._validate_resume_content, + ] + + def validate(self, data: NormalizedData) -> ValidationResult: + """ + 验证数据 + + Args: + data: 标准化后的数据 + + Returns: + 验证结果 + """ + errors = [] + warnings = [] + + for rule in self._rules: + result = rule(data) + if result: + if result.code == "error": + errors.append(result) + else: + warnings.append(result) + + return ValidationResult( + is_valid=len(errors) == 0, + errors=errors, + warnings=warnings + ) + + def _validate_candidate_name(self, data: NormalizedData) -> Optional[ValidationError]: + """验证候选人姓名""" + candidate = data.candidate + + if not candidate.name: + return ValidationError( + field="candidate.name", + message="候选人姓名不能为空", + code="error" + ) + + if len(candidate.name) < 2: + return ValidationError( + field="candidate.name", + message=f"候选人姓名过短: {candidate.name}", + code="warning" + ) + + return None + + def _validate_candidate_source(self, data: NormalizedData) -> Optional[ValidationError]: + """验证候选人来源""" + candidate = data.candidate + + if not candidate.source: + return ValidationError( + field="candidate.source", + message="候选人来源不能为空", + code="error" + ) + + return None + + def _validate_candidate_source_id(self, data: NormalizedData) -> Optional[ValidationError]: + """验证候选人来源ID""" + candidate = data.candidate + + if not candidate.source_id: + return ValidationError( + field="candidate.source_id", + message="候选人来源ID不能为空", + code="error" + ) + + return None + + def _validate_resume_content(self, data: NormalizedData) -> Optional[ValidationError]: + """验证简历内容""" + resume = data.resume + + if not resume.raw_content: + return ValidationError( + field="resume.raw_content", + message="简历内容为空", + code="warning" + ) + + # 检查简历内容长度 + if len(resume.raw_content) < 50: + return ValidationError( + field="resume.raw_content", + message=f"简历内容过短 ({len(resume.raw_content)} 字符)", + code="warning" + ) + + return None + + def add_rule(self, rule: callable) -> None: + """ + 添加自定义验证规则 + + Args: + rule: 验证规则函数,接收 NormalizedData 返回 ValidationError 或 None + """ + self._rules.append(rule) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/deduplication_service.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/deduplication_service.py new file mode 100644 index 0000000..885cfcf --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/deduplication_service.py @@ -0,0 +1,266 @@ +"""Deduplication service - Check and handle duplicate candidates""" +from dataclasses import dataclass +from typing import Optional, List, Dict, Any +from datetime import datetime + +from ...domain.candidate import Candidate, CandidateSource +from .data_normalizer import NormalizedData + + +@dataclass +class DuplicateCheckResult: + """去重检查结果""" + is_duplicate: bool + existing_candidate_id: Optional[str] = None + similarity: float = 0.0 # 相似度 0-1 + duplicate_type: Optional[str] = None # 'exact', 'fuzzy', 'phone', 'email' + message: str = "" + + +class DeduplicationService: + """ + 去重服务 + + 基于多维度检查候选人是否已存在: + 1. 精确匹配:source + source_id + 2. 手机号匹配 + 3. 邮箱匹配 + 4. 模糊匹配:姓名 + 公司 + 职位 + """ + + def __init__(self, candidate_repository=None): + """ + 初始化去重服务 + + Args: + candidate_repository: 候选人数据访问接口 + """ + self.candidate_repo = candidate_repository + + def check(self, data: NormalizedData) -> DuplicateCheckResult: + """ + 检查是否为重复候选人 + + Args: + data: 标准化后的数据 + + Returns: + 去重检查结果 + """ + candidate = data.candidate + + # 1. 精确匹配:source + source_id + exact_match = self._check_exact_match(candidate) + if exact_match.is_duplicate: + return exact_match + + # 2. 手机号匹配 + if candidate.phone: + phone_match = self._check_phone_match(candidate) + if phone_match.is_duplicate: + return phone_match + + # 3. 邮箱匹配 + if candidate.email: + email_match = self._check_email_match(candidate) + if email_match.is_duplicate: + return email_match + + # 4. 模糊匹配 + fuzzy_match = self._check_fuzzy_match(candidate) + if fuzzy_match.is_duplicate: + return fuzzy_match + + return DuplicateCheckResult(is_duplicate=False) + + def _check_exact_match(self, candidate: Candidate) -> DuplicateCheckResult: + """精确匹配检查""" + if not self.candidate_repo: + return DuplicateCheckResult(is_duplicate=False) + + existing = self.candidate_repo.find_by_source_and_source_id( + candidate.source, + candidate.source_id + ) + + if existing: + return DuplicateCheckResult( + is_duplicate=True, + existing_candidate_id=existing.id, + similarity=1.0, + duplicate_type='exact', + message=f"已存在相同来源的候选人: {existing.name}" + ) + + return DuplicateCheckResult(is_duplicate=False) + + def _check_phone_match(self, candidate: Candidate) -> DuplicateCheckResult: + """手机号匹配检查""" + if not self.candidate_repo or not candidate.phone: + return DuplicateCheckResult(is_duplicate=False) + + existing = self.candidate_repo.find_by_phone(candidate.phone) + + if existing: + return DuplicateCheckResult( + is_duplicate=True, + existing_candidate_id=existing.id, + similarity=0.9, + duplicate_type='phone', + message=f"已存在相同手机号的候选人: {existing.name}" + ) + + return DuplicateCheckResult(is_duplicate=False) + + def _check_email_match(self, candidate: Candidate) -> DuplicateCheckResult: + """邮箱匹配检查""" + if not self.candidate_repo or not candidate.email: + return DuplicateCheckResult(is_duplicate=False) + + existing = self.candidate_repo.find_by_email(candidate.email) + + if existing: + return DuplicateCheckResult( + is_duplicate=True, + existing_candidate_id=existing.id, + similarity=0.9, + duplicate_type='email', + message=f"已存在相同邮箱的候选人: {existing.name}" + ) + + return DuplicateCheckResult(is_duplicate=False) + + def _check_fuzzy_match(self, candidate: Candidate) -> DuplicateCheckResult: + """模糊匹配检查""" + if not self.candidate_repo: + return DuplicateCheckResult(is_duplicate=False) + + # 基于姓名、公司、职位的模糊匹配 + candidates = self.candidate_repo.find_by_name(candidate.name) + + for existing in candidates: + similarity = self._calculate_similarity(candidate, existing) + + if similarity >= 0.8: # 相似度阈值 + return DuplicateCheckResult( + is_duplicate=True, + existing_candidate_id=existing.id, + similarity=similarity, + duplicate_type='fuzzy', + message=f"发现高度相似的候选人: {existing.name} (相似度: {similarity:.2f})" + ) + + return DuplicateCheckResult(is_duplicate=False) + + def _calculate_similarity(self, c1: Candidate, c2: Candidate) -> float: + """ + 计算两个候选人的相似度 + + 基于以下维度: + - 姓名 (权重 0.3) + - 公司 (权重 0.3) + - 职位 (权重 0.2) + - 年龄 (权重 0.1) + - 学历 (权重 0.1) + """ + scores = [] + weights = [] + + # 姓名匹配 + if c1.name and c2.name: + name_sim = 1.0 if c1.name == c2.name else 0.0 + scores.append(name_sim) + weights.append(0.3) + + # 公司匹配 + if c1.current_company and c2.current_company: + company_sim = 1.0 if c1.current_company == c2.current_company else 0.0 + scores.append(company_sim) + weights.append(0.3) + + # 职位匹配 + if c1.current_position and c2.current_position: + position_sim = 1.0 if c1.current_position == c2.current_position else 0.0 + scores.append(position_sim) + weights.append(0.2) + + # 年龄匹配 + if c1.age and c2.age: + age_sim = 1.0 if c1.age == c2.age else 0.0 + scores.append(age_sim) + weights.append(0.1) + + # 学历匹配 + if c1.education and c2.education: + edu_sim = 1.0 if c1.education == c2.education else 0.0 + scores.append(edu_sim) + weights.append(0.1) + + if not scores: + return 0.0 + + # 加权平均 + total_weight = sum(weights) + weighted_sum = sum(s * w for s, w in zip(scores, weights)) + return weighted_sum / total_weight if total_weight > 0 else 0.0 + + +# 内存中的候选人存储(用于测试) +class InMemoryCandidateRepository: + """内存候选人存储(测试用)""" + + def __init__(self): + self._candidates: Dict[str, Candidate] = {} + self._by_source: Dict[tuple, str] = {} # (source, source_id) -> id + self._by_phone: Dict[str, str] = {} + self._by_email: Dict[str, str] = {} + self._by_name: Dict[str, List[str]] = {} + + def save(self, candidate: Candidate) -> Candidate: + """保存候选人""" + if not candidate.id: + import uuid + candidate.id = str(uuid.uuid4()) + + self._candidates[candidate.id] = candidate + + # 更新索引 + self._by_source[(candidate.source, candidate.source_id)] = candidate.id + + if candidate.phone: + self._by_phone[candidate.phone] = candidate.id + + if candidate.email: + self._by_email[candidate.email] = candidate.id + + if candidate.name: + if candidate.name not in self._by_name: + self._by_name[candidate.name] = [] + if candidate.id not in self._by_name[candidate.name]: + self._by_name[candidate.name].append(candidate.id) + + return candidate + + def find_by_source_and_source_id( + self, + source: CandidateSource, + source_id: str + ) -> Optional[Candidate]: + """根据来源和来源ID查找""" + candidate_id = self._by_source.get((source, source_id)) + return self._candidates.get(candidate_id) if candidate_id else None + + def find_by_phone(self, phone: str) -> Optional[Candidate]: + """根据手机号查找""" + candidate_id = self._by_phone.get(phone) + return self._candidates.get(candidate_id) if candidate_id else None + + def find_by_email(self, email: str) -> Optional[Candidate]: + """根据邮箱查找""" + candidate_id = self._by_email.get(email) + return self._candidates.get(candidate_id) if candidate_id else None + + def find_by_name(self, name: str) -> List[Candidate]: + """根据姓名查找""" + candidate_ids = self._by_name.get(name, []) + return [self._candidates[cid] for cid in candidate_ids if cid in self._candidates] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/unified_ingestion_service.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/unified_ingestion_service.py new file mode 100644 index 0000000..6462e54 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/ingestion/unified_ingestion_service.py @@ -0,0 +1,234 @@ +"""Unified ingestion service - Single entry point for all data sources""" +from dataclasses import dataclass +from typing import Optional, Dict, Any, Callable +from datetime import datetime +import uuid + +from ...domain.candidate import Candidate, CandidateSource, CandidateStatus +from ...domain.resume import Resume +from .data_normalizer import DataNormalizer, NormalizedData +from .data_validator import DataValidator, ValidationResult +from .deduplication_service import DeduplicationService, DuplicateCheckResult + + +@dataclass +class IngestionResult: + """入库结果""" + success: bool + candidate_id: Optional[str] = None + message: str = "" + errors: list = None + is_duplicate: bool = False + existing_candidate_id: Optional[str] = None + + @classmethod + def success_result(cls, candidate_id: str, message: str = "") -> "IngestionResult": + """创建成功结果""" + return cls( + success=True, + candidate_id=candidate_id, + message=message or "入库成功" + ) + + @classmethod + def failed_result(cls, errors: list, message: str = "") -> "IngestionResult": + """创建失败结果""" + return cls( + success=False, + message=message or "入库失败", + errors=errors or [] + ) + + @classmethod + def duplicate_result( + cls, + existing_id: str, + message: str = "" + ) -> "IngestionResult": + """创建重复结果""" + return cls( + success=True, # 重复不算失败 + is_duplicate=True, + existing_candidate_id=existing_id, + message=message or "候选人已存在" + ) + + +class UnifiedIngestionService: + """ + 统一数据入库服务 + + 所有渠道数据的唯一入口,负责: + 1. 数据标准化 + 2. 数据验证 + 3. 去重检查 + 4. 数据入库 + 5. 触发后续处理(分析、通知等) + + Usage: + service = UnifiedIngestionService( + candidate_repo=candidate_repo, + resume_repo=resume_repo, + normalizer=DataNormalizer(), + validator=DataValidator(), + deduplicator=DeduplicationService() + ) + + result = service.ingest( + source=CandidateSource.BOSS, + raw_data={...} + ) + """ + + def __init__( + self, + candidate_repo=None, + resume_repo=None, + normalizer: Optional[DataNormalizer] = None, + validator: Optional[DataValidator] = None, + deduplicator: Optional[DeduplicationService] = None, + on_analysis_triggered: Optional[Callable[[str], None]] = None + ): + """ + 初始化统一入库服务 + + Args: + candidate_repo: 候选人数据访问接口 + resume_repo: 简历数据访问接口 + normalizer: 数据标准化器 + validator: 数据验证器 + deduplicator: 去重服务 + on_analysis_triggered: 分析触发回调函数 + """ + self.candidate_repo = candidate_repo + self.resume_repo = resume_repo + self.normalizer = normalizer or DataNormalizer() + self.validator = validator or DataValidator() + self.deduplicator = deduplicator + self.on_analysis_triggered = on_analysis_triggered + + def ingest( + self, + source: CandidateSource, + raw_data: Dict[str, Any] + ) -> IngestionResult: + """ + 统一入库入口 + + 流程: + 1. 数据标准化 + 2. 数据验证 + 3. 去重检查 + 4. 保存候选人 + 5. 保存简历内容 + 6. 触发分析任务 + + Args: + source: 数据来源 + raw_data: 原始数据 + + Returns: + 入库结果 + """ + try: + # 1. 数据标准化 + normalized = self._normalize(source, raw_data) + + # 2. 数据验证 + validation_result = self._validate(normalized) + if not validation_result.is_valid: + return IngestionResult.failed_result( + errors=validation_result.error_messages, + message="数据验证失败" + ) + + # 3. 去重检查 + if self.deduplicator: + duplicate_check = self.deduplicator.check(normalized) + if duplicate_check.is_duplicate: + return self._handle_duplicate(normalized, duplicate_check) + + # 4. 生成ID + candidate_id = self._generate_id() + normalized.candidate.id = candidate_id + normalized.resume.candidate_id = candidate_id + + # 5. 保存候选人 + if self.candidate_repo: + self.candidate_repo.save(normalized.candidate) + + # 6. 保存简历 + if self.resume_repo: + self.resume_repo.save(normalized.resume) + + # 7. 触发分析 + self._trigger_analysis(candidate_id) + + return IngestionResult.success_result( + candidate_id=candidate_id, + message=f"候选人 {normalized.candidate.name} 入库成功" + ) + + except Exception as e: + return IngestionResult.failed_result( + errors=[str(e)], + message=f"入库异常: {str(e)}" + ) + + def _normalize( + self, + source: CandidateSource, + raw_data: Dict[str, Any] + ) -> NormalizedData: + """数据标准化""" + return self.normalizer.normalize(source, raw_data) + + def _validate(self, normalized: NormalizedData) -> ValidationResult: + """数据验证""" + return self.validator.validate(normalized) + + def _handle_duplicate( + self, + normalized: NormalizedData, + duplicate_check: DuplicateCheckResult + ) -> IngestionResult: + """处理重复数据""" + # 可以选择更新现有记录或跳过 + # 这里选择返回重复信息,不更新 + return IngestionResult.duplicate_result( + existing_id=duplicate_check.existing_candidate_id, + message=duplicate_check.message + ) + + def _generate_id(self) -> str: + """生成唯一ID""" + return str(uuid.uuid4()) + + def _trigger_analysis(self, candidate_id: str) -> None: + """触发分析任务""" + if self.on_analysis_triggered: + try: + self.on_analysis_triggered(candidate_id) + except Exception as e: + print(f"Failed to trigger analysis for {candidate_id}: {e}") + + def batch_ingest( + self, + source: CandidateSource, + raw_data_list: list + ) -> list: + """ + 批量入库 + + Args: + source: 数据来源 + raw_data_list: 原始数据列表 + + Returns: + 入库结果列表 + """ + results = [] + for raw_data in raw_data_list: + result = self.ingest(source, raw_data) + results.append(result) + return results diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/__init__.py new file mode 100644 index 0000000..cf900b2 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/__init__.py @@ -0,0 +1,21 @@ +"""Notification service layer - Multi-channel notification""" + +from .notification_service import NotificationService, NotificationResult +from .message_template import MessageTemplate, MessageTemplateEngine +from .channels.base_channel import NotificationChannel, NotificationMessage, SendResult +from .channels.wechat_work_channel import WeChatWorkChannel +from .channels.dingtalk_channel import DingTalkChannel +from .channels.email_channel import EmailChannel + +__all__ = [ + "NotificationService", + "NotificationResult", + "MessageTemplate", + "MessageTemplateEngine", + "NotificationChannel", + "NotificationMessage", + "SendResult", + "WeChatWorkChannel", + "DingTalkChannel", + "EmailChannel", +] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/__init__.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/__init__.py new file mode 100644 index 0000000..95720ad --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/__init__.py @@ -0,0 +1,15 @@ +"""Notification channels""" + +from .base_channel import NotificationChannel, NotificationMessage, SendResult +from .wechat_work_channel import WeChatWorkChannel +from .dingtalk_channel import DingTalkChannel +from .email_channel import EmailChannel + +__all__ = [ + "NotificationChannel", + "NotificationMessage", + "SendResult", + "WeChatWorkChannel", + "DingTalkChannel", + "EmailChannel", +] diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/base_channel.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/base_channel.py new file mode 100644 index 0000000..759b8a6 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/base_channel.py @@ -0,0 +1,89 @@ +"""Base notification channel""" +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import Optional, Dict, Any, List +from datetime import datetime + +from ....domain.candidate import Candidate +from ....domain.evaluation import Evaluation +from ....domain.enums import ChannelType + + +@dataclass +class NotificationMessage: + """通知消息""" + title: str = "" + content: str = "" + candidate: Optional[Candidate] = None + evaluation: Optional[Evaluation] = None + extra_data: Dict[str, Any] = field(default_factory=dict) + timestamp: datetime = field(default_factory=datetime.now) + + def to_dict(self) -> Dict[str, Any]: + """转换为字典""" + return { + "title": self.title, + "content": self.content, + "candidate": self.candidate.to_dict() if self.candidate else None, + "evaluation": self.evaluation.to_dict() if self.evaluation else None, + "extra_data": self.extra_data, + "timestamp": self.timestamp.isoformat() + } + + +@dataclass +class SendResult: + """发送结果""" + success: bool + message_id: Optional[str] = None + error_message: Optional[str] = None + response_data: Optional[Dict[str, Any]] = None + timestamp: datetime = field(default_factory=datetime.now) + + +class NotificationChannel(ABC): + """ + 通知渠道抽象基类 + + 所有通知渠道需要实现此接口 + """ + + @property + @abstractmethod + def channel_type(self) -> ChannelType: + """返回渠道类型""" + pass + + @abstractmethod + async def send(self, message: NotificationMessage) -> SendResult: + """ + 发送消息 + + Args: + message: 通知消息 + + Returns: + 发送结果 + """ + pass + + @abstractmethod + def is_configured(self) -> bool: + """检查渠道是否已配置""" + pass + + def format_message(self, message: NotificationMessage) -> Dict[str, Any]: + """ + 格式化消息为渠道特定格式 + + Args: + message: 通知消息 + + Returns: + 格式化后的消息字典 + """ + # 默认实现,子类可以覆盖 + return { + "title": message.title, + "content": message.content + } diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/dingtalk_channel.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/dingtalk_channel.py new file mode 100644 index 0000000..8fa8416 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/dingtalk_channel.py @@ -0,0 +1,183 @@ +"""DingTalk notification channel""" +from typing import Optional, Dict, Any +import json +import hmac +import hashlib +import base64 +import time + +from .base_channel import NotificationChannel, NotificationMessage, SendResult +from ....domain.enums import ChannelType + + +class DingTalkChannel(NotificationChannel): + """ + 钉钉通知渠道 + + 通过钉钉机器人 Webhook 发送消息 + """ + + def __init__( + self, + webhook_url: str, + secret: Optional[str] = None, + at_mobiles: Optional[list] = None, + is_at_all: bool = False + ): + """ + 初始化钉钉渠道 + + Args: + webhook_url: 钉钉机器人 Webhook 地址 + secret: 安全设置中的加签密钥 + at_mobiles: @提醒的手机号列表 + is_at_all: 是否@所有人 + """ + self.webhook_url = webhook_url + self.secret = secret + self.at_mobiles = at_mobiles or [] + self.is_at_all = is_at_all + + @property + def channel_type(self) -> ChannelType: + return ChannelType.DINGTALK + + def is_configured(self) -> bool: + """检查是否已配置""" + return bool(self.webhook_url) + + async def send(self, message: NotificationMessage) -> SendResult: + """发送钉钉消息""" + if not self.is_configured(): + return SendResult( + success=False, + error_message="Webhook URL not configured" + ) + + try: + # 构建带签名的 URL + url = self._build_signed_url() + + # 构建消息体 + payload = self._build_payload(message) + + # 发送请求 + import aiohttp + async with aiohttp.ClientSession() as session: + async with session.post( + url, + json=payload, + timeout=aiohttp.ClientTimeout(total=30) + ) as response: + result = await response.json() + + if result.get("errcode") == 0: + return SendResult( + success=True, + response_data=result + ) + else: + return SendResult( + success=False, + error_message=f"DingTalk API error: {result.get('errmsg')}" + ) + + except Exception as e: + return SendResult( + success=False, + error_message=f"Failed to send DingTalk message: {str(e)}" + ) + + def _build_signed_url(self) -> str: + """构建带签名的 URL""" + if not self.secret: + return self.webhook_url + + timestamp = str(round(time.time() * 1000)) + string_to_sign = f"{timestamp}\n{self.secret}" + + hmac_code = hmac.new( + self.secret.encode('utf-8'), + string_to_sign.encode('utf-8'), + digestmod=hashlib.sha256 + ).digest() + + sign = base64.b64encode(hmac_code).decode('utf-8') + + return f"{self.webhook_url}×tamp={timestamp}&sign={sign}" + + def _build_payload(self, message: NotificationMessage) -> Dict[str, Any]: + """构建钉钉消息体""" + # 使用 markdown 格式 + return { + "msgtype": "markdown", + "markdown": { + "title": message.title, + "text": self._format_content(message) + }, + "at": { + "atMobiles": self.at_mobiles, + "isAtAll": self.is_at_all + } + } + + def _format_content(self, message: NotificationMessage) -> str: + """格式化消息内容""" + lines = [] + + # 标题 + if message.title: + lines.append(f"### {message.title}") + + # 内容 + if message.content: + lines.append(message.content) + + # 候选人信息 + if message.candidate: + candidate = message.candidate + lines.append("\n**候选人信息:**") + lines.append(f"- 姓名:{candidate.name}") + if candidate.age: + lines.append(f"- 年龄:{candidate.age}岁") + if candidate.work_years: + lines.append(f"- 工作年限:{candidate.work_years}年") + if candidate.current_company: + lines.append(f"- 当前公司:{candidate.current_company}") + if candidate.current_position: + lines.append(f"- 当前职位:{candidate.current_position}") + if candidate.phone: + lines.append(f"- 联系方式:{candidate.phone}") + + # 评价信息 + if message.evaluation: + evaluation = message.evaluation + lines.append("\n**AI 评价:**") + lines.append(f"- 综合评分:**{evaluation.overall_score}/100**") + if evaluation.recommendation: + lines.append(f"- 推荐意见:{self._format_recommendation(evaluation.recommendation.value)}") + if evaluation.summary: + lines.append(f"- 评价摘要:{evaluation.summary}") + + if evaluation.strengths: + lines.append(f"- 优势:{', '.join(evaluation.strengths[:3])}") + + # @提醒 + if self.at_mobiles: + for mobile in self.at_mobiles: + lines.append(f"@{mobile}") + + if self.is_at_all: + lines.append("@所有人") + + return '\n'.join(lines) + + def _format_recommendation(self, value: str) -> str: + """格式化推荐意见""" + mapping = { + "strong_recommend": "**强烈推荐**", + "recommend": "**推荐**", + "consider": "**考虑**", + "not_recommend": "**不推荐**" + } + return mapping.get(value, value) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/email_channel.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/email_channel.py new file mode 100644 index 0000000..27cd4d3 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/email_channel.py @@ -0,0 +1,226 @@ +"""Email notification channel""" +from typing import Optional, Dict, Any, List +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +from .base_channel import NotificationChannel, NotificationMessage, SendResult +from ....domain.enums import ChannelType + + +class EmailChannel(NotificationChannel): + """ + 邮件通知渠道 + + 通过 SMTP 发送邮件通知 + """ + + def __init__( + self, + smtp_host: str, + smtp_port: int, + username: str, + password: str, + from_addr: str, + to_addrs: List[str], + use_tls: bool = True + ): + """ + 初始化邮件渠道 + + Args: + smtp_host: SMTP 服务器地址 + smtp_port: SMTP 端口 + username: 用户名 + password: 密码 + from_addr: 发件人地址 + to_addrs: 收件人地址列表 + use_tls: 是否使用 TLS + """ + self.smtp_host = smtp_host + self.smtp_port = smtp_port + self.username = username + self.password = password + self.from_addr = from_addr + self.to_addrs = to_addrs + self.use_tls = use_tls + + @property + def channel_type(self) -> ChannelType: + return ChannelType.EMAIL + + def is_configured(self) -> bool: + """检查是否已配置""" + return all([ + self.smtp_host, + self.smtp_port, + self.username, + self.password, + self.from_addr, + self.to_addrs + ]) + + async def send(self, message: NotificationMessage) -> SendResult: + """发送邮件""" + if not self.is_configured(): + return SendResult( + success=False, + error_message="Email not configured" + ) + + try: + # 构建邮件 + msg = self._build_message(message) + + # 发送邮件(使用线程池避免阻塞) + import asyncio + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, self._send_sync, msg) + + return SendResult(success=True) + + except Exception as e: + return SendResult( + success=False, + error_message=f"Failed to send email: {str(e)}" + ) + + def _build_message(self, message: NotificationMessage) -> MIMEMultipart: + """构建邮件消息""" + msg = MIMEMultipart('alternative') + msg['Subject'] = message.title or "候选人推荐通知" + msg['From'] = self.from_addr + msg['To'] = ', '.join(self.to_addrs) + + # 纯文本内容 + text_content = self._format_text_content(message) + msg.attach(MIMEText(text_content, 'plain', 'utf-8')) + + # HTML 内容 + html_content = self._format_html_content(message) + msg.attach(MIMEText(html_content, 'html', 'utf-8')) + + return msg + + def _send_sync(self, msg: MIMEMultipart): + """同步发送邮件""" + import smtplib + + with smtplib.SMTP(self.smtp_host, self.smtp_port) as server: + if self.use_tls: + server.starttls() + server.login(self.username, self.password) + server.send_message(msg) + + def _format_text_content(self, message: NotificationMessage) -> str: + """格式化纯文本内容""" + lines = [] + + if message.title: + lines.append(message.title) + lines.append("=" * len(message.title)) + + if message.content: + lines.append(message.content) + + if message.candidate: + candidate = message.candidate + lines.append("\n【候选人信息】") + lines.append(f"姓名:{candidate.name}") + if candidate.age: + lines.append(f"年龄:{candidate.age}岁") + if candidate.work_years: + lines.append(f"工作年限:{candidate.work_years}年") + if candidate.current_company: + lines.append(f"当前公司:{candidate.current_company}") + if candidate.current_position: + lines.append(f"当前职位:{candidate.current_position}") + if candidate.phone: + lines.append(f"联系方式:{candidate.phone}") + if candidate.email: + lines.append(f"邮箱:{candidate.email}") + + if message.evaluation: + evaluation = message.evaluation + lines.append("\n【AI 评价】") + lines.append(f"综合评分:{evaluation.overall_score}/100") + if evaluation.recommendation: + lines.append(f"推荐意见:{self._format_recommendation(evaluation.recommendation.value)}") + if evaluation.summary: + lines.append(f"评价摘要:{evaluation.summary}") + if evaluation.strengths: + lines.append(f"优势:{', '.join(evaluation.strengths)}") + if evaluation.weaknesses: + lines.append(f"不足:{', '.join(evaluation.weaknesses)}") + + return '\n'.join(lines) + + def _format_html_content(self, message: NotificationMessage) -> str: + """格式化 HTML 内容""" + html_parts = [] + + html_parts.append("") + + if message.title: + html_parts.append(f"

{message.title}

") + + if message.content: + html_parts.append(f"

{message.content}

") + + if message.candidate: + candidate = message.candidate + html_parts.append("

候选人信息

") + html_parts.append("") + + if message.evaluation: + evaluation = message.evaluation + html_parts.append("

AI 评价

") + html_parts.append("") + + html_parts.append("") + + return ''.join(html_parts) + + def _format_recommendation(self, value: str) -> str: + """格式化推荐意见""" + mapping = { + "strong_recommend": "强烈推荐", + "recommend": "推荐", + "consider": "考虑", + "not_recommend": "不推荐" + } + return mapping.get(value, value) + + def _get_recommendation_color(self, value: str) -> str: + """获取推荐意见对应的颜色""" + mapping = { + "strong_recommend": "#52c41a", + "recommend": "#1890ff", + "consider": "#faad14", + "not_recommend": "#f5222d" + } + return mapping.get(value, "#000000") diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/wechat_work_channel.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/wechat_work_channel.py new file mode 100644 index 0000000..98c3eec --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/channels/wechat_work_channel.py @@ -0,0 +1,167 @@ +"""WeChat Work (Enterprise WeChat) notification channel""" +from typing import Optional, Dict, Any +import json + +from .base_channel import NotificationChannel, NotificationMessage, SendResult +from ....domain.enums import ChannelType + + +class WeChatWorkChannel(NotificationChannel): + """ + 企业微信通知渠道 + + 通过企业微信机器人 Webhook 发送消息 + """ + + def __init__(self, webhook_url: str, mentioned_list: Optional[list] = None): + """ + 初始化企业微信渠道 + + Args: + webhook_url: 企业微信机器人 Webhook 地址 + mentioned_list: @提醒的成员列表,如 ["@all"] 或 ["UserID1", "UserID2"] + """ + self.webhook_url = webhook_url + self.mentioned_list = mentioned_list or [] + self._session = None + + @property + def channel_type(self) -> ChannelType: + return ChannelType.WECHAT_WORK + + def is_configured(self) -> bool: + """检查是否已配置""" + return bool(self.webhook_url) + + async def send(self, message: NotificationMessage) -> SendResult: + """发送企业微信消息""" + if not self.is_configured(): + return SendResult( + success=False, + error_message="Webhook URL not configured" + ) + + try: + payload = self._build_payload(message) + + # 发送请求 + import aiohttp + async with aiohttp.ClientSession() as session: + async with session.post( + self.webhook_url, + json=payload, + timeout=aiohttp.ClientTimeout(total=30) + ) as response: + result = await response.json() + + if result.get("errcode") == 0: + return SendResult( + success=True, + message_id=result.get("msgid"), + response_data=result + ) + else: + return SendResult( + success=False, + error_message=f"WeChat Work API error: {result.get('errmsg')}" + ) + + except Exception as e: + return SendResult( + success=False, + error_message=f"Failed to send WeChat Work message: {str(e)}" + ) + + def _build_payload(self, message: NotificationMessage) -> Dict[str, Any]: + """构建企业微信消息体""" + # 使用 markdown 格式 + content = self._format_content(message) + + return { + "msgtype": "markdown", + "markdown": { + "content": content + } + } + + def _format_content(self, message: NotificationMessage) -> str: + """格式化消息内容""" + lines = [] + + # 标题 + if message.title: + lines.append(f"## {message.title}") + + # 内容 + if message.content: + lines.append(message.content) + + # 候选人信息 + if message.candidate: + candidate = message.candidate + lines.append("\n**候选人信息:**") + lines.append(f"- 姓名:{candidate.name}") + if candidate.age: + lines.append(f"- 年龄:{candidate.age}岁") + if candidate.work_years: + lines.append(f"- 工作年限:{candidate.work_years}年") + if candidate.current_company: + lines.append(f"- 当前公司:{candidate.current_company}") + if candidate.current_position: + lines.append(f"- 当前职位:{candidate.current_position}") + if candidate.phone: + lines.append(f"- 联系方式:{candidate.phone}") + + # 评价信息 + if message.evaluation: + evaluation = message.evaluation + lines.append("\n**AI 评价:**") + lines.append(f"- 综合评分:{evaluation.overall_score}/100") + if evaluation.recommendation: + lines.append(f"- 推荐意见:{self._format_recommendation(evaluation.recommendation.value)}") + if evaluation.summary: + lines.append(f"- 评价摘要:{evaluation.summary}") + + if evaluation.strengths: + lines.append(f"- 优势:{', '.join(evaluation.strengths[:3])}") + + # @提醒 + if self.mentioned_list: + mentions = ' '.join(self.mentioned_list) + lines.append(f"\n{mentions}") + + return '\n'.join(lines) + + def _format_recommendation(self, value: str) -> str: + """格式化推荐意见""" + mapping = { + "strong_recommend": "强烈推荐", + "recommend": "推荐", + "consider": "考虑", + "not_recommend": "不推荐" + } + return mapping.get(value, value) + + +class WeChatWorkTextChannel(WeChatWorkChannel): + """ + 企业微信文本消息渠道 + + 发送纯文本消息 + """ + + def _build_payload(self, message: NotificationMessage) -> Dict[str, Any]: + """构建文本消息体""" + content = message.content or message.title + + payload = { + "msgtype": "text", + "text": { + "content": content + } + } + + if self.mentioned_list: + payload["text"]["mentioned_list"] = self.mentioned_list + + return payload diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/message_template.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/message_template.py new file mode 100644 index 0000000..1a67101 --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/message_template.py @@ -0,0 +1,245 @@ +"""Message template engine""" +from typing import Optional, Dict, Any +import re + +from ...domain.candidate import Candidate +from ...domain.evaluation import Evaluation + + +class MessageTemplate: + """ + 消息模板 + + 支持简单的变量替换语法:{{variable}} 或 {{object.property}} + """ + + DEFAULT_TEMPLATE = """【人才推荐】{{candidate.name}} + +基本信息: +- 年龄:{{candidate.age}}岁 +- 工作年限:{{candidate.work_years}}年 +- 当前公司:{{candidate.current_company}} +- 当前职位:{{candidate.current_position}} +- 学历:{{candidate.education}} +- 期望薪资:{{candidate.salary_expectation}} + +AI评价: +- 综合评分:{{evaluation.overall_score}}/100 +- 推荐意见:{{evaluation.recommendation}} +- 评价摘要:{{evaluation.summary}} + +优势: +{{#each evaluation.strengths}} +• {{this}} +{{/each}} + +联系方式:{{candidate.phone}} +""" + + def __init__(self, template: Optional[str] = None): + """ + 初始化消息模板 + + Args: + template: 模板字符串,不传使用默认模板 + """ + self.template = template or self.DEFAULT_TEMPLATE + + def render( + self, + candidate: Candidate, + evaluation: Evaluation, + extra_data: Optional[Dict[str, Any]] = None + ) -> str: + """ + 渲染模板 + + Args: + candidate: 候选人信息 + evaluation: 评价结果 + extra_data: 额外数据 + + Returns: + 渲染后的消息内容 + """ + data = { + "candidate": candidate, + "evaluation": evaluation, + **(extra_data or {}) + } + + return self._render_template(self.template, data) + + def _render_template(self, template: str, data: Dict[str, Any]) -> str: + """渲染模板""" + result = template + + # 处理 each 循环 + result = self._process_each(result, data) + + # 处理简单变量 + result = self._process_variables(result, data) + + return result + + def _process_variables(self, template: str, data: Dict[str, Any]) -> str: + """处理变量替换""" + def replace_var(match): + var_path = match.group(1).strip() + value = self._get_value(data, var_path) + + if value is None: + return "" + + # 处理枚举类型 + if hasattr(value, 'value'): + value = self._format_enum_value(value.value) + + return str(value) + + # 匹配 {{variable}} 格式 + pattern = r'\{\{(.*?)\}\}' + return re.sub(pattern, replace_var, template) + + def _process_each(self, template: str, data: Dict[str, Any]) -> str: + """处理 each 循环""" + pattern = r'\{\{#each\s+(.*?)\}\}(.*?)\{\{/each\}\}' + + def replace_each(match): + var_path = match.group(1).strip() + inner_template = match.group(2) + + items = self._get_value(data, var_path) + if not items or not isinstance(items, list): + return "" + + results = [] + for item in items: + item_data = {**data, "this": item} + rendered = self._process_variables(inner_template, item_data) + results.append(rendered) + + return ''.join(results) + + return re.sub(pattern, replace_each, template, flags=re.DOTALL) + + def _get_value(self, data: Dict[str, Any], path: str) -> Any: + """根据路径获取值""" + parts = path.split('.') + value = data + + for part in parts: + if value is None: + return None + + if isinstance(value, dict): + value = value.get(part) + elif hasattr(value, part): + value = getattr(value, part) + else: + return None + + return value + + def _format_enum_value(self, value: str) -> str: + """格式化枚举值""" + mapping = { + "strong_recommend": "强烈推荐", + "recommend": "推荐", + "consider": "考虑", + "not_recommend": "不推荐", + "male": "男", + "female": "女", + "unknown": "未知" + } + return mapping.get(value, value) + + +class MessageTemplateEngine: + """ + 消息模板引擎 + + 管理多个模板,支持按名称获取 + """ + + def __init__(self): + self._templates: Dict[str, MessageTemplate] = {} + self._register_default_templates() + + def _register_default_templates(self): + """注册默认模板""" + # 简洁模板 + self.register("simple", """【人才推荐】{{candidate.name}} + +{{candidate.current_company}} | {{candidate.current_position}} | {{candidate.work_years}}年经验 +评分:{{evaluation.overall_score}}/100 | {{evaluation.recommendation}} +联系方式:{{candidate.phone}} +""") + + # 详细模板 + self.register("detailed", MessageTemplate.DEFAULT_TEMPLATE) + + # 仅评价模板 + self.register("evaluation_only", """【AI评价】{{candidate.name}} + +综合评分:{{evaluation.overall_score}}/100 +推荐意见:{{evaluation.recommendation}} +评价摘要:{{evaluation.summary}} + +优势:{{#each evaluation.strengths}} {{this}} {{/each}} +不足:{{#each evaluation.weaknesses}} {{this}} {{/each}} +""") + + def register(self, name: str, template: str) -> None: + """ + 注册模板 + + Args: + name: 模板名称 + template: 模板字符串或 MessageTemplate 对象 + """ + if isinstance(template, str): + template = MessageTemplate(template) + self._templates[name] = template + + def get(self, name: str) -> Optional[MessageTemplate]: + """ + 获取模板 + + Args: + name: 模板名称 + + Returns: + 模板对象,不存在返回 None + """ + return self._templates.get(name) + + def render( + self, + template_name: str, + candidate: Candidate, + evaluation: Evaluation, + extra_data: Optional[Dict[str, Any]] = None + ) -> str: + """ + 使用指定模板渲染 + + Args: + template_name: 模板名称 + candidate: 候选人信息 + evaluation: 评价结果 + extra_data: 额外数据 + + Returns: + 渲染后的内容 + """ + template = self.get(template_name) + if not template: + # 使用默认模板 + template = MessageTemplate() + + return template.render(candidate, evaluation, extra_data) + + def list_templates(self) -> list: + """获取所有模板名称""" + return list(self._templates.keys()) diff --git a/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/notification_service.py b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/notification_service.py new file mode 100644 index 0000000..fff44de --- /dev/null +++ b/src/main/python/cn/yinlihupo/ylhp_hr_2.0/service/notification/notification_service.py @@ -0,0 +1,211 @@ +"""Notification service - Multi-channel notification""" +from typing import Dict, List, Optional +from dataclasses import dataclass, field +from datetime import datetime + +from .channels.base_channel import NotificationChannel, NotificationMessage, SendResult +from .message_template import MessageTemplateEngine +from ...domain.candidate import Candidate +from ...domain.evaluation import Evaluation +from ...domain.enums import ChannelType + + +@dataclass +class NotificationResult: + """通知结果""" + success: bool + channel_results: Dict[ChannelType, SendResult] = field(default_factory=dict) + failed_channels: List[ChannelType] = field(default_factory=list) + message: str = "" + timestamp: datetime = field(default_factory=datetime.now) + + @property + def all_success(self) -> bool: + """是否所有渠道都成功""" + return len(self.failed_channels) == 0 + + @property + def success_count(self) -> int: + """成功渠道数""" + return len(self.channel_results) - len(self.failed_channels) + + +class NotificationService: + """ + 通知服务 + + 统一的多渠道通知入口,支持: + - 企业微信 + - 钉钉 + - 邮件 + - Webhook + """ + + def __init__( + self, + channels: Optional[Dict[ChannelType, NotificationChannel]] = None, + template_engine: Optional[MessageTemplateEngine] = None + ): + """ + 初始化通知服务 + + Args: + channels: 渠道配置字典 + template_engine: 模板引擎 + """ + self.channels = channels or {} + self.template_engine = template_engine or MessageTemplateEngine() + + def register_channel(self, channel: NotificationChannel) -> None: + """ + 注册通知渠道 + + Args: + channel: 渠道实例 + """ + self.channels[channel.channel_type] = channel + + def unregister_channel(self, channel_type: ChannelType) -> None: + """ + 注销通知渠道 + + Args: + channel_type: 渠道类型 + """ + if channel_type in self.channels: + del self.channels[channel_type] + + async def notify( + self, + candidate: Candidate, + evaluation: Evaluation, + channels: Optional[List[ChannelType]] = None, + template_name: Optional[str] = None, + title: Optional[str] = None, + extra_data: Optional[Dict] = None + ) -> NotificationResult: + """ + 发送候选人通知 + + Args: + candidate: 候选人信息 + evaluation: 评价结果 + channels: 通知渠道列表,不传使用所有已配置渠道 + template_name: 消息模板名称 + title: 消息标题(覆盖模板中的标题) + extra_data: 额外数据 + + Returns: + 通知结果 + """ + # 确定要使用的渠道 + target_channels = channels or list(self.channels.keys()) + + # 构建消息内容 + content = self._build_message( + candidate=candidate, + evaluation=evaluation, + template_name=template_name, + extra_data=extra_data + ) + + # 构建消息对象 + message = NotificationMessage( + title=title or f"【人才推荐】{candidate.name}", + content=content, + candidate=candidate, + evaluation=evaluation, + extra_data=extra_data or {} + ) + + # 发送到各渠道 + channel_results = {} + failed_channels = [] + + for channel_type in target_channels: + channel = self.channels.get(channel_type) + + if not channel: + failed_channels.append(channel_type) + continue + + if not channel.is_configured(): + failed_channels.append(channel_type) + continue + + try: + result = await channel.send(message) + channel_results[channel_type] = result + + if not result.success: + failed_channels.append(channel_type) + + except Exception as e: + channel_results[channel_type] = SendResult( + success=False, + error_message=str(e) + ) + failed_channels.append(channel_type) + + # 构建结果 + success = len(failed_channels) < len(target_channels) + message = self._build_result_message(success, failed_channels, target_channels) + + return NotificationResult( + success=success, + channel_results=channel_results, + failed_channels=failed_channels, + message=message + ) + + def _build_message( + self, + candidate: Candidate, + evaluation: Evaluation, + template_name: Optional[str] = None, + extra_data: Optional[Dict] = None + ) -> str: + """构建消息内容""" + if template_name: + return self.template_engine.render( + template_name=template_name, + candidate=candidate, + evaluation=evaluation, + extra_data=extra_data + ) + + # 使用默认模板 + return self.template_engine.render( + template_name="detailed", + candidate=candidate, + evaluation=evaluation, + extra_data=extra_data + ) + + def _build_result_message( + self, + success: bool, + failed_channels: List[ChannelType], + target_channels: List[ChannelType] + ) -> str: + """构建结果消息""" + if success and not failed_channels: + return f"通知发送成功,共 {len(target_channels)} 个渠道" + + if not success and len(failed_channels) == len(target_channels): + return f"通知发送失败,{len(failed_channels)} 个渠道全部失败" + + success_count = len(target_channels) - len(failed_channels) + return f"通知部分成功:{success_count}/{len(target_channels)} 个渠道成功" + + def get_configured_channels(self) -> List[ChannelType]: + """获取已配置的渠道列表""" + return [ + ct for ct, channel in self.channels.items() + if channel.is_configured() + ] + + def is_channel_configured(self, channel_type: ChannelType) -> bool: + """检查指定渠道是否已配置""" + channel = self.channels.get(channel_type) + return channel.is_configured() if channel else False diff --git a/uv.lock b/uv.lock index bc31ebc..3a02745 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,113 @@ version = 1 revision = 3 -requires-python = ">=3.14" +requires-python = ">=3.12" + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463" }, + { url = "http://mirrors.aliyun.com/pypi/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423" }, + { url = "http://mirrors.aliyun.com/pypi/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57" }, + { url = "http://mirrors.aliyun.com/pypi/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687" }, + { url = "http://mirrors.aliyun.com/pypi/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926" }, + { url = "http://mirrors.aliyun.com/pypi/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43" }, + { url = "http://mirrors.aliyun.com/pypi/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592" }, + { url = "http://mirrors.aliyun.com/pypi/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587" }, + { url = "http://mirrors.aliyun.com/pypi/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632" }, + { url = "http://mirrors.aliyun.com/pypi/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64" }, + { url = "http://mirrors.aliyun.com/pypi/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56" }, + { url = "http://mirrors.aliyun.com/pypi/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa" }, + { url = "http://mirrors.aliyun.com/pypi/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e" }, +] [[package]] name = "annotated-types" @@ -11,12 +118,32 @@ wheels = [ { url = "http://mirrors.aliyun.com/pypi/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, ] +[[package]] +name = "anthropic" +version = "0.86.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57" }, +] + [[package]] name = "anyio" version = "4.12.1" source = { registry = "http://mirrors.aliyun.com/pypi/simple" } dependencies = [ { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "http://mirrors.aliyun.com/pypi/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703" } wheels = [ @@ -24,15 +151,45 @@ wheels = [ ] [[package]] -name = "boss-hr-2-0" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "ylhp-boss-hr" }, +name = "attrs" +version = "26.1.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309" }, ] -[package.metadata] -requires-dist = [{ name = "ylhp-boss-hr", specifier = ">=1.37" }] +[[package]] +name = "black" +version = "26.3.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54" }, + { url = "http://mirrors.aliyun.com/pypi/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56" }, + { url = "http://mirrors.aliyun.com/pypi/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839" }, + { url = "http://mirrors.aliyun.com/pypi/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568" }, + { url = "http://mirrors.aliyun.com/pypi/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b" }, +] [[package]] name = "certifi" @@ -43,6 +200,134 @@ wheels = [ { url = "http://mirrors.aliyun.com/pypi/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa" }, ] +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2" }, +] + +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29" }, + { url = "http://mirrors.aliyun.com/pypi/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa" }, + { url = "http://mirrors.aliyun.com/pypi/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027" }, + { url = "http://mirrors.aliyun.com/pypi/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506" }, + { url = "http://mirrors.aliyun.com/pypi/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888" }, + { url = "http://mirrors.aliyun.com/pypi/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806" }, + { url = "http://mirrors.aliyun.com/pypi/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930" }, + { url = "http://mirrors.aliyun.com/pypi/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24" }, + { url = "http://mirrors.aliyun.com/pypi/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef" }, + { url = "http://mirrors.aliyun.com/pypi/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df" }, + { url = "http://mirrors.aliyun.com/pypi/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -89,6 +374,330 @@ wheels = [ { url = "http://mirrors.aliyun.com/pypi/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" }, +] + +[[package]] +name = "jiter" +version = "0.13.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152" }, + { url = "http://mirrors.aliyun.com/pypi/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726" }, + { url = "http://mirrors.aliyun.com/pypi/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08" }, + { url = "http://mirrors.aliyun.com/pypi/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394" }, + { url = "http://mirrors.aliyun.com/pypi/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb" }, + { url = "http://mirrors.aliyun.com/pypi/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159" }, + { url = "http://mirrors.aliyun.com/pypi/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663" }, + { url = "http://mirrors.aliyun.com/pypi/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820" }, + { url = "http://mirrors.aliyun.com/pypi/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68" }, + { url = "http://mirrors.aliyun.com/pypi/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72" }, + { url = "http://mirrors.aliyun.com/pypi/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc" }, + { url = "http://mirrors.aliyun.com/pypi/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10" }, + { url = "http://mirrors.aliyun.com/pypi/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef" }, + { url = "http://mirrors.aliyun.com/pypi/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66" }, + { url = "http://mirrors.aliyun.com/pypi/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad" }, + { url = "http://mirrors.aliyun.com/pypi/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40" }, + { url = "http://mirrors.aliyun.com/pypi/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95" }, + { url = "http://mirrors.aliyun.com/pypi/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe" }, + { url = "http://mirrors.aliyun.com/pypi/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024" }, + { url = "http://mirrors.aliyun.com/pypi/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59" }, + { url = "http://mirrors.aliyun.com/pypi/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75" }, + { url = "http://mirrors.aliyun.com/pypi/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961" }, + { url = "http://mirrors.aliyun.com/pypi/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582" }, + { url = "http://mirrors.aliyun.com/pypi/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511" }, + { url = "http://mirrors.aliyun.com/pypi/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23" }, + { url = "http://mirrors.aliyun.com/pypi/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060" }, + { url = "http://mirrors.aliyun.com/pypi/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33" }, + { url = "http://mirrors.aliyun.com/pypi/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65" }, + { url = "http://mirrors.aliyun.com/pypi/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca" }, + { url = "http://mirrors.aliyun.com/pypi/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108" }, + { url = "http://mirrors.aliyun.com/pypi/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118" }, + { url = "http://mirrors.aliyun.com/pypi/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709" }, + { url = "http://mirrors.aliyun.com/pypi/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb" }, + { url = "http://mirrors.aliyun.com/pypi/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262" }, + { url = "http://mirrors.aliyun.com/pypi/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889" }, + { url = "http://mirrors.aliyun.com/pypi/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505" }, +] + +[[package]] +name = "openai" +version = "2.29.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/b4/15/203d537e58986b5673e7f232453a2a2f110f22757b15921cbdeea392e520/openai-2.29.0.tar.gz", hash = "sha256:32d09eb2f661b38d3edd7d7e1a2943d1633f572596febe64c0cd370c86d52bec" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/d0/b1/35b6f9c8cf9318e3dbb7146cc82dab4cf61182a8d5406fc9b50864362895/openai-2.29.0-py3-none-any.whl", hash = "sha256:b7c5de513c3286d17c5e29b92c4c98ceaf0d775244ac8159aeb1bddf840eb42a" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.4" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72" }, + { url = "http://mirrors.aliyun.com/pypi/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367" }, + { url = "http://mirrors.aliyun.com/pypi/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778" }, + { url = "http://mirrors.aliyun.com/pypi/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75" }, + { url = "http://mirrors.aliyun.com/pypi/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db" }, + { url = "http://mirrors.aliyun.com/pypi/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311" }, + { url = "http://mirrors.aliyun.com/pypi/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af" }, + { url = "http://mirrors.aliyun.com/pypi/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66" }, + { url = "http://mirrors.aliyun.com/pypi/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859" }, + { url = "http://mirrors.aliyun.com/pypi/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393" }, + { url = "http://mirrors.aliyun.com/pypi/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717" }, + { url = "http://mirrors.aliyun.com/pypi/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12" }, + { url = "http://mirrors.aliyun.com/pypi/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded" }, + { url = "http://mirrors.aliyun.com/pypi/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641" }, + { url = "http://mirrors.aliyun.com/pypi/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153" }, + { url = "http://mirrors.aliyun.com/pypi/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992" }, + { url = "http://mirrors.aliyun.com/pypi/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393" }, + { url = "http://mirrors.aliyun.com/pypi/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc" }, + { url = "http://mirrors.aliyun.com/pypi/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726" }, + { url = "http://mirrors.aliyun.com/pypi/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455" }, + { url = "http://mirrors.aliyun.com/pypi/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237" }, +] + [[package]] name = "pydantic" version = "2.12.5" @@ -113,6 +722,34 @@ dependencies = [ ] sdist = { url = "http://mirrors.aliyun.com/pypi/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e" } wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69" }, + { url = "http://mirrors.aliyun.com/pypi/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75" }, + { url = "http://mirrors.aliyun.com/pypi/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05" }, + { url = "http://mirrors.aliyun.com/pypi/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34" }, + { url = "http://mirrors.aliyun.com/pypi/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740" }, + { url = "http://mirrors.aliyun.com/pypi/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11" }, + { url = "http://mirrors.aliyun.com/pypi/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd" }, { url = "http://mirrors.aliyun.com/pypi/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a" }, { url = "http://mirrors.aliyun.com/pypi/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14" }, { url = "http://mirrors.aliyun.com/pypi/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1" }, @@ -141,6 +778,146 @@ wheels = [ { url = "http://mirrors.aliyun.com/pypi/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa" }, { url = "http://mirrors.aliyun.com/pypi/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c" }, { url = "http://mirrors.aliyun.com/pypi/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008" }, + { url = "http://mirrors.aliyun.com/pypi/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.13.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321" }, + { url = "http://mirrors.aliyun.com/pypi/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324" }, + { url = "http://mirrors.aliyun.com/pypi/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975" }, + { url = "http://mirrors.aliyun.com/pypi/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de" }, +] + +[[package]] +name = "ruff" +version = "0.15.7" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912" }, + { url = "http://mirrors.aliyun.com/pypi/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580" }, + { url = "http://mirrors.aliyun.com/pypi/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf" }, ] [[package]] @@ -164,6 +941,110 @@ wheels = [ { url = "http://mirrors.aliyun.com/pypi/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, ] +[[package]] +name = "yarl" +version = "1.23.0" +source = { registry = "http://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "http://mirrors.aliyun.com/pypi/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5" } +wheels = [ + { url = "http://mirrors.aliyun.com/pypi/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069" }, + { url = "http://mirrors.aliyun.com/pypi/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25" }, + { url = "http://mirrors.aliyun.com/pypi/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072" }, + { url = "http://mirrors.aliyun.com/pypi/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67" }, + { url = "http://mirrors.aliyun.com/pypi/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86" }, + { url = "http://mirrors.aliyun.com/pypi/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34" }, + { url = "http://mirrors.aliyun.com/pypi/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d" }, + { url = "http://mirrors.aliyun.com/pypi/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035" }, + { url = "http://mirrors.aliyun.com/pypi/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401" }, + { url = "http://mirrors.aliyun.com/pypi/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f" }, + { url = "http://mirrors.aliyun.com/pypi/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957" }, + { url = "http://mirrors.aliyun.com/pypi/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120" }, + { url = "http://mirrors.aliyun.com/pypi/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59" }, + { url = "http://mirrors.aliyun.com/pypi/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512" }, + { url = "http://mirrors.aliyun.com/pypi/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1" }, + { url = "http://mirrors.aliyun.com/pypi/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea" }, + { url = "http://mirrors.aliyun.com/pypi/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123" }, + { url = "http://mirrors.aliyun.com/pypi/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de" }, + { url = "http://mirrors.aliyun.com/pypi/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b" }, + { url = "http://mirrors.aliyun.com/pypi/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5" }, + { url = "http://mirrors.aliyun.com/pypi/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595" }, + { url = "http://mirrors.aliyun.com/pypi/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144" }, + { url = "http://mirrors.aliyun.com/pypi/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912" }, + { url = "http://mirrors.aliyun.com/pypi/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474" }, + { url = "http://mirrors.aliyun.com/pypi/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723" }, + { url = "http://mirrors.aliyun.com/pypi/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039" }, + { url = "http://mirrors.aliyun.com/pypi/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52" }, + { url = "http://mirrors.aliyun.com/pypi/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae" }, + { url = "http://mirrors.aliyun.com/pypi/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe" }, + { url = "http://mirrors.aliyun.com/pypi/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169" }, + { url = "http://mirrors.aliyun.com/pypi/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e" }, + { url = "http://mirrors.aliyun.com/pypi/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679" }, + { url = "http://mirrors.aliyun.com/pypi/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412" }, + { url = "http://mirrors.aliyun.com/pypi/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94" }, + { url = "http://mirrors.aliyun.com/pypi/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28" }, + { url = "http://mirrors.aliyun.com/pypi/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6" }, + { url = "http://mirrors.aliyun.com/pypi/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277" }, + { url = "http://mirrors.aliyun.com/pypi/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a" }, + { url = "http://mirrors.aliyun.com/pypi/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb" }, + { url = "http://mirrors.aliyun.com/pypi/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41" }, + { url = "http://mirrors.aliyun.com/pypi/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4" }, + { url = "http://mirrors.aliyun.com/pypi/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2" }, + { url = "http://mirrors.aliyun.com/pypi/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25" }, + { url = "http://mirrors.aliyun.com/pypi/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f" }, +] + [[package]] name = "ylhp-boss-hr" version = "1.37" @@ -176,3 +1057,41 @@ sdist = { url = "https://git.yinlihupo.cn/api/packages/LiQiuYu/pypi/files/ylhp-b wheels = [ { url = "https://git.yinlihupo.cn/api/packages/LiQiuYu/pypi/files/ylhp-boss-hr/1.37/ylhp_boss_hr-1.37-py3-none-any.whl", hash = "sha256:1f947be6f0eb044e1693df517342657eee48dc819a037e8813890fb9cb0bc142" }, ] + +[[package]] +name = "ylhp-hr-2-0" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "ylhp-boss-hr" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] +llm = [ + { name = "anthropic" }, + { name = "openai" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.8" }, + { name = "anthropic", marker = "extra == 'llm'", specifier = ">=0.20" }, + { name = "black", marker = "extra == 'dev'", specifier = ">=23.0" }, + { name = "openai", marker = "extra == 'llm'", specifier = ">=1.0" }, + { name = "pydantic", specifier = ">=2.0" }, + { name = "pydantic-settings", specifier = ">=2.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1" }, + { name = "ylhp-boss-hr", specifier = ">=1.37" }, +] +provides-extras = ["llm", "dev"]