* Ruff fixes

* Ruff fixes

* from __future__ import annotations breaks MicroPython

* noqa: FURB188 because there is no str.replacesuffix() in MicroPython

* Add ruff to pre-commit
This commit is contained in:
Christian Clauss
2025-02-20 09:43:09 +01:00
committed by GitHub
parent 0366e48fad
commit 46239caa19
20 changed files with 117 additions and 100 deletions

View File

@@ -73,14 +73,14 @@ def _eval_formatter(obj, print_method):
"""
if print_method == "__repr__":
return repr(obj)
elif hasattr(obj, print_method):
if hasattr(obj, print_method):
if print_method == "savefig":
buf = io.BytesIO()
obj.savefig(buf, format="png")
buf.seek(0)
return base64.b64encode(buf.read()).decode("utf-8")
return getattr(obj, print_method)()
elif print_method == "_repr_mimebundle_":
if print_method == "_repr_mimebundle_":
return {}, {}
return None
@@ -107,7 +107,7 @@ def _format_mime(obj):
if output is None:
continue
elif mime_type not in _MIME_RENDERERS:
if mime_type not in _MIME_RENDERERS:
not_available.append(mime_type)
continue
break
@@ -149,9 +149,11 @@ def display(*values, target=None, append=True):
if target is None:
target = current_target()
elif not isinstance(target, str):
raise TypeError(f"target must be str or None, not {target.__class__.__name__}")
msg = f"target must be str or None, not {target.__class__.__name__}"
raise TypeError(msg)
elif target == "":
raise ValueError("Cannot have an empty target")
msg = "Cannot have an empty target"
raise ValueError(msg)
elif target.startswith("#"):
# note: here target is str and not None!
# align with @when behavior
@@ -161,9 +163,8 @@ def display(*values, target=None, append=True):
# If target cannot be found on the page, a ValueError is raised
if element is None:
raise ValueError(
f"Invalid selector with id={target}. Cannot be found in the page."
)
msg = f"Invalid selector with id={target}. Cannot be found in the page."
raise ValueError(msg)
# if element is a <script type="py">, it has a 'target' attribute which
# points to the visual element holding the displayed values. In that case,

View File

@@ -36,7 +36,8 @@ class Event:
if listener not in self._listeners:
self._listeners.append(listener)
else:
raise ValueError("Listener must be callable or awaitable.")
msg = "Listener must be callable or awaitable."
raise ValueError(msg)
def remove_listener(self, *args):
"""
@@ -76,7 +77,8 @@ def when(target, *args, **kwargs):
# Extract the selector from the arguments or keyword arguments.
selector = args[0] if args else kwargs.pop("selector")
if not selector:
raise ValueError("No selector provided.")
msg = "No selector provided."
raise ValueError(msg)
# Grab the DOM elements to which the target event will be attached.
from pyscript.web import Element, ElementCollection

View File

@@ -31,7 +31,7 @@ def _object_keys(value):
def _is_array(value):
return isinstance(value, list) or isinstance(value, tuple)
return isinstance(value, (list, tuple))
def _is_object(value):
@@ -60,10 +60,10 @@ def _loop(keys, input, known, output):
def _ref(key, value, input, known, output):
if _is_array(value) and not value in known:
if _is_array(value) and value not in known:
known.append(value)
value = _loop(_array_keys(value), input, known, value)
elif _is_object(value) and not value in known:
elif _is_object(value) and value not in known:
known.append(value)
value = _loop(_object_keys(value), input, known, value)

View File

@@ -25,6 +25,7 @@ class JSModule:
# avoid pyodide looking for non existent fields
if not field.startswith("_"):
return getattr(getattr(js_modules, self.name), field)
return None
# generate N modules in the system that will proxy the real value

View File

