@@ -71,8 +71,9 @@ buffered, they aren't in MicroPython. (Indeed, that's one of the cases
7171for which we may introduce buffering support.)
7272
7373Note 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
7778Functions
7879---------
@@ -86,6 +87,69 @@ Functions
8687Classes
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