xref: /aosp_15_r20/external/tink/python/tink/streaming_aead/_file_object_adapter.py (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1# Copyright 2021 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"""FileObjectAdapter class.
15
16Used in conjunction with PythonOutputStream/PythonInputStream to allow a C++
17OutputStream/InputStream to interact with a Python file-like object.
18"""
19
20import io
21from typing import BinaryIO
22
23from tink.cc.pybind import tink_bindings
24
25
26class FileObjectAdapter(tink_bindings.PythonFileObjectAdapter):
27  """Adapts a Python file object for use in C++."""
28
29  def __init__(self, file_object: BinaryIO):
30    # Required to fix CLIF "Value invalidated due to capture by std::unique_ptr"
31    super().__init__()
32    self._file_object = file_object
33
34  def write(self, data: bytes) -> int:
35    """Writes to underlying file object and returns number of bytes written."""
36    try:
37      written = self._file_object.write(data)
38      return 0 if written is None else written
39    except io.BlockingIOError as e:
40      return e.characters_written
41
42  def close(self) -> None:
43    self._file_object.close()
44
45  def read(self, size: int) -> bytes:
46    """Reads at most 'size' bytes from the underlying file object.
47
48    Args:
49      size: A non-negative integer, maximum number of bytes to read.
50
51    Returns:
52      Bytes that were read. An empty bytes object is returned if no bytes are
53      available at the moment.
54
55    Raises:
56      EOFError if the file object is already at EOF.
57    """
58    if size < 0:
59      raise ValueError('size must be non-negative')
60
61    try:
62      data = self._file_object.read(size)
63      if data is None:
64        return b''
65      elif not data and size > 0:
66        raise EOFError('EOF')
67      return data
68    except io.BlockingIOError:
69      return b''
70