-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Install Python auto-complete helper files (*.pyi
)
#16987
Comments
Correct. Where VSCode / pylance are concerned, they want the |
The pybind/pybind11#2350 (comment) linked in the OP gives two possible tools to solve this: It's worth noting that we don't need a 100% perfect solution. Tab-completing 90% of the API is much better than 0%. It does run the risk of misleading users into thinking those last 10% of functions don't exist, but that seems like fair trade-off. We should feel free to open upstream PRs to whichever tool we use to make its output better. Perhaps the first step would be to manually run each of those two tools against a built pydrake, and write a little summary about which one works best out of the gate. Then we can debate which path to go. |
Update: per f2f,
|
It ran beautifully, and has some obvious advantages. However, it doesn't seem to have emitted any of the things that pybind11-stubgen complains about. It didn't spit out any errors/warnings, so I don't know if it's just missing things, or if it's ignoring things that are "broken". In any case, its output seems, at first blush, incomplete. (Note: for clarity, we should say pybind11-stubgen, as my understanding is that the mypy tool is properly called mypy-stubgen. Indeed, the binary from mypy is actually just
Example:
|
Here's some more detailed analysis, starting with what from _typeshed import Incomplete
class _EqualityProxyBase:
def __init__(self, value) -> None: ...
def __hash__(self): ...
def __eq__(self, other): ...
value: Incomplete
class _DictKeyWrap(dict):
def __init__(self, dict_in, key_wrap, key_unwrap) -> None: ...
def __setitem__(self, key, value): ...
def __getitem__(self, key): ...
def __delitem__(self, key): ...
def __contains__(self, key): ...
def items(self): ...
def keys(self): ...
def raw(self): ...
class EqualToDict(_DictKeyWrap):
def __init__(self, *args, **kwargs): ...
class NamedViewBase:
def __init__(self, value) -> None: ...
@classmethod
def get_fields(cls): ...
def __getitem__(self, i): ...
def __setitem__(self, i, value_i) -> None: ...
def __setattr__(self, name, value) -> None: ...
def __len__(self): ...
def __iter__(self): ...
def __array__(self): ...
def namedview(name, fields): ... And here's what """Provides extensions for containers of Drake-related objects."""
from __future__ import annotations
import pydrake.common.containers
import typing
import numpy
_Shape = typing.Tuple[int, ...]
__all__ = [
"EqualToDict",
"NamedViewBase",
"namedview",
"np"
]
class _DictKeyWrap(dict):
pass
class NamedViewBase():
"""
Base for classes generated by ``namedview``.
Inspired by: https://bitbucket.org/ericvsmith/namedlist
"""
_fields = None
pass
class EqualToDict(_DictKeyWrap, dict):
"""
Implements a dictionary where keys are compared using type and
`lhs.EqualTo(rhs)`.
"""
pass
class _EqualityProxyBase():
@property
def value(self) -> None:
"""
:type: None
"""
pass Overall, it seems like
|
Just to update the timeline here -- #17521 added the new dependencies necessary for pyi generation to the workspace. It's smoke test was timing out in CI (from a mypy timeout, not a bazel timeout), so we've disabled the test for now in #17549. The next step is probably to figure out why it was timing out and correct that, as well as moving pyi generation from test-time to build-time via a |
Some notes on our testing plan:
(¹ "Comprehensive", here, doesn't necessarily mean actually comprehensive, just that this test would be the most comprehensive of the set.) |
Quick update from f2f: some classnames (with square brackets) are unfriendly to pypi. As a first milestone to reach master, we'll try omitting those from the pyi files just to get something shipped. We might have only 10% of our API in the pyi files, but at least we'll get the build & release mechanics sorted out. Then in a second milestone, we can work on getting more of the API covered. Kitware is going to write up a longer post detailing some of the problems. |
@mwoehlke-kitware likely has additional information to share but I will try and give the full picture as I understand it. I've included information on how to test Visual Studio Code yourself for those wanting to try it out. TL;DR: it may be OK to just start installing broken stubs. If done, it would be a good idea to document that it can be helpful for your IDE but (a) there are some big GOTCHAs to be aware of and (b) pin an issue explaining that we're prioritizing editor assists and The Main HurdleLets choose a specific example to represent the overall issue, >>> import pydrake.math as dm
>>> print("\n".join(name for name in dir(dm) if "rigidtransform" in name.lower()))
RigidTransform
RigidTransform_
RigidTransform_[AutoDiffXd]
RigidTransform_[Expression]
RigidTransform_[float]
_TemporaryName_N5drake4math14RigidTransformIN5Eigen14AutoDiffScalarINS2_6MatrixIdLin1ELi1ELi0ELin1ELi1EEEEEEE
_TemporaryName_N5drake4math14RigidTransformINS_8symbolic10ExpressionEEE
_TemporaryName_N5drake4math14RigidTransformIdEE
_TemporaryName_N5drake5ValueINS_4math14RigidTransformIN5Eigen14AutoDiffScalarINS3_6MatrixIdLin1ELi1ELi0ELin1ELi1EEEEEEEEE
_TemporaryName_N5drake5ValueINS_4math14RigidTransformINS_8symbolic10ExpressionEEEEE
_TemporaryName_N5drake5ValueINS_4math14RigidTransformIdEEEE
_TemporaryName_N5drake5ValueISt6vectorINS_4math14RigidTransformIN5Eigen14AutoDiffScalarINS4_6MatrixIdLin1ELi1ELi0ELin1ELi1EEEEEEESaIS9_EEEE
_TemporaryName_N5drake5ValueISt6vectorINS_4math14RigidTransformINS_8symbolic10ExpressionEEESaIS6_EEEE
_TemporaryName_N5drake5ValueISt6vectorINS_4math14RigidTransformIdEESaIS4_EEEE These classes in particular create an issue for us:
Additionally, drake/bindings/pydrake/common/cpp_template_pybind.h Lines 86 to 89 in 6cb7f1c
means that # snippets from pydrake/math.pyi
# ... imports ...
RigidTransform_: pydrake.common.cpp_template.TemplateClass
# ...
class RigidTransform:
# ...
cast: Any
# NOTE: the "default" is not internally self consistent, as far as
# the hints are concerned RigidTransform != RigidTransform_[float]
def InvertAndCompose(self, other: RigidTransform_[float]) -> RigidTransform_[float]: ...
# ...
# Only listing cast once, but this definition is invalid syntax for python
# and affects every class that binds the casting operations.
def cast[AutoDiffXd](self, *args, **kwargs) -> Any: ...
def cast[Expression](self, *args, **kwargs) -> Any: ...
def cast[float](self, *args, **kwargs) -> Any: ...
class RigidTransform_[AutoDiffXd]:
# ...
def InvertAndCompose(self, other: RigidTransform_[AutoDiffXd]) -> RigidTransform_[AutoDiffXd]: ...
class RigidTransform_[Expression]:
# ...
def InvertAndCompose(self, other: RigidTransform_[Expression]) -> RigidTransform_[Expression]: ...
class RigidTransform_[float]:
# ...
def InvertAndCompose(self, other: RigidTransform_[float]) -> RigidTransform_[float]: ... These are not actually valid class names in the sense that you would not be able to type this code in a REPL and have it run (the
ExperimentationA "hackaway" approach was tested just to see how far we could get. Something about find "${dest_site_packages}/pydrake" -name '*.pyi' -print0 | \
xargs -0 -n1 sed -i -r \
-e 's/^(\s*)(class|def) (\w+)[[]([][a-zA-Z0-9_., ]+)[]]/\1@instantiation(\4)\n\1\2 \3/' \
-e '/\w+<\w+[)]/d' \
-e '/const$/{N;d}' \
-e '/[(][)][(]/d' \
-e '/\b(True|False)\b/d' \
-e '/AddContactMaterial/d' Sample -class RigidTransform_[float]:
+@instantiation(float)
+class RigidTransform_:
_original_name: ClassVar[str] = ...
_original_qualname: ClassVar[str] = ...
_pybind11_del_orig: ClassVar[None] = ...
@@ -410,9 +421,12 @@ class RigidTransform_[float]:
def IsNearlyIdentity() -> Any: ...
def SetFromIsometry3(self, pose: pydrake.common.eigen_geometry.Isometry3_[float]) -> None: ...
def SetIdentity(self) -> RigidTransform_[float]: ...
- def cast[AutoDiffXd](self, *args, **kwargs) -> Any: ...
- def cast[Expression](self, *args, **kwargs) -> Any: ...
- def cast[float](self, *args, **kwargs) -> Any: ...
+ @instantiation(AutoDiffXd)
+ def cast(self, *args, **kwargs) -> Any: ...
+ @instantiation(Expression)
+ def cast(self, *args, **kwargs) -> Any: ...
+ @instantiation(float)
+ def cast(self, *args, **kwargs) -> Any: ...
def inverse(self) -> RigidTransform_[float]: ...
def rotation(self, *args, **kwargs) -> Any: ...
def set(self, R, p: numpy.ndarray[numpy.float64[3,1]]) -> None: ...
@@ -464,7 +478,8 @@ class RollPitchYaw:
def __getstate__(self) -> numpy.ndarray[numpy.float64[3,1]]: ...
def __setstate__(self, arg0: numpy.ndarray[numpy.float64[3,1]]) -> None: ... This enable valid syntax in our Some of the issues seem fixable, some are a little odd that may need massaging of how Attaching for (mostly our) convenience a script that does all of this, assumed to be inside a It will install to #!/usr/bin/env python3
from pydrake.geometry import GeometryInstance, Sphere, Rgba
from pydrake.math import RigidTransform, RigidTransform_
assert __name__ == "__main__"
# snippets ...
# https://github.com/RobotLocomotion/drake/blob/master/bindings/pydrake/test/geometry_common_test.py
# geom: GeometryInstance = GeometryInstance(X_PG=RigidTransform(), shape=Sphere(1.0), name="sphere")
geom: GeometryInstance = GeometryInstance(X_PG=RigidTransform_[float](), shape=Sphere(1.0), name="sphere")
some_color: Rgba = Rgba(0, 0, 1, 1)
# Note that we get RigidTransform_ not RigidTransform (no _)
xform = geom.pose()
numpy_arr = xform.GetAsMatrix4()
numpy_arr.argmax() So there me be opportunity to continue whittling down the remaining issues and possibly work with upstream on how to avoid our issues altogether. What should be assumed: once we start installing Just Concerning Editors
So at least in Visual Studio Code, provided I configure my environment to know where No Modificationsdrake_pyi_no_modifications.mp4With Modificationsdrake_pyi_with_modifications.mp4All that is needed for consumption via Visual Studio Code is to edit your workspace settings and make sure that wherever pydrake and the stub files was installed is added as an extra path:
|
Some further musings on the above. We use instantiations like Now, it seems mypy does have some concept of generics, but only true generics. What we have is actual specilization; that is, the API of As I see it, if type checking is to be at all useful in the face of template types, we need to do one of two things:
Such mechanism (2) would need to:
Additionally, mypy would need to recognize when such an object is invoked, and evaluate (if possible) the Conceptually, this is similar to Note that other tools (pyright, pycharm) presumably have the same problem and would need to support the same mechanism. |
We'll need to revisit what to do about "template" instantiations, down the road. For the moment, do not spend anymore time on it. I think I will make it a TRI project instead of a Kitware project. Please work only on the first milestone -- create a PR that installs the pyi files (and anything else related), so that all of the release channels (apt, tgz, whl) contain the pyi files. We will test that and plan to merge it unless that we find that it will cause users problems in practice. |
The pyi files are built and installed now as of #17709. I'm moving this back to the TRI side to iterate on the design space of our template instantiations and how to make it compatible with the type stubs. |
Update: I'm going to try to wait for bazel 5.3.1 to appear -- it has some fixes for shared library rpaths that might make this a bit easier. |
The nightly builds will incorporate Note in particular that there are wheel nightly builds available, for early preview: I went back and watched the two videos upthread again. The first video (no modifications) seems perfectly fine to me. I guess the objection is that when suggesting the types of arguments like The suggestion isn't wrong (the more complicated type would work fine) but is not the typical, shorter spelling that the user probably wants. In short, it's correct but imperfect. I'll hypothesize that in practice, users will be able to overcome this imperfection. They know about the shorter types, and will probably edit the suggestion to use the shorter spelling. Thus, there is nothing left remaining to do in this ticket. In any case, the call to action was to install pyi files to enable IDE auto-complete. That part seems done. If we think that certain IDE suggestions are not good enough given the current pyi contents, then we can open more detailed tickets about those acute problems. I'd like to hear what additional improvements actual users request, instead of us trying to speculate. I'll close this issue; we'll open new issues for user-requested follow-ups (or users can open their own requests too!). |
See pybind/pybind11#2350 (comment).
As I understand it, some IDEs will not help the user auto-complete their function-call arguments unless we also install
*.pyi
files for our native modules.As part of our build process, we should import
mypy
(maybepython3-mypy
if it's new enough, otherwise the wheel) and then invoke it to create pyi files, and then install them alongside our modules.The text was updated successfully, but these errors were encountered: