current-data-py 0.1.5__tar.gz → 0.1.7__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.
Files changed (84) hide show
  1. {current_data_py-0.1.5/current_data_py.egg-info → current_data_py-0.1.7}/PKG-INFO +51 -13
  2. current_data_py-0.1.7/README.md +125 -0
  3. {current_data_py-0.1.5 → current_data_py-0.1.7/current_data_py.egg-info}/PKG-INFO +51 -13
  4. {current_data_py-0.1.5 → current_data_py-0.1.7}/examples/__init__.py +0 -0
  5. {current_data_py-0.1.5 → current_data_py-0.1.7}/examples/list_schemas.py +0 -0
  6. {current_data_py-0.1.5 → current_data_py-0.1.7}/examples/read_td.py +0 -0
  7. {current_data_py-0.1.5 → current_data_py-0.1.7}/examples/write_td.py +0 -0
  8. current_data_py-0.1.7/proto_gen/python/management/service/service_pb2.py +64 -0
  9. current_data_py-0.1.7/proto_gen/python/msg/custom_msgs/pose_rpy_pb2.py +26 -0
  10. {current_data_py-0.1.5 → current_data_py-0.1.7}/pyproject.toml +1 -1
  11. {current_data_py-0.1.5 → current_data_py-0.1.7}/remote/client.py +27 -5
  12. {current_data_py-0.1.5 → current_data_py-0.1.7}/schema/__init__.py +2 -0
  13. {current_data_py-0.1.5 → current_data_py-0.1.7}/schema/message_metadata.py +0 -0
  14. {current_data_py-0.1.5 → current_data_py-0.1.7}/setup.py +0 -0
  15. {current_data_py-0.1.5 → current_data_py-0.1.7}/tests/check_package.py +0 -0
  16. {current_data_py-0.1.5 → current_data_py-0.1.7}/tests/check_remote.py +60 -2
  17. current_data_py-0.1.5/README.md +0 -87
  18. current_data_py-0.1.5/proto_gen/python/management/service/service_pb2.py +0 -64
  19. {current_data_py-0.1.5 → current_data_py-0.1.7}/MANIFEST.in +0 -0
  20. {current_data_py-0.1.5 → current_data_py-0.1.7}/__init__.py +0 -0
  21. {current_data_py-0.1.5 → current_data_py-0.1.7}/current_data_py.egg-info/SOURCES.txt +0 -0
  22. {current_data_py-0.1.5 → current_data_py-0.1.7}/current_data_py.egg-info/dependency_links.txt +0 -0
  23. {current_data_py-0.1.5 → current_data_py-0.1.7}/current_data_py.egg-info/requires.txt +0 -0
  24. {current_data_py-0.1.5 → current_data_py-0.1.7}/current_data_py.egg-info/top_level.txt +0 -0
  25. {current_data_py-0.1.5 → current_data_py-0.1.7}/examples/read_remote_td.py +0 -0
  26. {current_data_py-0.1.5 → current_data_py-0.1.7}/management.py +0 -0
  27. {current_data_py-0.1.5 → current_data_py-0.1.7}/oss.py +0 -0
  28. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/management/metadata/metadata_pb2.py +0 -0
  29. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/management/service/service_pb2_grpc.py +0 -0
  30. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/builtin_interfaces/duration_pb2.py +0 -0
  31. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/builtin_interfaces/time_pb2.py +0 -0
  32. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/custom_msgs/joint_current_pb2.py +0 -0
  33. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/custom_msgs/slam_quality_pb2.py +0 -0
  34. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/custom_msgs/sonic_frame_pb2.py +0 -0
  35. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/custom_msgs/target_quality_pb2.py +0 -0
  36. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/foxglove/compressed_audio_pb2.py +0 -0
  37. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/foxglove/compressed_video_pb2.py +0 -0
  38. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/point32_pb2.py +0 -0
  39. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/point_pb2.py +0 -0
  40. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/pose_pb2.py +0 -0
  41. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/pose_stamped_pb2.py +0 -0
  42. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/pose_with_covariance_pb2.py +0 -0
  43. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/pose_with_covariance_stamped_pb2.py +0 -0
  44. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/quaternion_pb2.py +0 -0
  45. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/transform_pb2.py +0 -0
  46. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/transform_stamped_pb2.py +0 -0
  47. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/twist_pb2.py +0 -0
  48. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/twist_stamped_pb2.py +0 -0
  49. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/twist_with_covariance_pb2.py +0 -0
  50. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/twist_with_covariance_stamped_pb2.py +0 -0
  51. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/vector3_pb2.py +0 -0
  52. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/wrench_pb2.py +0 -0
  53. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/geometry_msgs/wrench_stamped_pb2.py +0 -0
  54. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/nav_msgs/map_meta_data_pb2.py +0 -0
  55. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/nav_msgs/occupancy_grid_pb2.py +0 -0
  56. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/nav_msgs/odometry_pb2.py +0 -0
  57. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/nav_msgs/path_pb2.py +0 -0
  58. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/battery_state_pb2.py +0 -0
  59. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/camera_info_pb2.py +0 -0
  60. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/compressed_image_pb2.py +0 -0
  61. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/image_pb2.py +0 -0
  62. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/imu_pb2.py +0 -0
  63. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/joint_state_pb2.py +0 -0
  64. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/laser_scan_pb2.py +0 -0
  65. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/nav_sat_fix_pb2.py +0 -0
  66. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/nav_sat_status_pb2.py +0 -0
  67. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/point_cloud2_pb2.py +0 -0
  68. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/point_field_pb2.py +0 -0
  69. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/range_pb2.py +0 -0
  70. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/sensor_msgs/region_of_interest_pb2.py +0 -0
  71. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/bool_pb2.py +0 -0
  72. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/color_rgba_pb2.py +0 -0
  73. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/float32_pb2.py +0 -0
  74. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/float64_pb2.py +0 -0
  75. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/header_pb2.py +0 -0
  76. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/int32_pb2.py +0 -0
  77. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/string_pb2.py +0 -0
  78. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/std_msgs/uint32_pb2.py +0 -0
  79. {current_data_py-0.1.5 → current_data_py-0.1.7}/proto_gen/python/msg/tf2_msgs/tf_message_pb2.py +0 -0
  80. {current_data_py-0.1.5 → current_data_py-0.1.7}/remote/__init__.py +0 -0
  81. {current_data_py-0.1.5 → current_data_py-0.1.7}/remote/source.py +0 -0
  82. {current_data_py-0.1.5 → current_data_py-0.1.7}/setup.cfg +0 -0
  83. {current_data_py-0.1.5 → current_data_py-0.1.7}/tests/check_management.py +0 -0
  84. {current_data_py-0.1.5 → current_data_py-0.1.7}/tests/check_oss.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: current-data-py
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Current Data protobuf messages and schema metadata
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -13,21 +13,29 @@ Requires-Dist: alibabacloud-oss-v2<2,>=1.4.0
13
13
 
14
14
  ## Management
15
15
 
