Skip to content

Commit 3c8a21d

Browse files
authored
feat(experimental): Add _AsyncReadObjectStream and it's stubs (#1547)
* add AsyncAbstractObjectStream this will be the parent class for AsyncReadObjectStream and AsyncWriteObjectStream * keep _AsyncAbstractObjectStream private * Add _AsyncReadObjectStream and it's stubs * fix doc strings, add licence and type hints * pass abstract methods * add handle param * include handle in tests * remove unit tests for abstract class * edit doc string for _AsyncReadObjectStream * bucket_name and object_name cannot be NONE * bucket_name and object_name cannot be None * minor edit - add bidi-stream in doc string * add checks for invalid inputs
1 parent 71e0904 commit 3c8a21d

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""
15+
NOTE:
16+
This is _experimental module for upcoming support for Rapid Storage.
17+
(https://cloud.google.com/blog/products/storage-data-transfer/high-performance-storage-innovations-for-ai-hpc#:~:text=your%20AI%20workloads%3A-,Rapid%20Storage,-%3A%20A%20new)
18+
19+
APIs may not work as intended and are not stable yet. Feature is not
20+
GA(Generally Available) yet, please contact your TAM(Technical Account Manager)
21+
if you want to use these APIs.
22+
23+
"""
24+
25+
from typing import Any, Optional
26+
from google.cloud import _storage_v2
27+
from google.cloud.storage._experimental.asyncio.async_grpc_client import AsyncGrpcClient
28+
from google.cloud.storage._experimental.asyncio.async_abstract_object_stream import (
29+
_AsyncAbstractObjectStream,
30+
)
31+
32+
33+
class _AsyncReadObjectStream(_AsyncAbstractObjectStream):
34+
"""Class representing a gRPC bidi-stream for reading data from a GCS ``Object``.
35+
36+
This class provides a unix socket-like interface to a GCS ``Object``, with
37+
methods like ``open``, ``close``, ``send``, and ``recv``.
38+
39+
:type client: :class:`~google.cloud.storage.asyncio.AsyncGrpcClient.grpc_client`
40+
:param client: async grpc client to use for making API requests.
41+
42+
:type bucket_name: str
43+
:param bucket_name: The name of the GCS ``bucket`` containing the object.
44+
45+
:type object_name: str
46+
:param object_name: The name of the GCS ``object`` to be read.
47+
48+
:type generation_number: int
49+
:param generation_number: (Optional) If present, selects a specific revision of
50+
this object.
51+
52+
:type read_handle: bytes
53+
:param read_handle: (Optional) An existing handle for reading the object.
54+
If provided, opening the bidi-gRPC connection will be faster.
55+
"""
56+
57+
def __init__(
58+
self,
59+
client: AsyncGrpcClient.grpc_client,
60+
bucket_name: str,
61+
object_name: str,
62+
generation_number: Optional[int] = None,
63+
read_handle: Optional[bytes] = None,
64+
) -> None:
65+
if client is None:
66+
raise ValueError("client must be provided")
67+
if bucket_name is None:
68+
raise ValueError("bucket_name must be provided")
69+
if object_name is None:
70+
raise ValueError("object_name must be provided")
71+
72+
super().__init__(
73+
bucket_name=bucket_name,
74+
object_name=object_name,
75+
generation_number=generation_number,
76+
)
77+
self.client: AsyncGrpcClient.grpc_client = client
78+
self.read_handle: Optional[bytes] = read_handle
79+
80+
async def open(self) -> None:
81+
pass
82+
83+
async def close(self) -> None:
84+
pass
85+
86+
async def send(
87+
self, bidi_read_object_request: _storage_v2.BidiReadObjectRequest
88+
) -> None:
89+
pass
90+
91+
async def recv(self) -> Any:
92+
pass
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
from unittest import mock
17+
18+
from google.cloud.storage._experimental.asyncio.async_abstract_object_stream import (
19+
_AsyncAbstractObjectStream,
20+
)
21+
from google.cloud.storage._experimental.asyncio.async_read_object_stream import (
22+
_AsyncReadObjectStream,
23+
)
24+
25+
26+
def test_inheritance():
27+
"""Test that _AsyncReadObjectStream inherits from _AsyncAbstractObjectStream."""
28+
assert issubclass(_AsyncReadObjectStream, _AsyncAbstractObjectStream)
29+
30+
31+
def test_init():
32+
"""Test the constructor of _AsyncReadObjectStream."""
33+
mock_client = mock.Mock(name="client")
34+
bucket_name = "test-bucket"
35+
object_name = "test-object"
36+
generation = 12345
37+
read_handle = "some-handle"
38+
39+
# Test with all parameters
40+
stream = _AsyncReadObjectStream(
41+
mock_client,
42+
bucket_name=bucket_name,
43+
object_name=object_name,
44+
generation_number=generation,
45+
read_handle=read_handle,
46+
)
47+
48+
assert stream.client is mock_client
49+
assert stream.bucket_name == bucket_name
50+
assert stream.object_name == object_name
51+
assert stream.generation_number == generation
52+
assert stream.read_handle == read_handle
53+
54+
# Test with default parameters
55+
stream_defaults = _AsyncReadObjectStream(
56+
mock_client, bucket_name=bucket_name, object_name=object_name
57+
)
58+
assert stream_defaults.client is mock_client
59+
assert stream_defaults.bucket_name is bucket_name
60+
assert stream_defaults.object_name is object_name
61+
assert stream_defaults.generation_number is None
62+
assert stream_defaults.read_handle is None
63+
64+
65+
def test_init_with_invalid_parameters():
66+
"""Test the constructor of _AsyncReadObjectStream with invalid params."""
67+
68+
with pytest.raises(ValueError):
69+
_AsyncReadObjectStream(None, bucket_name=None, object_name=None)
70+
71+
72+
@pytest.mark.asyncio
73+
async def test_async_methods_are_awaitable():
74+
"""Test that the async methods exist and are awaitable."""
75+
mock_client = mock.Mock(name="client")
76+
stream = _AsyncReadObjectStream(mock_client, "bucket", "object")
77+
78+
# These methods are currently empty, but we can test they are awaitable
79+
# and don't raise exceptions.
80+
try:
81+
await stream.open()
82+
await stream.close()
83+
await stream.send(mock.Mock())
84+
await stream.recv()
85+
except Exception as e:
86+
pytest.fail(f"Async methods should be awaitable without errors. Raised: {e}")

0 commit comments

Comments
 (0)