diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0b57d5ba22..952afbf2ae 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -209,6 +209,21 @@ jobs: cmd: "pytest -m lcm" dev-image: dev:${{ (needs.check-changes.outputs.python == 'true' || needs.check-changes.outputs.dev == 'true') && needs.dev.result == 'success' && needs.check-changes.outputs.branch-tag || 'dev' }} + run-mypy: + needs: [check-changes, ros-dev] + if: always() + uses: ./.github/workflows/tests.yml + secrets: inherit + with: + should-run: ${{ + needs.check-changes.result == 'success' && + ((needs.ros-dev.result == 'success') || + (needs.ros-dev.result == 'skipped' && + needs.check-changes.outputs.tests == 'true')) + }} + cmd: "MYPYPATH=/opt/ros/humble/lib/python3.10/site-packages mypy dimos" + dev-image: ros-dev:${{ (needs.check-changes.outputs.python == 'true' || needs.check-changes.outputs.dev == 'true' || needs.check-changes.outputs.ros == 'true') && needs.ros-dev.result == 'success' && needs.check-changes.outputs.branch-tag || 'dev' }} + # Run module tests directly to avoid pytest forking issues # run-module-tests: # needs: [check-changes, dev] diff --git a/bin/mypy-ros b/bin/mypy-ros new file mode 100755 index 0000000000..d40ac95cf8 --- /dev/null +++ b/bin/mypy-ros @@ -0,0 +1,19 @@ +#!/bin/bash + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +cd "$ROOT" + +main() { + if [ -z "$(docker images -q dimos-ros-dev)" ]; then + (cd docker/ros; docker build -t dimos-ros .) + docker build -t dimos-ros-python --build-arg FROM_IMAGE=dimos-ros -f docker/python/Dockerfile . + docker build -t dimos-ros-dev --build-arg FROM_IMAGE=dimos-ros-python -f docker/dev/Dockerfile . + fi + + docker run --rm -v $(pwd):/app -w /app dimos-ros-dev bash -c "source /opt/ros/humble/setup.bash && MYPYPATH=/opt/ros/humble/lib/python3.10/site-packages mypy dimos" +} + +main "$@" diff --git a/bin/mypy-strict b/bin/mypy-strict deleted file mode 100755 index 33e1f0c798..0000000000 --- a/bin/mypy-strict +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/bash -# -# Run mypy with strict settings on the dimos codebase. -# -# Usage: -# ./bin/mypy-strict # Run mypy and show all errors -# ./bin/mypy-strict --user me # Filter for your git user.email -# ./bin/mypy-strict --after cutoff # Filter for lines committed on or after 2025-10-08 -# ./bin/mypy-strict --after 2025-11-11 # Filter for lines committed on or after specific date -# ./bin/mypy-strict --user me --after cutoff # Chain filters -# - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -cd "$ROOT" - -. .venv/bin/activate - -run_mypy() { - export MYPYPATH=/opt/ros/jazzy/lib/python3.12/site-packages - - mypy_args=( - --show-error-codes - --hide-error-context - --no-pretty - dimos - ) - mypy "${mypy_args[@]}" -} - -main() { - local user_email="none" - local after_date="" - local in_this_branch="" - - # Parse arguments - while [[ $# -gt 0 ]]; do - case "$1" in - --user) - if [[ $# -lt 2 ]]; then - echo "Error: --user requires an argument" >&2 - exit 1 - fi - case "$2" in - me) - user_email="$(git config user.email || echo none)" - ;; - all) - user_email="none" - ;; - *) - user_email="$2" - ;; - esac - shift 2 - ;; - --after) - if [[ $# -lt 2 ]]; then - echo "Error: --after requires an argument" >&2 - exit 1 - fi - case "$2" in - cutoff) - after_date="2025-10-10" - ;; - start) - after_date="" - ;; - *) - after_date="$2" - ;; - esac - shift 2 - ;; - --in-this-branch) - in_this_branch=true - shift 1 - ;; - *) - echo "Error: Unknown argument '$1'" >&2 - exit 1 - ;; - esac - done - - # Build filter pipeline - local pipeline="run_mypy" - - if [[ -n "$after_date" ]]; then - pipeline="$pipeline | ./bin/filter-errors-after-date '$after_date'" - fi - - if [[ "$user_email" != "none" ]]; then - pipeline="$pipeline | ./bin/filter-errors-for-user '$user_email'" - fi - - if [[ "$in_this_branch" ]]; then - pipeline="$pipeline | grep -Ff <(git diff --name-only dev..HEAD) -" - fi - - eval "$pipeline" -} - -main "$@" diff --git a/dimos/core/stream.py b/dimos/core/stream.py index 30a015fa6e..37a6fce766 100644 --- a/dimos/core/stream.py +++ b/dimos/core/stream.py @@ -256,7 +256,7 @@ def transport(self) -> Transport[T]: def publish(self, msg) -> None: # type: ignore[no-untyped-def] self.transport.broadcast(self, msg) # type: ignore[arg-type] - @transport.setter # type: ignore[attr-defined, misc, no-redef] + @transport.setter # type: ignore[attr-defined, misc, no-redef, untyped-decorator] def transport(self, value: Transport[T]) -> None: self.owner.set_transport(self.name, value).result() # type: ignore[union-attr] self._transport = value diff --git a/dimos/hardware/camera/zed/camera.py b/dimos/hardware/camera/zed/camera.py index afe400b35c..160062173b 100644 --- a/dimos/hardware/camera/zed/camera.py +++ b/dimos/hardware/camera/zed/camera.py @@ -591,7 +591,7 @@ def __init__( # type: ignore[no-untyped-def] self.tf = TF() # Initialize storage for recording if path provided - self.storages = None + self.storages: dict[str, Any] | None = None if self.recording_path: from dimos.utils.testing import TimedSensorStorage diff --git a/dimos/hardware/gstreamer_camera.py b/dimos/hardware/gstreamer_camera.py index 79fad6c930..076e1ac40d 100644 --- a/dimos/hardware/gstreamer_camera.py +++ b/dimos/hardware/gstreamer_camera.py @@ -29,11 +29,11 @@ if "/usr/lib/python3/dist-packages" not in sys.path: sys.path.insert(0, "/usr/lib/python3/dist-packages") -import gi # type: ignore[import-not-found] +import gi # type: ignore[import-untyped,import-not-found] gi.require_version("Gst", "1.0") gi.require_version("GstApp", "1.0") -from gi.repository import GLib, Gst # type: ignore[import-not-found] +from gi.repository import GLib, Gst # type: ignore[import-untyped,import-not-found] logger = setup_logger(level=logging.INFO) diff --git a/dimos/hardware/gstreamer_sender.py b/dimos/hardware/gstreamer_sender.py index 4f10c8eb76..3cd03fdce7 100755 --- a/dimos/hardware/gstreamer_sender.py +++ b/dimos/hardware/gstreamer_sender.py @@ -24,11 +24,11 @@ if "/usr/lib/python3/dist-packages" not in sys.path: sys.path.insert(0, "/usr/lib/python3/dist-packages") -import gi # type: ignore[import-not-found] +import gi # type: ignore[import-untyped,import-not-found] gi.require_version("Gst", "1.0") gi.require_version("GstVideo", "1.0") -from gi.repository import GLib, Gst # type: ignore[import-not-found] +from gi.repository import GLib, Gst # type: ignore[import-untyped,import-not-found] # Initialize GStreamer Gst.init(None) diff --git a/dimos/hardware/piper_arm.py b/dimos/hardware/piper_arm.py index e8be04a9b3..8837e577e1 100644 --- a/dimos/hardware/piper_arm.py +++ b/dimos/hardware/piper_arm.py @@ -27,7 +27,7 @@ from piper_sdk import * # type: ignore[import-not-found] # from the official Piper SDK import pytest from reactivex.disposable import Disposable -from scipy.spatial.transform import Rotation as R +from scipy.spatial.transform import Rotation as R # type: ignore[import-untyped] import dimos.core as core from dimos.core import In, Module, rpc diff --git a/dimos/manipulation/visual_servoing/pbvs.py b/dimos/manipulation/visual_servoing/pbvs.py index 10cf66c726..3fab3692d8 100644 --- a/dimos/manipulation/visual_servoing/pbvs.py +++ b/dimos/manipulation/visual_servoing/pbvs.py @@ -21,7 +21,7 @@ from dimos_lcm.vision_msgs import Detection3D # type: ignore[import-untyped] import numpy as np -from scipy.spatial.transform import Rotation as R +from scipy.spatial.transform import Rotation as R # type: ignore[import-untyped] from dimos.manipulation.visual_servoing.utils import ( create_pbvs_visualization, diff --git a/dimos/models/vl/base.py b/dimos/models/vl/base.py index acb998d274..cc9da9e540 100644 --- a/dimos/models/vl/base.py +++ b/dimos/models/vl/base.py @@ -72,7 +72,7 @@ def warmup(self) -> None: pass # requery once if JSON parsing fails - @retry(max_retries=2, on_exception=json.JSONDecodeError, delay=0.0) # type: ignore[misc] + @retry(max_retries=2, on_exception=json.JSONDecodeError, delay=0.0) # type: ignore[misc, untyped-decorator] def query_json(self, image: Image, query: str) -> dict: # type: ignore[type-arg] response = self.query(image, query) return extract_json(response) # type: ignore[return-value] diff --git a/dimos/msgs/geometry_msgs/Pose.py b/dimos/msgs/geometry_msgs/Pose.py index a6e428cf25..079461b9df 100644 --- a/dimos/msgs/geometry_msgs/Pose.py +++ b/dimos/msgs/geometry_msgs/Pose.py @@ -22,7 +22,7 @@ ) try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] Point as ROSPoint, Pose as ROSPose, Quaternion as ROSQuaternion, diff --git a/dimos/msgs/geometry_msgs/PoseStamped.py b/dimos/msgs/geometry_msgs/PoseStamped.py index 803a621d80..930bdb213d 100644 --- a/dimos/msgs/geometry_msgs/PoseStamped.py +++ b/dimos/msgs/geometry_msgs/PoseStamped.py @@ -20,7 +20,9 @@ from dimos_lcm.geometry_msgs import PoseStamped as LCMPoseStamped # type: ignore[import-untyped] try: - from geometry_msgs.msg import PoseStamped as ROSPoseStamped # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[import-untyped] + PoseStamped as ROSPoseStamped, # type: ignore[attr-defined] + ) except ImportError: ROSPoseStamped = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/geometry_msgs/PoseWithCovariance.py b/dimos/msgs/geometry_msgs/PoseWithCovariance.py index 5348efdc13..53523ee43d 100644 --- a/dimos/msgs/geometry_msgs/PoseWithCovariance.py +++ b/dimos/msgs/geometry_msgs/PoseWithCovariance.py @@ -23,7 +23,7 @@ from plum import dispatch try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] PoseWithCovariance as ROSPoseWithCovariance, ) except ImportError: diff --git a/dimos/msgs/geometry_msgs/PoseWithCovarianceStamped.py b/dimos/msgs/geometry_msgs/PoseWithCovarianceStamped.py index a79088007d..45037eb883 100644 --- a/dimos/msgs/geometry_msgs/PoseWithCovarianceStamped.py +++ b/dimos/msgs/geometry_msgs/PoseWithCovarianceStamped.py @@ -24,7 +24,7 @@ from plum import dispatch try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] PoseWithCovarianceStamped as ROSPoseWithCovarianceStamped, ) except ImportError: diff --git a/dimos/msgs/geometry_msgs/Quaternion.py b/dimos/msgs/geometry_msgs/Quaternion.py index 14933ab712..d1420e78e1 100644 --- a/dimos/msgs/geometry_msgs/Quaternion.py +++ b/dimos/msgs/geometry_msgs/Quaternion.py @@ -22,7 +22,7 @@ from dimos_lcm.geometry_msgs import Quaternion as LCMQuaternion # type: ignore[import-untyped] import numpy as np from plum import dispatch -from scipy.spatial.transform import Rotation as R +from scipy.spatial.transform import Rotation as R # type: ignore[import-untyped] from dimos.msgs.geometry_msgs.Vector3 import Vector3 diff --git a/dimos/msgs/geometry_msgs/Transform.py b/dimos/msgs/geometry_msgs/Transform.py index a2f8eaf58c..4863d3f167 100644 --- a/dimos/msgs/geometry_msgs/Transform.py +++ b/dimos/msgs/geometry_msgs/Transform.py @@ -23,7 +23,7 @@ ) try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] Quaternion as ROSQuaternion, Transform as ROSTransform, TransformStamped as ROSTransformStamped, diff --git a/dimos/msgs/geometry_msgs/Twist.py b/dimos/msgs/geometry_msgs/Twist.py index 8dea77dac5..9e4b41d59f 100644 --- a/dimos/msgs/geometry_msgs/Twist.py +++ b/dimos/msgs/geometry_msgs/Twist.py @@ -18,7 +18,7 @@ from plum import dispatch try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] Twist as ROSTwist, Vector3 as ROSVector3, ) diff --git a/dimos/msgs/geometry_msgs/TwistStamped.py b/dimos/msgs/geometry_msgs/TwistStamped.py index 71dfdbc6e4..85ad674aac 100644 --- a/dimos/msgs/geometry_msgs/TwistStamped.py +++ b/dimos/msgs/geometry_msgs/TwistStamped.py @@ -21,7 +21,9 @@ from plum import dispatch try: - from geometry_msgs.msg import TwistStamped as ROSTwistStamped # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[import-untyped] + TwistStamped as ROSTwistStamped, # type: ignore[attr-defined] + ) except ImportError: ROSTwistStamped = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/geometry_msgs/TwistWithCovariance.py b/dimos/msgs/geometry_msgs/TwistWithCovariance.py index ceeb3a0193..566156e2a8 100644 --- a/dimos/msgs/geometry_msgs/TwistWithCovariance.py +++ b/dimos/msgs/geometry_msgs/TwistWithCovariance.py @@ -23,7 +23,7 @@ from plum import dispatch try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] TwistWithCovariance as ROSTwistWithCovariance, ) except ImportError: diff --git a/dimos/msgs/geometry_msgs/TwistWithCovarianceStamped.py b/dimos/msgs/geometry_msgs/TwistWithCovarianceStamped.py index 256dca79e3..efc249cb83 100644 --- a/dimos/msgs/geometry_msgs/TwistWithCovarianceStamped.py +++ b/dimos/msgs/geometry_msgs/TwistWithCovarianceStamped.py @@ -24,7 +24,7 @@ from plum import dispatch try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] TwistWithCovarianceStamped as ROSTwistWithCovarianceStamped, ) except ImportError: diff --git a/dimos/msgs/nav_msgs/OccupancyGrid.py b/dimos/msgs/nav_msgs/OccupancyGrid.py index 068422a7f2..e66ec4fb1d 100644 --- a/dimos/msgs/nav_msgs/OccupancyGrid.py +++ b/dimos/msgs/nav_msgs/OccupancyGrid.py @@ -24,7 +24,7 @@ ) from dimos_lcm.std_msgs import Time as LCMTime # type: ignore[import-untyped] import numpy as np -from scipy import ndimage +from scipy import ndimage # type: ignore[import-untyped] from dimos.msgs.geometry_msgs import Pose, Vector3, VectorLike from dimos.types.timestamped import Timestamped diff --git a/dimos/msgs/nav_msgs/Odometry.py b/dimos/msgs/nav_msgs/Odometry.py index d88a2269eb..9d770f452a 100644 --- a/dimos/msgs/nav_msgs/Odometry.py +++ b/dimos/msgs/nav_msgs/Odometry.py @@ -22,7 +22,7 @@ from plum import dispatch try: - from nav_msgs.msg import Odometry as ROSOdometry # type: ignore[attr-defined] + from nav_msgs.msg import Odometry as ROSOdometry # type: ignore[attr-defined, import-untyped] except ImportError: ROSOdometry = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/nav_msgs/Path.py b/dimos/msgs/nav_msgs/Path.py index e86396e678..859d268b53 100644 --- a/dimos/msgs/nav_msgs/Path.py +++ b/dimos/msgs/nav_msgs/Path.py @@ -27,7 +27,7 @@ from dimos_lcm.std_msgs import Header as LCMHeader, Time as LCMTime # type: ignore[import-untyped] try: - from nav_msgs.msg import Path as ROSPath # type: ignore[attr-defined] + from nav_msgs.msg import Path as ROSPath # type: ignore[attr-defined, import-untyped] except ImportError: ROSPath = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/sensor_msgs/CameraInfo.py b/dimos/msgs/sensor_msgs/CameraInfo.py index 866ee896fd..c6b83dfda4 100644 --- a/dimos/msgs/sensor_msgs/CameraInfo.py +++ b/dimos/msgs/sensor_msgs/CameraInfo.py @@ -23,11 +23,11 @@ # Import ROS types try: - from sensor_msgs.msg import ( # type: ignore[attr-defined] + from sensor_msgs.msg import ( # type: ignore[attr-defined, import-untyped] CameraInfo as ROSCameraInfo, RegionOfInterest as ROSRegionOfInterest, ) - from std_msgs.msg import Header as ROSHeader # type: ignore[attr-defined] + from std_msgs.msg import Header as ROSHeader # type: ignore[attr-defined, import-untyped] ROS_AVAILABLE = True except ImportError: diff --git a/dimos/msgs/sensor_msgs/Image.py b/dimos/msgs/sensor_msgs/Image.py index 7f26f88a71..493eca92e9 100644 --- a/dimos/msgs/sensor_msgs/Image.py +++ b/dimos/msgs/sensor_msgs/Image.py @@ -50,7 +50,7 @@ cp = None try: - from sensor_msgs.msg import Image as ROSImage # type: ignore[attr-defined] + from sensor_msgs.msg import Image as ROSImage # type: ignore[attr-defined, import-untyped] except ImportError: ROSImage = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/sensor_msgs/Joy.py b/dimos/msgs/sensor_msgs/Joy.py index 3cf083c553..8d8029640a 100644 --- a/dimos/msgs/sensor_msgs/Joy.py +++ b/dimos/msgs/sensor_msgs/Joy.py @@ -20,7 +20,7 @@ from dimos_lcm.sensor_msgs import Joy as LCMJoy # type: ignore[import-untyped] try: - from sensor_msgs.msg import Joy as ROSJoy # type: ignore[attr-defined] + from sensor_msgs.msg import Joy as ROSJoy # type: ignore[attr-defined, import-untyped] except ImportError: ROSJoy = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/sensor_msgs/PointCloud2.py b/dimos/msgs/sensor_msgs/PointCloud2.py index a7db0ca24b..677c753c0b 100644 --- a/dimos/msgs/sensor_msgs/PointCloud2.py +++ b/dimos/msgs/sensor_msgs/PointCloud2.py @@ -30,11 +30,11 @@ # Import ROS types try: - from sensor_msgs.msg import ( # type: ignore[attr-defined] + from sensor_msgs.msg import ( # type: ignore[attr-defined, import-untyped] PointCloud2 as ROSPointCloud2, PointField as ROSPointField, ) - from std_msgs.msg import Header as ROSHeader # type: ignore[attr-defined] + from std_msgs.msg import Header as ROSHeader # type: ignore[attr-defined, import-untyped] ROS_AVAILABLE = True except ImportError: diff --git a/dimos/msgs/std_msgs/Bool.py b/dimos/msgs/std_msgs/Bool.py index 293b216882..caebceef8c 100644 --- a/dimos/msgs/std_msgs/Bool.py +++ b/dimos/msgs/std_msgs/Bool.py @@ -18,7 +18,7 @@ from dimos_lcm.std_msgs import Bool as LCMBool # type: ignore[import-untyped] try: - from std_msgs.msg import Bool as ROSBool # type: ignore[attr-defined] + from std_msgs.msg import Bool as ROSBool # type: ignore[attr-defined, import-untyped] except ImportError: ROSBool = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/std_msgs/Int8.py b/dimos/msgs/std_msgs/Int8.py index f7bb580981..4f610efbce 100644 --- a/dimos/msgs/std_msgs/Int8.py +++ b/dimos/msgs/std_msgs/Int8.py @@ -22,7 +22,7 @@ from dimos_lcm.std_msgs import Int8 as LCMInt8 # type: ignore[import-untyped] try: - from std_msgs.msg import Int8 as ROSInt8 # type: ignore[attr-defined] + from std_msgs.msg import Int8 as ROSInt8 # type: ignore[attr-defined, import-untyped] except ImportError: ROSInt8 = None # type: ignore[assignment, misc] diff --git a/dimos/msgs/tf2_msgs/TFMessage.py b/dimos/msgs/tf2_msgs/TFMessage.py index 732ecf3872..da6812be34 100644 --- a/dimos/msgs/tf2_msgs/TFMessage.py +++ b/dimos/msgs/tf2_msgs/TFMessage.py @@ -32,10 +32,10 @@ from dimos_lcm.tf2_msgs import TFMessage as LCMTFMessage # type: ignore[import-untyped] try: - from geometry_msgs.msg import ( # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] TransformStamped as ROSTransformStamped, ) - from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined] + from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined, import-untyped] except ImportError: ROSTFMessage = None # type: ignore[assignment, misc] ROSTransformStamped = None # type: ignore[assignment, misc] diff --git a/dimos/navigation/bt_navigator/goal_validator.py b/dimos/navigation/bt_navigator/goal_validator.py index 6583f157cb..cdf86ccac5 100644 --- a/dimos/navigation/bt_navigator/goal_validator.py +++ b/dimos/navigation/bt_navigator/goal_validator.py @@ -227,7 +227,7 @@ def _find_safe_goal_voronoi( - Requires scipy for efficient implementation """ - from scipy import ndimage + from scipy import ndimage # type: ignore[import-untyped] from skimage.morphology import skeletonize # type: ignore[import-not-found] # Convert goal to grid coordinates diff --git a/dimos/navigation/demo_ros_navigation.py b/dimos/navigation/demo_ros_navigation.py index 855e9ecae5..d32c3794af 100644 --- a/dimos/navigation/demo_ros_navigation.py +++ b/dimos/navigation/demo_ros_navigation.py @@ -14,7 +14,7 @@ import time -import rclpy +import rclpy # type: ignore[import-untyped] from dimos import core from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Twist, Vector3 diff --git a/dimos/navigation/frontier_exploration/utils.py b/dimos/navigation/frontier_exploration/utils.py index d307749531..40073ded3a 100644 --- a/dimos/navigation/frontier_exploration/utils.py +++ b/dimos/navigation/frontier_exploration/utils.py @@ -59,7 +59,7 @@ def costmap_to_pil_image(costmap: OccupancyGrid, scale_factor: int = 2) -> Image # Scale up if requested if scale_factor > 1: new_size = (img.width * scale_factor, img.height * scale_factor) - img = img.resize(new_size, Image.NEAREST) # Use NEAREST to keep sharp pixels + img = img.resize(new_size, Image.NEAREST) # type: ignore[attr-defined] # Use NEAREST to keep sharp pixels return img diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index f65559ddb4..7db695e759 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -24,22 +24,25 @@ import threading import time -from geometry_msgs.msg import ( # type: ignore[attr-defined] +from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] PointStamped as ROSPointStamped, PoseStamped as ROSPoseStamped, TwistStamped as ROSTwistStamped, ) -from nav_msgs.msg import Path as ROSPath # type: ignore[attr-defined] -import rclpy -from rclpy.node import Node +from nav_msgs.msg import Path as ROSPath # type: ignore[attr-defined, import-untyped] +import rclpy # type: ignore[import-untyped] +from rclpy.node import Node # type: ignore[import-untyped] from reactivex import operators as ops from reactivex.subject import Subject -from sensor_msgs.msg import ( # type: ignore[attr-defined] +from sensor_msgs.msg import ( # type: ignore[attr-defined, import-untyped] Joy as ROSJoy, PointCloud2 as ROSPointCloud2, ) -from std_msgs.msg import Bool as ROSBool, Int8 as ROSInt8 # type: ignore[attr-defined] -from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined] +from std_msgs.msg import ( # type: ignore[attr-defined, import-untyped] + Bool as ROSBool, + Int8 as ROSInt8, +) +from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined, import-untyped] from dimos import spec from dimos.agents2 import Reducer, Stream, skill # type: ignore[attr-defined] diff --git a/dimos/perception/common/export_tensorrt.py b/dimos/perception/common/export_tensorrt.py index 71499173ca..f349390b57 100644 --- a/dimos/perception/common/export_tensorrt.py +++ b/dimos/perception/common/export_tensorrt.py @@ -14,7 +14,7 @@ import argparse -from ultralytics import YOLO, FastSAM +from ultralytics import YOLO, FastSAM # type: ignore[attr-defined] def parse_args(): # type: ignore[no-untyped-def] @@ -46,7 +46,7 @@ def main() -> None: int8 = args.precision == "int8" # Load the appropriate model if args.model_type == "yolo": - model = YOLO(args.model_path) + model: YOLO | FastSAM = YOLO(args.model_path) else: model = FastSAM(args.model_path) diff --git a/dimos/perception/detection/detectors/person/yolo.py b/dimos/perception/detection/detectors/person/yolo.py index 0e6864af42..5b92fc5c7e 100644 --- a/dimos/perception/detection/detectors/person/yolo.py +++ b/dimos/perception/detection/detectors/person/yolo.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ultralytics import YOLO +from ultralytics import YOLO # type: ignore[attr-defined] from dimos.msgs.sensor_msgs import Image from dimos.perception.detection.detectors.types import Detector diff --git a/dimos/perception/detection/detectors/yolo.py b/dimos/perception/detection/detectors/yolo.py index 005d01bfc7..4c3071e3ba 100644 --- a/dimos/perception/detection/detectors/yolo.py +++ b/dimos/perception/detection/detectors/yolo.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ultralytics import YOLO +from ultralytics import YOLO # type: ignore[attr-defined] from dimos.msgs.sensor_msgs import Image from dimos.perception.detection.detectors.types import Detector diff --git a/dimos/perception/pointcloud/utils.py b/dimos/perception/pointcloud/utils.py index f79d1c9d74..682f65903c 100644 --- a/dimos/perception/pointcloud/utils.py +++ b/dimos/perception/pointcloud/utils.py @@ -25,7 +25,7 @@ import cv2 import numpy as np import open3d as o3d # type: ignore[import-untyped] -from scipy.spatial import cKDTree +from scipy.spatial import cKDTree # type: ignore[import-untyped] import yaml from dimos.perception.common.utils import project_3d_points_to_2d diff --git a/dimos/perception/segmentation/sam_2d_seg.py b/dimos/perception/segmentation/sam_2d_seg.py index 83da24a8cf..bc489147f4 100644 --- a/dimos/perception/segmentation/sam_2d_seg.py +++ b/dimos/perception/segmentation/sam_2d_seg.py @@ -20,7 +20,7 @@ import cv2 import onnxruntime # type: ignore[import-untyped] -from ultralytics import FastSAM +from ultralytics import FastSAM # type: ignore[attr-defined] from dimos.perception.common.detection2d_tracker import get_tracked_results, target2dTracker from dimos.perception.segmentation.image_analyzer import ImageAnalyzer diff --git a/dimos/protocol/skill/test_coordinator.py b/dimos/protocol/skill/test_coordinator.py index fa5588b409..bb673514f4 100644 --- a/dimos/protocol/skill/test_coordinator.py +++ b/dimos/protocol/skill/test_coordinator.py @@ -47,51 +47,53 @@ def delayadd(self, x: int, y: int) -> int: time.sleep(0.3) return x + y - @skill(stream=Stream.call_agent, reducer=Reducer.all) + @skill(stream=Stream.call_agent, reducer=Reducer.all) # type: ignore[arg-type] def counter(self, count_to: int, delay: float | None = 0.05) -> Generator[int, None, None]: """Counts from 1 to count_to, with an optional delay between counts.""" for i in range(1, count_to + 1): - if delay > 0: + if delay is not None and delay > 0: time.sleep(delay) yield i - @skill(stream=Stream.passive, reducer=Reducer.sum) + @skill(stream=Stream.passive, reducer=Reducer.sum) # type: ignore[arg-type] def counter_passive_sum( self, count_to: int, delay: float | None = 0.05 ) -> Generator[int, None, None]: """Counts from 1 to count_to, with an optional delay between counts.""" for i in range(1, count_to + 1): - if delay > 0: + if delay is not None and delay > 0: time.sleep(delay) yield i - @skill(stream=Stream.passive, reducer=Reducer.latest) + @skill(stream=Stream.passive, reducer=Reducer.latest) # type: ignore[arg-type] def current_time(self, frequency: float | None = 10) -> Generator[str, None, None]: """Provides current time.""" while True: yield str(datetime.datetime.now()) - time.sleep(1 / frequency) + if frequency is not None: + time.sleep(1 / frequency) - @skill(stream=Stream.passive, reducer=Reducer.latest) + @skill(stream=Stream.passive, reducer=Reducer.latest) # type: ignore[arg-type] def uptime_seconds(self, frequency: float | None = 10) -> Generator[float, None, None]: """Provides current uptime.""" start_time = datetime.datetime.now() while True: yield (datetime.datetime.now() - start_time).total_seconds() - time.sleep(1 / frequency) + if frequency is not None: + time.sleep(1 / frequency) @skill() def current_date(self, frequency: float | None = 10) -> str: """Provides current date.""" - return datetime.datetime.now() + return str(datetime.datetime.now()) @skill(output=Output.image) - def take_photo(self) -> str: + def take_photo(self) -> Image: # type: ignore[type-arg] """Takes a camera photo""" print("Taking photo...") - img = Image.from_file(get_data("cafe-smol.jpg")) + img = Image.from_file(str(get_data("cafe-smol.jpg"))) # type: ignore[arg-type] print("Photo taken.") - return img + return img # type: ignore[return-value] @pytest.mark.asyncio @@ -113,7 +115,7 @@ async def test_coordinator_parallel_calls() -> None: skill_id = f"test-call-{cnt}" tool_msg = skillstates[skill_id].agent_encode() - assert tool_msg.content == cnt + 2 + assert tool_msg.content == cnt + 2 # type: ignore[union-attr] cnt += 1 if cnt < 5: diff --git a/dimos/robot/position_stream.py b/dimos/robot/position_stream.py index 34d07daabb..67b82c7fda 100644 --- a/dimos/robot/position_stream.py +++ b/dimos/robot/position_stream.py @@ -21,9 +21,9 @@ import logging import time -from geometry_msgs.msg import PoseStamped # type: ignore[attr-defined] -from nav_msgs.msg import Odometry # type: ignore[attr-defined] -from rclpy.node import Node +from geometry_msgs.msg import PoseStamped # type: ignore[attr-defined, import-untyped] +from nav_msgs.msg import Odometry # type: ignore[attr-defined, import-untyped] +from rclpy.node import Node # type: ignore[import-untyped] from reactivex import Observable, Subject, operators as ops from dimos.utils.logging_config import setup_logger diff --git a/dimos/robot/ros_bridge.py b/dimos/robot/ros_bridge.py index 0727e6a997..bccf5d2c02 100644 --- a/dimos/robot/ros_bridge.py +++ b/dimos/robot/ros_bridge.py @@ -18,10 +18,15 @@ from typing import Any try: - import rclpy - from rclpy.executors import SingleThreadedExecutor - from rclpy.node import Node - from rclpy.qos import QoSDurabilityPolicy, QoSHistoryPolicy, QoSProfile, QoSReliabilityPolicy + import rclpy # type: ignore[import-untyped] + from rclpy.executors import SingleThreadedExecutor # type: ignore[import-untyped] + from rclpy.node import Node # type: ignore[import-untyped] + from rclpy.qos import ( # type: ignore[import-untyped] + QoSDurabilityPolicy, + QoSHistoryPolicy, + QoSProfile, + QoSReliabilityPolicy, + ) except ImportError: rclpy = None # type: ignore[assignment] SingleThreadedExecutor = None # type: ignore[assignment, misc] diff --git a/dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py b/dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py index cf6df79f06..2dc8b78fad 100644 --- a/dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py +++ b/dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py @@ -42,9 +42,11 @@ # Handle ROS imports for environments where ROS is not available like CI try: - from geometry_msgs.msg import TwistStamped as ROSTwistStamped # type: ignore[attr-defined] - from nav_msgs.msg import Odometry as ROSOdometry # type: ignore[attr-defined] - from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[import-untyped] + TwistStamped as ROSTwistStamped, # type: ignore[attr-defined] + ) + from nav_msgs.msg import Odometry as ROSOdometry # type: ignore[attr-defined, import-untyped] + from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined, import-untyped] ROS_AVAILABLE = True except ImportError: diff --git a/dimos/types/ros_polyfill.py b/dimos/types/ros_polyfill.py index 200964dcc6..f3d674afbd 100644 --- a/dimos/types/ros_polyfill.py +++ b/dimos/types/ros_polyfill.py @@ -13,14 +13,19 @@ # limitations under the License. try: - from geometry_msgs.msg import Vector3 # type: ignore[attr-defined] + from geometry_msgs.msg import Vector3 # type: ignore[attr-defined, import-untyped] except ImportError: from dimos.msgs.geometry_msgs import Vector3 try: - from geometry_msgs.msg import Point, Pose, Quaternion, Twist # type: ignore[attr-defined] - from nav_msgs.msg import OccupancyGrid, Odometry # type: ignore[attr-defined] - from std_msgs.msg import Header # type: ignore[attr-defined] + from geometry_msgs.msg import ( # type: ignore[attr-defined, import-untyped] + Point, + Pose, + Quaternion, + Twist, + ) + from nav_msgs.msg import OccupancyGrid, Odometry # type: ignore[attr-defined, import-untyped] + from std_msgs.msg import Header # type: ignore[attr-defined, import-untyped] except ImportError: from dimos_lcm.geometry_msgs import ( # type: ignore[import-untyped, no-redef] Point, diff --git a/dimos/utils/transform_utils.py b/dimos/utils/transform_utils.py index c108bc34b9..6185bfb1ca 100644 --- a/dimos/utils/transform_utils.py +++ b/dimos/utils/transform_utils.py @@ -14,7 +14,7 @@ import numpy as np -from scipy.spatial.transform import Rotation as R +from scipy.spatial.transform import Rotation as R # type: ignore[import-untyped] from dimos.msgs.geometry_msgs import Pose, Quaternion, Transform, Vector3 diff --git a/dimos/web/websocket_vis/websocket_vis_module.py b/dimos/web/websocket_vis/websocket_vis_module.py index 2aec94067e..ba0352f595 100644 --- a/dimos/web/websocket_vis/websocket_vis_module.py +++ b/dimos/web/websocket_vis/websocket_vis_module.py @@ -186,7 +186,7 @@ async def serve_index(request): # type: ignore[no-untyped-def] self.app = socketio.ASGIApp(self.sio, starlette_app) # Register SocketIO event handlers - @self.sio.event # type: ignore[misc] + @self.sio.event # type: ignore[misc, untyped-decorator] async def connect(sid, environ) -> None: # type: ignore[no-untyped-def] with self.state_lock: current_state = dict(self.vis_state) @@ -196,7 +196,7 @@ async def connect(sid, environ) -> None: # type: ignore[no-untyped-def] await self.sio.emit("full_state", current_state, room=sid) # type: ignore[union-attr] - @self.sio.event # type: ignore[misc] + @self.sio.event # type: ignore[misc, untyped-decorator] async def click(sid, position) -> None: # type: ignore[no-untyped-def] goal = PoseStamped( position=(position[0], position[1], 0), @@ -206,22 +206,22 @@ async def click(sid, position) -> None: # type: ignore[no-untyped-def] self.goal_request.publish(goal) logger.info(f"Click goal published: ({goal.position.x:.2f}, {goal.position.y:.2f})") - @self.sio.event # type: ignore[misc] + @self.sio.event # type: ignore[misc, untyped-decorator] async def gps_goal(sid, goal) -> None: # type: ignore[no-untyped-def] logger.info(f"Set GPS goal: {goal}") self.gps_goal.publish(LatLon(lat=goal["lat"], lon=goal["lon"])) - @self.sio.event # type: ignore[misc] + @self.sio.event # type: ignore[misc, untyped-decorator] async def start_explore(sid) -> None: # type: ignore[no-untyped-def] logger.info("Starting exploration") self.explore_cmd.publish(Bool(data=True)) - @self.sio.event # type: ignore[misc] + @self.sio.event # type: ignore[misc, untyped-decorator] async def stop_explore(sid) -> None: # type: ignore[no-untyped-def] logger.info("Stopping exploration") self.stop_explore_cmd.publish(Bool(data=True)) - @self.sio.event # type: ignore[misc] + @self.sio.event # type: ignore[misc, untyped-decorator] async def move_command(sid, data) -> None: # type: ignore[no-untyped-def] # Publish Twist if transport is configured if self.cmd_vel and self.cmd_vel.transport: diff --git a/docker/python/Dockerfile b/docker/python/Dockerfile index 6fbd5545e5..c12f7ea5d9 100644 --- a/docker/python/Dockerfile +++ b/docker/python/Dockerfile @@ -49,4 +49,4 @@ COPY . /app/ # Install dependencies with UV (10-100x faster than pip) RUN uv pip install --upgrade 'pip>=24' 'setuptools>=70' 'wheel' 'packaging>=24' && \ - uv pip install '.[cpu]' + uv pip install '.[cpu,sim]' diff --git a/pyproject.toml b/pyproject.toml index 4b80d6c6f6..1b3e879ce0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -186,7 +186,7 @@ cuda = [ dev = [ "ruff==0.14.3", - "mypy==1.18.2", + "mypy==1.19.0", "pre_commit==4.2.0", "pytest==8.3.5", "pytest-asyncio==0.26.0", @@ -271,6 +271,7 @@ force-sort-within-sections = true python_version = "3.12" incremental = true strict = true +warn_unused_ignores = false exclude = "^dimos/models/Detic(/|$)|.*/test_.|.*/conftest.py*" [tool.pytest.ini_options]