16
- `ManagementClient()` connects to `data.current-robotics.work:443` over TLS.
17
- Pass `address="host:443"` to override it, or supply your own gRPC channel.
16
+ `ManagementClient` connects over TLS. Pass `address=` to configure the service,
17
+ or supply your own gRPC channel.
18
+
19
+ The examples below read deployment-specific values from environment variables.
20
+ Set `MANAGEMENT_ADDRESS` to your service address and `UPLOAD_DATA_TYPE` to a valid
21
+ integer enum value supplied by your deployment administrator. Set the bucket and
22
+ object-key variables to resources you are authorized to access. These variables
23
+ are read explicitly by the examples; they are not automatic SDK configuration.
18
24
 
19
25
  ```python
26
+ import os
27
+
20
28
  from current_data_py.management import ManagementClient, Metadata, MetadataFilter
21
29
 
22
- with ManagementClient() as client:
23
- upload = client.get_upload_credentials(data_type="CURRENT_V2")
24
- read = client.get_read_credentials(bucket="data-infra-raw-data")
30
+ with ManagementClient(address=os.environ["MANAGEMENT_ADDRESS"]) as client:
31
+ upload = client.get_upload_credentials(data_type=int(os.environ["UPLOAD_DATA_TYPE"]))
32
+ read = client.get_read_credentials(bucket=os.environ["READ_BUCKET"])
25
33
 
26
34
  # Register an already uploaded file; replace the example file details.
27
35
  record = client.register_data(Metadata(
28
36
  name="recording",
29
- data_type="DATA_TYPE_CURRENT_V2",
30
- storages=[{"bucket": "data-infra-202607", "object_key": "raw_converted/recording.td"}],
37
+ data_type=int(os.environ["UPLOAD_DATA_TYPE"]),
38
+ storages=[{"bucket": os.environ["UPLOADED_BUCKET"], "object_key": os.environ["UPLOADED_OBJECT_KEY"]}],
31
39
  file_info={"size_bytes": 1234, "checksum": "sha256:<file-hash>"},
32
40
  ))
33
41
  for item in client.stream_data_references(MetadataFilter(name_prefix="recording")):
@@ -39,25 +47,55 @@ SDK. Registration only saves metadata; it does not upload files. Credential
39
47
  calls return fresh credentials without caching. Calls accept `timeout=` and
40
48
  `metadata=` for gRPC headers.
41
49
 
50
+ ## Remote TD reads
51
+
52
+ `DataClient.open_td()` accepts an optional `bucket` argument. Without it, reads
53
+ use the management service's `OSS_READ_BUCKET` and `OSS_READ_PREFIX` defaults.
54
+ With it, the SDK requests whole-bucket read credentials, limited by the server's
55
+ RAM role, and selects the first matching storage in the reference. An explicit
56
+ bucket does not inherit the default prefix. Default and per-bucket credentials
57
+ are cached separately and refreshed automatically.
58
+
59
+ ```python
60
+ import os
61
+
62
+ from current_data_py.remote import DataClient
63
+
64
+ with DataClient(address=os.environ["MANAGEMENT_ADDRESS"]) as client:
65
+ for reference in client.iter_data():
66
+ # Use the service's default bucket and prefix.
67
+ with client.open_td(reference) as reader:
68
+ print(reader.summary())
69
+
70
+ # Request read access to the selected bucket.
71
+ with client.open_td(reference, bucket=os.environ["READ_BUCKET"]) as reader:
72
+ print(reader.summary())
73
+ ```
74
+
75
+ The explicit bucket must contain a storage listed in the reference. This uses
76
+ the existing bucket-aware management API; no protocol changes are required.
77
+
42
78
  ## OSS
43
79
 
44
80
  ```python
81
+ import os
82
+
45
83
  from current_data_py.management import ManagementClient
46
84
  from current_data_py.oss import OSSClient
47
85
 
48
- with ManagementClient() as management:
49
- with OSSClient(management, permission="read", bucket="data-infra-raw-data") as source:
50
- source.download_file("collections/recording/input.h5", "/tmp/input.h5")
86
+ with ManagementClient(address=os.environ["MANAGEMENT_ADDRESS"]) as management:
87
+ with OSSClient(management, permission="read", bucket=os.environ["READ_BUCKET"]) as source:
88
+ source.download_file(os.environ["SOURCE_OBJECT_KEY"], "/tmp/input.h5")
51
89
 
52
90
  # Convert the input into /tmp/output.td before uploading.
53
- with OSSClient(management, permission="upload", data_type="CURRENT_V2") as destination:
91
+ with OSSClient(management, permission="upload", data_type=int(os.environ["UPLOAD_DATA_TYPE"])) as destination:
54
92
  key = destination.object_prefix + "recording/output.td"
55
93
  destination.upload_file(key, "/tmp/output.td")
56
94
  print(destination.bucket, key) # Use these values when registering metadata.