@@ -44,8 +44,7 @@ class Device:
for k in video:
setattr(options.video, k, to_js(video[k]))
stream = await window.navigator.mediaDevices.getUserMedia(options)
return stream
return await window.navigator.mediaDevices.getUserMedia(options)
async def get_stream(self):
key = self.kind.replace("input", "").replace("output", "")

View File

@@ -10,10 +10,11 @@ def _to_idb(value):
if isinstance(value, (bool, float, int, str, list, dict, tuple)):
return _stringify(["generic", value])
if isinstance(value, bytearray):
return _stringify(["bytearray", [v for v in value]])
return _stringify(["bytearray", list(value)])
if isinstance(value, memoryview):
return _stringify(["memoryview", [v for v in value]])
raise TypeError(f"Unexpected value: {value}")
return _stringify(["memoryview", list(value)])
msg = f"Unexpected value: {value}"
raise TypeError(msg)
# convert an IndexedDB compatible entry into a Python value
@@ -56,5 +57,6 @@ class Storage(dict):
async def storage(name="", storage_class=Storage):
if not name:
raise ValueError("The storage name must be defined")
msg = "The storage name must be defined"
raise ValueError(msg)
return storage_class(await _storage(f"@pyscript/{name}"))

View File

@@ -11,7 +11,7 @@ def as_bytearray(buffer):
ui8a = js.Uint8Array.new(buffer)
size = ui8a.length
ba = bytearray(size)
for i in range(0, size):
for i in range(size):
ba[i] = ui8a[i]
return ba

View File

@@ -2,7 +2,10 @@
# `when` is not used in this module. It is imported here save the user an additional
# import (i.e. they can get what they need from `pyscript.web`).
from pyscript import document, when, Event # NOQA
# from __future__ import annotations # CAUTION: This is not supported in MicroPython.
from pyscript import document, when, Event # noqa: F401
from pyscript.ffi import create_proxy
@@ -100,7 +103,7 @@ class Element:
If `key` is an integer or a slice we use it to index/slice the element's
children. Otherwise, we use `key` as a query selector.
"""
if isinstance(key, int) or isinstance(key, slice):
if isinstance(key, (int, slice)):
return self.children[key]
return self.find(key)
@@ -120,7 +123,7 @@ class Element:
# attribute `for` which is a Python keyword, so you can access it on the
# Element instance via `for_`).
if name.endswith("_"):
name = name[:-1]
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
return getattr(self._dom_element, name)
def __setattr__(self, name, value):
@@ -138,7 +141,7 @@ class Element:
# attribute `for` which is a Python keyword, so you can access it on the
# Element instance via `for_`).
if name.endswith("_"):
name = name[:-1]
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
if name.startswith("on_"):
# Ensure on-events are cached in the _on_events dict if the
@@ -152,10 +155,12 @@ class Element:
Get an `Event` instance for the specified event name.
"""
if not name.startswith("on_"):
raise ValueError("Event names must start with 'on_'.")
msg = "Event names must start with 'on_'."
raise ValueError(msg)
event_name = name[3:] # Remove the "on_" prefix.
if not hasattr(self._dom_element, event_name):
raise ValueError(f"Element has no '{event_name}' event.")
msg = f"Element has no '{event_name}' event."
raise ValueError(msg)
if name in self._on_events:
return self._on_events[name]
# Such an on-event exists in the DOM element, but we haven't yet
@@ -203,7 +208,7 @@ class Element:
# We check for list/tuple here and NOT for any iterable as it will match
# a JS Nodelist which is handled explicitly below.
# NodeList.
elif isinstance(item, list) or isinstance(item, tuple):
elif isinstance(item, (list, tuple)):
for child in item:
self.append(child)
@@ -227,10 +232,11 @@ class Element:
except AttributeError:
# Nope! This is not an element or a NodeList.
raise TypeError(
msg = (
f'Element "{item}" is a proxy object, "'
f"but not a valid element or a NodeList."
)
raise TypeError(msg)
def clone(self, clone_id=None):
"""Make a clone of the element (clones the underlying DOM object too)."""
@@ -401,9 +407,8 @@ class Options:
new_option = option(**kwargs)
if before:
if isinstance(before, Element):
before = before._dom_element
if before and isinstance(before, Element):
before = before._dom_element
self._element._dom_element.add(new_option._dom_element, before)
@@ -463,7 +468,7 @@ class ContainerElement(Element):
)
for child in list(args) + (children or []):
if isinstance(child, Element) or isinstance(child, ElementCollection):
if isinstance(child, (Element, ElementCollection)):
self.append(child)
else:
@@ -493,14 +498,13 @@ class ClassesCollection:
)
def __iter__(self):
for class_name in self._all_class_names():
yield class_name
yield from self._all_class_names()
def __len__(self):
return len(self._all_class_names())
def __repr__(self):
return f"ClassesCollection({repr(self._collection)})"
return f"ClassesCollection({self._collection!r})"
def __str__(self):
return " ".join(self._all_class_names())
@@ -553,7 +557,7 @@ class StyleCollection:
element.style[key] = value
def __repr__(self):
return f"StyleCollection({repr(self._collection)})"
return f"StyleCollection({self._collection!r})"
def remove(self, key):
"""Remove a CSS property from the elements in the collection."""
@@ -588,7 +592,7 @@ class ElementCollection:
if isinstance(key, int):
return self._elements[key]
elif isinstance(key, slice):
if isinstance(key, slice):
return ElementCollection(self._elements[key])
return self.find(key)
@@ -1125,7 +1129,8 @@ class video(ContainerElement):
elif isinstance(to, Element):
if to.tag != "canvas":
raise TypeError("Element to snap to must be a canvas.")
msg = "Element to snap to must be a canvas."
raise TypeError(msg)
elif getattr(to, "tagName", "") == "CANVAS":
to = canvas(dom_element=to)
@@ -1134,10 +1139,12 @@ class video(ContainerElement):
elif isinstance(to, str):
nodelist = document.querySelectorAll(to) # NOQA
if nodelist.length == 0:
raise TypeError("No element with selector {to} to snap to.")
msg = "No element with selector {to} to snap to."
raise TypeError(msg)
if nodelist[0].tagName != "CANVAS":
raise TypeError("Element to snap to must be a canvas.")
msg = "Element to snap to must be a canvas."
raise TypeError(msg)
to = canvas(dom_element=nodelist[0])

