griip-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: griip-core
3
+ Version: 0.1.0
4
+ Summary: GRIIP shared foundation: the griip protobuf contract and shared helpers (transforms, joint math, VRMCS registration), depended on by both the mmai-griip-api server and the griip-sdk client.
5
+ Author: VentionCo
6
+ License: Proprietary
7
+ Requires-Python: <3.11,>=3.10
8
+ Requires-Dist: grpcio>=1.78.0
9
+ Requires-Dist: numpy<2,>=1.24
10
+ Requires-Dist: protobuf>=4.0
11
+ Requires-Dist: spatialmath-python<2.0.0,>=1.1.15
@@ -0,0 +1,38 @@
1
+ # griip-core
2
+
3
+ Shared foundation for GRIIP, by [Vention](https://vention.io).
4
+
5
+ Holds the code that both the `mmai-griip-api` server and the `griip-sdk` client
6
+ need. The SDK talks to the API over gRPC, so neither can depend on the other.
7
+ Code both sides need lives here.
8
+
9
+ ## Contents
10
+
11
+ The proto contract: `proto/griip.proto` and its committed generated stubs
12
+ (`griip_core.generated`). One definition of the five services (`GriipApiAdmin`,
13
+ `PlanningService`, `PerceptionService`, `PickingService`, `PlacingService`), so
14
+ server and client import the same stubs and the wire contract cannot drift.
15
+
16
+ Shared primitives:
17
+
18
+ - `utils/transforms.py`: SE3 and rotation-matrix math (`se3_to_pose_list`,
19
+ `normalize_rotation_matrix_to_se3`, `normalize_rotation_matrix_to_so3`).
20
+ - `utils/joint_math.py`: `normalize_joint_configurations_to_reference`, which
21
+ wraps joint angles to the value closest to a reference.
22
+ - `hardware/vrmcs_registration.py`: `find_compatible_registration` and
23
+ `adopt_existing_registration`, which reuse an existing VRMCS robot
24
+ registration instead of registering again.
25
+
26
+ That is the whole package. No orchestration, hardware clients, or calibration;
27
+ those stay in `griip-sdk`.
28
+
29
+ ## Proto changes
30
+
31
+ Edit `proto/griip.proto`, then regenerate the stubs:
32
+
33
+ ```bash
34
+ python scripts/generate_proto.py
35
+ ```
36
+
37
+ CI runs `tools/check-griip-proto-stubs-current.py` and fails if the committed
38
+ stubs are stale.
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "griip-core",
3
+ "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
4
+ "projectType": "library",
5
+ "sourceRoot": "projects/physical-ai/libs/griip-core/src",
6
+ "release": {
7
+ "version": {
8
+ "versionActions": "@nxlv/python/release/version-actions"
9
+ }
10
+ },
11
+ "targets": {
12
+ "generate-proto": {
13
+ "executor": "@nxlv/python:run-commands",
14
+ "options": {
15
+ "command": "uv run python scripts/generate_proto.py",
16
+ "cwd": "projects/physical-ai/libs/griip-core"
17
+ }
18
+ },
19
+ "proto-check": {
20
+ "executor": "@nxlv/python:run-commands",
21
+ "cache": true,
22
+ "inputs": [
23
+ "{projectRoot}/proto/griip.proto",
24
+ "{projectRoot}/scripts/generate_proto.py",
25
+ "{projectRoot}/src/griip_core/generated/**/*",
26
+ "{workspaceRoot}/tools/check-griip-proto-stubs-current.py"
27
+ ],
28
+ "options": {
29
+ "command": "uv run python tools/check-griip-proto-stubs-current.py",
30
+ "cwd": "."
31
+ }
32
+ },
33
+ "test": {
34
+ "executor": "@nxlv/python:run-commands",
35
+ "cache": true,
36
+ "dependsOn": ["proto-check"],
37
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
38
+ "options": {
39
+ "commands": [
40
+ {
41
+ "command": "PYTHONPATH=./src uv run pytest src/ -v --cov=src/griip_core --cov-report=xml:../../../../coverage/projects/physical-ai/libs/griip-core/coverage.xml --cov-report=html:../../../../coverage/projects/physical-ai/libs/griip-core/html",
42
+ "forwardAllArgs": false
43
+ }
44
+ ],
45
+ "cwd": "{projectRoot}"
46
+ }
47
+ },
48
+ "lint": {
49
+ "executor": "@nxlv/python:run-commands",
50
+ "options": {
51
+ "command": "uv run ruff check src/",
52
+ "cwd": "projects/physical-ai/libs/griip-core"
53
+ }
54
+ },
55
+ "format": {
56
+ "executor": "@nxlv/python:run-commands",
57
+ "options": {
58
+ "command": "uv run ruff format src/",
59
+ "cwd": "projects/physical-ai/libs/griip-core"
60
+ }
61
+ },
62
+ "build": {
63
+ "executor": "@nxlv/python:build",
64
+ "outputs": ["{projectRoot}/dist"],
65
+ "options": {
66
+ "outputPath": "projects/physical-ai/libs/griip-core/dist",
67
+ "publish": false,
68
+ "lockedVersions": false
69
+ }
70
+ },
71
+ "nx-release-publish": {
72
+ "executor": "nx:run-commands",
73
+ "dependsOn": ["build"],
74
+ "options": {
75
+ "command": "npx tsx tools/python-publish-if-not-exists/src/python-publish-if-not-exists.ts --projectRoot={projectRoot} --dryRun={args.dryRun}"
76
+ }
77
+ },
78
+ "upload-codecov": {
79
+ "executor": "nx:run-commands",
80
+ "options": {
81
+ "commands": ["bash tools/codecov-upload.sh griip-core coverage/projects/physical-ai/libs/griip-core/coverage.xml"]
82
+ }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,452 @@
1
+ syntax = "proto3";
2
+
3
+ package griip;
4
+
5
+ option java_multiple_files = true;
6
+ option java_package = "com.vention.griip";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // GriipApiAdmin
10
+ //
11
+ // One-shot warm-up RPC. The API process starts idle (no yaml on disk, no env
12
+ // var) and rejects Pick/Plan/Place RPCs with UNAVAILABLE until Initialize
13
+ // returns OK. The SDK consumer calls Initialize from its app yaml's cell
14
+ // + active part. Calling Initialize a second time atomically swaps the
15
+ // subsystems for the new config (e.g. switching parts).
16
+ // ---------------------------------------------------------------------------
17
+
18
+ service GriipApiAdmin {
19
+ rpc Initialize (InitializeRequest) returns (InitializeResponse);
20
+ rpc StartPerceptionMcapRecording (StartPerceptionMcapRecordingRequest) returns (StartPerceptionMcapRecordingResponse);
21
+ rpc StopPerceptionMcapRecording (StopPerceptionMcapRecordingRequest) returns (StopPerceptionMcapRecordingResponse);
22
+ }
23
+
24
+ message McapRecordingConfig {
25
+ bool enabled = 1;
26
+ string output_dir = 2;
27
+ int32 max_duration_s = 3;
28
+ string filename_prefix = 4;
29
+ }
30
+
31
+ message CameraCalibrationInput {
32
+ repeated double camera_intrinsics = 1;
33
+ int32 image_width = 2;
34
+ int32 image_height = 3;
35
+ string frame_id = 4;
36
+ }
37
+
38
+ message InitializeRequest {
39
+ MockConfig mocks = 1;
40
+ RobotConfig robot = 2;
41
+ PerceptionServicesConfig perception_services = 3;
42
+ PlannerConfig planner = 4;
43
+ GraspFilteringConfig grasp_filtering = 5;
44
+ PartConfig part = 6;
45
+ JointAngles home_joint_angles = 7;
46
+ repeated CollisionLineSegment collision_checking_line_segments = 8;
47
+ double simulated_delay_seconds = 9;
48
+ McapRecordingConfig perception_mcap = 10;
49
+ CameraCalibrationInput camera_calibration = 11;
50
+ }
51
+
52
+ message InitializeResponse {
53
+ enum Status {
54
+ STATUS_UNSPECIFIED = 0;
55
+ STATUS_OK = 1;
56
+ STATUS_INVALID_CONFIG = 2;
57
+ STATUS_BUILD_FAILED = 3;
58
+ }
59
+ Status status = 1;
60
+ string error_message = 2;
61
+ repeated double tcp_to_flange_offset = 3;
62
+ }
63
+
64
+ message StartPerceptionMcapRecordingRequest {}
65
+
66
+ message StartPerceptionMcapRecordingResponse {
67
+ enum Status {
68
+ STATUS_UNSPECIFIED = 0;
69
+ STATUS_OK = 1;
70
+ STATUS_NOT_INITIALIZED = 2;
71
+ STATUS_NOT_CONFIGURED = 3;
72
+ }
73
+ Status status = 1;
74
+ string error_message = 2;
75
+ }
76
+
77
+ message StopPerceptionMcapRecordingRequest {}
78
+
79
+ message StopPerceptionMcapRecordingResponse {
80
+ enum Status {
81
+ STATUS_UNSPECIFIED = 0;
82
+ STATUS_OK = 1;
83
+ }
84
+ Status status = 1;
85
+ string error_message = 2;
86
+ }
87
+
88
+ message MockConfig {
89
+ bool vamp = 1;
90
+ bool vrmcs_robot = 2;
91
+ bool collision_model = 3;
92
+ bool sam = 4;
93
+ bool foundation_pose = 5;
94
+ bool foundation_stereo = 6;
95
+ }
96
+
97
+ message RobotConfig {
98
+ string model = 1;
99
+ string driver = 2;
100
+ string vrmcs_url = 3;
101
+ string robot_ip = 4;
102
+ repeated double joint_lower_limits = 5;
103
+ repeated double joint_upper_limits = 6;
104
+ }
105
+
106
+ message PerceptionServicesConfig {
107
+ string sam_url = 1;
108
+ string foundation_pose_url = 2;
109
+ string foundation_stereo_url = 3;
110
+ }
111
+
112
+ message PlannerConfig {
113
+ string planner_name = 1;
114
+ int32 max_iterations = 2;
115
+ int32 max_samples = 3;
116
+ double range = 4;
117
+ bool dynamic_domain = 5;
118
+ double radius = 6;
119
+ bool start_tree_first = 7;
120
+ int32 simp_max_iterations = 8;
121
+ }
122
+
123
+ message GraspFilteringConfig {
124
+ double aggressive_angle_threshold_bin_floor_degrees = 1;
125
+ double aggressive_angle_threshold_gravity_axis_degrees = 2;
126
+ }
127
+
128
+ // `tcp_offset` is the [x,y,z] translation from the flange origin to the TCP;
129
+ // rotation is always identity in current cells, so we send the offset only.
130
+ // `grasp_definitions_json` is the raw contents of the part's grasps_<id>.json
131
+ // (the SDK reads the file locally and inlines it). Polymorphic schema
132
+ // (ExternalJawGrasp / InternalJawGrasp / ...) is preserved without bloating
133
+ // the proto with one field per subclass.
134
+ message PartConfig {
135
+ string part_id = 1;
136
+ string part_model_id = 2;
137
+ repeated double tcp_offset = 3;
138
+ string grasp_definitions_json = 4;
139
+ repeated CollisionSpheres held_object_collision_spheres = 5;
140
+ string mesh_path = 6;
141
+ }
142
+
143
+ message CollisionLineSegment {
144
+ repeated double points = 1;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // PlanningService
149
+ // ---------------------------------------------------------------------------
150
+
151
+ service PlanningService {
152
+ rpc ConfigurePlanner (ConfigurePlannerRequest) returns (ConfigurePlannerResponse);
153
+ rpc PlanPath (PlanPathRequest) returns (PlanPathResponse);
154
+ }
155
+
156
+ message ConfigurePlannerRequest {
157
+ string cuboids_path = 1;
158
+ string robot_model = 2;
159
+ string gripper_collision_spheres_path = 3;
160
+ optional string object_mesh_path = 4;
161
+ repeated double tcp_to_camera_transform = 5;
162
+ optional int32 planner_max_iterations = 6;
163
+ optional double planner_max_cost = 7;
164
+ repeated CollisionSpheres gripper_collision_spheres = 8;
165
+ }
166
+
167
+ message ConfigurePlannerResponse {
168
+ string environment_id = 1;
169
+ int32 robot_dof = 2;
170
+ int32 num_cuboids_loaded = 3;
171
+ }
172
+
173
+ message PlanPathRequest {
174
+ repeated double start_joint_angles = 1;
175
+ oneof target {
176
+ JointAngles target_joint_angles = 2;
177
+ Pose target_pose = 3;
178
+ }
179
+ bool linear = 4;
180
+ double velocity_scaling = 5;
181
+ double acceleration_scaling = 6;
182
+ repeated CollisionSpheres held_object_collision_spheres = 7;
183
+ repeated CollisionSpheres world_collision_spheres = 8;
184
+ optional bytes additional_point_clouds = 9;
185
+ optional double gripper_opening_width = 10;
186
+ repeated CollisionSpheres gripper_collision_spheres_override = 11;
187
+ optional bool normalize_to_start = 12;
188
+ repeated Cuboid cuboids = 13;
189
+ optional bool cuboids_replace_scene = 14;
190
+ }
191
+
192
+ message PlanPathResponse {
193
+ bool success = 1;
194
+ optional string failure_reason = 2;
195
+ repeated JointAngles trajectory = 3;
196
+ optional double planning_time_ms = 4;
197
+ optional double path_cost = 5;
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // PerceptionService
202
+ // ---------------------------------------------------------------------------
203
+
204
+ service PerceptionService {
205
+ rpc EstimateDepth (EstimateDepthRequest) returns (EstimateDepthResponse);
206
+ rpc DetectObjects (DetectObjectsRequest) returns (DetectObjectsResponse);
207
+ rpc EstimatePose (EstimatePoseRequest) returns (EstimatePoseResponse);
208
+ }
209
+
210
+ message EstimateDepthRequest {
211
+ bytes left_image = 1;
212
+ bytes right_image = 2;
213
+ repeated double camera_intrinsics = 3;
214
+ double stereo_baseline = 4;
215
+ }
216
+
217
+ message EstimateDepthResponse {
218
+ bytes depth_data = 1;
219
+ }
220
+
221
+ message DetectObjectsRequest {
222
+ string image_id = 1;
223
+ bytes color_image = 2;
224
+ bytes depth_image = 3;
225
+ repeated double camera_intrinsics = 4;
226
+ repeated double camera_pose_in_world = 5;
227
+ repeated BinWall bin_walls = 6;
228
+ repeated int32 roi = 7;
229
+ }
230
+
231
+ message DetectObjectsResponse {
232
+ repeated DetectedObject detections = 1;
233
+ }
234
+
235
+ message DetectedObject {
236
+ int32 index = 1;
237
+ bytes mask_data = 2;
238
+ int32 mask_height = 3;
239
+ int32 mask_width = 4;
240
+ repeated int32 bbox = 5;
241
+ double score = 6;
242
+ }
243
+
244
+ message EstimatePoseRequest {
245
+ string image_id = 1;
246
+ bytes color_image = 2;
247
+ bytes depth_image = 3;
248
+ bytes object_mask = 4;
249
+ int32 mask_height = 5;
250
+ int32 mask_width = 6;
251
+ repeated double camera_intrinsics = 7;
252
+ }
253
+
254
+ message EstimatePoseResponse {
255
+ bool success = 1;
256
+ Pose pose = 2;
257
+ }
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // PickingService
261
+ // ---------------------------------------------------------------------------
262
+
263
+ service PickingService {
264
+ rpc GeneratePicks (GeneratePicksRequest) returns (stream GeneratePicksResponse);
265
+ rpc ReportSelectedPick (ReportSelectedPickRequest) returns (ReportSelectedPickResponse);
266
+ }
267
+
268
+ message ReportSelectedPickRequest {
269
+ string image_id = 1;
270
+ repeated double object_pose_in_world = 2;
271
+ repeated double prep_pose = 3;
272
+ int64 capture_timestamp_ns = 4;
273
+ }
274
+
275
+ message ReportSelectedPickResponse {
276
+ bool ok = 1;
277
+ }
278
+
279
+ message GeneratePicksRequest {
280
+ string image_id = 1;
281
+ bytes color_image = 2;
282
+ bytes depth_image = 3;
283
+ repeated double camera_intrinsics = 4;
284
+ repeated double camera_pose_in_world = 5;
285
+ repeated double tcp_pose_at_capture = 6;
286
+ JointAngles capture_joint_angles = 7;
287
+ repeated BinWall bin_walls = 8;
288
+ int32 max_detections = 9;
289
+ int32 max_picks_per_object = 10;
290
+ BinWall bin_floor = 11;
291
+ repeated int32 roi = 12;
292
+ int64 capture_timestamp_ns = 13;
293
+ // Per-phase end-effector collision spheres (flange frame), generated by the
294
+ // SDK's collision model. prep: all spheres, fingers closed. plunge: all
295
+ // spheres, fingers opened. scoop: all except fingertips, fingers opened.
296
+ // Empty => fall back to the spheres configured via ConfigurePlanner.
297
+ repeated CollisionSpheres prep_gripper_collision_spheres = 14;
298
+ repeated CollisionSpheres plunge_gripper_collision_spheres = 15;
299
+ repeated CollisionSpheres scoop_gripper_collision_spheres = 16;
300
+ }
301
+
302
+ message GeneratePicksResponse {
303
+ oneof payload {
304
+ PickResultMessage pick_result = 1;
305
+ PickTerminalMessage terminal_message = 2;
306
+ }
307
+ }
308
+
309
+ message PickResultMessage {
310
+ int32 pick_id = 1;
311
+ string grasp_name = 2;
312
+ repeated double object_pose_in_world = 4;
313
+ repeated double object_in_tcp = 5;
314
+ repeated double prep_pose = 6;
315
+ repeated double plunge_pose = 7;
316
+ repeated double scoop_pose = 8;
317
+ repeated JointAngles prep_trajectory = 9;
318
+ int32 priority = 10;
319
+ double distance_to_home = 11;
320
+ double retract_move_distance = 12;
321
+ int32 detected_object_index = 16;
322
+ oneof executor_params {
323
+ ParallelJawParams parallel_jaw = 20;
324
+ SuctionParams suction = 21;
325
+ ThreeFingerParams three_finger = 22;
326
+ }
327
+ }
328
+
329
+ enum GraspMode {
330
+ GRASP_MODE_EXTERNAL = 0;
331
+ GRASP_MODE_INTERNAL = 1;
332
+ }
333
+
334
+ message ScoopMotionParams {
335
+ double plunge_velocity = 1;
336
+ double scoop_velocity = 2;
337
+ double force_torque_sensor_threshold = 3;
338
+ }
339
+
340
+ message ParallelJawParams {
341
+ GraspMode mode = 1;
342
+ int32 prep_width = 2;
343
+ int32 engaged_width = 3;
344
+ int32 grip_force = 4;
345
+ int32 grip_check_min_width = 5;
346
+ int32 grip_check_max_width = 6;
347
+ ScoopMotionParams scoop_motion = 7;
348
+ }
349
+
350
+ message SuctionParams {
351
+ double vacuum_engage_threshold = 1;
352
+ ScoopMotionParams scoop_motion = 2;
353
+ }
354
+
355
+ message ThreeFingerParams {
356
+ GraspMode mode = 1;
357
+ int32 grip_force = 2;
358
+ ScoopMotionParams scoop_motion = 3;
359
+ // IO-backed three-finger grippers (e.g. OnRobot 3FG15). When set, the SDK
360
+ // uses IOThreeFingerExecutor instead of the width-based ThreeFingerExecutor.
361
+ int32 prep_io_state = 4;
362
+ int32 engaged_io_state = 5;
363
+ }
364
+
365
+ message PickTerminalMessage {
366
+ bool no_parts_detected = 1;
367
+ int32 total_picks_generated = 2;
368
+ bool has_only_ungraspable_objects = 3;
369
+ }
370
+
371
+ // ---------------------------------------------------------------------------
372
+ // PlacingService
373
+ // ---------------------------------------------------------------------------
374
+
375
+ service PlacingService {
376
+ rpc GeneratePlacement (GeneratePlacementRequest) returns (GeneratePlacementResponse);
377
+ }
378
+
379
+ message GeneratePlacementRequest {
380
+ string image_id = 1;
381
+ bytes left_png = 2;
382
+ bytes depth_png = 3;
383
+ repeated double camera_intrinsics = 4;
384
+ repeated double tcp_to_camera = 5;
385
+ repeated double object_in_tcp = 6;
386
+ repeated double target_pose = 7;
387
+ repeated double planning_start_joint_angles = 8;
388
+ double max_object_diameter = 9;
389
+ repeated double axis_in_object_frame = 10;
390
+ int32 number_of_rotations = 11;
391
+ bool enable_in_hand_pose_estimation = 12;
392
+ }
393
+
394
+ message GeneratePlacementResponse {
395
+ bool success = 1;
396
+ optional string failure_reason = 2;
397
+ repeated double refined_object_in_tcp = 3;
398
+ repeated PlacementIkSolution ik_solutions = 4;
399
+ }
400
+
401
+ message PlacementIkSolution {
402
+ repeated double joint_angles = 1;
403
+ repeated double target_object_pose_in_world = 2;
404
+ double distance_to_start = 3;
405
+ }
406
+
407
+ // ---------------------------------------------------------------------------
408
+ // Reusable sub-messages
409
+ // ---------------------------------------------------------------------------
410
+
411
+ message JointAngles {
412
+ repeated double values = 1;
413
+ }
414
+
415
+ message Quaternion {
416
+ double w = 1;
417
+ double x = 2;
418
+ double y = 3;
419
+ double z = 4;
420
+ }
421
+
422
+ message Vector3 {
423
+ double x = 1;
424
+ double y = 2;
425
+ double z = 3;
426
+ }
427
+
428
+ message Pose {
429
+ Vector3 position = 1;
430
+ Quaternion orientation = 2;
431
+ }
432
+
433
+ message CollisionSpheres {
434
+ Vector3 center = 1;
435
+ double radius = 2;
436
+ }
437
+
438
+ message BinWall {
439
+ string name = 1;
440
+ double width_x = 2;
441
+ double width_y = 3;
442
+ double width_z = 4;
443
+ repeated double pose = 5;
444
+ }
445
+
446
+ message Cuboid {
447
+ string name = 1;
448
+ double wx = 2;
449
+ double wy = 3;
450
+ double wz = 4;
451
+ repeated double pose = 5;
452
+ }
@@ -0,0 +1,36 @@
1
+ dependency-groups = { }
2
+
3
+ [project]
4
+ name = "griip-core"
5
+ version = "0.1.0"
6
+ description = "GRIIP shared foundation: the griip protobuf contract and shared helpers (transforms, joint math, VRMCS registration), depended on by both the mmai-griip-api server and the griip-sdk client."
7
+ requires-python = ">=3.10,<3.11"
8
+ dependencies = [
9
+ "grpcio>=1.78.0",
10
+ "protobuf>=4.0",
11
+ "numpy>=1.24,<2",
12
+ "spatialmath-python>=1.1.15,<2.0.0"
13
+ ]
14
+
15
+ [[project.authors]]
16
+ name = "VentionCo"
17
+
18
+ [project.license]
19
+ text = "Proprietary"
20
+
21
+ [build-system]
22
+ requires = [ "hatchling" ]
23
+ build-backend = "hatchling.build"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = [ "src/griip_core" ]
27
+
28
+ [tool.hatch.metadata]
29
+ allow-direct-references = true
30
+
31
+ [tool.ruff]
32
+ line-length = 140
33
+ extend-exclude = [ "src/griip_core/generated" ]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = [ "src" ]
@@ -0,0 +1,70 @@
1
+ """Generate Python gRPC stubs from proto/griip.proto.
2
+
3
+ griip-core is the single source of truth for the 'griip' wire contract. Both
4
+ the mmai-griip-api server and the griip-sdk client depend on griip-core and
5
+ import these stubs, so only one descriptor set is ever registered.
6
+
7
+ The generated stubs are committed so the published griip-core wheel ships them
8
+ and resolves standalone. CI guards against stale stubs via
9
+ tools/check-griip-proto-stubs-current.py.
10
+
11
+ Usage:
12
+ python scripts/generate_proto.py
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ import subprocess
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ PROJECT_ROOT = Path(__file__).resolve().parent.parent
23
+ PROTO_DIR = PROJECT_ROOT / "proto"
24
+ PROTO_FILE = PROTO_DIR / "griip.proto"
25
+ OUT_DIR = PROJECT_ROOT / "src" / "griip_core" / "generated"
26
+
27
+
28
+ def _fix_imports(directory: Path) -> None:
29
+ """Rewrite absolute proto imports to relative imports so they work inside a package."""
30
+ for py_file in directory.glob("*_pb2_grpc.py"):
31
+ content = py_file.read_text()
32
+ fixed = re.sub(r"^import (\w+_pb2) as", r"from . import \1 as", content, flags=re.MULTILINE)
33
+ if fixed != content:
34
+ py_file.write_text(fixed)
35
+ print(f" Fixed imports in {py_file.name}")
36
+
37
+
38
+ def main() -> None:
39
+ OUT_DIR.mkdir(parents=True, exist_ok=True)
40
+
41
+ cmd = [
42
+ sys.executable,
43
+ "-m",
44
+ "grpc_tools.protoc",
45
+ f"--proto_path={PROTO_DIR}",
46
+ f"--python_out={OUT_DIR}",
47
+ f"--grpc_python_out={OUT_DIR}",
48
+ f"--pyi_out={OUT_DIR}",
49
+ f"--mypy_grpc_out={OUT_DIR}",
50
+ str(PROTO_FILE),
51
+ ]
52
+
53
+ print(f"Running: {' '.join(cmd)}")
54
+ result = subprocess.run(cmd, capture_output=True, text=True)
55
+
56
+ if result.returncode != 0:
57
+ print(f"protoc failed:\n{result.stderr}", file=sys.stderr)
58
+ sys.exit(result.returncode)
59
+
60
+ _fix_imports(OUT_DIR)
61
+
62
+ init_file = OUT_DIR / "__init__.py"
63
+ if not init_file.exists():
64
+ init_file.touch()
65
+
66
+ print(f"Generated stubs in {OUT_DIR}")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
File without changes
File without changes