feat(core): 重构项目为简历智能体系统基础架构
- 重命名项目及包结构为ylhp-hr-2-0,支持多平台简历爬取与AI分析 - 移除旧的main.py,新增统一主应用入口及初始化流程 - 实现配置模块,支持数据库、LLM、通知和爬虫多种配置项及环境变量加载 - 构建领域模型,包括候选人、简历、职位、评价等实体与枚举定义 - 设计评价方案服务,提供默认评价模板及方案管理接口 - 开发分析服务,整合LLM客户端实现基于AI的简历分析功能 - 实现多种通知渠道支持,包括企业微信、钉钉、邮件 - 引入爬虫工厂及Boss爬虫模块支持候选人数据抓取 - 统一入库服务,完成数据归一化、验证及去重功能 - 添加异步任务协调流程,支持爬取后自动分析及通知 - 配置项目依赖管理,支持选装LLM和开发工具插件 - 初步搭建代码目录结构,划分配置、领域、服务、映射、控制器层等模块
This commit is contained in:
23
main.py
23
main.py
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
1
src/main/python/cn/__init__.py
Normal file
1
src/main/python/cn/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""cn package"""
|
||||
1
src/main/python/cn/yinlihupo/__init__.py
Normal file
1
src/main/python/cn/yinlihupo/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""yinlihupo package"""
|
||||
3
src/main/python/cn/yinlihupo/ylhp_hr_2.0/__init__.py
Normal file
3
src/main/python/cn/yinlihupo/ylhp_hr_2.0/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""ylhp_hr_2.0 - Resume Intelligence Agent"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1 @@
|
||||
"""Common utilities"""
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Configuration module"""
|
||||
|
||||
from .settings import Settings, get_settings
|
||||
|
||||
__all__ = ["Settings", "get_settings"]
|
||||
95
src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/settings.py
Normal file
95
src/main/python/cn/yinlihupo/ylhp_hr_2.0/config/settings.py
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
"""Controller layer - API endpoints"""
|
||||
24
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/__init__.py
Normal file
24
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/__init__.py
Normal 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",
|
||||
]
|
||||
113
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/candidate.py
Normal file
113
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/candidate.py
Normal 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
|
||||
43
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/enums.py
Normal file
43
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/enums.py
Normal 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"
|
||||
131
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/evaluation.py
Normal file
131
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/evaluation.py
Normal 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
|
||||
}
|
||||
61
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/job.py
Normal file
61
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/job.py
Normal 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()
|
||||
69
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/resume.py
Normal file
69
src/main/python/cn/yinlihupo/ylhp_hr_2.0/domain/resume.py
Normal 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()
|
||||
293
src/main/python/cn/yinlihupo/ylhp_hr_2.0/main.py
Normal file
293
src/main/python/cn/yinlihupo/ylhp_hr_2.0/main.py
Normal 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())
|
||||
@@ -0,0 +1 @@
|
||||
"""Mapper layer - Data access"""
|
||||
@@ -0,0 +1 @@
|
||||
"""Service layer - Business logic"""
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 "(简历内容为空)"
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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"]
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
"""DingTalk notification channel"""
|
||||
from typing import Optional, Dict, Any
|
||||
import json
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
|
||||
from .base_channel import NotificationChannel, NotificationMessage, SendResult
|
||||
from ....domain.enums import ChannelType
|
||||
|
||||
|
||||
class DingTalkChannel(NotificationChannel):
|
||||
"""
|
||||
钉钉通知渠道
|
||||
|
||||
通过钉钉机器人 Webhook 发送消息
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
webhook_url: str,
|
||||
secret: Optional[str] = None,
|
||||
at_mobiles: Optional[list] = None,
|
||||
is_at_all: bool = False
|
||||
):
|
||||
"""
|
||||
初始化钉钉渠道
|
||||
|
||||
Args:
|
||||
webhook_url: 钉钉机器人 Webhook 地址
|
||||
secret: 安全设置中的加签密钥
|
||||
at_mobiles: @提醒的手机号列表
|
||||
is_at_all: 是否@所有人
|
||||
"""
|
||||
self.webhook_url = webhook_url
|
||||
self.secret = secret
|
||||
self.at_mobiles = at_mobiles or []
|
||||
self.is_at_all = is_at_all
|
||||
|
||||
@property
|
||||
def channel_type(self) -> ChannelType:
|
||||
return ChannelType.DINGTALK
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
"""检查是否已配置"""
|
||||
return bool(self.webhook_url)
|
||||
|
||||
async def send(self, message: NotificationMessage) -> SendResult:
|
||||
"""发送钉钉消息"""
|
||||
if not self.is_configured():
|
||||
return SendResult(
|
||||
success=False,
|
||||
error_message="Webhook URL not configured"
|
||||
)
|
||||
|
||||
try:
|
||||
# 构建带签名的 URL
|
||||
url = self._build_signed_url()
|
||||
|
||||
# 构建消息体
|
||||
payload = self._build_payload(message)
|
||||
|
||||
# 发送请求
|
||||
import aiohttp
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
url,
|
||||
json=payload,
|
||||
timeout=aiohttp.ClientTimeout(total=30)
|
||||
) as response:
|
||||
result = await response.json()
|
||||
|
||||
if result.get("errcode") == 0:
|
||||
return SendResult(
|
||||
success=True,
|
||||
response_data=result
|
||||
)
|
||||
else:
|
||||
return SendResult(
|
||||
success=False,
|
||||
error_message=f"DingTalk API error: {result.get('errmsg')}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return SendResult(
|
||||
success=False,
|
||||
error_message=f"Failed to send DingTalk message: {str(e)}"
|
||||
)
|
||||
|
||||
def _build_signed_url(self) -> str:
|
||||
"""构建带签名的 URL"""
|
||||
if not self.secret:
|
||||
return self.webhook_url
|
||||
|
||||
timestamp = str(round(time.time() * 1000))
|
||||
string_to_sign = f"{timestamp}\n{self.secret}"
|
||||
|
||||
hmac_code = hmac.new(
|
||||
self.secret.encode('utf-8'),
|
||||
string_to_sign.encode('utf-8'),
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
|
||||
return f"{self.webhook_url}×tamp={timestamp}&sign={sign}"
|
||||
|
||||
def _build_payload(self, message: NotificationMessage) -> Dict[str, Any]:
|
||||
"""构建钉钉消息体"""
|
||||
# 使用 markdown 格式
|
||||
return {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": message.title,
|
||||
"text": self._format_content(message)
|
||||
},
|
||||
"at": {
|
||||
"atMobiles": self.at_mobiles,
|
||||
"isAtAll": self.is_at_all
|
||||
}
|
||||
}
|
||||
|
||||
def _format_content(self, message: NotificationMessage) -> str:
|
||||
"""格式化消息内容"""
|
||||
lines = []
|
||||
|
||||
# 标题
|
||||
if message.title:
|
||||
lines.append(f"### {message.title}")
|
||||
|
||||
# 内容
|
||||
if message.content:
|
||||
lines.append(message.content)
|
||||
|
||||
# 候选人信息
|
||||
if message.candidate:
|
||||
candidate = message.candidate
|
||||
lines.append("\n**候选人信息:**")
|
||||
lines.append(f"- 姓名:{candidate.name}")
|
||||
if candidate.age:
|
||||
lines.append(f"- 年龄:{candidate.age}岁")
|
||||
if candidate.work_years:
|
||||
lines.append(f"- 工作年限:{candidate.work_years}年")
|
||||
if candidate.current_company:
|
||||
lines.append(f"- 当前公司:{candidate.current_company}")
|
||||
if candidate.current_position:
|
||||
lines.append(f"- 当前职位:{candidate.current_position}")
|
||||
if candidate.phone:
|
||||
lines.append(f"- 联系方式:{candidate.phone}")
|
||||
|
||||
# 评价信息
|
||||
if message.evaluation:
|
||||
evaluation = message.evaluation
|
||||
lines.append("\n**AI 评价:**")
|
||||
lines.append(f"- 综合评分:**{evaluation.overall_score}/100**")
|
||||
if evaluation.recommendation:
|
||||
lines.append(f"- 推荐意见:{self._format_recommendation(evaluation.recommendation.value)}")
|
||||
if evaluation.summary:
|
||||
lines.append(f"- 评价摘要:{evaluation.summary}")
|
||||
|
||||
if evaluation.strengths:
|
||||
lines.append(f"- 优势:{', '.join(evaluation.strengths[:3])}")
|
||||
|
||||
# @提醒
|
||||
if self.at_mobiles:
|
||||
for mobile in self.at_mobiles:
|
||||
lines.append(f"@{mobile}")
|
||||
|
||||
if self.is_at_all:
|
||||
lines.append("@所有人")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def _format_recommendation(self, value: str) -> str:
|
||||
"""格式化推荐意见"""
|
||||
mapping = {
|
||||
"strong_recommend": "**强烈推荐**",
|
||||
"recommend": "**推荐**",
|
||||
"consider": "**考虑**",
|
||||
"not_recommend": "**不推荐**"
|
||||
}
|
||||
return mapping.get(value, value)
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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
935
uv.lock
generated
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user