让 ty 在额外搜索路径(extra-paths / PYTHONPATH)下正确识别 Pydantic:从 mdtest 配置到源码级原理

让 ty 在额外搜索路径(extra-paths / PYTHONPATH)下正确识别 Pydantic:从 mdtest 配置到源码级原理 让 ty 在额外搜索路径extra-paths / PYTHONPATH下正确识别 Pydantic从 mdtest 配置到源码级原理【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff本篇技术指南围绕 ty 的 Pydantic 专项类型推断展开重点剖析仓库中一份特殊的测试文档 ——pydantic_extra_search_paths.md它验证了一个关键行为——即使 pydantic 不是安装在默认虚拟环境而是从extra-paths例如写入PYTHONPATH的site-packages目录解析而来Pydantic 专属的语义如ConfigDict(extraallow)允许传入多余关键字参数依然生效。读完本文你将掌握[environment]下extra-paths、python-version、python-platform的完整配置语义理解 ty 如何把PYTHONPATH合并进搜索路径并能在源码与测试层面复现、验证这套行为。一、文档定位一份外部依赖 额外搜索路径的 mdtest该文档位于 crates/ty_python_semantic/resources/mdtest/external/pydantic_extra_search_paths.md是 ty 的 mdtestMarkdown 驱动的类型推断测试体系的一部分。同目录的 README.md 说明该目录专门存放使用外部包的 mdtest 用例与import/、regression/等目录下的纯内置用例区分开。这份用例的核心命题是Pydantic-specific behavior still applies when the installed package is resolved from an extra search path, such as when itssite-packagesdirectory is included inPYTHONPATH.即Pydantic 的专属类型推断行为不依赖于包的标准安装位置。只要模块解析器能从配置的额外搜索路径中找到 pydantic其语义分析就必须与常规安装完全一致。二、完整配置复现environment project 双表结构文档给出的完整配置如下[environment] python-version 3.11 python-platform linux extra-paths [/.venv/path-to-site-packages] [project] dependencies [pydantic2.13.4]配套的测试代码from pydantic import BaseModel, ConfigDict class Model(BaseModel): model_config ConfigDict(extraallow) Model(a1)Model(a1)在ConfigDict(extraallow)下是合法调用a并非声明的字段但允许作为额外关键字参数传入ty 不应报错。这正是该用例要验证的 Pydantic 专属行为。三、[environment]三个关键参数逐一拆解EnvironmentOptions的完整字段定义在 crates/ty_project/src/metadata/options.rs本用例用到其中三个1.extra-paths用户自定义搜索路径最高优先级这是本用例的主角。源码注释options.rs原文明确作用在模块解析中拥有第一优先级的用户提供路径定位高级选项通常只用于未按常规方式安装进 Python 环境的第三方模块类比相当于 mypy 的MYPYPATH环境变量、pyright 的stubPath配置项类型字符串数组list[str]相对路径会基于配置文件所在目录解析默认值[]。在解析实现中options.rsextra-paths会被转换为绝对路径合并进SearchPathSettingslet mut extra_paths: VecSystemPathBuf environment .extra_paths .as_deref() .unwrap_or_default() .iter() .map(|path| path.absolute(context.configuration_root(), system)) .collect();2.python-version目标 Python 版本类型字符串格式M.m如3.11语义ty 会针对该版本分析源码若代码使用了该版本不支持的语言特性会报错优先级显式配置 推断。若不设置ty 依次尝试project.requires-python取范围下限→ 从已激活/配置的 Python 环境推断 → 回退默认值当前仓库默认标注为3.14见 options.rs。值得一提本用例的python-version 3.11与配套锁文件 pydantic_extra_search_paths.lock 中的requires-python 3.11.*完全一致保证测试环境的解释器版本与依赖解析条件自洽。3.python-platform目标运行平台类型win32 | darwin | android | ios | linux | all或任意字符串语义ty 据此理解sys.platform分支例如 typeshed 中标准库因平台不同而内容不同默认值当前宿主机平台Linux 等非 Windows/macOS 平台统一视为linux见 options.rs。四、[project]依赖声明与锁文件测试环境的确定性保证[project] dependencies [pydantic2.13.4]配合 pydantic_extra_search_paths.lock 可看出该用例的完整依赖解析结果锁文件格式版本version 1revision 3要求python 3.11.*虚拟根包mdtest-deps 0.1.0声明requires-dist [{ name pydantic, specifier 2.13.4 }]实际解析出的传递依赖pydantic 2.13.4、pydantic-core 2.46.4、annotated-types 0.8.0、typing-extensions 4.16.0、typing-inspection 0.4.2。也就是说该 mdtest 会先在隔离环境中按锁文件安装上述依赖再把某个包含site-packages的路径塞进extra-paths以模拟包来自额外搜索路径的现实场景。五、PYTHONPATH也会被并入 extra paths源码级验证文档开篇提到PYTHONPATH场景其背后有直接的源码支撑。在 options.rs 的to_search_paths中ty 会读取PYTHONPATH环境变量并做三件事用std::env::split_paths按平台分隔符拆分过滤掉非 UTF-8 或不存在/不是目录的路径把存在且为目录的路径追加进extra_paths并注释说明与 Python 解释器一致它们应在 site-packages 之前被检查// read all the paths off the PYTHONPATH environment variable, check // they exist as a directory, and add them to the vec of extra_paths // as they should be checked before site-packages just like python // interpreter does if let Ok(python_path) system.env_var(EnvVars::PYTHONPATH) { for path in std::env::split_paths(python_path.as_str()) { // ...过滤... extra_paths.push(abspath); } }随后这些路径连同site-packages、typeshed、项目根目录一起构造成SearchPathSettings交给ty_module_resolver完成最终的模块解析options.rs。从源码结构可以推断extra-paths与PYTHONPATH殊途同归最终都汇入同一条搜索路径链并优先于site-packages——这正是本用例从额外路径解析 pydantic 依然生效的机制基础。六、Pydantic 专属语义在类型推断层如何实现要理解Pydantic 专属行为依然适用意味着什么需要看 ty 对 Pydantic 的专用类型推断模块crates/ty_python_semantic/src/types/dedicated/pydantic.rs。1.ConfigDict.extra的三态语义extra配置被建模为ExtraBehavior枚举其取值来自ConfigDict(extra...)调用中的extra关键字参数pydantic.rsForbid拒绝额外关键字参数Ignore接受但丢弃Allow接受并保留未配置None时按Ignore处理。2. 合成__init__如何决定能不能传a1ty 会为BaseModel子类合成构造函数签名其行为由两个判定方法驱动pydantic.rsfn accepts_extra(self, db: db dyn Db) - bool { !matches!(self.config(db).extra, Some(ExtraBehavior::Forbid)) } fn discards_extra(self, db: db dyn Db) - bool { matches!(self.config(db).extra, None | Some(ExtraBehavior::Ignore)) }即只要extra不是Forbid合成构造函数就接受未声明字段的关键字参数**extra: Any。因此用例中ConfigDict(extraallow)下的Model(a1)不会触发missing-argument、unexpected-keyword-argument之类的诊断——这正是文档要守护的行为契约。作为对照组可在 crates/ty_python_semantic/resources/mdtest/external/pydantic.md 中看到常规安装场景下同样的签名形态例如User.__init__被揭示为(self: User, *, id: LaxInt, name: LaxStr, **extra: Any) - None。两份文档配合印证无论 pydantic 从标准环境还是额外路径解析类型推断结果必须一致。3. 相关联的 lint 规则与extra语义配套仓库还有一份 lint 文档 pydantic-discarded-extra-argument.md说明当extra处于Ignore语义时ty 如何对被静默丢弃的额外参数给出提示。这与本用例同属一个Pydantic extra 行为主题可一并阅读。七、如何运行与验证这份测试mdtest 是 ty 语义分析的可执行测试形式运行入口位于测试驱动脚本crates/ty_python_semantic/mdtest.py及其锁文件 mdtest.py.lock解析与断言引擎crates/mdtest含parser.rs、assertion.rs、matcher.rs等模块。验证方式通常是在构建好 ty 后运行 mdtest 套件执行到external/pydantic_extra_search_paths.md时测试框架会依据同目录锁文件创建隔离环境、安装pydantic2.13.4及其传递依赖然后以配置中的extra-paths作为搜索路径对测试代码执行类型检查断言Model(a1)不产生任何诊断。仓库为只读资源你可以在本地 clone 后按此流程复现无需修改任何文件。八、延伸同一机制的更多用例extra-paths 场景在仓库中还有更丰富的验证样本均可在 crates/ty_python_semantic/resources/mdtest 下找到external/attrs、numpy、pytest、sqlalchemy、sqlmodel、strawberry等同样依赖外部包做端到端验证import/legacy_namespace.md用extra-paths [/airflow-core/src, /providers/amazon/src/]模拟多来源分发验证pkg_resources.declare_namespace与pkgutil.extend_path等旧式命名空间包namespace package语义import/namespace.md、import/stub_packages.md 等覆盖命名空间包与 stub 包的搜索路径行为。这些用例共同说明ty 的模块解析与类型推断对包从哪来不敏感但对包是什么高度敏感——无论依赖来自默认环境、PYTHONPATH还是显式extra-paths推断语义始终一致这正是pydantic_extra_search_paths.md这份文档在整条测试体系中的价值所在。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考