Skip to content

Commit

Permalink
changes from MicroPython v1.22-release; CIRCUITPY-CHANGES annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
dhalbert committed Aug 7, 2024
1 parent 2032ac1 commit b544f2f
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 100 deletions.
10 changes: 7 additions & 3 deletions asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# SPDX-FileCopyrightText: 2019 Damien P. George
# CIRCUITPY-CHANGE: SPDX
# SPDX-FileCopyrightText: 2019-2020 Damien P. George
#
# SPDX-License-Identifier: MIT
#
# MicroPython uasyncio module

# MicroPython asyncio module
# MIT license; Copyright (c) 2019 Damien P. George
#
# CIRCUITPY-CHANGE
# This code comes from MicroPython, and has not been run through black or pylint there.
# Altering these files significantly would make merging difficult, so we will not use
# pylint or black.
Expand All @@ -13,6 +15,7 @@

from .core import *

# CIRCUITPY-CHANGE: use CircuitPython version
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/Adafruit/Adafruit_CircuitPython_asyncio.git"

Expand All @@ -29,6 +32,7 @@
"StreamWriter": "stream",
}


# Lazy loader, effectively does:
# global attr
# from .mod import attr
Expand Down
67 changes: 59 additions & 8 deletions asyncio/core.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# SPDX-FileCopyrightText: 2019 Damien P. George
# CIRCUITPY-CHANGE: SPDX
# SPDX-FileCopyrightText: 2019-2020 Damien P. George
#
# SPDX-License-Identifier: MIT
#
# MicroPython uasyncio module

# MicroPython asyncio module
# MIT license; Copyright (c) 2019 Damien P. George
#
# # CIRCUITPY-CHANGE: use CircuitPython version
# This code comes from MicroPython, and has not been run through black or pylint there.
# Altering these files significantly would make merging difficult, so we will not use
# pylint or black.
# pylint: skip-file
# fmt: off
"""
Core
====
"""

# CIRCUITPY-CHANGE: use our ticks library
from adafruit_ticks import ticks_ms as ticks, ticks_diff, ticks_add
import sys, select

# CIRCUITPY-CHANGE: CircuitPython traceback support
try:
from traceback import print_exception
except:
Expand All @@ -26,13 +26,15 @@
# Import TaskQueue and Task, preferring built-in C code over Python code
try:
from _asyncio import TaskQueue, Task
# CIRCUITPY-CHANGE: more specific error checking
except ImportError:
from .task import TaskQueue, Task

################################################################################
# Exceptions


# CIRCUITPY-CHANGE
# Depending on the release of CircuitPython these errors may or may not
# exist in the C implementation of `_asyncio`. However, when they
# do exist, they must be preferred over the Python code.
Expand All @@ -50,6 +52,7 @@ class InvalidStateError(Exception):


class TimeoutError(Exception):
# CIRCUITPY-CHANGE: docstring
"""Raised when waiting for a task longer than the specified timeout."""

pass
Expand All @@ -62,6 +65,7 @@ class TimeoutError(Exception):
################################################################################
# Sleep functions


# "Yield" once, then raise StopIteration
class SingletonGenerator:
def __init__(self):
Expand All @@ -71,11 +75,13 @@ def __init__(self):
def __iter__(self):
return self

# CIRCUITPY-CHANGE: provide await
def __await__(self):
return self

def __next__(self):
if self.state is not None:
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
_task_queue.push_sorted(cur_task, self.state)
self.state = None
return None
Expand All @@ -87,18 +93,21 @@ def __next__(self):
# Pause task execution for the given time (integer in milliseconds, uPy extension)
# Use a SingletonGenerator to do it without allocating on the heap
def sleep_ms(t, sgen=SingletonGenerator()):
# CIRCUITPY-CHANGE: doc
"""Sleep for *t* milliseconds.
This is a coroutine, and a MicroPython extension.
"""

# CIRCUITPY-CHANGE: add debugging hint
assert sgen.state is None, "Check for a missing `await` in your code"
sgen.state = ticks_add(ticks(), max(0, t))
return sgen


# Pause task execution for the given time (in seconds)
def sleep(t):
# CIRCUITPY-CHANGE: doc
"""Sleep for *t* seconds
This is a coroutine.
Expand All @@ -107,6 +116,7 @@ def sleep(t):
return sleep_ms(int(t * 1000))


# CIRCUITPY-CHANGE: see https://github.com/adafruit/Adafruit_CircuitPython_asyncio/pull/30
################################################################################
# "Never schedule" object"
# Don't re-schedule the object that awaits _never().
Expand Down Expand Up @@ -166,12 +176,16 @@ def _dequeue(self, s):
del self.map[id(s)]
self.poller.unregister(s)

# CIRCUITPY-CHANGE: async
async def queue_read(self, s):
self._enqueue(s, 0)
# CIRCUITPY-CHANGE: do not reschedule
await _never()

# CIRCUITPY-CHANGE: async
async def queue_write(self, s):
self._enqueue(s, 1)
# CIRCUITPY-CHANGE: do not reschedule
await _never()

def remove(self, task):
Expand All @@ -193,10 +207,12 @@ def wait_io_event(self, dt):
# print('poll', s, sm, ev)
if ev & ~select.POLLOUT and sm[0] is not None:
# POLLIN or error
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
_task_queue.push_head(sm[0])
sm[0] = None
if ev & ~select.POLLIN and sm[1] is not None:
# POLLOUT or error
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
_task_queue.push_head(sm[1])
sm[1] = None
if sm[0] is None and sm[1] is None:
Expand All @@ -210,13 +226,15 @@ def wait_io_event(self, dt):
################################################################################
# Main run loop


# Ensure the awaitable is a task
def _promote_to_task(aw):
return aw if isinstance(aw, Task) else create_task(aw)


# Create and schedule a new task from a coroutine
def create_task(coro):
# CIRCUITPY-CHANGE: doc
"""Create a new task from the given coroutine and schedule it to run.
Returns the corresponding `Task` object.
Expand All @@ -225,12 +243,14 @@ def create_task(coro):
if not hasattr(coro, "send"):
raise TypeError("coroutine expected")
t = Task(coro, globals())
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
_task_queue.push_head(t)
return t


# Keep scheduling tasks until there are none left to schedule
def run_until_complete(main_task=None):
# CIRCUITPY-CHANGE: doc
"""Run the given *main_task* until it completes."""

global cur_task
Expand All @@ -247,11 +267,13 @@ def run_until_complete(main_task=None):
dt = max(0, ticks_diff(t.ph_key, ticks()))
elif not _io_queue.map:
# No tasks can be woken so finished running
cur_task = None
return
# print('(poll {})'.format(dt), len(_io_queue.map))
_io_queue.wait_io_event(dt)

# Get next task to run and continue it
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .pop()
t = _task_queue.pop_head()
cur_task = t
try:
Expand All @@ -271,6 +293,7 @@ def run_until_complete(main_task=None):
assert t.data is None
# This task is done, check if it's the main task and then loop should stop
if t is main_task:
cur_task = None
if isinstance(er, StopIteration):
return er.value
raise er
Expand All @@ -288,6 +311,7 @@ def run_until_complete(main_task=None):
else:
# Schedule any other tasks waiting on the completion of this task.
while t.state.peek():
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push() and .pop()
_task_queue.push_head(t.state.pop_head())
waiting = True
# "False" indicates that the task is complete and has been await'ed on.
Expand All @@ -296,19 +320,26 @@ def run_until_complete(main_task=None):
# An exception ended this detached task, so queue it for later
# execution to handle the uncaught exception if no other task retrieves
# the exception in the meantime (this is handled by Task.throw).
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
_task_queue.push_head(t)
# Save return value of coro to pass up to caller.
t.data = er
elif t.state is None:
# Task is already finished and nothing await'ed on the task,
# so call the exception handler.