57
95
  ```
58
96
 
59
97
  - Read access covers the requested bucket, subject to server permissions.
60
- Uploads default to `data_type="RAW"`; the server chooses the bucket and prefix.
98
+ The server chooses the upload bucket and prefix based on the requested data type.
61
99
  - Credentials refresh automatically. Expired tokens trigger one refresh and retry.
62
100
  - Uploads reject overwrites by default; set `forbid_overwrite=False` to replace.
63
101
  Downloads replace the local file only on success; its parent directory must exist.
@@ -0,0 +1,125 @@
1
+ # current-data-py
2
+
3
+ ## Management
4
+
5
+ `ManagementClient` connects over TLS. Pass `address=` to configure the service,
6
+ or supply your own gRPC channel.
7
+
8
+ The examples below read deployment-specific values from environment variables.
9
+ Set `MANAGEMENT_ADDRESS` to your service address and `UPLOAD_DATA_TYPE` to a valid
10
+ integer enum value supplied by your deployment administrator. Set the bucket and
11
+ object-key variables to resources you are authorized to access. These variables
12
+ are read explicitly by the examples; they are not automatic SDK configuration.
13
+
14
+ ```python
15
+ import os
16
+
17
+ from current_data_py.management import ManagementClient, Metadata, MetadataFilter
18
+
19
+ with ManagementClient(address=os.environ["MANAGEMENT_ADDRESS"]) as client:
20
+ upload = client.get_upload_credentials(data_type=int(os.environ["UPLOAD_DATA_TYPE"]))
21
+ read = client.get_read_credentials(bucket=os.environ["READ_BUCKET"])
22
+
23
+ # Register an already uploaded file; replace the example file details.
24
+ record = client.register_data(Metadata(
25
+ name="recording",
26
+ data_type=int(os.environ["UPLOAD_DATA_TYPE"]),
27
+ storages=[{"bucket": os.environ["UPLOADED_BUCKET"], "object_key": os.environ["UPLOADED_OBJECT_KEY"]}],
28
+ file_info={"size_bytes": 1234, "checksum": "sha256:<file-hash>"},
29
+ ))
30
+ for item in client.stream_data_references(MetadataFilter(name_prefix="recording")):
31
+ print(item.id, item.storages)
32
+ ```
33
+
34
+ `Metadata`, `MetadataFilter`, and responses are Protobuf objects exported by the
35
+ SDK. Registration only saves metadata; it does not upload files. Credential
36
+ calls return fresh credentials without caching. Calls accept `timeout=` and
37
+ `metadata=` for gRPC headers.
38
+
39
+ ## Remote TD reads
40
+
41
+ `DataClient.open_td()` accepts an optional `bucket` argument. Without it, reads
42
+ use the management service's `OSS_READ_BUCKET` and `OSS_READ_PREFIX` defaults.
43
+ With it, the SDK requests whole-bucket read credentials, limited by the server's
44
+ RAM role, and selects the first matching storage in the reference. An explicit
45
+ bucket does not inherit the default prefix. Default and per-bucket credentials
46
+ are cached separately and refreshed automatically.
47
+
48
+ ```python
49
+ import os
50
+
51
+ from current_data_py.remote import DataClient
52
+
53
+ with DataClient(address=os.environ["MANAGEMENT_ADDRESS"]) as client:
54
+ for reference in client.iter_data():
55
+ # Use the service's default bucket and prefix.
56
+ with client.open_td(reference) as reader:
57
+ print(reader.summary())
58
+
59
+ # Request read access to the selected bucket.
60
+ with client.open_td(reference, bucket=os.environ["READ_BUCKET"]) as reader:
61
+ print(reader.summary())
62
+ ```
63
+
64
+ The explicit bucket must contain a storage listed in the reference. This uses
65
+ the existing bucket-aware management API; no protocol changes are required.
66
+
67
+ ## OSS
68
+
69
+ ```python
70
+ import os
71
+
72
+ from current_data_py.management import ManagementClient
73
+ from current_data_py.oss import OSSClient
74
+
75
+ with ManagementClient(address=os.environ["MANAGEMENT_ADDRESS"]) as management:
76
+ with OSSClient(management, permission="read", bucket=os.environ["READ_BUCKET"]) as source:
77
+ source.download_file(os.environ["SOURCE_OBJECT_KEY"], "/tmp/input.h5")
78
+
79
+ # Convert the input into /tmp/output.td before uploading.
80
+ with OSSClient(management, permission="upload", data_type=int(os.environ["UPLOAD_DATA_TYPE"])) as destination:
81
+ key = destination.object_prefix + "recording/output.td"
82
+ destination.upload_file(key, "/tmp/output.td")
83
+ print(destination.bucket, key) # Use these values when registering metadata.
84
+ ```
85
+
86
+ - Read access covers the requested bucket, subject to server permissions.
87
+ The server chooses the upload bucket and prefix based on the requested data type.
88
+ - Credentials refresh automatically. Expired tokens trigger one refresh and retry.
89
+ - Uploads reject overwrites by default; set `forbid_overwrite=False` to replace.
90
+ Downloads replace the local file only on success; its parent directory must exist.
91
+ - Uploads use a single PutObject request ([maximum 5 GiB](https://www.alibabacloud.com/help/en/oss/developer-reference/upload-files-using-oss-sdk-for-python-v2/)); multipart upload is not supported.
92
+ - Supply `region=` for custom upload endpoints. Deploy the current management
93
+ credential API before using the new bucket and data-type arguments.
94
+
95
+ ## 打包
96
+
97
+ ```bash
98
+ cd current_data_py # 从仓库根目录执行
99
+ # 发布新版本前,更新 pyproject.toml 中的 version
100
+ python -m pip install build twine
101
+ rm -rf build dist
102
+ python -m build
103
+ ```
104
+
105
+ ## 测试
106
+
107
+ ```bash
108
+ python -m twine check dist/*
109
+ python tests/check_distribution.py
110
+ python -m pip install --force-reinstall dist/*.whl
111
+ python -I tests/check_package.py
112
+ python -I tests/check_management.py
113
+ python -I tests/check_remote.py
114
+ python -I tests/check_oss.py
115
+ ```
116
+
117
+ ## 推送
118
+
119
+ ```bash
120
+ # TestPyPI
121
+ python -m twine upload --repository testpypi dist/*
122
+
123
+ # PyPI
124
+ python -m twine upload dist/*
125
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: current-data-py
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Current Data protobuf messages and schema metadata
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -13,21 +13,29 @@ Requires-Dist: alibabacloud-oss-v2<2,>=1.4.0
13
13
 
14
14
  ## Management
15
15
 
16
- `ManagementClient()` connects to `data.current-robotics.work:443` over TLS.
17
- Pass `address="host:443"` to override it, or supply your own gRPC channel.
16
+ `ManagementClient` connects over TLS. Pass `address=` to configure the service,
17
+ or supply your own gRPC channel.
18
+
19
+ The examples below read deployment-specific values from environment variables.
20
+ Set `MANAGEMENT_ADDRESS` to your service address and `UPLOAD_DATA_TYPE` to a valid
21
+ integer enum value supplied by your deployment administrator. Set the bucket and
22
+ object-key variables to resources you are authorized to access. These variables
23
+ are read explicitly by the examples; they are not automatic SDK configuration.
18
24
 
19
25
  ```python
26
+ import os
27
+
20
28
  from current_data_py.management import ManagementClient, Metadata, MetadataFilter
21
29
 
22
- with ManagementClient() as client:
23
- upload = client.get_upload_credentials(data_type="CURRENT_V2")
24
- read = client.get_read_credentials(bucket="data-infra-raw-data")
30
+ with ManagementClient(address=os.environ["MANAGEMENT_ADDRESS"]) as client:
31
+ upload = client.get_upload_credentials(data_type=int(os.environ["UPLOAD_DATA_TYPE"]))
32
+ read = client.get_read_credentials(bucket=os.environ["READ_BUCKET"])
25
33
 
26
34
  # Register an already uploaded file; replace the example file details.
27
35
  record = client.register_data(Metadata(
28
36
  name="recording",
29
- data_type="DATA_TYPE_CURRENT_V2",
30
- storages=[{"bucket": "data-infra-202607", "object_key": "raw_converted/recording.td"}],
37
+ data_type=int(os.environ["UPLOAD_DATA_TYPE"]),
38
+ storages=[{"bucket": os.environ["UPLOADED_BUCKET"], "object_key": os.environ["UPLOADED_OBJECT_KEY"]}],
31
39
  file_info={"size_bytes": 1234, "checksum": "sha256:<file-hash>"},
32
40
  ))
33
41
  for item in client.stream_data_references(MetadataFilter(name_prefix="recording")):
@@ -39,25 +47,55 @@ SDK. Registration only saves metadata; it does not upload files. Credential
39
47
  calls return fresh credentials without caching. Calls accept `timeout=` and
40
48
  `metadata=` for gRPC headers.
41
49
 
50
+ ## Remote TD reads
51
+
52
+ `DataClient.open_td()` accepts an optional `bucket` argument. Without it, reads
53
+ use the management service's `OSS_READ_BUCKET` and `OSS_READ_PREFIX` defaults.
54
+ With it, the SDK requests whole-bucket read credentials, limited by the server's
55
+ RAM role, and selects the first matching storage in the reference. An explicit
56
+ bucket does not inherit the default prefix. Default and per-bucket credentials
57
+ are cached separately and refreshed automatically.
58
+
59
+ ```python
60
+ import os
61
+
62
+ from current_data_py.remote import DataClient
63
+
64
+ with DataClient(address=os.environ["MANAGEMENT_ADDRESS"]) as client:
65
+ for reference in client.iter_data():
66
+ # Use the service's default bucket and prefix.
67
+ with client.open_td(reference) as reader:
68
+ print(reader.summary())
69
+
70
+ # Request read access to the selected bucket.
71
+ with client.open_td(reference, bucket=os.environ["READ_BUCKET"]) as reader:
72
+ print(reader.summary())
73
+ ```
74
+
75
+ The explicit bucket must contain a storage listed in the reference. This uses
76
+ the existing bucket-aware management API; no protocol changes are required.
77
+
42
78
  ## OSS
43
79
 
44
80
  ```python
81
+ import os
82
+
45
83
  from current_data_py.management import ManagementClient
46
84
  from current_data_py.oss import OSSClient
47
85
 
48
- with ManagementClient() as management:
49
- with OSSClient(management, permission="read", bucket="data-infra-raw-data") as source:
50
- source.download_file("collections/recording/input.h5", "/tmp/input.h5")
86
+ with ManagementClient(address=os.environ["MANAGEMENT_ADDRESS"]) as management:
87
+ with OSSClient(management, permission="read", bucket=os.environ["READ_BUCKET"]) as source:
88
+ source.download_file(os.environ["SOURCE_OBJECT_KEY"], "/tmp/input.h5")
51
89
 
52
90
  # Convert the input into /tmp/output.td before uploading.
53
- with OSSClient(management, permission="upload", data_type="CURRENT_V2") as destination:
91
+ with OSSClient(management, permission="upload", data_type=int(os.environ["UPLOAD_DATA_TYPE"])) as destination:
54
92
  key = destination.object_prefix + "recording/output.td"
55
93
  destination.upload_file(key, "/tmp/output.td")
56
94
  print(destination.bucket, key) # Use these values when registering metadata.
57
95
  ```
58
96
 
59
97
  - Read access covers the requested bucket, subject to server permissions.
60
- Uploads default to `data_type="RAW"`; the server chooses the bucket and prefix.
98
+ The server chooses the upload bucket and prefix based on the requested data type.
61
99
  - Credentials refresh automatically. Expired tokens trigger one refresh and retry.
62
100
  - Uploads reject overwrites by default; set `forbid_overwrite=False` to replace.
63
101
  Downloads replace the local file only on success; its parent directory must exist.
@@ -0,0 +1,64 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: management/service/service.proto
4
+ """Generated protocol buffer code."""
5
+ from google.protobuf.internal import builder as _builder
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ # @@protoc_insertion_point(imports)
10
+
11
+ _sym_db = _symbol_database.Default()
12
+
13
+
14
+ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
15
+ from management.metadata import metadata_pb2 as management_dot_metadata_dot_metadata__pb2
16
+
17
+
18
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n management/service/service.proto\x12\x15management.service.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\"management/metadata/metadata.proto\"R\n\x1bGetUploadCredentialsRequest\x12\x33\n\tdata_type\x18\x01 \x01(\x0e\x32 .management.metadata.v1.DataType\"\xd5\x01\n\x1cGetUploadCredentialsResponse\x12\x15\n\raccess_key_id\x18\x01 \x01(\t\x12\x19\n\x11\x61\x63\x63\x65ss_key_secret\x18\x02 \x01(\t\x12\x16\n\x0esecurity_token\x18\x03 \x01(\t\x12.\n\nexpires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0coss_endpoint\x18\x05 \x01(\t\x12\x0e\n\x06\x62ucket\x18\x06 \x01(\t\x12\x15\n\robject_prefix\x18\x07 \x01(\t\"r\n\x16\x43reateUploadURLRequest\x12\x0f\n\x07\x64\x61ta_id\x18\x01 \x01(\t\x12\x33\n\tdata_type\x18\x02 \x01(\x0e\x32 .management.metadata.v1.DataType\x12\x12\n\nobject_key\x18\x03 \x01(\t\"z\n\x17\x43reateUploadURLResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x0e\n\x06\x62ucket\x18\x02 \x01(\t\x12\x12\n\nobject_key\x18\x03 \x01(\t\x12.\n\nexpires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"E\n\x13RegisterDataRequest\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .management.metadata.v1.Metadata\"F\n\x14RegisterDataResponse\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .management.metadata.v1.Metadata\"p\n\x0fListDataRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\x12\x36\n\x06\x66ilter\x18\x03 \x01(\x0b\x32&.management.metadata.v1.MetadataFilter\"[\n\x10ListDataResponse\x12.\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32 .management.metadata.v1.Metadata\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"!\n\x0eGetDataRequest\x12\x0f\n\x07\x64\x61ta_id\x18\x01 \x01(\t\"A\n\x0fGetDataResponse\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .management.metadata.v1.Metadata\"+\n\x18\x43reateDownloadURLRequest\x12\x0f\n\x07\x64\x61ta_id\x18\x01 \x01(\t\"X\n\x19\x43reateDownloadURLResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12.\n\nexpires_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"K\n\x11SelectDataRequest\x12\x36\n\x06\x66ilter\x18\x01 \x01(\x0b\x32&.management.metadata.v1.MetadataFilter\"&\n\x12SelectDataResponse\x12\x10\n\x08\x64\x61ta_ids\x18\x01 \x03(\t\"U\n\x1bStreamDataReferencesRequest\x12\x36\n\x06\x66ilter\x18\x01 \x01(\x0b\x32&.management.metadata.v1.MetadataFilter\"N\n\x1cStreamDataReferencesResponse\x12.\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32 .management.metadata.v1.Metadata\"+\n\x19GetReadCredentialsRequest\x12\x0e\n\x06\x62ucket\x18\x01 \x01(\t\"\xcc\x01\n\x1aGetReadCredentialsResponse\x12\x15\n\raccess_key_id\x18\x01 \x01(\t\x12\x19\n\x11\x61\x63\x63\x65ss_key_secret\x18\x02 \x01(\t\x12\x16\n\x0esecurity_token\x18\x03 \x01(\t\x12.\n\nexpires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0coss_endpoint\x18\x05 \x01(\t\x12\x0e\n\x06region\x18\x06 \x01(\t\x12\x0e\n\x06\x62ucket\x18\x07 \x01(\t2\x84\x08\n\x15\x44\x61taManagementService\x12\x81\x01\n\x14StreamDataReferences\x12\x32.management.service.v1.StreamDataReferencesRequest\x1a\x33.management.service.v1.StreamDataReferencesResponse0\x01\x12y\n\x12GetReadCredentials\x12\x30.management.service.v1.GetReadCredentialsRequest\x1a\x31.management.service.v1.GetReadCredentialsResponse\x12g\n\x0cRegisterData\x12*.management.service.v1.RegisterDataRequest\x1a+.management.service.v1.RegisterDataResponse\x12p\n\x0f\x43reateUploadURL\x12-.management.service.v1.CreateUploadURLRequest\x1a..management.service.v1.CreateUploadURLResponse\x12\x7f\n\x14GetUploadCredentials\x12\x32.management.service.v1.GetUploadCredentialsRequest\x1a\x33.management.service.v1.GetUploadCredentialsResponse\x12[\n\x08ListData\x12&.management.service.v1.ListDataRequest\x1a\'.management.service.v1.ListDataResponse\x12X\n\x07GetData\x12%.management.service.v1.GetDataRequest\x1a&.management.service.v1.GetDataResponse\x12v\n\x11\x43reateDownloadURL\x12/.management.service.v1.CreateDownloadURLRequest\x1a\x30.management.service.v1.CreateDownloadURLResponse\x12\x61\n\nSelectData\x12(.management.service.v1.SelectDataRequest\x1a).management.service.v1.SelectDataResponseB(Z&proto_gen/management/service;servicepbb\x06proto3')
19
+
20
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
21
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'management.service.service_pb2', globals())
22
+ if _descriptor._USE_C_DESCRIPTORS == False:
23
+
24
+ DESCRIPTOR._options = None
25
+ DESCRIPTOR._serialized_options = b'Z&proto_gen/management/service;servicepb'
26
+ _GETUPLOADCREDENTIALSREQUEST._serialized_start=128
27
+ _GETUPLOADCREDENTIALSREQUEST._serialized_end=210
28
+ _GETUPLOADCREDENTIALSRESPONSE._serialized_start=213
29
+ _GETUPLOADCREDENTIALSRESPONSE._serialized_end=426
30
+ _CREATEUPLOADURLREQUEST._serialized_start=428
31
+ _CREATEUPLOADURLREQUEST._serialized_end=542
32
+ _CREATEUPLOADURLRESPONSE._serialized_start=544
33
+ _CREATEUPLOADURLRESPONSE._serialized_end=666
34
+ _REGISTERDATAREQUEST._serialized_start=668
35
+ _REGISTERDATAREQUEST._serialized_end=737
36
+ _REGISTERDATARESPONSE._serialized_start=739
37
+ _REGISTERDATARESPONSE._serialized_end=809
38
+ _LISTDATAREQUEST._serialized_start=811
39
+ _LISTDATAREQUEST._serialized_end=923
40
+ _LISTDATARESPONSE._serialized_start=925
41
+ _LISTDATARESPONSE._serialized_end=1016
42
+ _GETDATAREQUEST._serialized_start=1018
43
+ _GETDATAREQUEST._serialized_end=1051
44
+ _GETDATARESPONSE._serialized_start=1053
45
+ _GETDATARESPONSE._serialized_end=1118
46
+ _CREATEDOWNLOADURLREQUEST._serialized_start=1120
47
+ _CREATEDOWNLOADURLREQUEST._serialized_end=1163
48
+ _CREATEDOWNLOADURLRESPONSE._serialized_start=1165
49
+ _CREATEDOWNLOADURLRESPONSE._serialized_end=1253
50
+ _SELECTDATAREQUEST._serialized_start=1255
51
+ _SELECTDATAREQUEST._serialized_end=1330
52
+ _SELECTDATARESPONSE._serialized_start=1332
53
+ _SELECTDATARESPONSE._serialized_end=1370
54
+ _STREAMDATAREFERENCESREQUEST._serialized_start=1372
55
+ _STREAMDATAREFERENCESREQUEST._serialized_end=1457
56
+ _STREAMDATAREFERENCESRESPONSE._serialized_start=1459
57
+ _STREAMDATAREFERENCESRESPONSE._serialized_end=1537
58
+ _GETREADCREDENTIALSREQUEST._serialized_start=1539
59
+ _GETREADCREDENTIALSREQUEST._serialized_end=1582
60
+ _GETREADCREDENTIALSRESPONSE._serialized_start=1585
61
+ _GETREADCREDENTIALSRESPONSE._serialized_end=1789
62
+ _DATAMANAGEMENTSERVICE._serialized_start=1792
63
+ _DATAMANAGEMENTSERVICE._serialized_end=2820
64
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: custom_msgs/pose_rpy.proto
4
+ """Generated protocol buffer code."""
5
+ from google.protobuf.internal import builder as _builder
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ # @@protoc_insertion_point(imports)
10
+
11
+ _sym_db = _symbol_database.Default()
12
+
13
+
14
+ from geometry_msgs import point_pb2 as geometry__msgs_dot_point__pb2
15
+
16
+
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x63ustom_msgs/pose_rpy.proto\x12\x0b\x63ustom_msgs\x1a\x19geometry_msgs/point.proto\"[\n\x07PoseRPY\x12&\n\x08position\x18\x01 \x01(\x0b\x32\x14.geometry_msgs.Point\x12\x0c\n\x04roll\x18\x02 \x01(\x01\x12\r\n\x05pitch\x18\x03 \x01(\x01\x12\x0b\n\x03yaw\x18\x04 \x01(\x01\x62\x06proto3')
18
+
19
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
20
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'custom_msgs.pose_rpy_pb2', globals())
21
+ if _descriptor._USE_C_DESCRIPTORS == False:
22
+
23
+ DESCRIPTOR._options = None
24
+ _POSERPY._serialized_start=70
25
+ _POSERPY._serialized_end=161
26
+ # @@protoc_insertion_point(module_scope)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "current-data-py"
7
- version = "0.1.5"
7
+ version = "0.1.7"
8
8
  description = "Current Data protobuf messages and schema metadata"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -13,8 +13,9 @@ from .source import OSSReadSource
13
13
 
14
14
 
15
15
  class _Credentials(oss.credentials.CredentialsProvider):
16
- def __init__(self, client):
16
+ def __init__(self, client, bucket=None):
17
17
  self.client = client
18
+ self.bucket = bucket
18
19
  self.lock = threading.Lock()
19
20
  self.cached = None
20
21
  self.scope = None
@@ -22,7 +23,10 @@ class _Credentials(oss.credentials.CredentialsProvider):
22
23
  def get(self):
23
24
  with self.lock:
24
25
  if self.cached is None or self.cached.expires_at.seconds <= time.time() + 300:
25
- value = self.client.get_read_credentials()
26
+ value = (self.client.get_read_credentials() if self.bucket is None
27
+ else self.client.get_read_credentials(bucket=self.bucket))
28
+ if self.bucket is not None and value.bucket != self.bucket:
29
+ raise ValueError("Management returned a different read bucket")
26
30
  scope = (value.oss_endpoint, value.region, value.bucket)
27
31
  if (not all(scope) or not all((value.access_key_id, value.access_key_secret, value.security_token))
28
32
  or value.expires_at.seconds <= time.time() + 300):
@@ -48,14 +52,32 @@ class DataClient(ManagementClient):
48
52
  def __init__(self, channel=None, *, address=None):
49
53
  super().__init__(channel, address=address)
50
54
  self._credentials = _Credentials(self)
55
+ # Default scope may restrict a prefix even when it names the same bucket.
56
+ self._credentials_by_bucket = {None: self._credentials}
57
+ self._credentials_lock = threading.Lock()
51
58
 
52
59
  def iter_data(self, filter=None, *, timeout=None):
53
60
  """Yield partial Metadata (ID, storages and file size). Close to cancel early."""
54
61
  return self.stream_data_references(filter, timeout=timeout)
55
62
 
56
63
  @contextmanager
57
- def open_td(self, reference):
58
- """Read the first storage in the credential bucket; keep open until reads finish."""
59
- with OSSReadSource(reference, self._credentials) as source:
64
+ def open_td(self, reference, *, bucket=None):
65
+ """Read TD from the first storage matching the credential bucket.
66
+
67
+ Supply bucket to request whole-bucket reads, subject to the server's RAM
68
+ role. Omit it (or pass None) to use the management service's configured
69
+ default bucket and prefix. Keep the context open until reads finish.
70
+ """
71
+ if bucket is not None and (
72
+ not isinstance(bucket, str) or not bucket or bucket != bucket.strip()
73
+ or any(c in bucket for c in "*?/:\\")
74
+ ):
75
+ raise ValueError("bucket must be a nonempty bucket name")
76
+ with self._credentials_lock:
77
+ credentials = self._credentials_by_bucket.get(bucket)
78
+ if credentials is None:
79
+ credentials = _Credentials(self, bucket)
80
+ self._credentials_by_bucket[bucket] = credentials
81
+ with OSSReadSource(reference, credentials) as source:
60
82
  yield Reader(source)
61
83
 
@@ -9,6 +9,7 @@ from .._proto.foxglove.compressed_video_pb2 import CompressedVideo
9
9
  from .._proto.geometry_msgs.point_pb2 import Point
10
10
  from .._proto.geometry_msgs.point32_pb2 import Point32
11
11
  from .._proto.geometry_msgs.pose_pb2 import Pose
12
+ from .._proto.custom_msgs.pose_rpy_pb2 import PoseRPY
12
13
  from .._proto.geometry_msgs.pose_stamped_pb2 import PoseStamped
13
14
  from .._proto.geometry_msgs.pose_with_covariance_pb2 import PoseWithCovariance
14
15
  from .._proto.geometry_msgs.pose_with_covariance_stamped_pb2 import PoseWithCovarianceStamped
@@ -62,6 +63,7 @@ SCHEMA_TO_CLASS = {
62
63
  "geometry_msgs.Point": Point,
63
64
  "geometry_msgs.Point32": Point32,
64
65
  "geometry_msgs.Pose": Pose,
66
+ "custom_msgs.PoseRPY": PoseRPY,
65
67
  "geometry_msgs.PoseStamped": PoseStamped,
66
68
  "geometry_msgs.PoseWithCovariance": PoseWithCovariance,
67
69
  "geometry_msgs.PoseWithCovarianceStamped": PoseWithCovarianceStamped,
@@ -114,13 +114,14 @@ class RemoteTest(unittest.TestCase):
114
114
  writer.write_message("/pose", msg.SerializeToString(), timestamp)
115
115
  writer.close_topic()
116
116
  data = output.getvalue()
117
- state = {"mode": "ok", "gets": 0, "credentials": 0, "cancelled": threading.Event()}
117
+ state = {"mode": "ok", "gets": 0, "credentials": 0, "credential_buckets": [], "oss_requests": [], "cancelled": threading.Event()}
118
118
 
119
119
  class Handler(BaseHTTPRequestHandler):
120
120
  def log_message(self, *args):
121
121
  pass
122
122
 
123
123
  def do_HEAD(self):
124
+ state["oss_requests"].append((self.path, self.headers.get("x-oss-security-token")))
124
125
  self.send_response(200)
125
126
  self.send_header("Content-Length", str(len(data)))
126
127
  self.send_header("ETag", '"etag"')
@@ -152,9 +153,10 @@ class RemoteTest(unittest.TestCase):
152
153
  class Management(rpc.DataManagementServiceServicer):
153
154
  def GetReadCredentials(self, request, context):
154
155
  state["credentials"] += 1
156
+ state["credential_buckets"].append(request.bucket)
155
157
  value = pb.GetReadCredentialsResponse(access_key_id="id", access_key_secret="secret",
156
158
  security_token=f"token-{state['credentials']}", oss_endpoint=f"http://127.0.0.1:{http.server_port}",
157
- region="cn-hangzhou", bucket="bucket")
159
+ region="cn-hangzhou", bucket=request.bucket or "bucket")
158
160
  value.expires_at.seconds = int(time.time()) + 3600
159
161
  return value
160
162
 
@@ -225,6 +227,62 @@ class RemoteTest(unittest.TestCase):
225
227
  self.assertEqual([line.split()[:3] for line in result.stdout.splitlines()],
226
228
  [["id", str(t), "/pose"] for t in (3, 4, 5)])
227
229
 
230
+ # Default scope and explicit reads of the same bucket cannot share
231
+ # credentials: only the latter requests whole-bucket permission.
232
+ multi = Metadata(storages=[Storage(bucket="bucket", object_key="outside/file.td"),
233
+ Storage(bucket="other", object_key="nested/file.td")])
234
+ def read(bucket):
235
+ with client.open_td(multi, bucket=bucket) as reader:
236
+ self.assertEqual(len(list(reader.read_messages())), 10)
237
+ return state["oss_requests"][-1]
238
+
239
+ baseline = len(state["credential_buckets"])
240
+ first = read("bucket")
241
+ second = read("other")
242
+ self.assertEqual(state["credential_buckets"][baseline:], ["bucket", "other"])
243
+ self.assertEqual(first[0], "/bucket/outside/file.td")
244
+ self.assertEqual(second[0], "/other/nested/file.td")
245
+ self.assertNotEqual(first[1], second[1])
246
+ self.assertNotEqual(first[1], client._credentials.cached.security_token)
247
+ self.assertEqual(read("bucket"), first)
248
+ self.assertEqual(read("other"), second)
249
+ self.assertEqual(len(state["credential_buckets"]), baseline + 2)
250
+ with client.open_td(reference):
251
+ pass
252
+ self.assertEqual(len(state["credential_buckets"]), baseline + 2)
253
+
254
+ # Expiry recovery stays on the explicitly requested bucket.
255
+ state["mode"], state["old_token"] = "expired", first[1]
256
+ read("bucket")
257
+ self.assertEqual(state["credential_buckets"][baseline:], ["bucket", "other", "bucket"])
258
+ state["mode"] = "ok"
259
+ self.assertEqual(read("other"), second)
260
+ before = len(state["credential_buckets"])
261
+ with patch("current_data_py.remote.client.time.time", return_value=time.time()+3400):
262
+ with ThreadPoolExecutor(max_workers=4) as pool:
263
+ list(pool.map(read, ["other"] * 4))
264
+ self.assertEqual(state["credential_buckets"][before:], ["other"])
265
+
266
+ def test_invalid_bucket_fails_before_credentials(self):
267
+ with DataClient(MagicMock()) as client, patch.object(client, "get_read_credentials") as fetch:
268
+ for bucket in ("", " ", " bucket", "bucket ", "bucket/path", "oss://bucket", "*", "a?b", 123):
269
+ with self.subTest(bucket=bucket), self.assertRaises(ValueError):
270
+ with client.open_td(Metadata(), bucket=bucket):
271
+ self.fail("invalid bucket accepted")
272
+ fetch.assert_not_called()
273
+
274
+ def test_explicit_bucket_mismatch_does_not_read_objects(self):
275
+ with DataClient(MagicMock()) as client, patch.object(client, "get_read_credentials") as fetch, \
276
+ patch.object(OSSReadSource, "_request") as request:
277
+ fetch.return_value = pb.GetReadCredentialsResponse(bucket="wrong-bucket")
278
+ reference = Metadata(storages=[Storage(bucket="wrong-bucket", object_key="file.td")])
279
+ with self.assertRaisesRegex(ValueError, "different read bucket"):
280
+ with client.open_td(reference, bucket="requested-bucket"):
281
+ self.fail("mismatched credentials accepted")
282
+ fetch.assert_called_once_with(bucket="requested-bucket")
283
+ request.assert_not_called()
284
+
285
+
228
286
 
229
287
 
230
288
  if __name__ == "__main__":
@@ -1,87 +0,0 @@
1
- # current-data-py
2
-
3
- ## Management
4
-
5
- `ManagementClient()` connects to `data.current-robotics.work:443` over TLS.
6
- Pass `address="host:443"` to override it, or supply your own gRPC channel.
7
-
8
- ```python
9
- from current_data_py.management import ManagementClient, Metadata, MetadataFilter
10
-
11
- with ManagementClient() as client:
12
- upload = client.get_upload_credentials(data_type="CURRENT_V2")
13
- read = client.get_read_credentials(bucket="data-infra-raw-data")
14
-
15
- # Register an already uploaded file; replace the example file details.
16
- record = client.register_data(Metadata(
17
- name="recording",
18
- data_type="DATA_TYPE_CURRENT_V2",
19
- storages=[{"bucket": "data-infra-202607", "object_key": "raw_converted/recording.td"}],
20
- file_info={"size_bytes": 1234, "checksum": "sha256:<file-hash>"},
21
- ))
22
- for item in client.stream_data_references(MetadataFilter(name_prefix="recording")):
23
- print(item.id, item.storages)
24
- ```
25
-
26
- `Metadata`, `MetadataFilter`, and responses are Protobuf objects exported by the
27
- SDK. Registration only saves metadata; it does not upload files. Credential
28
- calls return fresh credentials without caching. Calls accept `timeout=` and
29
- `metadata=` for gRPC headers.
30
-
31
- ## OSS
32
-
33
- ```python
34
- from current_data_py.management import ManagementClient
35
- from current_data_py.oss import OSSClient
36
-
37
- with ManagementClient() as management:
38
- with OSSClient(management, permission="read", bucket="data-infra-raw-data") as source:
39
- source.download_file("collections/recording/input.h5", "/tmp/input.h5")
40
-
41
- # Convert the input into /tmp/output.td before uploading.
42
- with OSSClient(management, permission="upload", data_type="CURRENT_V2") as destination:
43
- key = destination.object_prefix + "recording/output.td"
44
- destination.upload_file(key, "/tmp/output.td")
45
- print(destination.bucket, key) # Use these values when registering metadata.
46
- ```
47
-
48
- - Read access covers the requested bucket, subject to server permissions.
49
- Uploads default to `data_type="RAW"`; the server chooses the bucket and prefix.
50
- - Credentials refresh automatically. Expired tokens trigger one refresh and retry.
51
- - Uploads reject overwrites by default; set `forbid_overwrite=False` to replace.
52
- Downloads replace the local file only on success; its parent directory must exist.
53
- - Uploads use a single PutObject request ([maximum 5 GiB](https://www.alibabacloud.com/help/en/oss/developer-reference/upload-files-using-oss-sdk-for-python-v2/)); multipart upload is not supported.
54
- - Supply `region=` for custom upload endpoints. Deploy the current management
55
- credential API before using the new bucket and data-type arguments.
56
-
57
- ## 打包
58
-
59
- ```bash
60
- cd current_data_py # 从仓库根目录执行
61
- # 发布新版本前,更新 pyproject.toml 中的 version
62
- python -m pip install build twine
63
- rm -rf build dist
64
- python -m build
65
- ```
66
-
67
- ## 测试
68
-
69
- ```bash
70
- python -m twine check dist/*
71
- python tests/check_distribution.py
72
- python -m pip install --force-reinstall dist/*.whl
73
- python -I tests/check_package.py
74
- python -I tests/check_management.py
75
- python -I tests/check_remote.py
76
- python -I tests/check_oss.py
77
- ```
78
-
79
- ## 推送
80
-
81
- ```bash
82
- # TestPyPI
83
- python -m twine upload --repository testpypi dist/*
84
-
85
- # PyPI
86
- python -m twine upload dist/*
87
- ```
@@ -1,64 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Generated by the protocol buffer compiler. DO NOT EDIT!
3
- # source: management/service/service.proto
4
- """Generated protocol buffer code."""
5
- from google.protobuf.internal import builder as _builder
6
- from google.protobuf import descriptor as _descriptor
7
- from google.protobuf import descriptor_pool as _descriptor_pool
8
- from google.protobuf import symbol_database as _symbol_database
9
- # @@protoc_insertion_point(imports)
10
-
11
- _sym_db = _symbol_database.Default()
12
-
13
-
14
- from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
15
- from management.metadata import metadata_pb2 as management_dot_metadata_dot_metadata__pb2
16
-
17
-
18
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n management/service/service.proto\x12\x15management.service.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\"management/metadata/metadata.proto\"R\n\x1bGetUploadCredentialsRequest\x12\x33\n\tdata_type\x18\x01 \x01(\x0e\x32 .management.metadata.v1.DataType\"\xd5\x01\n\x1cGetUploadCredentialsResponse\x12\x15\n\raccess_key_id\x18\x01 \x01(\t\x12\x19\n\x11\x61\x63\x63\x65ss_key_secret\x18\x02 \x01(\t\x12\x16\n\x0esecurity_token\x18\x03 \x01(\t\x12.\n\nexpires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0coss_endpoint\x18\x05 \x01(\t\x12\x0e\n\x06\x62ucket\x18\x06 \x01(\t\x12\x15\n\robject_prefix\x18\x07 \x01(\t\")\n\x16\x43reateUploadURLRequest\x12\x0f\n\x07\x64\x61ta_id\x18\x01 \x01(\t\"z\n\x17\x43reateUploadURLResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x0e\n\x06\x62ucket\x18\x02 \x01(\t\x12\x12\n\nobject_key\x18\x03 \x01(\t\x12.\n\nexpires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"E\n\x13RegisterDataRequest\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .management.metadata.v1.Metadata\"F\n\x14RegisterDataResponse\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .management.metadata.v1.Metadata\"p\n\x0fListDataRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\x12\x36\n\x06\x66ilter\x18\x03 \x01(\x0b\x32&.management.metadata.v1.MetadataFilter\"[\n\x10ListDataResponse\x12.\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32 .management.metadata.v1.Metadata\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"!\n\x0eGetDataRequest\x12\x0f\n\x07\x64\x61ta_id\x18\x01 \x01(\t\"A\n\x0fGetDataResponse\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .management.metadata.v1.Metadata\"+\n\x18\x43reateDownloadURLRequest\x12\x0f\n\x07\x64\x61ta_id\x18\x01 \x01(\t\"X\n\x19\x43reateDownloadURLResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12.\n\nexpires_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"K\n\x11SelectDataRequest\x12\x36\n\x06\x66ilter\x18\x01 \x01(\x0b\x32&.management.metadata.v1.MetadataFilter\"&\n\x12SelectDataResponse\x12\x10\n\x08\x64\x61ta_ids\x18\x01 \x03(\t\"U\n\x1bStreamDataReferencesRequest\x12\x36\n\x06\x66ilter\x18\x01 \x01(\x0b\x32&.management.metadata.v1.MetadataFilter\"N\n\x1cStreamDataReferencesResponse\x12.\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32 .management.metadata.v1.Metadata\"+\n\x19GetReadCredentialsRequest\x12\x0e\n\x06\x62ucket\x18\x01 \x01(\t\"\xcc\x01\n\x1aGetReadCredentialsResponse\x12\x15\n\raccess_key_id\x18\x01 \x01(\t\x12\x19\n\x11\x61\x63\x63\x65ss_key_secret\x18\x02 \x01(\t\x12\x16\n\x0esecurity_token\x18\x03 \x01(\t\x12.\n\nexpires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0coss_endpoint\x18\x05 \x01(\t\x12\x0e\n\x06region\x18\x06 \x01(\t\x12\x0e\n\x06\x62ucket\x18\x07 \x01(\t2\x84\x08\n\x15\x44\x61taManagementService\x12\x81\x01\n\x14StreamDataReferences\x12\x32.management.service.v1.StreamDataReferencesRequest\x1a\x33.management.service.v1.StreamDataReferencesResponse0\x01\x12y\n\x12GetReadCredentials\x12\x30.management.service.v1.GetReadCredentialsRequest\x1a\x31.management.service.v1.GetReadCredentialsResponse\x12g\n\x0cRegisterData\x12*.management.service.v1.RegisterDataRequest\x1a+.management.service.v1.RegisterDataResponse\x12p\n\x0f\x43reateUploadURL\x12-.management.service.v1.CreateUploadURLRequest\x1a..management.service.v1.CreateUploadURLResponse\x12\x7f\n\x14GetUploadCredentials\x12\x32.management.service.v1.GetUploadCredentialsRequest\x1a\x33.management.service.v1.GetUploadCredentialsResponse\x12[\n\x08ListData\x12&.management.service.v1.ListDataRequest\x1a\'.management.service.v1.ListDataResponse\x12X\n\x07GetData\x12%.management.service.v1.GetDataRequest\x1a&.management.service.v1.GetDataResponse\x12v\n\x11\x43reateDownloadURL\x12/.management.service.v1.CreateDownloadURLRequest\x1a\x30.management.service.v1.CreateDownloadURLResponse\x12\x61\n\nSelectData\x12(.management.service.v1.SelectDataRequest\x1a).management.service.v1.SelectDataResponseB(Z&proto_gen/management/service;servicepbb\x06proto3')
19
-
20
- _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
21
- _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'management.service.service_pb2', globals())
22
- if _descriptor._USE_C_DESCRIPTORS == False:
23
-
24
- DESCRIPTOR._options = None
25
- DESCRIPTOR._serialized_options = b'Z&proto_gen/management/service;servicepb'
26
- _GETUPLOADCREDENTIALSREQUEST._serialized_start=128
27
- _GETUPLOADCREDENTIALSREQUEST._serialized_end=210
28
- _GETUPLOADCREDENTIALSRESPONSE._serialized_start=213
29
- _GETUPLOADCREDENTIALSRESPONSE._serialized_end=426
30
- _CREATEUPLOADURLREQUEST._serialized_start=428
31
- _CREATEUPLOADURLREQUEST._serialized_end=469
32
- _CREATEUPLOADURLRESPONSE._serialized_start=471
33
- _CREATEUPLOADURLRESPONSE._serialized_end=593
34
- _REGISTERDATAREQUEST._serialized_start=595
35
- _REGISTERDATAREQUEST._serialized_end=664
36
- _REGISTERDATARESPONSE._serialized_start=666
37
- _REGISTERDATARESPONSE._serialized_end=736
38
- _LISTDATAREQUEST._serialized_start=738
39
- _LISTDATAREQUEST._serialized_end=850
40
- _LISTDATARESPONSE._serialized_start=852
41
- _LISTDATARESPONSE._serialized_end=943
42
- _GETDATAREQUEST._serialized_start=945
43
- _GETDATAREQUEST._serialized_end=978
44
- _GETDATARESPONSE._serialized_start=980
45
- _GETDATARESPONSE._serialized_end=1045
46
- _CREATEDOWNLOADURLREQUEST._serialized_start=1047
47
- _CREATEDOWNLOADURLREQUEST._serialized_end=1090
48
- _CREATEDOWNLOADURLRESPONSE._serialized_start=1092
49
- _CREATEDOWNLOADURLRESPONSE._serialized_end=1180
50
- _SELECTDATAREQUEST._serialized_start=1182
51
- _SELECTDATAREQUEST._serialized_end=1257
52
- _SELECTDATARESPONSE._serialized_start=1259
53
- _SELECTDATARESPONSE._serialized_end=1297
54
- _STREAMDATAREFERENCESREQUEST._serialized_start=1299
55
- _STREAMDATAREFERENCESREQUEST._serialized_end=1384
56
- _STREAMDATAREFERENCESRESPONSE._serialized_start=1386
57
- _STREAMDATAREFERENCESRESPONSE._serialized_end=1464
58
- _GETREADCREDENTIALSREQUEST._serialized_start=1466
59
- _GETREADCREDENTIALSREQUEST._serialized_end=1509
60
- _GETREADCREDENTIALSRESPONSE._serialized_start=1512
61
- _GETREADCREDENTIALSRESPONSE._serialized_end=1716
62
- _DATAMANAGEMENTSERVICE._serialized_start=1719
63
- _DATAMANAGEMENTSERVICE._serialized_end=2747
64
- # @@protoc_insertion_point(module_scope)
File without changes