diff --git a/pyproject.toml b/pyproject.toml index 3c6c3b0b..4d743f81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ dependencies = [ "xmltodict>=1.0.2", "pandas>=2.2.0", "trimesh>=4.8.2", + "nicegui>=3.0.4", ] [dependency-groups] @@ -104,4 +105,4 @@ select = "DOC" style = "google" skip-checking-short-docstrings = false allow-init-docstring = true -check-style-mismatch = true \ No newline at end of file +check-style-mismatch = true diff --git a/ros2_ws/src/rcdt_franka_moveit_config/config/fr3.srdf b/ros2_ws/src/rcdt_franka_moveit_config/config/fr3.srdf index aa9d15ee..03c599ce 100644 --- a/ros2_ws/src/rcdt_franka_moveit_config/config/fr3.srdf +++ b/ros2_ws/src/rcdt_franka_moveit_config/config/fr3.srdf @@ -28,7 +28,7 @@ are defined group--> - + @@ -37,10 +37,20 @@ are defined + + + + + + + + + + - + @@ -49,7 +59,7 @@ are defined - + @@ -61,6 +71,7 @@ are defined + + + + rcdt_grasping + 0.1.0 + Package that contains functionality for grasping objects. + RCDT + Apache 2.0 + + ament_cmake + ament_cmake_python + + rcdt_utilities + + rclpy + + + ament_cmake + + \ No newline at end of file diff --git a/ros2_ws/src/rcdt_grasping/rcdt_grasping/__init__.py b/ros2_ws/src/rcdt_grasping/rcdt_grasping/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ros2_ws/src/rcdt_grasping/src_py/generate_grasp.py b/ros2_ws/src/rcdt_grasping/src_py/generate_grasp.py new file mode 100755 index 00000000..ace19116 --- /dev/null +++ b/ros2_ws/src/rcdt_grasping/src_py/generate_grasp.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: Alliander N. V. +# +# SPDX-License-Identifier: Apache-2.0 + + +import numpy as np +import open3d as o3d +import rclpy +import torch +from graspnetAPI import GraspGroup +from graspnetpy_models.graspnet import GraspNet, pred_decode +from graspnetpy_utils import collision_detector, data_utils +from rcdt_messages.msg import Grasp +from rcdt_messages.srv import GenerateGraspnetGrasp +from rcdt_utilities.cv_utils import ros_image_to_cv2_image +from rcdt_utilities.launch_utils import spin_node +from rclpy import logging +from rclpy.node import Node +from scipy.spatial.transform import Rotation + +ros_logger = logging.get_logger(__name__) + +CHECKPOINT_DIR = "/home/rcdt/rcdt_robotics/ros2_ws/src/rcdt_grasping/checkpoint/" +CHECKPOINT_FILE = "checkpoint.tar" +CHECKPOINT = CHECKPOINT_DIR + CHECKPOINT_FILE + +NUM_VIEW = 300 +NUM_POINT = 20000 + + +class GenerateGrasp(Node): + """Node to generate grasps using the GraspNet model.""" + + def __init__(self) -> None: + """Initialize the PublishImage node.""" + super().__init__("graspnet_node") + self.init_net() + + self.collision_threshold = 0.01 + self.voxel_size = 0.01 + self.visualize = False + + self.depth = None + self.color = None + self.camera_info = None + self.depth_frame_id = None + + self.create_service(GenerateGraspnetGrasp, "/graspnet/generate", self.callback) + + def init_net(self) -> None: + """Initialize the GraspNet model and load the pre-trained weights.""" + # Init the model + self.net = GraspNet( + input_feature_dim=0, + num_view=NUM_VIEW, + num_angle=12, + num_depth=4, + cylinder_radius=0.05, + hmin=-0.02, + hmax_list=[0.01, 0.02, 0.03, 0.04], + is_training=False, + ) + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + self.net.to(device) + + checkpoint = torch.load(CHECKPOINT) + self.net.load_state_dict(checkpoint["model_state_dict"]) + + # set model to eval mode + self.net.eval() + + ros_logger.info("GraspNet model initialized and weights loaded") + + def callback( + self, + request: GenerateGraspnetGrasp.Request, + response: GenerateGraspnetGrasp.Response, + ) -> GenerateGraspnetGrasp.Response: + """Callback for the generate grasps service. + + Args: + request (GenerateGraspnetGrasp.Request): The request for the service. + response (GenerateGraspnetGrasp.Response): The response to be filled. + + Returns: + GenerateGraspnetGrasp.Response: The response indicating success or failure of the grasp generation. + """ + if not self.convert_messages(request): + response.success = False + response.message = "Failed to convert messages" + return response + + # Define cloud and grasps: + end_points, cloud = self.process_data() + + grasps = self.get_grasps(end_points) + + if len(grasps) == 0: + response.success = False + response.message = "GraspNet returned 0 grasps" + self.get_logger().warn("GraspNet returned 0 grasps") + return response + + ros_logger.info(f"GraspNet returned {len(grasps)} grasps") + + grasps.nms() + grasps.sort_by_score() + grasps = self.collision_detection(grasps, np.array(cloud.points)) + if self.visualize: + self.vis_grasps(grasps, cloud) + grasps_msg = self.grasps_to_ros_msg(grasps) + response.success = True + response.message = "worked!" + response.grasps = grasps_msg + ros_logger.info("Returning success response!") + return response + + def grasps_to_ros_msg(self, grasps: GraspGroup) -> list[Grasp]: + """Convert a GraspGroup into a list of ROS Grasp messages. + + Each grasp is transformed from the GraspNet frame into the frame of the gripper by + applying a fixed axis remapping (X → Z, Z → -X, Y → Y). The resulting + rotation matrix is converted to a quaternion and, together with the grasp + translation, used to populate the ROS message fields. + + Args: + grasps (GraspGroup): The GraspGroup containing the grasps to be converted. + + Returns: + list[Grasp]: A list of ROS Grasp messages. + """ + msgs: list[Grasp] = [] + + remap_matrix = np.array( + [ + [0, 0, -1], # new X = old Z + [0, 1, 0], # new Y = old Y + [1, 0, 0], # new Z = old X + ] + ) + + for g in grasps: + t = np.asarray(g.translation, dtype=float).reshape(3) + rotation_matrix = np.asarray( + getattr(g, "rotation", g.rotation_matrix), float + ).reshape(3, 3) + + rotation_new = rotation_matrix @ remap_matrix.T + qx, qy, qz, qw = Rotation.from_matrix(rotation_new).as_quat() + + m = Grasp() + m.pose.header.frame_id = self.depth_frame_id + m.pose.pose.position.x = float(t[0]) + m.pose.pose.position.y = float(t[1]) + m.pose.pose.position.z = float(t[2]) + m.pose.pose.orientation.x = float(qx) + m.pose.pose.orientation.y = float(qy) + m.pose.pose.orientation.z = float(qz) + m.pose.pose.orientation.w = float(qw) + + m.score = float(g.score) + m.width = float(g.width) + m.depth = float(g.depth) + + msgs.append(m) + return msgs + + def process_data(self) -> tuple[dict, o3d.geometry.PointCloud]: # type: ignore[attr-defined] + """Process the depth and camera info data to create a point cloud and end points. + + Returns: + tuple[dict, o3d.geometry.PointCloud]: A tuple containing the end points dictionary and the point cloud object. + """ + cloud = data_utils.create_point_cloud_from_depth_image( + self.depth, self.camera_info, organized=True + ) + + mask = self.depth > 0 + cloud_masked = cloud[mask] + color_masked = self.color[mask] + + # sample points + if len(cloud_masked) >= NUM_POINT: + idxs = np.random.choice(len(cloud_masked), NUM_POINT, replace=False) + else: + idxs1 = np.arange(len(cloud_masked)) + idxs2 = np.random.choice( + len(cloud_masked), NUM_POINT - len(cloud_masked), replace=True + ) + idxs = np.concatenate([idxs1, idxs2], axis=0) + cloud_sampled = cloud_masked[idxs] + color_sampled = color_masked[idxs] + + # convert data + cloud = o3d.geometry.PointCloud() # type: ignore[attr-defined] + cloud.points = o3d.utility.Vector3dVector(cloud_masked.astype(np.float32)) # type: ignore[attr-defined] + cloud.colors = o3d.utility.Vector3dVector(color_masked.astype(np.float32)) # type: ignore[attr-defined] + end_points = {} + cloud_sampled = torch.from_numpy(cloud_sampled[np.newaxis].astype(np.float32)) + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + cloud_sampled = cloud_sampled.to(device) + end_points["point_clouds"] = cloud_sampled + end_points["cloud_colors"] = color_sampled + + assert len(cloud.points) > 0, "PointCloud has no points" + ros_logger.info(f"PointCloud has {len(cloud.points)} points") + return end_points, cloud + + def get_grasps(self, end_points: dict) -> GraspGroup: + """Define grasps from the end points using the GraspNet model. + + Args: + end_points (dict): The end points dictionary containing the point cloud and colors. + + Returns: + GraspGroup: The GraspGroup containing the predicted grasps. + """ + with torch.no_grad(): + end_points = self.net(end_points) + grasp_preds = pred_decode(end_points) + + grasps_array = grasp_preds[0].detach().cpu().numpy() + return GraspGroup(grasps_array) + + def collision_detection(self, grasps: GraspGroup, cloud: np.ndarray) -> GraspGroup: + """Perform collision detection on the grasps using the point cloud. + + Args: + grasps (GraspGroup): The GraspGroup containing the grasps to be checked. + cloud (np.ndarray): The point cloud as a numpy array. + + Returns: + GraspGroup: The filtered GraspGroup after collision detection. + """ + mfcdetector = collision_detector.ModelFreeCollisionDetector( + cloud, voxel_size=self.voxel_size + ) + collision_mask = mfcdetector.detect( + grasps, approach_dist=0.05, collision_thresh=self.collision_threshold + ) + grasps = grasps[~collision_mask] + ros_logger.info( + f"Collision detection filtered {np.sum(collision_mask)} grasps, {len(grasps)} grasps remaining" + ) + return grasps + + @staticmethod + def vis_grasps(gg: GraspGroup, cloud: np.ndarray) -> None: + """Visualize the grasps using Open3D. + + Args: + gg (GraspGroup): The GraspGroup containing the grasps to visualize. + cloud (np.ndarray): The point cloud as a numpy array. + """ + gg = gg[:1] + grippers = gg.to_open3d_geometry_list() + ros_logger.info(f"Visualizing {len(grippers)} grasps") + + o3d.visualization.draw_geometries([cloud, *grippers]) # type: ignore[attr-defined] + + def convert_messages(self, request: GenerateGraspnetGrasp.Request) -> bool: + """Convert the messages to correct format. + + Args: + request (GenerateGraspnetGrasp.Request): The request containing the messages. + + Returns: + bool: True if all messages are successfully retrieved, False otherwise. + """ + try: + self.color = (ros_image_to_cv2_image(request.color)) / 255.0 + self.depth = ros_image_to_cv2_image(request.depth) + self.depth_frame_id = request.depth.header.frame_id + # Camera intrinsics + self.camera_info = data_utils.CameraInfo( + request.camera_info.width, + request.camera_info.height, + request.camera_info.k[0], + request.camera_info.k[4], + request.camera_info.k[2], + request.camera_info.k[5], + 1, + ) + return True + except Exception as e: + ros_logger.error(f"Error converting messages: {e}") + return False + + +def main(args: list | None = None) -> None: + """Main function to initialize the ROS 2 node and spin it. + + Args: + args (list | None): Command line arguments. Defaults to None. + """ + rclpy.init(args=args) + node = GenerateGrasp() + spin_node(node) + + +if __name__ == "__main__": + main() diff --git a/ros2_ws/src/rcdt_launch/launch/robots.launch.py b/ros2_ws/src/rcdt_launch/launch/robots.launch.py index 8db01c19..2c590bb8 100644 --- a/ros2_ws/src/rcdt_launch/launch/robots.launch.py +++ b/ros2_ws/src/rcdt_launch/launch/robots.launch.py @@ -90,7 +90,11 @@ def launch_setup(context: LaunchContext) -> list: # noqa: PLR0912, PLR0915 Arm("franka", [1.0, 0, 0], gripper=True, moveit=True) Arm("franka", [-1.0, 0, 0], gripper=True, moveit=True) case "franka_realsense": - arm = Arm("franka", [0, 0, 0], moveit=True) + Platform.world = "table_with_1_brick.sdf" + Rviz.add_markers() + Rviz.load_robot_state = True + Rviz.load_trajectory = True + arm = Arm("franka", [0, 0, 0], moveit=True, graspnet=True) Camera("realsense", [0.05, 0, 0], [0, -90, 180], parent=arm) case "panther": Vehicle("panther", [0, 0, 0.2], namespace="panther") diff --git a/ros2_ws/src/rcdt_launch/rcdt_launch/moveit.py b/ros2_ws/src/rcdt_launch/rcdt_launch/moveit.py index 6b7faf94..3b49b816 100644 --- a/ros2_ws/src/rcdt_launch/rcdt_launch/moveit.py +++ b/ros2_ws/src/rcdt_launch/rcdt_launch/moveit.py @@ -96,6 +96,11 @@ def add_prefix_in_robot_description_semantic(description: dict, prefix: str) -> prefix (str): The prefix to add to each link. """ xml_dict = xmltodict.parse(description["robot_description_semantic"]) + ee_parent_link = xml_dict["robot"]["end_effector"]["@parent_link"] + xml_dict["robot"]["end_effector"]["@parent_link"] = f"{prefix}/{ee_parent_link}" + for group in xml_dict["robot"]["group"]: + if "link" in group: + group["link"]["@name"] = f"{prefix}/{group['link']['@name']}" for disable_collision in xml_dict["robot"]["disable_collisions"]: link1 = disable_collision["@link1"] link2 = disable_collision["@link2"] diff --git a/ros2_ws/src/rcdt_launch/rcdt_launch/robot.py b/ros2_ws/src/rcdt_launch/rcdt_launch/robot.py index a2636e6d..6821ab4d 100644 --- a/ros2_ws/src/rcdt_launch/rcdt_launch/robot.py +++ b/ros2_ws/src/rcdt_launch/rcdt_launch/robot.py @@ -21,6 +21,7 @@ class Platform: # noqa: PLR0904 Attributes: simulation (bool): Whether the platforms are in simulation mode or not. + world (str): The world file to be used in Gazebo. platforms (list[Platform]): A list of all the platforms. platform_indices (dict[str, int]): A collections of the different platforms and the number of occurrences. names (list[str]): A list of all robot names. @@ -28,6 +29,7 @@ class Platform: # noqa: PLR0904 """ simulation: bool = True + world: str = "walls.sdf" platforms: list["Platform"] = [] platform_indices: dict[str, int] = {} names: list[str] = [] @@ -132,6 +134,7 @@ def create_gazebo_launch(load_gazebo_ui: bool) -> RegisteredLaunchDescription: get_file_path("rcdt_gazebo", ["launch"], "gazebo_robot.launch.py"), launch_arguments={ "load_gazebo_ui": str(load_gazebo_ui), + "world": Platform.world, "platforms": " ".join(platforms), "positions": " ".join(positions), "orientations": " ".join(orientations), @@ -761,6 +764,7 @@ def __init__( # noqa: PLR0913 moveit: bool = False, gripper: bool = False, ip_address: str = "", + graspnet: bool = False, ): """Initialize the Arm platform. @@ -774,6 +778,7 @@ def __init__( # noqa: PLR0913 moveit (bool): Whether to use MoveIt for the arm. gripper (bool): Whether to add a start the gripper services. ip_address (str): The IP address of the arm. + graspnet (bool): Whether to start the GraspNet node for the arm. """ super().__init__( platform, position, orientation, namespace, parent, parent_link @@ -782,12 +787,17 @@ def __init__( # noqa: PLR0913 self.moveit = moveit self.gripper = gripper self.ip_address = ip_address + self.graspnet = graspnet self.camera: Camera | None = None if moveit: Moveit.add(self.namespace, self.robot_description, self.platform) + Rviz.moveit_namespaces.append(self.namespace) Rviz.add_motion_planning_plugin(self.namespace) + Rviz.add_planning_scene(self.namespace) + Rviz.add_robot_state(self.namespace) + Rviz.add_trajectory(self.namespace) def create_launch_description(self) -> list[RegisteredLaunchDescription]: """Create the launch description with specific elements for an arm. @@ -800,6 +810,19 @@ def create_launch_description(self) -> list[RegisteredLaunchDescription]: launch_descriptions.append(self.create_gripper_launch()) if self.moveit: launch_descriptions.append(self.create_moveit_launch()) + if self.graspnet: + launch_descriptions.append( + RegisteredLaunchDescription( + get_file_path("rcdt_grasping", ["launch"], "grasping.launch.py"), + launch_arguments={ + "namespace_arm": self.namespace, + "namespace_camera": self.camera.namespace + if self.camera + else None, + }, + ) + ) + return launch_descriptions def joystick_nodes(self) -> list[Node]: diff --git a/ros2_ws/src/rcdt_launch/rcdt_launch/rviz.py b/ros2_ws/src/rcdt_launch/rcdt_launch/rviz.py index 0759e22a..9331e53e 100644 --- a/ros2_ws/src/rcdt_launch/rcdt_launch/rviz.py +++ b/ros2_ws/src/rcdt_launch/rcdt_launch/rviz.py @@ -13,6 +13,9 @@ class Rviz: yaml (dict): The default RViz configuration. displays (list): The list of displays in the RViz configuration. load_motion_planning_plugin (bool): Whether to load the motion planning plugin. + load_planning_scene (bool): Whether to load the planning scene display. + load_robot_state (bool): Whether to load the robot state display. + load_trajectory (bool): Whether to load the trajectory display. load_point_cloud (bool): Whether to load point cloud displays. moveit_namespaces (list[str]): A list of the namespaces where MoveIt is launched. """ @@ -20,6 +23,9 @@ class Rviz: yaml: dict = get_yaml(get_file_path("rcdt_utilities", ["rviz"], "default.rviz")) displays: list = yaml["Visualization Manager"]["Displays"] load_motion_planning_plugin: bool = False + load_planning_scene: bool = False + load_robot_state: bool = False + load_trajectory: bool = False load_point_cloud: bool = False moveit_namespaces: list[str] = [] @@ -142,7 +148,6 @@ def add_motion_planning_plugin(namespace: str) -> None: """ if not Rviz.load_motion_planning_plugin: return - Rviz.moveit_namespaces.append(namespace) Rviz.displays.append( { "Enabled": True, @@ -154,6 +159,66 @@ def add_motion_planning_plugin(namespace: str) -> None: } ) + @staticmethod + def add_planning_scene(namespace: str) -> None: + """Add the planning scene display to the RViz configuration. + + Args: + namespace (str): The namespace of the robot. + """ + if not Rviz.load_planning_scene: + return + Rviz.displays.append( + { + "Enabled": True, + "Class": "moveit_rviz_plugin/PlanningScene", + "Move Group Namespace": namespace, + "Robot Description": f"{namespace}_robot_description", + "Name": f"{namespace}_planning_scene", + "Planning Scene Topic": f"/{namespace}/planning_scene", + } + ) + + @staticmethod + def add_robot_state(namespace: str) -> None: + """Add the robot state display to the RViz configuration. + + Args: + namespace (str): The namespace of the robot. + """ + if not Rviz.load_robot_state: + return + Rviz.displays.append( + { + "Enabled": True, + "Class": "moveit_rviz_plugin/RobotState", + "Robot Description": f"{namespace}_robot_description", + "Robot State Topic": f"/{namespace}/display_robot_state", + "Name": f"{namespace}_robot_state", + "TF Prefix": namespace, + } + ) + + @staticmethod + def add_trajectory(namespace: str) -> None: + """Add the trajectory display to the RViz configuration. + + Args: + namespace (str): The namespace of the robot. + """ + if not Rviz.load_trajectory: + return + Rviz.displays.append( + { + "Enabled": True, + "Class": "moveit_rviz_plugin/Trajectory", + "Robot Description": f"{namespace}_robot_description", + "Name": f"{namespace}_trajectory", + "Trajectory Topic": f"/{namespace}/display_planned_path_custom", + "State Display Time": "0.5s", + } + ) + @staticmethod def add_map(topic: str) -> None: """Add a map to the RViz configuration. @@ -205,6 +270,22 @@ def add_polygon(topic: str) -> None: } ) + @staticmethod + def add_markers(topic: str = "/rviz_markers") -> None: + """Add a MarkerArray display (e.g., for MoveItVisualTools). + + Args: + topic (str): The topic of the MarkerArray. + """ + Rviz.displays.append( + { + "Enabled": True, + "Class": "rviz_default_plugins/MarkerArray", + "Name": topic, + "Topic": {"Value": topic}, + } + ) + @staticmethod def create_rviz_file() -> None: """Create the RViz configuration file.""" diff --git a/ros2_ws/src/rcdt_messages/msg/Grasp.msg b/ros2_ws/src/rcdt_messages/msg/Grasp.msg new file mode 100644 index 00000000..fdb2cf6f --- /dev/null +++ b/ros2_ws/src/rcdt_messages/msg/Grasp.msg @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: Alliander N. V. +# +# SPDX-License-Identifier: Apache-2.0 + +# Grasp.msg +geometry_msgs/PoseStamped pose +float32 score +float32 width +float32 depth \ No newline at end of file diff --git a/ros2_ws/src/rcdt_messages/srv/GenerateGraspnetGrasp.srv b/ros2_ws/src/rcdt_messages/srv/GenerateGraspnetGrasp.srv new file mode 100644 index 00000000..31d514d7 --- /dev/null +++ b/ros2_ws/src/rcdt_messages/srv/GenerateGraspnetGrasp.srv @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: Alliander N. V. +# +# SPDX-License-Identifier: Apache-2.0 + +# Request +sensor_msgs/Image color +sensor_msgs/Image depth +sensor_msgs/CameraInfo camera_info +--- +# Response +bool success +string message +rcdt_messages/Grasp[] grasps diff --git a/ros2_ws/src/rcdt_messages/srv/PoseStampedSrv.srv b/ros2_ws/src/rcdt_messages/srv/PoseStampedSrv.srv new file mode 100644 index 00000000..bd1e77d6 --- /dev/null +++ b/ros2_ws/src/rcdt_messages/srv/PoseStampedSrv.srv @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Alliander N. V. +# +# SPDX-License-Identifier: Apache-2.0 + + +geometry_msgs/PoseStamped pose +--- +bool success \ No newline at end of file diff --git a/ros2_ws/src/rcdt_moveit/include/moveit_manager.hpp b/ros2_ws/src/rcdt_moveit/include/moveit_manager.hpp index b8bcac42..3e10070f 100644 --- a/ros2_ws/src/rcdt_moveit/include/moveit_manager.hpp +++ b/ros2_ws/src/rcdt_moveit/include/moveit_manager.hpp @@ -2,6 +2,7 @@ // // # SPDX-License-Identifier: Apache-2.0 +#include "geometry_msgs/msg/transform_stamped.hpp" #include #include #include @@ -14,20 +15,26 @@ #include #include #include +#include #include #include +#include #include +#include +#include typedef rcdt_messages::srv::AddObject AddObject; typedef rcdt_messages::srv::AddMarker AddMarker; typedef rcdt_messages::srv::DefineGoalPose DefineGoalPose; +typedef rcdt_messages::srv::ExpressPoseInOtherFrame ExpressPoseInOtherFrame; typedef rcdt_messages::srv::TransformGoalPose TransformGoalPose; typedef rcdt_messages::srv::MoveToConfiguration MoveToConf; typedef rcdt_messages::srv::MoveHandToPose MoveHandToPose; +typedef rcdt_messages::srv::PoseStampedSrv PoseStampedSrv; typedef moveit_msgs::srv::ServoCommandType ServoCommandType; typedef std_srvs::srv::Trigger Trigger; typedef geometry_msgs::msg::PoseStamped PoseStamped; -typedef rcdt_messages::srv::ExpressPoseInOtherFrame ExpressPoseInOtherFrame; +typedef geometry_msgs::msg::TransformStamped TransformStamped; struct Action { std::string name; @@ -42,9 +49,16 @@ class MoveitManager { private: rclcpp::Node::SharedPtr node; rclcpp::Node::SharedPtr client_node; + tf2_ros::TransformBroadcaster tf_broadcaster; moveit::planning_interface::MoveGroupInterface move_group; moveit::planning_interface::PlanningSceneInterface planning_scene_interface; - const moveit::core::JointModelGroup *joint_model_group; + const moveit::core::JointModelGroup *jmg_arm; + const moveit::core::JointModelGroup *jmg_hand; + const moveit::core::JointModelGroup *jmg_tcp; + std::string base_frame = "map"; + std::string marker_topic = "/rviz_markers"; + moveit::planning_interface::MoveGroupInterface::Plan plan; + rviz_visual_tools::RvizVisualTools rviz_visual_tools; moveit_visual_tools::MoveItVisualTools moveit_visual_tools; PoseStamped goal_pose; @@ -87,6 +101,23 @@ class MoveitManager { void add_marker(const std::shared_ptr request, std::shared_ptr response); + rclcpp::Service::SharedPtr visualize_grasp_pose_service; + void + visualize_grasp_pose(const std::shared_ptr request, + std::shared_ptr response); + + rclcpp::Service::SharedPtr create_plan_service; + void create_plan(const std::shared_ptr request, + std::shared_ptr response); + + rclcpp::Service::SharedPtr visualize_plan_service; + void visualize_plan(const std::shared_ptr request, + std::shared_ptr response); + + rclcpp::Service::SharedPtr execute_plan_service; + void execute_plan(const std::shared_ptr request, + std::shared_ptr response); + rclcpp::Service::SharedPtr clear_markers_service; void clear_markers(const std::shared_ptr request, std::shared_ptr response); @@ -94,6 +125,6 @@ class MoveitManager { // Methods: void initialize_clients(); void initialize_services(); - PoseStamped change_frame_to_world(PoseStamped pose); + PoseStamped change_frame(PoseStamped pose, std::string target_frame = ""); bool plan_and_execute(std::string planning_type = ""); }; \ No newline at end of file diff --git a/ros2_ws/src/rcdt_moveit/src/moveit_manager.cpp b/ros2_ws/src/rcdt_moveit/src/moveit_manager.cpp index 1da19280..60bc64ad 100644 --- a/ros2_ws/src/rcdt_moveit/src/moveit_manager.cpp +++ b/ros2_ws/src/rcdt_moveit/src/moveit_manager.cpp @@ -4,27 +4,37 @@ #include "moveit_manager.hpp" #include +#include #include #include #include #include +#include using std::placeholders::_1; using std::placeholders::_2; MoveitManager::MoveitManager(rclcpp::Node::SharedPtr node_) - : node(node_), + : node(node_), tf_broadcaster(node_), move_group( node, moveit::planning_interface::MoveGroupInterface::Options( - "fr3_arm", + "arm", moveit::planning_interface::MoveGroupInterface::ROBOT_DESCRIPTION, node->get_namespace())), - moveit_visual_tools(node, "base", "/rviz_markers") { + rviz_visual_tools(base_frame, marker_topic, node), + moveit_visual_tools(node, base_frame, marker_topic) { + + jmg_arm = move_group.getRobotModel()->getJointModelGroup("arm"); + jmg_hand = move_group.getRobotModel()->getJointModelGroup("hand"); + jmg_tcp = move_group.getRobotModel()->getJointModelGroup("tcp"); moveit_visual_tools.loadMarkerPub(false); - move_group.setEndEffectorLink("fr3_hand"); - joint_model_group = move_group.getRobotModel()->getJointModelGroup("fr3_arm"); + moveit_visual_tools.loadRobotStatePub("display_robot_state"); + moveit_visual_tools.loadTrajectoryPub("display_planned_path_custom"); + + auto link_tcp = jmg_tcp->getLinkModelNames().back(); + move_group.setEndEffectorLink(link_tcp); initialize_clients(); initialize_services(); @@ -64,6 +74,20 @@ void MoveitManager::initialize_services() { add_marker_service = node->create_service( "~/add_marker", std::bind(&MoveitManager::add_marker, this, _1, _2)); + visualize_grasp_pose_service = node->create_service( + "~/visualize_grasp_pose", + std::bind(&MoveitManager::visualize_grasp_pose, this, _1, _2)); + + create_plan_service = node->create_service( + "~/create_plan", std::bind(&MoveitManager::create_plan, this, _1, _2)); + + visualize_plan_service = node->create_service( + "~/visualize_plan", + std::bind(&MoveitManager::visualize_plan, this, _1, _2)); + + execute_plan_service = node->create_service( + "~/execute_plan", std::bind(&MoveitManager::execute_plan, this, _1, _2)); + clear_markers_service = node->create_service( "~/clear_markers", std::bind(&MoveitManager::clear_markers, this, _1, _2)); @@ -84,12 +108,14 @@ void MoveitManager::add_object( return; } + std::vector collision_objects; solid_primitive.dimensions = {request->d1, request->d2, request->d3}; collision_object.primitives.push_back(solid_primitive); collision_object.primitive_poses.push_back(request->pose.pose); collision_object.operation = collision_object.ADD; collision_object.id = "object"; - planning_scene_interface.applyCollisionObject(collision_object); + collision_objects.push_back(collision_object); + planning_scene_interface.addCollisionObjects(collision_objects); response->success = true; }; @@ -103,7 +129,7 @@ void MoveitManager::clear_objects( void MoveitManager::define_goal_pose( const std::shared_ptr request, std::shared_ptr response) { - auto pose = change_frame_to_world(request->pose); + auto pose = change_frame(request->pose); goal_pose = pose; response->success = true; }; @@ -155,7 +181,7 @@ bool MoveitManager::plan_and_execute(std::string planning_type) { moveit_visual_tools.deleteAllMarkers("Path"); moveit_visual_tools.deleteAllMarkers("Sphere"); - moveit_visual_tools.publishTrajectoryLine(plan.trajectory, joint_model_group); + moveit_visual_tools.publishTrajectoryLine(plan.trajectory, jmg_arm); moveit_visual_tools.trigger(); error_code = move_group.execute(plan); @@ -166,10 +192,14 @@ bool MoveitManager::plan_and_execute(std::string planning_type) { return true; }; -PoseStamped MoveitManager::change_frame_to_world(PoseStamped pose) { +PoseStamped MoveitManager::change_frame(PoseStamped pose, + std::string target_frame) { + if (target_frame == "") { + target_frame = base_frame; + } auto request = std::make_shared(); request->pose = pose; - request->target_frame = "world"; + request->target_frame = target_frame; auto future = express_pose_in_other_frame_client->async_send_request(request); rclcpp::spin_until_future_complete(client_node, future); auto response = future.get(); @@ -179,12 +209,85 @@ PoseStamped MoveitManager::change_frame_to_world(PoseStamped pose) { void MoveitManager::add_marker( const std::shared_ptr request, std::shared_ptr response) { - auto pose = change_frame_to_world(request->marker_pose); + auto pose = change_frame(request->marker_pose); moveit_visual_tools.publishAxis(pose.pose); moveit_visual_tools.trigger(); response->success = true; }; +void MoveitManager::visualize_grasp_pose( + const std::shared_ptr request, + std::shared_ptr response) { + + // Broadcast a tf frame at the desired Tool Center Point location: + TransformStamped tf; + tf.header = request->pose.header; + tf.header.stamp = node->now(); + tf.transform.translation.x = request->pose.pose.position.x; + tf.transform.translation.y = request->pose.pose.position.y; + tf.transform.translation.z = request->pose.pose.position.z; + tf.transform.rotation = request->pose.pose.orientation; + tf.child_frame_id = "desired_tcp"; + tf_broadcaster.sendTransform(tf); + + // Define the arm_end_link in the tcp_frame: + // TODO: Do only once at initialization, since it does not change. + auto link_arm_end = jmg_arm->getLinkModelNames().back(); + auto link_tcp = jmg_tcp->getLinkModelNames().back(); + PoseStamped arm_end_in_arm_frame; + arm_end_in_arm_frame.header.frame_id = link_arm_end; + auto arm_end_in_tcp_frame = change_frame(arm_end_in_arm_frame, link_tcp); + + // Define the arm_end_link in the desired_tcp_frame and convert to base: + PoseStamped arm_end_in_desired_tcp_frame; + arm_end_in_desired_tcp_frame.header.frame_id = "desired_tcp"; + arm_end_in_desired_tcp_frame.pose = arm_end_in_tcp_frame.pose; + auto arm_end_in_base_frame = change_frame(arm_end_in_desired_tcp_frame); + + // Publish the End Effector marker: + std::vector positions = {0.04}; + moveit_visual_tools.publishEEMarkers(arm_end_in_base_frame.pose, jmg_hand, + positions, rviz_visual_tools::BLUE); + response->success = true; +} + +void MoveitManager::create_plan( + const std::shared_ptr request, + std::shared_ptr response) { + + move_group.setPoseTarget(request->pose); + auto error_code = move_group.plan(plan); + if (error_code != moveit::core::MoveItErrorCode::SUCCESS) { + RCLCPP_ERROR(node->get_logger(), "Failed to generate plan."); + } + + // Visualize the goal state in RViz: + auto goal_positions = + plan.trajectory.joint_trajectory.points.back().positions; + moveit::core::RobotState goal_state(move_group.getRobotModel()); + goal_state.setJointGroupPositions(jmg_arm, goal_positions); + moveit_visual_tools.publishRobotState(goal_state, rviz_visual_tools::ORANGE); + response->success = true; +} + +void MoveitManager::visualize_plan( + const std::shared_ptr request, + std::shared_ptr response) { + moveit::core::RobotState state(move_group.getRobotModel()); + moveit_visual_tools.publishTrajectoryPath(plan.trajectory, state); + response->success = true; +} + +void MoveitManager::execute_plan( + const std::shared_ptr request, + std::shared_ptr response) { + auto error_code = move_group.execute(plan); + response->success = (error_code == moveit::core::MoveItErrorCode::SUCCESS); + if (!response->success) { + RCLCPP_ERROR(node->get_logger(), "Failed to execute plan."); + } +} + void MoveitManager::clear_markers( const std::shared_ptr request, std::shared_ptr response) { diff --git a/ros2_ws/src/rcdt_utilities/launch/rviz.launch.py b/ros2_ws/src/rcdt_utilities/launch/rviz.launch.py index 7003164d..456903d8 100644 --- a/ros2_ws/src/rcdt_utilities/launch/rviz.launch.py +++ b/ros2_ws/src/rcdt_utilities/launch/rviz.launch.py @@ -29,7 +29,14 @@ def launch_setup(context: LaunchContext) -> list: if os.path.isfile(file_name): arguments.extend(["--display-config", file_name]) - if Rviz.load_motion_planning_plugin: + if any( + [ + Rviz.load_motion_planning_plugin, + Rviz.load_planning_scene, + Rviz.load_robot_state, + Rviz.load_trajectory, + ] + ): for namespace in Rviz.moveit_namespaces: configuration = Moveit.configurations[namespace] diff --git a/ros2_ws/src/rcdt_utilities/rcdt_utilities/test_utils.py b/ros2_ws/src/rcdt_utilities/rcdt_utilities/test_utils.py index ca579356..4593df8c 100644 --- a/ros2_ws/src/rcdt_utilities/rcdt_utilities/test_utils.py +++ b/ros2_ws/src/rcdt_utilities/rcdt_utilities/test_utils.py @@ -139,7 +139,7 @@ def call_trigger_service(node: Node, service_name: str, timeout: int) -> bool: future = client.call_async(Trigger.Request()) rclpy.spin_until_future_complete(node, future=future, timeout_sec=timeout) - return future.result() is not None + return future.result().success def create_ready_action_client( diff --git a/ros2_ws/src/rcdt_utilities/src_py/simple_gui.py b/ros2_ws/src/rcdt_utilities/src_py/simple_gui.py new file mode 100755 index 00000000..de051a69 --- /dev/null +++ b/ros2_ws/src/rcdt_utilities/src_py/simple_gui.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: Alliander N. V. +# +# SPDX-License-Identifier: Apache-2.0 + +import pickle +import threading +from dataclasses import dataclass +from typing import Any + +import numpy as np +import PIL.Image as PILImage +import rclpy +from geometry_msgs.msg import PoseStamped +from nicegui import app, ui +from rcdt_messages.msg import Grasp +from rcdt_messages.srv import ( + AddObject, + GenerateGraspnetGrasp, + MoveToConfiguration, + PoseStampedSrv, +) +from rcdt_utilities.cv_utils import cv2_image_to_ros_image, ros_image_to_cv2_image +from rcdt_utilities.launch_utils import spin_node +from rclpy.node import Node +from rclpy.wait_for_message import wait_for_message +from sensor_msgs.msg import CameraInfo, Image +from std_srvs.srv import Trigger + +TIMEOUT = 3 + + +@dataclass +class Data: + """Class to hold the data for the UI. + + Attributes: + color_ros (Image | None): The color image in ROS format. + color_cv (np.ndarray | None): The color image in OpenCV format. + color_pil (PILImage.Image | None): The color image in PIL format. + depth_ros (Image | None): The depth image in ROS format. + depth_cv (np.ndarray | None): The depth image in OpenCV format. + depth_pil (PILImage.Image | None): The depth image in PIL format. + camera_info (CameraInfo | None): The camera info. + grasp_pose (PoseStamped | None): The grasp pose. + """ + + color_ros: Image | None = None + color_cv: np.ndarray | None = None + color_pil: PILImage.Image | None = None + depth_ros: Image | None = None + depth_cv: np.ndarray | None = None + depth_pil: PILImage.Image | None = None + camera_info: CameraInfo | None = None + + grasp_pose: PoseStamped | None = None + + def update(self) -> None: + """Update the OpenCV and PIL images from the ROS images.""" + if self.color_ros is not None: + self.color_cv = ros_image_to_cv2_image(self.color_ros) + self.color_pil = PILImage.fromarray(self.color_cv) + if self.depth_ros is not None: + self.depth_cv = ros_image_to_cv2_image(self.depth_ros) + self.depth_pil = PILImage.fromarray(self.depth_cv) + + +@dataclass +class Message: + """Data class to define messages that will be obtained using the wait_for_message function. + + Attributes: + topic (str): The ROS topic to subscribe to. + message_type (type): The type of the message. + """ + + topic: str + message_type: type + + def get_message(self, node: Node) -> Any: + """Get the message from the topic. + + Args: + node (Node): The ROS 2 node to use. + + Returns: + Any: The received message. + """ + success, message = wait_for_message( + self.message_type, node, self.topic, time_to_wait=TIMEOUT + ) + if not success: + node.get_logger().error(f"Failed to get message from {self.topic}") + return message + + +class UI(Node): + """Node of the UI.""" + + def __init__(self): + """Initialize the node.""" + super().__init__("simple_gui") + + self.image_placeholder = np.ones((480, 640), dtype=np.uint8) * 150 + self.data = Data() + + self.color_image: ui.image + self.depth_image: ui.image + self.camera_info: ui.label + self.grasp_info: ui.label + + self.clear_objects_client = self.create_client( + Trigger, "/franka1/moveit_manager/clear_objects" + ) + + self.add_object_client = self.create_client( + AddObject, "/franka1/moveit_manager/add_object" + ) + + self.move_to_configuration_client = self.create_client( + MoveToConfiguration, "/franka1/moveit_manager/move_to_configuration" + ) + + self.generate_grasp_client = self.create_client( + GenerateGraspnetGrasp, "/graspnet/generate" + ) + + self.visualize_grasp_pose_client = self.create_client( + PoseStampedSrv, "/franka1/moveit_manager/visualize_grasp_pose" + ) + + self.create_plan_client = self.create_client( + PoseStampedSrv, "/franka1/moveit_manager/create_plan" + ) + + self.visualize_plan_client = self.create_client( + Trigger, "/franka1/moveit_manager/visualize_plan" + ) + + self.execute_plan_client = self.create_client( + Trigger, "/franka1/moveit_manager/execute_plan" + ) + + self.setup_gui() + + def setup_gui(self) -> None: + """Setup the GUI pages.""" + + @ui.page("/") + def page() -> None: + """Setup the page of the GUI.""" + with ui.row(): + ui.button("Load Data", on_click=self.load) + ui.button("Save Data", on_click=self.save) + ui.button("Move Home", on_click=self.move_to_home) + with ui.row(): + ui.button("Add Object", on_click=self.add_object) + ui.button("Clear Objects", on_click=self.clear_objects) + with ui.row(): + self.color_image = ui.image(self.data.color_pil).classes("w-32") + self.depth_image = ui.image(self.data.depth_pil).classes("w-32") + self.camera_info = ui.label("No camera info.") + with ui.row(): + ui.button("Update Image", on_click=lambda: self.update_image("color")) + ui.button("Update Image", on_click=lambda: self.update_image("depth")) + ui.button("Update Camera Info", on_click=self.update_camera_info) + self.grasp_info = ui.label("No grasp generated.") + with ui.row(): + ui.button("Generate Grasp", on_click=self.generate_grasp) + ui.button("Visualize Grasp", on_click=self.visualize_grasp_pose) + ui.button("Create Plan", on_click=self.create_plan) + ui.button("Visualize Plan", on_click=self.visualize_plan) + ui.button("Execute Plan", on_click=self.execute_plan) + + def update_ui(self) -> None: + """Update the UI.""" + self.color_image.source = self.data.color_pil + self.depth_image.source = self.data.depth_pil + self.camera_info.text = ( + self.data.camera_info.header.stamp.sec + if self.data.camera_info + else "No camera info." + ) + self.grasp_info.text = ( + str(self.data.grasp_pose.pose.position) + if self.data.grasp_pose + else "No grasp generated." + ) + + def load(self) -> None: + """Load the data from a file.""" + try: + with open("/tmp/data.pkl", "rb") as f: + self.data = pickle.load(f) + except FileNotFoundError: + self.get_logger().error("No saved data found.") + self.update_ui() + + def save(self) -> None: + """Save the data to a file.""" + with open("/tmp/data.pkl", "wb") as f: + pickle.dump(self.data, f) + + def move_to_home(self) -> None: + """Move the robot to the home configuration.""" + request = MoveToConfiguration.Request() + request.configuration = "home" + if self.move_to_configuration_client.call(request, TIMEOUT) is None: + self.get_logger().error("Failed to call move to configuration service.") + else: + self.get_logger().info("Successfully called move to configuration service.") + + def execute_plan(self) -> None: + """Execute the plan.""" + if self.execute_plan_client.call(Trigger.Request(), TIMEOUT) is None: + self.get_logger().error("Failed to call execute plan service.") + else: + self.get_logger().info("Successfully called execute plan service.") + + def visualize_plan(self) -> None: + """Visualize the plan in Rviz.""" + if self.visualize_plan_client.call(Trigger.Request(), TIMEOUT) is None: + self.get_logger().error("Failed to call visualize plan service.") + else: + self.get_logger().info("Successfully called visualize plan service.") + + def create_plan(self) -> None: + """Create a plan to reach the grasp pose.""" + request = PoseStampedSrv.Request() + request.pose = self.data.grasp_pose + if self.create_plan_client.call(request, TIMEOUT) is None: + self.get_logger().error("Failed to call create plan service.") + else: + self.get_logger().info("Successfully called create plan service.") + + def visualize_grasp_pose(self) -> None: + """Visualize the grasp pose in Rviz.""" + if self.data.grasp_pose is None: + self.get_logger().error("No grasp pose to visualize.") + return + request = PoseStampedSrv.Request() + request.pose = self.data.grasp_pose + if self.visualize_grasp_pose_client.call(request, TIMEOUT) is None: + self.get_logger().error("Failed to call visualize gripper pose service.") + else: + self.get_logger().info( + "Successfully called visualize gripper pose service." + ) + + def clear_objects(self) -> None: + """Clear objects from the planning scene.""" + if self.clear_objects_client.call(Trigger.Request(), TIMEOUT) is None: + self.get_logger().error("Failed to call clear objects service.") + else: + self.get_logger().info("Successfully called clear objects service.") + + def add_object(self) -> None: + """Add an object to the planning scene.""" + request = AddObject.Request() + request.shape = "SPHERE" + request.pose.header.frame_id = "franka1/fr3_link0" + request.d1 = 0.1 # radius + + if self.add_object_client.call(request, TIMEOUT) is None: + self.get_logger().error("Failed to call add object service.") + else: + self.get_logger().info("Successfully called add object service.") + + def update_image(self, image_type: str) -> None: + """Update the image from the ROS topic. + + Args: + image_type (str): The type of image to update ("color" or "depth"). + """ + match image_type: + case "color": + topic = "/realsense1/color/image_raw" + case "depth": + topic = "/realsense1/depth/image_rect_raw" + case _: + return + + image_ros: Image = Message(topic=topic, message_type=Image).get_message(self) + if image_ros is None: + return + setattr(self.data, f"{image_type}_ros", image_ros) + self.data.update() + self.update_ui() + + def update_camera_info(self) -> None: + """Update the camera info from the ROS topic.""" + camera_info: CameraInfo = Message( + topic="/realsense1/depth/camera_info", message_type=CameraInfo + ).get_message(self) + if camera_info: + self.data.camera_info = camera_info + self.update_ui() + + def generate_grasp(self) -> None: + """Generate a grasp using the Graspnet service.""" + request = GenerateGraspnetGrasp.Request() + request.color = self.data.color_ros + request.depth = cv2_image_to_ros_image(self.data.depth_cv / 1000) + request.depth.header = self.data.depth_ros.header + request.camera_info = self.data.camera_info + + if not self.generate_grasp_client.wait_for_service(TIMEOUT): + self.get_logger().error("Generate Graspnet Grasp service not available.") + return + response: GenerateGraspnetGrasp.Response = self.generate_grasp_client.call( + request, 10 + ) + grasp: Grasp = response.grasps[0] + self.data.grasp_pose = grasp.pose + self.update_ui() + + +def ros_main(args: list | None = None) -> None: + """Main function to initialize the ROS 2 node and start the executor. + + Args: + args (list | None): Command line arguments, defaults to None. + """ + rclpy.init(args=args) + node = UI() + spin_node(node) + + +app.on_startup(lambda: threading.Thread(target=ros_main).start()) +ui.run() diff --git a/uv.lock b/uv.lock index 13ae1856..0d252b04 100644 --- a/uv.lock +++ b/uv.lock @@ -16,6 +16,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, ] +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/f1/8515650ac3121a9e55c7b217c60e7fae3e0134b5acfe65691781b5356929/aiohttp-3.13.0.tar.gz", hash = "sha256:378dbc57dd8cf341ce243f13fa1fa5394d68e2e02c15cd5f28eae35a70ec7f67", size = 7832348, upload-time = "2025-10-06T19:58:48.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/95/7e8bdfa6e79099a086d59d42589492f1fe9d29aae3cefb58b676015ce278/aiohttp-3.13.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c272a9a18a5ecc48a7101882230046b83023bb2a662050ecb9bfcb28d9ab53a", size = 735585, upload-time = "2025-10-06T19:55:43.401Z" }, + { url = "https://files.pythonhosted.org/packages/9f/20/2f1d3ee06ee94eafe516810705219bff234d09f135d6951661661d5595ae/aiohttp-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:97891a23d7fd4e1afe9c2f4473e04595e4acb18e4733b910b6577b74e7e21985", size = 490613, upload-time = "2025-10-06T19:55:45.237Z" }, + { url = "https://files.pythonhosted.org/packages/74/15/ab8600ef6dc1dcd599009a81acfed2ea407037e654d32e47e344e0b08c34/aiohttp-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:475bd56492ce5f4cffe32b5533c6533ee0c406d1d0e6924879f83adcf51da0ae", size = 489750, upload-time = "2025-10-06T19:55:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/33/59/752640c2b86ca987fe5703a01733b00d375e6cd2392bc7574489934e64e5/aiohttp-3.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c32ada0abb4bc94c30be2b681c42f058ab104d048da6f0148280a51ce98add8c", size = 1736812, upload-time = "2025-10-06T19:55:48.917Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/dd6b86ddb852a7fdbcdc7a45b6bdc80178aef713c08279afcaee7a5a9f07/aiohttp-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4af1f8877ca46ecdd0bc0d4a6b66d4b2bddc84a79e2e8366bc0d5308e76bceb8", size = 1698535, upload-time = "2025-10-06T19:55:50.75Z" }, + { url = "https://files.pythonhosted.org/packages/33/e2/27c92d205b9e8cee7661670e8e9f187931b71e26d42796b153d2a0ba6949/aiohttp-3.13.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e04ab827ec4f775817736b20cdc8350f40327f9b598dec4e18c9ffdcbea88a93", size = 1766573, upload-time = "2025-10-06T19:55:53.106Z" }, + { url = "https://files.pythonhosted.org/packages/df/6a/1fc1ad71d130a30f7a207d8d958a41224c29b834463b5185efb2dbff6ad4/aiohttp-3.13.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a6d9487b9471ec36b0faedf52228cd732e89be0a2bbd649af890b5e2ce422353", size = 1865229, upload-time = "2025-10-06T19:55:55.01Z" }, + { url = "https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8", size = 1750379, upload-time = "2025-10-06T19:55:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3d/2ec4b934f85856de1c0c18e90adc8902adadbfac2b3c0b831bfeb7214fc8/aiohttp-3.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469167d5372f5bb3aedff4fc53035d593884fff2617a75317740e885acd48b04", size = 1560798, upload-time = "2025-10-06T19:55:58.888Z" }, + { url = "https://files.pythonhosted.org/packages/38/56/e23d9c3e13006e599fdce3851517c70279e177871e3e567d22cf3baf5d6c/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a9f3546b503975a69b547c9fd1582cad10ede1ce6f3e313a2f547c73a3d7814f", size = 1697552, upload-time = "2025-10-06T19:56:01.172Z" }, + { url = "https://files.pythonhosted.org/packages/56/cb/caa32c2ccaeca0a3dc39129079fd2ad02f9406c3a5f7924340435b87d4cd/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6b4174fcec98601f0cfdf308ee29a6ae53c55f14359e848dab4e94009112ee7d", size = 1718609, upload-time = "2025-10-06T19:56:03.102Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c0/5911856fef9e40fd1ccbb8c54a90116875d5753a92c1cac66ce2059b390d/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a533873a7a4ec2270fb362ee5a0d3b98752e4e1dc9042b257cd54545a96bd8ed", size = 1735887, upload-time = "2025-10-06T19:56:04.841Z" }, + { url = "https://files.pythonhosted.org/packages/0e/48/8d6f4757a24c02f0a454c043556593a00645d10583859f7156db44d8b7d3/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ce887c5e54411d607ee0959cac15bb31d506d86a9bcaddf0b7e9d63325a7a802", size = 1553079, upload-time = "2025-10-06T19:56:07.197Z" }, + { url = "https://files.pythonhosted.org/packages/39/fa/e82c9445e40b50e46770702b5b6ca2f767966d53e1a5eef03583ceac6df6/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d871f6a30d43e32fc9252dc7b9febe1a042b3ff3908aa83868d7cf7c9579a59b", size = 1762750, upload-time = "2025-10-06T19:56:09.376Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e6/9d30554e7f1e700bfeae4ab6b153d5dc7441606a9ec5e929288fa93a1477/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:222c828243b4789d79a706a876910f656fad4381661691220ba57b2ab4547865", size = 1717461, upload-time = "2025-10-06T19:56:11.551Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e5/29cca547990a59ea54f0674fc01de98519fc628cfceeab6175711750eca7/aiohttp-3.13.0-cp312-cp312-win32.whl", hash = "sha256:682d2e434ff2f1108314ff7f056ce44e457f12dbed0249b24e106e385cf154b9", size = 424633, upload-time = "2025-10-06T19:56:13.316Z" }, + { url = "https://files.pythonhosted.org/packages/8b/68/46dd042d7bc62eab30bafdb8569f55ef125c3a88bb174270324224f8df56/aiohttp-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a2be20eb23888df130214b91c262a90e2de1553d6fb7de9e9010cec994c0ff2", size = 451401, upload-time = "2025-10-06T19:56:15.188Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "alabaster" version = "1.0.0" @@ -25,6 +90,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "anyio" version = "4.11.0" @@ -116,6 +190,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] +[[package]] +name = "bidict" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, +] + [[package]] name = "blinker" version = "1.9.0" @@ -391,6 +474,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "fastapi" +version = "0.119.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/f9/5c5bcce82a7997cc0eb8c47b7800f862f6b56adc40486ed246e5010d443b/fastapi-0.119.0.tar.gz", hash = "sha256:451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7", size = 336756, upload-time = "2025-10-11T17:13:40.53Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/70/584c4d7cad80f5e833715c0a29962d7c93b4d18eed522a02981a6d1b6ee5/fastapi-0.119.0-py3-none-any.whl", hash = "sha256:90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2", size = 107095, upload-time = "2025-10-11T17:13:39.048Z" }, +] + [[package]] name = "fastjsonschema" version = "2.21.2" @@ -480,6 +577,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/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", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "fsspec" version = "2025.9.0" @@ -576,6 +698,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/de/5b1410719816f20f14c0973e1fd0224cba375e1c2022aad2f7f145b60502/h5py-3.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:590d2547c0f174df7e0fa87fcc7ddba37dba8a366959621e9e6b2015e8a26dd7", size = 2458197, upload-time = "2025-10-13T12:59:43.288Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "identify" version = "2.6.15" @@ -594,6 +759,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485, upload-time = "2022-06-15T21:40:27.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, +] + [[package]] name = "imageio" version = "2.37.0" @@ -821,6 +995,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] +[[package]] +name = "markdown2" +version = "2.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/f8/b2ae8bf5f28f9b510ae097415e6e4cb63226bb28d7ee01aec03a755ba03b/markdown2-2.5.4.tar.gz", hash = "sha256:a09873f0b3c23dbfae589b0080587df52ad75bb09a5fa6559147554736676889", size = 145652, upload-time = "2025-07-27T16:16:24.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/06/2697b5043c3ecb720ce0d243fc7cf5024c0b5b1e450506e9b21939019963/markdown2-2.5.4-py3-none-any.whl", hash = "sha256:3c4b2934e677be7fec0e6f2de4410e116681f4ad50ec8e5ba7557be506d3f439", size = 49954, upload-time = "2025-07-27T16:16:23.026Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -929,6 +1112,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + [[package]] name = "multiprocess" version = "0.70.18" @@ -1004,6 +1214,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, ] +[[package]] +name = "nicegui" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "certifi" }, + { name = "docutils" }, + { name = "fastapi" }, + { name = "h11" }, + { name = "httpx" }, + { name = "ifaddr" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markdown2" }, + { name = "orjson", marker = "platform_machine != 'i386' and platform_machine != 'i686'" }, + { name = "pygments" }, + { name = "python-engineio" }, + { name = "python-multipart" }, + { name = "python-socketio", extra = ["asyncio-client"] }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "uvicorn", extra = ["standard"] }, + { name = "watchfiles" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/23/86a51147161d30471fcac0eec6fe4f5393c285770ee2c118805ffaf1702f/nicegui-3.0.4.tar.gz", hash = "sha256:d00fefae2f28020d3fd09ddd03a09e8255f31eec03239cdb4575cde98be8a477", size = 20103535, upload-time = "2025-10-10T12:35:07.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/6b/c04876a7bdf6172e6310766096fdabc032a03c0fd3c3ce32be023c389fff/nicegui-3.0.4-py3-none-any.whl", hash = "sha256:c6901815f2d670dded8303bf93d15dcd3078ddf66e983ae0899074e5ba02899a", size = 20751601, upload-time = "2025-10-10T12:35:04.189Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1236,6 +1477,29 @@ numpy = [ { name = "numpy-typing-compat" }, ] +[[package]] +name = "orjson" +version = "3.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" }, + { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" }, + { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" }, + { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" }, + { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -1370,6 +1634,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/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", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + [[package]] name = "psutil" version = "7.1.0" @@ -1413,6 +1701,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd", size = 816358, upload-time = "2025-10-14T15:02:21.842Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae", size = 460628, upload-time = "2025-10-14T15:02:19.623Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, +] + [[package]] name = "pydoclint" version = "0.7.3" @@ -1549,6 +1881,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-engineio" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "simple-websocket" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/d8/63e5535ab21dc4998ba1cfe13690ccf122883a38f025dca24d6e56c05eba/python_engineio-4.12.3.tar.gz", hash = "sha256:35633e55ec30915e7fc8f7e34ca8d73ee0c080cec8a8cd04faf2d7396f0a7a7a", size = 91910, upload-time = "2025-09-28T06:31:36.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl", hash = "sha256:7c099abb2a27ea7ab429c04da86ab2d82698cdd6c52406cb73766fe454feb7e1", size = 59637, upload-time = "2025-09-28T06:31:35.354Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "python-socketio" +version = "5.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bidict" }, + { name = "python-engineio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/80/f31077368adbf65be17245ee69cbccde2891582623e6a8ad647c7a254db3/python_socketio-5.14.2.tar.gz", hash = "sha256:5da35caa04059048a4521b1378340a33a15439c6a748765046112fa0d145fb1b", size = 124111, upload-time = "2025-10-15T18:59:43.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl", hash = "sha256:d7133f040ac7ba540f9a907cb4c36d6c4d1d54eb35e482aad9d45c22fca10654", size = 78941, upload-time = "2025-10-15T18:59:42.122Z" }, +] + +[package.optional-dependencies] +asyncio-client = [ + { name = "aiohttp" }, +] + [[package]] name = "pytz" version = "2025.2" @@ -1611,6 +1991,7 @@ dependencies = [ { name = "lark" }, { name = "mashumaro" }, { name = "myst-parser" }, + { name = "nicegui" }, { name = "numpy" }, { name = "opencv-python" }, { name = "pandas" }, @@ -1664,6 +2045,7 @@ requires-dist = [ { name = "lark", specifier = ">=1.2.2" }, { name = "mashumaro", specifier = ">=3.16" }, { name = "myst-parser", specifier = ">=4.0.1" }, + { name = "nicegui", specifier = ">=3.0.4" }, { name = "numpy", specifier = "<2.0" }, { name = "opencv-python", specifier = ">=4.11.0.86" }, { name = "pandas", specifier = ">=2.2.0" }, @@ -1929,6 +2311,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "simple-websocket" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, +] + [[package]] name = "simplejpeg" version = "1.9.0" @@ -2346,6 +2740,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -2377,6 +2783,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, ] +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, +] + [[package]] name = "virtualenv" version = "20.35.3" @@ -2465,6 +2896,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, ] +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, +] + [[package]] name = "xmltodict" version = "1.0.2" @@ -2474,6 +2917,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d", size = 13893, upload-time = "2025-09-17T21:59:24.859Z" }, ] +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + [[package]] name = "zipp" version = "3.23.0"