# Save exception raised by the coro for later use.
t.data = exc

# Create exception context and call the exception handler.
_exc_context["exception"] = exc
_exc_context["future"] = t
Loop.call_exception_handler(_exc_context)


# Create a new task from a coroutine and run it until it finishes
def run(coro):
# CIRCUITPY-CHANGE: doc
"""Create a new task from the given coroutine and run it until it completes.
Returns the value returned by *coro*.
Expand All @@ -325,20 +356,24 @@ async def _stopper():
pass


cur_task = None
_stop_task = None


class Loop:
# CIRCUITPY-CHANGE: doc
"""Class representing the event loop"""

_exc_handler = None

def create_task(coro):
# CIRCUITPY-CHANGE: doc
"""Create a task from the given *coro* and return the new `Task` object."""

return create_task(coro)

def run_forever():
# CIRCUITPY-CHANGE: doc
"""Run the event loop until `Loop.stop()` is called."""

global _stop_task
Expand All @@ -347,47 +382,56 @@ def run_forever():
# TODO should keep running until .stop() is called, even if there're no tasks left

def run_until_complete(aw):
# CIRCUITPY-CHANGE: doc
"""Run the given *awaitable* until it completes. If *awaitable* is not a task then
it will be promoted to one.
"""

return run_until_complete(_promote_to_task(aw))

def stop():
# CIRCUITPY-CHANGE: doc
"""Stop the event loop"""

global _stop_task
if _stop_task is not None:
# CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
_task_queue.push_head(_stop_task)
# If stop() is called again, do nothing
_stop_task = None

def close():
# CIRCUITPY-CHANGE: doc
"""Close the event loop."""

pass

def set_exception_handler(handler):
# CIRCUITPY-CHANGE: doc
"""Set the exception handler to call when a Task raises an exception that is not
caught. The *handler* should accept two arguments: ``(loop, context)``
"""

Loop._exc_handler = handler

def get_exception_handler():
# CIRCUITPY-CHANGE: doc
"""Get the current exception handler. Returns the handler, or ``None`` if no
custom handler is set.
"""

return Loop._exc_handler

def default_exception_handler(loop, context):
# CIRCUITPY-CHANGE: doc
"""The default exception handler that is called."""

# CIRCUITPY_CHANGE: use CircuitPython traceback printing
exc = context["exception"]
print_exception(None, exc, exc.__traceback__)

def call_exception_handler(context):
# CIRCUITPY-CHANGE: doc
"""Call the current exception handler. The argument *context* is passed through
and is a dictionary containing keys:
``'message'``, ``'exception'``, ``'future'``
Expand All @@ -397,29 +441,36 @@ def call_exception_handler(context):

# The runq_len and waitq_len arguments are for legacy uasyncio compatibility
def get_event_loop(runq_len=0, waitq_len=0):
"""Return the event loop used to schedule and run tasks. See `Loop`."""
# CIRCUITPY-CHANGE: doc
"""Return the event loop used to schedule and run tasks. See `Loop`. Deprecated and will be removed later."""

return Loop


def current_task():
# CIRCUITPY-CHANGE: doc
"""Return the `Task` object associated with the currently running task."""

if cur_task is None:
raise RuntimeError("no running event loop")
return cur_task


def new_event_loop():
# CIRCUITPY-CHANGE: doc
"""Reset the event loop and return it.
**NOTE**: Since MicroPython only has a single event loop, this function just resets
the loop's state, it does not create a new one
"""

# CIRCUITPY-CHANGE: add _exc_context, cur_task
global _task_queue, _io_queue, _exc_context, cur_task
# TaskQueue of Task instances
_task_queue = TaskQueue()
# Task queue and poller for stream IO
_io_queue = IOQueue()
# CIRCUITPY-CHANGE: exception info
cur_task = None
_exc_context['exception'] = None
_exc_context['future'] = None
Expand Down
Loading

0 comments on commit b544f2f

Please sign in to comment.