Return number of bytes written in case of BlockingIOError.

In case of a partial write BufferedIOBase doesn't return the length of data written, but gives a BlockingIOError that contains the number of characters written.
See https://docs.python.org/3/library/io.html#io.BufferedIOBase.write

Also, RawIOBase returns None (and not 0) when no data can be written.

PiperOrigin-RevId: 268167212
diff --git a/python/util/file_object_adapter.py b/python/util/file_object_adapter.py
index 727c4b0..42f1cd4 100644
--- a/python/util/file_object_adapter.py
+++ b/python/util/file_object_adapter.py
@@ -20,6 +20,8 @@
 from __future__ import google_type_annotations
 from __future__ import print_function
 
+import io
+
 from tink.python.cc.clif import simple_output_stream
 
 
@@ -35,7 +37,11 @@
 
   def write(self, data: bytes) -> int:
     """Writes to underlying file object and returns number of bytes written."""
-    return self._file_object.write(data)
+    try:
+      written = self._file_object.write(data)
+      return 0 if written is None else written
+    except io.BlockingIOError as e:
+      return e.characters_written
 
   def close(self) -> None:
     self._file_object.close()
diff --git a/python/util/file_object_adapter_test.py b/python/util/file_object_adapter_test.py
index d83d9f3..e49a053 100644
--- a/python/util/file_object_adapter_test.py
+++ b/python/util/file_object_adapter_test.py
@@ -18,6 +18,7 @@
 import io
 
 from absl.testing import absltest
+import mock
 from tink.python.util import file_object_adapter
 
 
@@ -57,41 +58,35 @@
     adapter.close()
 
   def test_non_writable(self):
+    file_object = mock.Mock()
+    file_object.writable = mock.Mock(return_value=False)
 
-    class TestNonWritableObject(io.RawIOBase):
-      """Test non-writable file-like object."""
-
-      def writable(self):
-        return False
-
-    non_writable_object = TestNonWritableObject()
     self.assertRaises(TypeError, file_object_adapter.FileObjectAdapter,
-                      non_writable_object)
+                      file_object)
 
-  def test_partial_write(self):
+  def test_write_returns_none(self):
+    file_object = mock.Mock()
+    file_object.writable = mock.Mock(return_value=True)
+    file_object.write = mock.Mock(return_value=None)
 
-    class TestFileObject(io.RawIOBase):
-      """Test file-like object that always writes only first 5 bytes of data."""
+    adapter = file_object_adapter.FileObjectAdapter(file_object)
+    self.assertEqual(0, adapter.write(b'something'))
 
-      def __init__(self):
-        super(TestFileObject, self).__init__()
-        self.value = b''
+  def test_write_raises_blocking_error(self):
+    file_object = mock.Mock()
+    file_object.writable = mock.Mock(return_value=True)
+    file_object.write = mock.Mock(side_effect=io.BlockingIOError(None, None, 5))
 
-      def writable(self):
-        return True
-
-      def write(self, data):
-        self.value += data[:5]
-        return 5
-
-      def tell(self):
-        return len(self.value)
-
-    file_object = TestFileObject()
     adapter = file_object_adapter.FileObjectAdapter(file_object)
     self.assertEqual(5, adapter.write(b'something'))
-    self.assertEqual(5, adapter.position())
-    self.assertEqual(b'somet', file_object.value)
+
+  def test_partial_write(self):
+    file_object = mock.Mock()
+    file_object.writable = mock.Mock(return_value=True)
+    file_object.write = mock.Mock(wraps=lambda data: len(data) - 1)
+
+    adapter = file_object_adapter.FileObjectAdapter(file_object)
+    self.assertEqual(8, adapter.write(b'something'))
 
 
 if __name__ == '__main__':