Shipped rdkit-stubs .pyi files are not valid Python, which stops mypy before it checks any user code
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 45/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Lĩnh vực
- build-system, testing-qa, tooling
Hướng nghiên cứu
Start with generate_stubs_internal in Scripts/gen_rdkit_stubs/ and inspect rdkit-stubs/CMakeLists.txt to understand how generated files are validated and how failures affect the stubs target. Compare the generated .pyi files across platforms and review Code/RDBoost/Wrap.h for the compiler-dependent names. Done means the chosen fix is tested through the relevant generation path and every shipped .pyi parses with ast.parse.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
Three of the .pyi files shipped in rdkit-stubs are not syntactically valid Python. Because mypy aborts on a syntax error in a followed import, a single one of these stops the type check of the entire user project before a single line of user code is analysed:
rdkit-stubs/Chem/rdMolDescriptors.pyi:10: error: Invalid syntax. Perhaps you forgot a comma? [syntax]
Found 1 error in 1 file (errors prevented further checking)
mypy exits 2, so in CI this is indistinguishable from a broken configuration.
Two of the three are Windows-only; one ships on every platform.
To Reproduce
Any project that imports rdkit.Chem.rdMolDescriptors and runs mypy will hit it on Windows. The defects themselves can be shown without mypy:
import ast, pathlib, rdkit
root = pathlib.Path(rdkit.__file__).resolve().parent.parent / "rdkit-stubs"
for f in sorted(root.rglob("*.pyi")):
try:
ast.parse(f.read_text(encoding="utf-8", errors="replace"))
except SyntaxError as e:
print(f"{f.relative_to(root)}:{e.lineno}: {e.msg}")
rdkit==2026.3.5, cp313, win_amd64:
Chem\rdMolDescriptors.pyi:10: invalid syntax. Perhaps you forgot a comma?
Chem\rdmolfiles.pyi:332: parameter without a default follows parameter with a default
Chem\rdRGroupDecomposition.pyi:60: illegal target for annotation
rdkit==2026.3.5, cp312, manylinux_2_28_x86_64 and cp312, macosx_11_0_arm64 (283 stubs each):
Chem/rdRGroupDecomposition.pyi:60: illegal target for annotation
The three lines
-
Chem/rdMolDescriptors.pyi:10— Windows only# win_amd64 atomTypes: typing.ClassVar[rdkit.rdBase._vectunsigned int] # value = <rdkit.rdBase._vectunsigned int object at 0x0000023BAACBDEC0> # manylinux atomTypes: typing.ClassVar[rdkit.rdBase._vectj] # value = <rdkit.rdBase._vectj object> -
Chem/rdmolfiles.pyi:332— Windows only# win_amd64 (props_list/structstd/classstd repeated, and a non-default after a default) def GetText(mol: Mol, confId: int = -1, props_list: ..., structstd: ..., classstd: ... = ..., structstd: ..., classstd: ...) -> str: # manylinux def GetText(mol: Mol, confId: int = -1, props_list: _vectNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE = ...) -> str: -
Chem/rdRGroupDecomposition.pyi:60— all platformsNone: typing.ClassVar[RGroupCoreAlignment] # value = rdkit.Chem.rdRGroupDecomposition.RGroupCoreAlignment.NoneRGroupCoreAlignmenthas a member namedNone, which is a keyword and cannot be an annotation target.
Cause
(1) and (2) are not really stub bugs. Code/RDBoost/Wrap.h builds the Python
class name for the container converters out of typeid(T).name():
template <typename T>
void RegisterVectorConverter(bool noproxy = false) {
std::string name = "_vect";
name += typeid(T).name();
RegisterVectorConverter<T>(name.c_str(), noproxy);
}
RegisterListConverter does the same a few lines below. The return value of
typeid(T).name() is implementation-defined:
typeid(unsigned int).name() |
resulting class name | valid identifier | |
|---|---|---|---|
| gcc/clang | j (mangled) |
_vectj |
yes |
| MSVC | unsigned int (demangled) |
_vectunsigned int |
no |
The Itanium mangled name happens to contain only [A-Za-z0-9_], so on Linux
and macOS these names are accidentally valid Python identifiers. MSVC returns a
human-readable spelling, so the name carries spaces and angle brackets. On a
win_amd64 build seven of these classes have names that are not identifiers:
_vectunsigned int
_vectclass std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
_vectclass std::vector<double,class std::allocator<double> >
_vectclass std::vector<int,class std::allocator<int> >
_vectclass std::vector<unsigned int,class std::allocator<unsigned int> >
_listclass std::vector<int,class std::allocator<int> >
_listclass std::vector<unsigned int,class std::allocator<unsigned int> >
Only _vectdouble and _vectint come out usable, and the generated stub takes
such a name as-is.
That the generator was written against gcc naming is visible in
ProcessDocLines.CPP_PYTHONIC_RETURN_TYPES, whose keys are all Itanium mangled
names:
CPP_PYTHONIC_RETURN_TYPES = {
"_vectd": "typing.Sequence[double]",
"_vecti": "typing.Sequence[int]",
"_vectj": "typing.Sequence[int]",
"_vectNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE": "typing.Sequence[str]",
"_vectSt6vectorIiSaIiEE": "typing.Sequence[typing.Sequence[int]]",
...
}
_vectj and the NSt7__cxx11 libstdc++ ABI tag only ever appear on a gcc or
clang build. On MSVC the corresponding names are _vectunsigned int and
_vectclass std::basic_string<...>, so no key matches and the raw C++ spelling
falls through into the stub.
(3) is independent of the compiler: RGroupCoreAlignment has a member named
None, which is a legal C++ identifier and a Python keyword, so it is only
reachable as getattr(RGroupCoreAlignment, "None") and cannot be written as an
annotation target.
Underlying all three: nothing parses the generated stubs, so an unparseable one
is packaged and shipped rather than failing the build.
Expected behavior
Every .pyi in rdkit-stubs parses with ast.parse.
Possible directions
Two separate layers, and which one you want is your call:
Root — Wrap.h deriving a Python identifier from typeid(T).name(). Making
those names identifier-safe would fix the class of problem rather than its
symptoms, but it changes names that are visible on rdBase, so it did not seem
like something to propose as a patch from outside.
Mitigation — have stub generation refuse to emit something it cannot parse.
The cheapest form is a check at the end of generate_stubs_internal, which
turns a silently shipped defect into a visible failure of the stubs target:
import ast
...
try:
ast.parse(generated)
except SyntaxError as e:
raise RuntimeError(f"{pyi}:{e.lineno}: {e.msg}")
On its own that does not break the build — rdkit-stubs/CMakeLists.txt reports a
non-zero return with message() rather than message(FATAL_ERROR ...), so the
stubs target would print the failure and carry on. The effect would be that
win_amd64 stops shipping stubs at all, which is arguably still better than
shipping ones that stop mypy dead, but it is a trade rather than a fix.
A prototype that repairs the two shapes seen here and raises on anything else is
on a branch:
https://github.com/HiroYokoyama/rdkit/tree/fix/validate-generated-stubs-min
It drops an annotation line that does not parse, rewrites a def whose
parameter list does not parse to *args/**kwargs, and fails otherwise. Run
against the published 2026.3.5 wheels it takes win_amd64 from 3 unparseable
stubs to 0 and manylinux_2_28_x86_64 from 1 to 0, touching only the offending
files (3 of 278 and 1 of 283).
It is a prototype, it is unverified, and it has no tests. Specifically:
- No unit test.
Scripts/gen_rdkit_stubs/has none today, so where one belongs
is a question for you rather than something to guess at. - Not exercised through a real build. I cannot build the RDKit here, so the
function was run against the.pyifiles from the released wheels instead of
throughcmake --build . --target stubs. Whether it sits correctly inside
generate_stubs_internalis read from the source, not observed. - Only the two defect shapes seen here are handled. Anything else raises, which
is deliberate, but it does mean an unfamiliar shape would stop a build rather
than degrade.
Opened as an issue and not a pull request for that reason. It is 57 added lines
in one file with nothing removed and no existing function touched, so it is
small — it is just not something I can honestly say I have verified end to end.
Happy to turn it into a proper PR with tests if this is a direction you want to
take, or to drop it entirely if you would rather fix the naming in Wrap.h.
The existing patch mechanism does not cover (1) and (2) on its own: a diff
written against the win_amd64 stub does not apply to the manylinux one, and
apply_patch only logs when git apply fails, so those builds would carry a
CRITICAL line on every run.
Configuration
- RDKit version:
2026.3.5(also reproduced against themanylinux_2_28_x86_64wheel of the same version) - OS: Windows 11 (Linux comparison from the published wheel)
- Python version: 3.13 (Windows), 3.12 (Linux wheel inspected)
- Are you using conda? No
- How installed:
pip install rdkit==2026.3.5from PyPI
Additional context
Two earlier reports of the same family were closed by the stale bot without a fix, and both still reproduce in 2026.3.5:
-
#8339 — "mypy error: parameter without a default follows parameter with a default" — this is defect (2) above.
-
#8673 — "Incorrect type stub for EmbedParameters attributes results in mypy errors" — still present.
EmbedParametersproperties are declared as baredef randomSeed(*args, **kwargs)with no annotations, sofrom rdkit.Chem import rdDistGeom params = rdDistGeom.ETKDGv2() params.randomSeed = 42gives
error: Incompatible types in assignment (expression has type "int", variable has type "EmbedParameters"). Note this only becomes visible once the stubs above parse — otherwise mypy stops before reaching it.
The practical impact is larger than the three lines suggest: because mypy stops at the first unparseable stub, a downstream project on Windows cannot type-check at all until it patches its own site-packages.
The analysis in this report — comparing the win_amd64, manylinux and macOS
wheels, tracing the naming back to Wrap.h, and the prototype branch — was done
with the assistance of Claude Opus 5.
- Ngôn ngữ chính
- HTML
- Star
- 3.6k
- Fork
- 1.1k
- Merge trung bình
- 3 ngày 8 giờ
- Pull request đã merge (30 ngày)
- 44
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của rdkit/rdkit
-
Clarify mol_from_smiles vs qmol_from_smarts for substruct_count queries in Postgresql cartridge Đang mởenhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
-
bug
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 70/100
-
bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 65/100
-
enhancement
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 50/100
-
Improve depiction of RNA Đang mởenhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Issue tương tự
-
nix: vendorHash is outdated Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
typelevel/sbt-typelevel#929 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
openSUSE/python-rpm-macros#219 ·
-
HMR stops working Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Qiskit/mcp-servers#221 ·