1- # SPDX-FileCopyrightText: 2019 Damien P. George
1+ # CIRCUITPY-CHANGE: SPDX
2+ # SPDX-FileCopyrightText: 2019-2020 Damien P. George
23#
34# SPDX-License-Identifier: MIT
4- #
5- # MicroPython uasyncio module
5+
6+ # MicroPython asyncio module
67# MIT license; Copyright (c) 2019 Damien P. George
78#
9+ # # CIRCUITPY-CHANGE: use CircuitPython version
810# This code comes from MicroPython, and has not been run through black or pylint there.
911# Altering these files significantly would make merging difficult, so we will not use
1012# pylint or black.
1113# pylint: skip-file
1214# fmt: off
13- """
14- Core
15- ====
16- """
1715
16+ # CIRCUITPY-CHANGE: use our ticks library
1817from adafruit_ticks import ticks_ms as ticks , ticks_diff , ticks_add
1918import sys , select
2019
20+ # CIRCUITPY-CHANGE: CircuitPython traceback support
2121try :
2222 from traceback import print_exception
2323except :
2626# Import TaskQueue and Task, preferring built-in C code over Python code
2727try :
2828 from _asyncio import TaskQueue , Task
29+ # CIRCUITPY-CHANGE: more specific error checking
2930except ImportError :
3031 from .task import TaskQueue , Task
3132
3233################################################################################
3334# Exceptions
3435
3536
37+ # CIRCUITPY-CHANGE
3638# Depending on the release of CircuitPython these errors may or may not
3739# exist in the C implementation of `_asyncio`. However, when they
3840# do exist, they must be preferred over the Python code.
@@ -50,6 +52,7 @@ class InvalidStateError(Exception):
5052
5153
5254class TimeoutError (Exception ):
55+ # CIRCUITPY-CHANGE: docstring
5356 """Raised when waiting for a task longer than the specified timeout."""
5457
5558 pass
@@ -62,6 +65,7 @@ class TimeoutError(Exception):
6265################################################################################
6366# Sleep functions
6467
68+
6569# "Yield" once, then raise StopIteration
6670class SingletonGenerator :
6771 def __init__ (self ):
@@ -71,11 +75,13 @@ def __init__(self):
7175 def __iter__ (self ):
7276 return self
7377
78+ # CIRCUITPY-CHANGE: provide await
7479 def __await__ (self ):
7580 return self
7681
7782 def __next__ (self ):
7883 if self .state is not None :
84+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
7985 _task_queue .push_sorted (cur_task , self .state )
8086 self .state = None
8187 return None
@@ -87,18 +93,21 @@ def __next__(self):
8793# Pause task execution for the given time (integer in milliseconds, uPy extension)
8894# Use a SingletonGenerator to do it without allocating on the heap
8995def sleep_ms (t , sgen = SingletonGenerator ()):
96+ # CIRCUITPY-CHANGE: doc
9097 """Sleep for *t* milliseconds.
9198
9299 This is a coroutine, and a MicroPython extension.
93100 """
94101
102+ # CIRCUITPY-CHANGE: add debugging hint
95103 assert sgen .state is None , "Check for a missing `await` in your code"
96104 sgen .state = ticks_add (ticks (), max (0 , t ))
97105 return sgen
98106
99107
100108# Pause task execution for the given time (in seconds)
101109def sleep (t ):
110+ # CIRCUITPY-CHANGE: doc
102111 """Sleep for *t* seconds
103112
104113 This is a coroutine.
@@ -107,6 +116,7 @@ def sleep(t):
107116 return sleep_ms (int (t * 1000 ))
108117
109118
119+ # CIRCUITPY-CHANGE: see https://github.com/adafruit/Adafruit_CircuitPython_asyncio/pull/30
110120################################################################################
111121# "Never schedule" object"
112122# Don't re-schedule the object that awaits _never().
@@ -166,12 +176,16 @@ def _dequeue(self, s):
166176 del self .map [id (s )]
167177 self .poller .unregister (s )
168178
179+ # CIRCUITPY-CHANGE: async
169180 async def queue_read (self , s ):
170181 self ._enqueue (s , 0 )
182+ # CIRCUITPY-CHANGE: do not reschedule
171183 await _never ()
172184
185+ # CIRCUITPY-CHANGE: async
173186 async def queue_write (self , s ):
174187 self ._enqueue (s , 1 )
188+ # CIRCUITPY-CHANGE: do not reschedule
175189 await _never ()
176190
177191 def remove (self , task ):
@@ -193,10 +207,12 @@ def wait_io_event(self, dt):
193207 # print('poll', s, sm, ev)
194208 if ev & ~ select .POLLOUT and sm [0 ] is not None :
195209 # POLLIN or error
210+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
196211 _task_queue .push_head (sm [0 ])
197212 sm [0 ] = None
198213 if ev & ~ select .POLLIN and sm [1 ] is not None :
199214 # POLLOUT or error
215+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
200216 _task_queue .push_head (sm [1 ])
201217 sm [1 ] = None
202218 if sm [0 ] is None and sm [1 ] is None :
@@ -210,13 +226,15 @@ def wait_io_event(self, dt):
210226################################################################################
211227# Main run loop
212228
229+
213230# Ensure the awaitable is a task
214231def _promote_to_task (aw ):
215232 return aw if isinstance (aw , Task ) else create_task (aw )
216233
217234
218235# Create and schedule a new task from a coroutine
219236def create_task (coro ):
237+ # CIRCUITPY-CHANGE: doc
220238 """Create a new task from the given coroutine and schedule it to run.
221239
222240 Returns the corresponding `Task` object.
@@ -225,12 +243,14 @@ def create_task(coro):
225243 if not hasattr (coro , "send" ):
226244 raise TypeError ("coroutine expected" )
227245 t = Task (coro , globals ())
246+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
228247 _task_queue .push_head (t )
229248 return t
230249
231250
232251# Keep scheduling tasks until there are none left to schedule
233252def run_until_complete (main_task = None ):
253+ # CIRCUITPY-CHANGE: doc
234254 """Run the given *main_task* until it completes."""
235255
236256 global cur_task
@@ -247,11 +267,13 @@ def run_until_complete(main_task=None):
247267 dt = max (0 , ticks_diff (t .ph_key , ticks ()))
248268 elif not _io_queue .map :
249269 # No tasks can be woken so finished running
270+ cur_task = None
250271 return
251272 # print('(poll {})'.format(dt), len(_io_queue.map))
252273 _io_queue .wait_io_event (dt )
253274
254275 # Get next task to run and continue it
276+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .pop()
255277 t = _task_queue .pop_head ()
256278 cur_task = t
257279 try :
@@ -271,6 +293,7 @@ def run_until_complete(main_task=None):
271293 assert t .data is None
272294 # This task is done, check if it's the main task and then loop should stop
273295 if t is main_task :
296+ cur_task = None
274297 if isinstance (er , StopIteration ):
275298 return er .value
276299 raise er
@@ -288,6 +311,7 @@ def run_until_complete(main_task=None):
288311 else :
289312 # Schedule any other tasks waiting on the completion of this task.
290313 while t .state .peek ():
314+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push() and .pop()
291315 _task_queue .push_head (t .state .pop_head ())
292316 waiting = True
293317 # "False" indicates that the task is complete and has been await'ed on.
@@ -296,19 +320,26 @@ def run_until_complete(main_task=None):
296320 # An exception ended this detached task, so queue it for later
297321 # execution to handle the uncaught exception if no other task retrieves
298322 # the exception in the meantime (this is handled by Task.throw).
323+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
299324 _task_queue .push_head (t )
300325 # Save return value of coro to pass up to caller.
301326 t .data = er
302327 elif t .state is None :
303328 # Task is already finished and nothing await'ed on the task,
304329 # so call the exception handler.
330+
331+ # Save exception raised by the coro for later use.
332+ t .data = exc
333+
334+ # Create exception context and call the exception handler.
305335 _exc_context ["exception" ] = exc
306336 _exc_context ["future" ] = t
307337 Loop .call_exception_handler (_exc_context )
308338
309339
310340# Create a new task from a coroutine and run it until it finishes
311341def run (coro ):
342+ # CIRCUITPY-CHANGE: doc
312343 """Create a new task from the given coroutine and run it until it completes.
313344
314345 Returns the value returned by *coro*.
@@ -325,20 +356,24 @@ async def _stopper():
325356 pass
326357
327358
359+ cur_task = None
328360_stop_task = None
329361
330362
331363class Loop :
364+ # CIRCUITPY-CHANGE: doc
332365 """Class representing the event loop"""
333366
334367 _exc_handler = None
335368
336369 def create_task (coro ):
370+ # CIRCUITPY-CHANGE: doc
337371 """Create a task from the given *coro* and return the new `Task` object."""
338372
339373 return create_task (coro )
340374
341375 def run_forever ():
376+ # CIRCUITPY-CHANGE: doc
342377 """Run the event loop until `Loop.stop()` is called."""
343378
344379 global _stop_task
@@ -347,47 +382,56 @@ def run_forever():
347382 # TODO should keep running until .stop() is called, even if there're no tasks left
348383
349384 def run_until_complete (aw ):
385+ # CIRCUITPY-CHANGE: doc
350386 """Run the given *awaitable* until it completes. If *awaitable* is not a task then
351387 it will be promoted to one.
352388 """
353389
354390 return run_until_complete (_promote_to_task (aw ))
355391
356392 def stop ():
393+ # CIRCUITPY-CHANGE: doc
357394 """Stop the event loop"""
358395
359396 global _stop_task
360397 if _stop_task is not None :
398+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
361399 _task_queue .push_head (_stop_task )
362400 # If stop() is called again, do nothing
363401 _stop_task = None
364402
365403 def close ():
404+ # CIRCUITPY-CHANGE: doc
366405 """Close the event loop."""
367406
368407 pass
369408
370409 def set_exception_handler (handler ):
410+ # CIRCUITPY-CHANGE: doc
371411 """Set the exception handler to call when a Task raises an exception that is not
372412 caught. The *handler* should accept two arguments: ``(loop, context)``
373413 """
374414
375415 Loop ._exc_handler = handler
376416
377417 def get_exception_handler ():
418+ # CIRCUITPY-CHANGE: doc
378419 """Get the current exception handler. Returns the handler, or ``None`` if no
379420 custom handler is set.
380421 """
381422
382423 return Loop ._exc_handler
383424
384425 def default_exception_handler (loop , context ):
426+ # CIRCUITPY-CHANGE: doc
385427 """The default exception handler that is called."""
386428
429+ # CIRCUITPY_CHANGE: use CircuitPython traceback printing
387430 exc = context ["exception" ]
388431 print_exception (None , exc , exc .__traceback__ )
389432
390433 def call_exception_handler (context ):
434+ # CIRCUITPY-CHANGE: doc
391435 """Call the current exception handler. The argument *context* is passed through
392436 and is a dictionary containing keys:
393437 ``'message'``, ``'exception'``, ``'future'``
@@ -397,29 +441,36 @@ def call_exception_handler(context):
397441
398442# The runq_len and waitq_len arguments are for legacy uasyncio compatibility
399443def get_event_loop (runq_len = 0 , waitq_len = 0 ):
400- """Return the event loop used to schedule and run tasks. See `Loop`."""
444+ # CIRCUITPY-CHANGE: doc
445+ """Return the event loop used to schedule and run tasks. See `Loop`. Deprecated and will be removed later."""
401446
402447 return Loop
403448
404449
405450def current_task ():
451+ # CIRCUITPY-CHANGE: doc
406452 """Return the `Task` object associated with the currently running task."""
407453
454+ if cur_task is None :
455+ raise RuntimeError ("no running event loop" )
408456 return cur_task
409457
410458
411459def new_event_loop ():
460+ # CIRCUITPY-CHANGE: doc
412461 """Reset the event loop and return it.
413462
414463 **NOTE**: Since MicroPython only has a single event loop, this function just resets
415464 the loop's state, it does not create a new one
416465 """
417466
467+ # CIRCUITPY-CHANGE: add _exc_context, cur_task
418468 global _task_queue , _io_queue , _exc_context , cur_task
419469 # TaskQueue of Task instances
420470 _task_queue = TaskQueue ()
421471 # Task queue and poller for stream IO
422472 _io_queue = IOQueue ()
473+ # CIRCUITPY-CHANGE: exception info
423474 cur_task = None
424475 _exc_context ['exception' ] = None
425476 _exc_context ['future' ] = None
0 commit comments