| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,7 +32,7 @@ class _AsyncAbstractObjectStream(abc.ABC): | |||
| 32 | 32 | :param generation_number: (Optional) If present, selects a specific revision of | |
| 33 | 33 | this object. | |
| 34 | 34 | ||
| 35 | - :type handle: bytes | ||
| 35 | + :type handle: Any | ||
| 36 | 36 | :param handle: (Optional) The handle for the object, could be read_handle or | |
| 37 | 37 | write_handle, based on how the stream is used. | |
| 38 | 38 | """ | |
@@ -42,13 +42,13 @@ def __init__( | |||
| 42 | 42 | bucket_name: str, | |
| 43 | 43 | object_name: str, | |
| 44 | 44 | generation_number: Optional[int] = None, | |
| 45 | - handle: Optional[bytes] = None, | ||
| 45 | + handle: Optional[Any] = None, | ||
| 46 | 46 | ) -> None: | |
| 47 | 47 | super().__init__() | |
| 48 | 48 | self.bucket_name: str = bucket_name | |
| 49 | 49 | self.object_name: str = object_name | |
| 50 | 50 | self.generation_number: Optional[int] = generation_number | |
| 51 | - self.handle: Optional[bytes] = handle | ||
| 51 | + self.handle: Optional[Any] = handle | ||
| 52 | 52 | ||
| 53 | 53 | @abc.abstractmethod | |
| 54 | 54 | async def open(self) -> None: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -49,8 +49,8 @@ def __init__( | |||
| 49 | 49 | client: AsyncGrpcClient.grpc_client, | |
| 50 | 50 | bucket_name: str, | |
| 51 | 51 | object_name: str, | |
| 52 | - generation=None, | ||
| 53 | - write_handle=None, | ||
| 52 | + generation: Optional[int] = None, | ||
| 53 | + write_handle: Optional[_storage_v2.BidiWriteHandle] = None, | ||
| 54 | 54 | writer_options: Optional[dict] = None, | |
| 55 | 55 | ): | |
| 56 | 56 | """ | |
@@ -96,7 +96,7 @@ def __init__( | |||
| 96 | 96 | :type object_name: str | |
| 97 | 97 | :param object_name: The name of the GCS Appendable Object to be written. | |
| 98 | 98 | ||
| 99 | - :type generation: int | ||
| 99 | + :type generation: Optional[int] | ||
| 100 | 100 | :param generation: (Optional) If present, creates writer for that | |
| 101 | 101 | specific revision of that object. Use this to append data to an | |
| 102 | 102 | existing Appendable Object. | |
@@ -106,10 +106,10 @@ def __init__( | |||
| 106 | 106 | overwriting existing objects). | |
| 107 | 107 | ||
| 108 | 108 | Warning: If `None`, a new object is created. If an object with the | |
| 109 | - same name already exists, it will be overwritten the moment | ||
| 109 | + same name already exists, it will be overwritten the moment | ||
| 110 | 110 | `writer.open()` is called. | |
| 111 | 111 | ||
| 112 | - :type write_handle: bytes | ||
| 112 | + :type write_handle: _storage_v2.BidiWriteHandle | ||
| 113 | 113 | :param write_handle: (Optional) An handle for writing the object. | |
| 114 | 114 | If provided, opening the bidi-gRPC connection will be faster. | |
| 115 | 115 | ||
@@ -363,7 +363,6 @@ async def finalize(self) -> _storage_v2.Object: | |||
| 363 | 363 | def is_stream_open(self) -> bool: | |
| 364 | 364 | return self._is_stream_open | |
| 365 | 365 | ||
| 366 | - | ||
| 367 | 366 | # helper methods. | |
| 368 | 367 | async def append_from_string(self, data: str): | |
| 369 | 368 | """ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -129,7 +129,7 @@ async def create_mrd( | |||
| 129 | 129 | bucket_name: str, | |
| 130 | 130 | object_name: str, | |
| 131 | 131 | generation_number: Optional[int] = None, | |
| 132 | - read_handle: Optional[bytes] = None, | ||
| 132 | + read_handle: Optional[_storage_v2.BidiReadHandle] = None, | ||
| 133 | 133 | retry_policy: Optional[AsyncRetry] = None, | |
| 134 | 134 | metadata: Optional[List[Tuple[str, str]]] = None, | |
| 135 | 135 | ) -> AsyncMultiRangeDownloader: | |
@@ -149,7 +149,7 @@ async def create_mrd( | |||
| 149 | 149 | :param generation_number: (Optional) If present, selects a specific | |
| 150 | 150 | revision of this object. | |
| 151 | 151 | ||
| 152 | - :type read_handle: bytes | ||
| 152 | + :type read_handle: _storage_v2.BidiReadHandle | ||
| 153 | 153 | :param read_handle: (Optional) An existing handle for reading the object. | |
| 154 | 154 | If provided, opening the bidi-gRPC connection will be faster. | |
| 155 | 155 | ||
@@ -172,7 +172,7 @@ def __init__( | |||
| 172 | 172 | bucket_name: str, | |
| 173 | 173 | object_name: str, | |
| 174 | 174 | generation_number: Optional[int] = None, | |
| 175 | - read_handle: Optional[bytes] = None, | ||
| 175 | + read_handle: Optional[_storage_v2.BidiReadHandle] = None, | ||
| 176 | 176 | ) -> None: | |
| 177 | 177 | """Constructor for AsyncMultiRangeDownloader, clients are not adviced to | |
| 178 | 178 | use it directly. Instead it's adviced to use the classmethod `create_mrd`. | |
@@ -190,7 +190,7 @@ def __init__( | |||
| 190 | 190 | :param generation_number: (Optional) If present, selects a specific revision of | |
| 191 | 191 | this object. | |
| 192 | 192 | ||
| 193 | - :type read_handle: bytes | ||
| 193 | + :type read_handle: _storage_v2.BidiReadHandle | ||
| 194 | 194 | :param read_handle: (Optional) An existing read handle. | |
| 195 | 195 | """ | |
| 196 | 196 | ||
@@ -200,7 +200,7 @@ def __init__( | |||
| 200 | 200 | self.bucket_name = bucket_name | |
| 201 | 201 | self.object_name = object_name | |
| 202 | 202 | self.generation_number = generation_number | |
| 203 | - self.read_handle = read_handle | ||
| 203 | + self.read_handle: Optional[_storage_v2.BidiReadHandle] = read_handle | ||
| 204 | 204 | self.read_obj_str: Optional[_AsyncReadObjectStream] = None | |
| 205 | 205 | self._is_stream_open: bool = False | |
| 206 | 206 | self._routing_token: Optional[str] = None | |
@@ -493,4 +493,4 @@ async def close(self): | |||
| 493 | 493 | ||
| 494 | 494 | @property | |
| 495 | 495 | def is_stream_open(self) -> bool: | |
| 496 | - return self._is_stream_open | ||
| 496 | + return self._is_stream_open | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,7 +51,7 @@ class _AsyncReadObjectStream(_AsyncAbstractObjectStream): | |||
| 51 | 51 | :param generation_number: (Optional) If present, selects a specific revision of | |
| 52 | 52 | this object. | |
| 53 | 53 | ||
| 54 | - :type read_handle: bytes | ||
| 54 | + :type read_handle: _storage_v2.BidiReadHandle | ||
| 55 | 55 | :param read_handle: (Optional) An existing handle for reading the object. | |
| 56 | 56 | If provided, opening the bidi-gRPC connection will be faster. | |
| 57 | 57 | """ | |
@@ -62,7 +62,7 @@ def __init__( | |||
| 62 | 62 | bucket_name: str, | |
| 63 | 63 | object_name: str, | |
| 64 | 64 | generation_number: Optional[int] = None, | |
| 65 | - read_handle: Optional[bytes] = None, | ||
| 65 | + read_handle: Optional[_storage_v2.BidiReadHandle] = None, | ||
| 66 | 66 | ) -> None: | |
| 67 | 67 | if client is None: | |
| 68 | 68 | raise ValueError("client must be provided") | |
@@ -77,7 +77,7 @@ def __init__( | |||
| 77 | 77 | generation_number=generation_number, | |
| 78 | 78 | ) | |
| 79 | 79 | self.client: AsyncGrpcClient.grpc_client = client | |
| 80 | - self.read_handle: Optional[bytes] = read_handle | ||
| 80 | + self.read_handle: Optional[_storage_v2.BidiReadHandle] = read_handle | ||
| 81 | 81 | ||
| 82 | 82 | self._full_bucket_name = f"projects/_/buckets/{self.bucket_name}" | |
| 83 | 83 | ||
@@ -195,4 +195,4 @@ async def recv(self) -> _storage_v2.BidiReadObjectResponse: | |||
| 195 | 195 | ||
| 196 | 196 | @property | |
| 197 | 197 | def is_stream_open(self) -> bool: | |
| 198 | - return self._is_stream_open | ||
| 198 | + return self._is_stream_open | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,7 +59,7 @@ class _AsyncWriteObjectStream(_AsyncAbstractObjectStream): | |||
| 59 | 59 | same name already exists, it will be overwritten the moment | |
| 60 | 60 | `writer.open()` is called. | |
| 61 | 61 | ||
| 62 | - :type write_handle: bytes | ||
| 62 | + :type write_handle: _storage_v2.BidiWriteHandle | ||
| 63 | 63 | :param write_handle: (Optional) An existing handle for writing the object. | |
| 64 | 64 | If provided, opening the bidi-gRPC connection will be faster. | |
| 65 | 65 | """ | |
@@ -70,7 +70,7 @@ def __init__( | |||
| 70 | 70 | bucket_name: str, | |
| 71 | 71 | object_name: str, | |
| 72 | 72 | generation_number: Optional[int] = None, # None means new object | |
| 73 | - write_handle: Optional[bytes] = None, | ||
| 73 | + write_handle: Optional[_storage_v2.BidiWriteHandle] = None, | ||
| 74 | 74 | ) -> None: | |
| 75 | 75 | if client is None: | |
| 76 | 76 | raise ValueError("client must be provided") | |
@@ -85,7 +85,7 @@ def __init__( | |||
| 85 | 85 | generation_number=generation_number, | |
| 86 | 86 | ) | |
| 87 | 87 | self.client: AsyncGrpcClient.grpc_client = client | |
| 88 | - self.write_handle: Optional[bytes] = write_handle | ||
| 88 | + self.write_handle: Optional[_storage_v2.BidiWriteHandle] = write_handle | ||
| 89 | 89 | ||
| 90 | 90 | self._full_bucket_name = f"projects/_/buckets/{self.bucket_name}" | |
| 91 | 91 | ||
@@ -120,6 +120,9 @@ async def open(self) -> None: | |||
| 120 | 120 | # Created object type would be Appendable Object. | |
| 121 | 121 | # if `generation_number` == 0 new object will be created only if there | |
| 122 | 122 | # isn't any existing object. | |
| 123 | + is_open_via_write_handle = ( | ||
| 124 | + self.write_handle is not None and self.generation_number | ||
| 125 | + ) | ||
| 123 | 126 | if self.generation_number is None or self.generation_number == 0: | |
| 124 | 127 | self.first_bidi_write_req = _storage_v2.BidiWriteObjectRequest( | |
| 125 | 128 | write_object_spec=_storage_v2.WriteObjectSpec( | |
@@ -136,6 +139,7 @@ async def open(self) -> None: | |||
| 136 | 139 | bucket=self._full_bucket_name, | |
| 137 | 140 | object=self.object_name, | |
| 138 | 141 | generation=self.generation_number, | |
| 142 | + write_handle=self.write_handle, | ||
| 139 | 143 | ), | |
| 140 | 144 | ) | |
| 141 | 145 | self.socket_like_rpc = AsyncBidiRpc( | |
@@ -145,25 +149,32 @@ async def open(self) -> None: | |||
| 145 | 149 | await self.socket_like_rpc.open() # this is actually 1 send | |
| 146 | 150 | response = await self.socket_like_rpc.recv() | |
| 147 | 151 | self._is_stream_open = True | |
| 148 | - | ||
| 149 | - if not response.resource: | ||
| 150 | - raise ValueError( | ||
| 151 | - "Failed to obtain object resource after opening the stream" | ||
| 152 | - ) | ||
| 153 | - if not response.resource.generation: | ||
| 154 | - raise ValueError( | ||
| 155 | - "Failed to obtain object generation after opening the stream" | ||
| 156 | - ) | ||
| 152 | + if is_open_via_write_handle: | ||
| 153 | + # Don't use if not response.persisted_size because this will be true | ||
| 154 | + # if persisted_size==0 (0 is considered "Falsy" in Python) | ||
| 155 | + if response.persisted_size is None: | ||
| 156 | + raise ValueError( | ||
| 157 | + "Failed to obtain persisted_size after opening the stream via write_handle" | ||
| 158 | + ) | ||
| 159 | + self.persisted_size = response.persisted_size | ||
| 160 | + else: | ||
| 161 | + if not response.resource: | ||
| 162 | + raise ValueError( | ||
| 163 | + "Failed to obtain object resource after opening the stream" | ||
| 164 | + ) | ||
| 165 | + if not response.resource.generation: | ||
| 166 | + raise ValueError( | ||
| 167 | + "Failed to obtain object generation after opening the stream" | ||
| 168 | + ) | ||
| 169 | + if not response.resource.size: | ||
| 170 | + # Appending to a 0 byte appendable object. | ||
| 171 | + self.persisted_size = 0 | ||
| 172 | + else: | ||
| 173 | + self.persisted_size = response.resource.size | ||
| 157 | 174 | ||
| 158 | 175 | if not response.write_handle: | |
| 159 | 176 | raise ValueError("Failed to obtain write_handle after opening the stream") | |
| 160 | 177 | ||
| 161 | - if not response.resource.size: | ||
| 162 | - # Appending to a 0 byte appendable object. | ||
| 163 | - self.persisted_size = 0 | ||
| 164 | - else: | ||
| 165 | - self.persisted_size = response.resource.size | ||
| 166 | - | ||
| 167 | 178 | self.generation_number = response.resource.generation | |
| 168 | 179 | self.write_handle = response.write_handle | |
| 169 | 180 | ||
@@ -212,4 +223,3 @@ async def recv(self) -> _storage_v2.BidiWriteObjectResponse: | |||
| 212 | 223 | @property | |
| 213 | 224 | def is_stream_open(self) -> bool: | |
| 214 | 225 | return self._is_stream_open | |
| 215 | - | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -333,6 +333,56 @@ async def _run(): | |||
| 333 | 333 | event_loop.run_until_complete(_run()) | |
| 334 | 334 | ||
| 335 | 335 | ||
| 336 | + def test_wrd_open_with_write_handle( | ||
| 337 | + event_loop, grpc_client_direct, storage_client, blobs_to_delete | ||
| 338 | + ): | ||
| 339 | + object_name = f"test_write_handl-{str(uuid.uuid4())[:4]}" | ||
| 340 | + | ||
| 341 | + async def _run(): | ||
| 342 | + # 1. Create an object and get its write_handle | ||
| 343 | + writer = AsyncAppendableObjectWriter( | ||
| 344 | + grpc_client_direct, _ZONAL_BUCKET, object_name | ||
| 345 | + ) | ||
| 346 | + await writer.open() | ||
| 347 | + write_handle = writer.write_handle | ||
| 348 | + await writer.close() | ||
| 349 | + | ||
| 350 | + # 2. Open a new writer using the obtained `write_handle` and generation | ||
| 351 | + new_writer = AsyncAppendableObjectWriter( | ||
| 352 | + grpc_client_direct, | ||
| 353 | + _ZONAL_BUCKET, | ||
| 354 | + object_name, | ||
| 355 | + write_handle=write_handle, | ||
| 356 | + generation=writer.generation, | ||
| 357 | + ) | ||
| 358 | + await new_writer.open() | ||
| 359 | + # Verify that the new writer is open and has the same write_handle | ||
| 360 | + assert new_writer.is_stream_open | ||
| 361 | + assert new_writer.generation == writer.generation | ||
| 362 | + | ||
| 363 | + # 3. Append some data using the new writer | ||
| 364 | + test_data = b"data_from_new_writer" | ||
| 365 | + await new_writer.append(test_data) | ||
| 366 | + await new_writer.close() | ||
| 367 | + | ||
| 368 | + # 4. Verify the data was written correctly by reading it back | ||
| 369 | + mrd = AsyncMultiRangeDownloader(grpc_client_direct, _ZONAL_BUCKET, object_name) | ||
| 370 | + buffer = BytesIO() | ||
| 371 | + await mrd.open() | ||
| 372 | + await mrd.download_ranges([(0, 0, buffer)]) | ||
| 373 | + await mrd.close() | ||
| 374 | + assert buffer.getvalue() == test_data | ||
| 375 | + | ||
| 376 | + # Clean up | ||
| 377 | + blobs_to_delete.append(storage_client.bucket(_ZONAL_BUCKET).blob(object_name)) | ||
| 378 | + del writer | ||
| 379 | + del new_writer | ||
| 380 | + del mrd | ||
| 381 | + gc.collect() | ||
| 382 | + | ||
| 383 | + event_loop.run_until_complete(_run()) | ||
| 384 | + | ||
| 385 | + | ||
| 336 | 386 | def test_read_unfinalized_appendable_object_with_generation( | |
| 337 | 387 | storage_client, blobs_to_delete, event_loop, grpc_client_direct | |
| 338 | 388 | ): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments