Skip to content

Commit 586ad0d

Browse files
pi-anldpgeorge
authored andcommitted
docs/library/io: Add documentation for the io.IOBase class.
Document the three methods that IOBase subclasses implement (readinto, write, ioctl) and the common ioctl operations. Includes a simple write-only stream example and a pollable ring buffer example. IOBase docs are inline in io.rst before StringIO/BytesIO, following CPython's structure. Also fix a statement in io.rst that incorrectly claimed streams cannot be subclassed in pure Python. Signed-off-by: Andrew Leech <andrew.leech@planet-innovation.com>
1 parent 7f7adad commit 586ad0d

1 file changed

Lines changed: 140 additions & 2 deletions

File tree

docs/library/io.rst

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ buffered, they aren't in MicroPython. (Indeed, that's one of the cases
7171
for which we may introduce buffering support.)
7272

7373
Note that for efficiency, MicroPython doesn't provide abstract base
74-
classes corresponding to the hierarchy above, and it's not possible
75-
to implement, or subclass, a stream class in pure Python.
74+
classes corresponding to the hierarchy above. However, the
75+
:class:`IOBase` class can be subclassed to implement custom stream
76+
objects in pure Python.
7677

7778
Functions
7879
---------
@@ -86,6 +87,69 @@ Functions
8687
Classes
8788
-------
8889

90+
.. class:: IOBase()
91+
92+
Base class for implementing custom stream objects in Python. Subclasses
93+
can override ``readinto``, ``write``, and ``ioctl`` to create objects
94+
that work with ``print()``, ``json.dump()``, ``select.poll()``,
95+
``open()`` via a user filesystem, and other stream consumers.
96+
97+
.. admonition:: Difference to CPython
98+
:class: attention
99+
100+
In CPython, ``io.IOBase`` has a much larger API surface. MicroPython's
101+
``IOBase`` is minimal: the C stream infrastructure provides standard
102+
methods like ``read()``, ``readline()``, ``seek()``, ``close()``, and
103+
``flush()`` automatically. Subclasses only need to implement the
104+
methods below.
105+
106+
Subclasses implement some or all of the following methods. The C stream
107+
layer calls these internally when user code calls standard stream
108+
functions.
109+
110+
.. method:: IOBase.readinto(buf)
111+
112+
Read data into *buf* (a ``bytearray`` sized by the caller). Return the
113+
number of bytes read, 0 at EOF, or ``None`` if no data is available on
114+
a non-blocking stream. Return a negative errno value (e.g. ``-errno.EIO``
115+
or ``-1``) to signal an error.
116+
117+
.. method:: IOBase.write(buf)
118+
119+
Write *buf* (a ``bytearray``) to the stream. Return the number of
120+
bytes written, or ``None`` if a non-blocking stream cannot accept data.
121+
Return a negative errno value to signal an error.
122+
123+
.. method:: IOBase.ioctl(op, arg)
124+
125+
Control the stream and query its properties. The operation to perform
126+
is given by *op* which is one of the following integers:
127+
128+
- 1 -- flush write buffers (*arg* is unused)
129+
- 3 -- poll for readiness; *arg* is a bitmask of events to check,
130+
return a bitmask of ready events. Poll flags:
131+
132+
* ``0x0001`` -- data available for reading
133+
* ``0x0004`` -- stream ready for writing
134+
* ``0x0008`` -- error condition
135+
* ``0x0010`` -- hang up (e.g. connection closed)
136+
* ``0x0020`` -- invalid request
137+
138+
- 4 -- close the stream (*arg* is unused)
139+
- 11 -- return the preferred read buffer size, or 0 (*arg* is unused)
140+
141+
As a minimum ``ioctl(4, ...)`` should be handled to support stream
142+
closure. Implement ``ioctl(3, ...)`` if the stream will be used with
143+
``select.poll()`` or ``asyncio``.
144+
145+
Other operations exist for advanced use cases (2 = seek, 5 = timeout,
146+
10 = fileno); see ``py/stream.h`` for the full list.
147+
148+
Must always return an integer. Return 0 for success, or ``-1`` for
149+
unsupported operations. (Returning 0 for an unhandled operation tells
150+
the C layer the operation was processed successfully, which may cause
151+
incorrect behaviour.)
152+
89153
.. class:: StringIO([string])
90154
.. class:: BytesIO([string])
91155

@@ -119,3 +183,77 @@ Classes
119183
:class: attention
120184

121185
These constructors are a MicroPython extension.
186+
187+
IOBase Examples
188+
---------------
189+
190+
A minimal write-only stream that collects output into a buffer::
191+
192+
import io
193+
194+
class MyOutput(io.IOBase):
195+
def __init__(self):
196+
self.data = bytearray()
197+
198+
def write(self, buf):
199+
self.data.extend(buf)
200+
return len(buf)
201+
202+
def ioctl(self, op, arg):
203+
if op == 4: # close
204+
return 0
205+
return -1
206+
207+
s = MyOutput()
208+
print("hello", file=s)
209+
print(s.data) # bytearray(b'hello\n')
210+
211+
A readable stream that can be used with ``select.poll()``::
212+
213+
import io, select
214+
from micropython import const
215+
216+
_MP_STREAM_POLL = const(3)
217+
_MP_STREAM_POLL_RD = const(0x0001)
218+
_MP_STREAM_CLOSE = const(4)
219+
220+
class RingBuffer(io.IOBase):
221+
def __init__(self, size):
222+
self._buf = bytearray(size)
223+
self._size = size
224+
self._wpos = 0
225+
self._rpos = 0
226+
227+
def _available(self):
228+
return (self._wpos - self._rpos) % self._size
229+
230+
def put(self, data):
231+
for b in data:
232+
self._buf[self._wpos % self._size] = b
233+
self._wpos = (self._wpos + 1) % self._size
234+
235+
def readinto(self, buf):
236+
n = min(len(buf), self._available())
237+
for i in range(n):
238+
buf[i] = self._buf[self._rpos % self._size]
239+
self._rpos = (self._rpos + 1) % self._size
240+
return n
241+
242+
def ioctl(self, op, arg):
243+
if op == _MP_STREAM_POLL:
244+
if arg & _MP_STREAM_POLL_RD and self._available() > 0:
245+
return _MP_STREAM_POLL_RD
246+
return 0
247+
if op == _MP_STREAM_CLOSE:
248+
return 0
249+
return -1
250+
251+
rb = RingBuffer(64)
252+
rb.put(b"test data")
253+
254+
poller = select.poll()
255+
poller.register(rb, select.POLLIN)
256+
for obj, flags in poller.poll(0):
257+
buf = bytearray(16)
258+
n = obj.readinto(buf)
259+
print(buf[:n]) # bytearray(b'test data')

0 commit comments

Comments
 (0)