View File

@@ -24,7 +24,7 @@ class EventMessage:
return value
class WebSocket(object):
class WebSocket:
CONNECTING = 0
OPEN = 1
CLOSING = 2

View File

@@ -25,10 +25,12 @@ async def create_named_worker(src="", name="", config=None, type="py"):
from json import dumps
if not src:
raise ValueError("Named workers require src")
msg = "Named workers require src"
raise ValueError(msg)
if not name:
raise ValueError("Named workers require a name")
msg = "Named workers require a name"
raise ValueError(msg)
s = _js.document.createElement("script")
s.type = type
@@ -37,7 +39,7 @@ async def create_named_worker(src="", name="", config=None, type="py"):
_set(s, "name", name)
if config:
_set(s, "config", isinstance(config, str) and config or dumps(config))
_set(s, "config", (isinstance(config, str) and config) or dumps(config))
_js.document.body.append(s)
return await workers[name]

View File

@@ -1,6 +1,6 @@
import numpy
import matplotlib
import numpy as np
import matplotlib as mpl
# just do something with the packages
print(len(dir(numpy)))
print(len(dir(matplotlib)))
print(len(dir(np)))
print(len(dir(mpl)))

View File

@@ -4,4 +4,4 @@ def runtime_version():
return sys.version
__export__ = ['runtime_version']
__export__ = ["runtime_version"]

View File

@@ -8,7 +8,7 @@ if TEST == "implicit":
await fs.mount("/persistent")
print(
RUNNING_IN_WORKER and "Worker" or "Main",
(RUNNING_IN_WORKER and "Worker") or "Main",
os.listdir("/persistent"),
)

View File

@@ -35,7 +35,8 @@ import pygame
# see if we can load more than standard BMP
if not pygame.image.get_extended():
raise SystemExit("Sorry, extended image module required")
msg = "Sorry, extended image module required"
raise SystemExit(msg)
# game constants
@@ -56,7 +57,8 @@ def load_image(file):
try:
surface = pygame.image.load(file)
except pygame.error:
raise SystemExit(f'Could not load image "{file}" {pygame.get_error()}')
msg = f'Could not load image "{file}" {pygame.get_error()}'
raise SystemExit(msg)
return surface.convert()
@@ -66,8 +68,7 @@ def load_sound(file):
return None
file = os.path.join(main_dir, "data", file)
try:
sound = pygame.mixer.Sound(file)
return sound
return pygame.mixer.Sound(file)
except pygame.error:
print(f"Warning, unable to load, {file}")
return None
@@ -227,7 +228,7 @@ class Score(pygame.sprite.Sprite):
def update(self):
"""We only update the score in update() when it has changed."""
if SCORE != self.lastscore:
if self.lastscore != SCORE:
self.lastscore = SCORE
msg = "Score: %d" % SCORE
self.image = self.font.render(msg, 0, self.color)
@@ -296,7 +297,7 @@ async def main(winstyle=0):
# Create Some Starting Values
global score
alienreload = ALIEN_RELOAD
clock = pygame.Clock()
_clock = pygame.Clock()
# initialize our starting sprites
global SCORE
@@ -313,24 +314,23 @@ async def main(winstyle=0):
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_f:
if not fullscreen:
print("Changing to FULLSCREEN")
screen_backup = screen.copy()
screen = pygame.display.set_mode(
SCREENRECT.size, winstyle | pygame.FULLSCREEN, bestdepth
)
screen.blit(screen_backup, (0, 0))
else:
print("Changing to windowed mode")
screen_backup = screen.copy()
screen = pygame.display.set_mode(
SCREENRECT.size, winstyle, bestdepth
)
screen.blit(screen_backup, (0, 0))
pygame.display.flip()
fullscreen = not fullscreen
if event.type == pygame.KEYDOWN and event.key == pygame.K_f:
if not fullscreen:
print("Changing to FULLSCREEN")
screen_backup = screen.copy()
screen = pygame.display.set_mode(
SCREENRECT.size, winstyle | pygame.FULLSCREEN, bestdepth
)
screen.blit(screen_backup, (0, 0))
else:
print("Changing to windowed mode")
screen_backup = screen.copy()
screen = pygame.display.set_mode(
SCREENRECT.size, winstyle, bestdepth
)
screen.blit(screen_backup, (0, 0))
pygame.display.flip()
fullscreen = not fullscreen
keystate = pygame.key.get_pressed()
@@ -371,7 +371,7 @@ async def main(winstyle=0):
player.kill()
# See if shots hit the aliens.
for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1).keys():
for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1):
if pygame.mixer:
boom_sound.play()
Explosion(alien)

View File

