feat(core): 重构项目为简历智能体系统基础架构

- 重命名项目及包结构为ylhp-hr-2-0,支持多平台简历爬取与AI分析
- 移除旧的main.py,新增统一主应用入口及初始化流程
- 实现配置模块,支持数据库、LLM、通知和爬虫多种配置项及环境变量加载
- 构建领域模型,包括候选人、简历、职位、评价等实体与枚举定义
- 设计评价方案服务,提供默认评价模板及方案管理接口
- 开发分析服务,整合LLM客户端实现基于AI的简历分析功能
- 实现多种通知渠道支持,包括企业微信、钉钉、邮件
- 引入爬虫工厂及Boss爬虫模块支持候选人数据抓取
- 统一入库服务,完成数据归一化、验证及去重功能
- 添加异步任务协调流程,支持爬取后自动分析及通知
- 配置项目依赖管理,支持选装LLM和开发工具插件
- 初步搭建代码目录结构,划分配置、领域、服务、映射、控制器层等模块
This commit is contained in:
2026-03-24 11:29:53 +08:00
parent 6f38bc02c1
commit 507a2522cd
42 changed files with 5424 additions and 38 deletions

23
main.py
View File

@@ -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)

View File

@@ -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"

View File

@@ -0,0 +1 @@
"""cn package"""

View File

@@ -0,0 +1 @@
"""yinlihupo package"""

View File

@@ -0,0 +1,3 @@
"""ylhp_hr_2.0 - Resume Intelligence Agent"""
__version__ = "0.1.0"

View File

@@ -0,0 +1 @@
"""Common utilities"""

View File

@@ -0,0 +1,5 @@
"""Configuration module"""
from .settings import Settings, get_settings
__all__ = ["Settings", "get_settings"]

View File

@@ -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

View File

@@ -0,0 +1 @@
"""Controller layer - API endpoints"""

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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())

View File

@@ -0,0 +1 @@
"""Mapper layer - Data access"""

View File

@@ -0,0 +1 @@
"""Service layer - Business logic"""

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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)

View File

@@ -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 "(简历内容为空)"

View File

@@ -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

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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")

View File

@@ -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

View File

@@ -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)

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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)

View File

@@ -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]

View File

@@ -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

View File

@@ -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",
]

View File

@@ -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",
]

View File

@@ -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
}

View File

@@ -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}&timestamp={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)

View File

@@ -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("<html><body style='font-family: Arial, sans-serif;'>")
if message.title:
html_parts.append(f"<h2>{message.title}</h2>")
if message.content:
html_parts.append(f"<p>{message.content}</p>")
if message.candidate:
candidate = message.candidate
html_parts.append("<h3>候选人信息</h3>")
html_parts.append("<ul>")
html_parts.append(f"<li><strong>姓名:</strong>{candidate.name}</li>")
if candidate.age:
html_parts.append(f"<li><strong>年龄:</strong>{candidate.age}岁</li>")
if candidate.work_years:
html_parts.append(f"<li><strong>工作年限:</strong>{candidate.work_years}年</li>")
if candidate.current_company:
html_parts.append(f"<li><strong>当前公司:</strong>{candidate.current_company}</li>")
if candidate.current_position:
html_parts.append(f"<li><strong>当前职位:</strong>{candidate.current_position}</li>")
if candidate.phone:
html_parts.append(f"<li><strong>联系方式:</strong>{candidate.phone}</li>")
if candidate.email:
html_parts.append(f"<li><strong>邮箱:</strong>{candidate.email}</li>")
html_parts.append("</ul>")
if message.evaluation:
evaluation = message.evaluation
html_parts.append("<h3>AI 评价</h3>")
html_parts.append("<ul>")
html_parts.append(f"<li><strong>综合评分:</strong><span style='color: #1890ff; font-size: 18px;'>{evaluation.overall_score}/100</span></li>")
if evaluation.recommendation:
color = self._get_recommendation_color(evaluation.recommendation.value)
html_parts.append(f"<li><strong>推荐意见:</strong><span style='color: {color};'>{self._format_recommendation(evaluation.recommendation.value)}</span></li>")
if evaluation.summary:
html_parts.append(f"<li><strong>评价摘要:</strong>{evaluation.summary}</li>")
if evaluation.strengths:
html_parts.append(f"<li><strong>优势:</strong>{', '.join(evaluation.strengths)}</li>")
if evaluation.weaknesses:
html_parts.append(f"<li><strong>不足:</strong>{', '.join(evaluation.weaknesses)}</li>")
html_parts.append("</ul>")
html_parts.append("</body></html>")
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")

View File

@@ -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"- 综合评分:<font color=\"info\">{evaluation.overall_score}/100</font>")
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": "<font color=\"info\">强烈推荐</font>",
"recommend": "<font color=\"info\">推荐</font>",
"consider": "<font color=\"warning\">考虑</font>",
"not_recommend": "<font color=\"comment\">不推荐</font>"
}
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

View File

@@ -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())

View File

@@ -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

935
uv.lock generated
View File

@@ -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"]