@@ -13,10 +13,7 @@ def test_current_target():
"""
expected = "py-0"
if is_micropython:
if RUNNING_IN_WORKER:
expected = "mpy-w0-target"
else:
expected = "mpy-0"
expected = "mpy-w0-target" if RUNNING_IN_WORKER else "mpy-0"
elif RUNNING_IN_WORKER:
expected = "py-w0-target"
assert current_target() == expected, f"Expected {expected} got {current_target()}"

View File

@@ -256,7 +256,7 @@ async def test_image_display():
"""
Check an image is displayed correctly.
"""
mpl = await py_import("matplotlib")
_mpl = await py_import("matplotlib")
import matplotlib.pyplot as plt
xpoints = [3, 6, 9]

View File

@@ -65,7 +65,6 @@ async def test_storage_types():
assert test_store["string"] == "hello"
assert isinstance(test_store["string"], str)
assert test_store["none"] is None
assert isinstance(test_store["none"], type(None))
assert test_store["list"] == [1, 2, 3]
assert isinstance(test_store["list"], list)
assert test_store["dict"] == {"a": 1, "b": 2}

View File

@@ -248,7 +248,7 @@ class TestCollection:
def test_iter_eq_children(self):
elements = web.page.find(".multi-elems")
assert [el for el in elements] == [el for el in elements.elements]
assert list(elements) == list(elements.elements)
assert len(elements) == 3
def test_slices(self):
@@ -427,18 +427,18 @@ class TestInput:
class TestSelect:
def test_select_options_iter(self):
select = web.page.find(f"#test_select_element_w_options")[0]
select = web.page.find("#test_select_element_w_options")[0]
for i, option in enumerate(select.options, 1):
assert option.value == f"{i}"
assert option.innerHTML == f"Option {i}"
def test_select_options_len(self):
select = web.page.find(f"#test_select_element_w_options")[0]
select = web.page.find("#test_select_element_w_options")[0]
assert len(select.options) == 2
def test_select_options_clear(self):
select = web.page.find(f"#test_select_element_to_clear")[0]
select = web.page.find("#test_select_element_to_clear")[0]
assert len(select.options) == 3
select.options.clear()
@@ -447,7 +447,7 @@ class TestSelect:
def test_select_element_add(self):
# GIVEN the existing select element with no options
select = web.page.find(f"#test_select_element")[0]
select = web.page.find("#test_select_element")[0]
# EXPECT the select element to have no options
assert len(select.options) == 0
@@ -498,20 +498,14 @@ class TestSelect:
# EXPECT the middle option to have the value and html we passed in
assert select.options[0].value == "1"
assert select.options[0].innerHTML == "Option 1"
assert (
select.options[0].selected
== select.options[0]._dom_element.selected
== False
)
assert select.options[0].selected == select.options[0]._dom_element.selected
assert select.options[0].selected is False
assert select.options[1].value == "2"
assert select.options[1].innerHTML == "Option 2"
assert select.options[2].value == "3"
assert select.options[2].innerHTML == "Option 3"
assert (
select.options[2].selected
== select.options[2]._dom_element.selected
== True
)
assert select.options[2].selected == select.options[2]._dom_element.selected
assert select.options[2].selected is True
assert select.options[3].value == ""
assert select.options[3].innerHTML == ""
@@ -538,7 +532,7 @@ class TestSelect:
def test_select_options_remove(self):
# GIVEN the existing select element with 3 options
select = web.page.find(f"#test_select_element_to_remove")[0]
select = web.page.find("#test_select_element_to_remove")[0]
# EXPECT the select element to have 3 options
assert len(select.options) == 4
@@ -560,7 +554,7 @@ class TestSelect:
def test_select_get_selected_option(self):
# GIVEN the existing select element with one selected option
select = web.page.find(f"#test_select_element_w_options")[0]
select = web.page.find("#test_select_element_w_options")[0]
# WHEN we get the selected option
selected_option = select.options.selected
@@ -568,7 +562,8 @@ class TestSelect:
# EXPECT the selected option to be correct
assert selected_option.value == "2"
assert selected_option.innerHTML == "Option 2"
assert selected_option.selected == selected_option._dom_element.selected == True
assert selected_option.selected == selected_option._dom_element.selected
assert selected_option.selected is True
class TestElements:
@@ -625,7 +620,8 @@ class TestElements:
el = klass(*args, **kwargs)
container.append(el)
except Exception as e:
assert False, f"Failed to create element {el_type}: {e}"
msg = f"Failed to create element {el_type}: {e}"
raise AssertionError(msg)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector