Skip to content

Commit 01363f9

Browse files
authored
chore: Select ALL Ruff rules (manzt#677)
* chore: Enable all the ruff rules * chore: Replace typing.Any with object * chore: Apply fixes * chore: Fix test_widget * Prefer type annotations * Prefer object over Any * Fix remaining linting issues * Allow use of any for get state
1 parent 12c0699 commit 01363f9

16 files changed

+368
-269
lines changed

anywidget/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ def _jupyter_nbextension_paths() -> list[dict]:
1919
"src": "nbextension",
2020
"dest": "anywidget",
2121
"require": "anywidget/extension",
22-
}
22+
},
2323
]
2424

2525

26-
def load_ipython_extension(ipython): # type: ignore[no-untyped-def]
26+
def load_ipython_extension(ipython) -> None: # type: ignore[no-untyped-def] # noqa: ANN001
2727
from ._cellmagic import load_ipython_extension
2828

2929
load_ipython_extension(ipython)

anywidget/_cellmagic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def vfile(self, line: str, cell: str) -> None:
3838
_VIRTUAL_FILES[name] = vfile
3939

4040
@line_magic # type: ignore[misc]
41-
def clear_vfiles(self, line: str) -> None:
41+
def clear_vfiles(self, line: str) -> None: # noqa: ARG002
4242
"""Clear all virtual files."""
4343
self._files.clear()
4444

anywidget/_descriptor.py

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
from ._protocols import CommMessage
5858

5959
class _GetState(Protocol):
60-
def __call__(self, obj: Any, include: set[str] | None) -> dict: ...
60+
def __call__(self, obj: Any, include: set[str] | None) -> dict: ... # noqa: ANN401
6161

6262
# catch all for types that can be serialized ... too hard to actually type
6363
Serializable: TypeAlias = Any
@@ -85,7 +85,8 @@ def __call__(self, obj: Any, include: set[str] | None) -> dict: ...
8585

8686

8787
def open_comm(
88-
target_name: str = _TARGET_NAME, version: str = _PROTOCOL_VERSION
88+
target_name: str = _TARGET_NAME,
89+
version: str = _PROTOCOL_VERSION,
8990
) -> comm.base_comm.BaseComm:
9091
import comm
9192

@@ -177,7 +178,7 @@ def __init__(
177178
follow_changes: bool = True,
178179
autodetect_observer: bool = True,
179180
no_view: bool = False,
180-
**extra_state: Any,
181+
**extra_state: object,
181182
) -> None:
182183
extra_state.setdefault(_ESM_KEY, _DEFAULT_ESM)
183184
self._extra_state = extra_state
@@ -187,7 +188,8 @@ def __init__(
187188
self._no_view = no_view
188189

189190
for k, v in self._extra_state.items():
190-
# TODO: use := when we drop python 3.7
191+
# TODO(manzt): use := when we drop python 3.7
192+
# https://github.com/manzt/anywidget/pull/167
191193
file_contents = try_file_contents(v)
192194
if file_contents is not None:
193195
self._extra_state[k] = file_contents
@@ -198,7 +200,7 @@ def __set_name__(self, owner: type, name: str) -> None:
198200
In most cases, we won't *want* `name` to be anything other than
199201
`'_repr_mimebundle_'`.
200202
"""
201-
# TODO: conceivably emit a warning if name != '_repr_mimebundle_'
203+
# TODO(tlambert03): conceivably emit a warning if name != '_repr_mimebundle_' # noqa: E501, TD003
202204
self._name = name
203205

204206
@overload
@@ -208,7 +210,9 @@ def __get__(self, instance: None, owner: type) -> MimeBundleDescriptor: ...
208210
def __get__(self, instance: object, owner: type) -> ReprMimeBundle: ...
209211

210212
def __get__(
211-
self, instance: object | None, owner: type
213+
self,
214+
instance: object | None,
215+
owner: type,
212216
) -> ReprMimeBundle | MimeBundleDescriptor:
213217
"""Called when this descriptor's name is accessed on a class or instance.
214218
@@ -293,16 +297,16 @@ def __init__(
293297
self,
294298
obj: object,
295299
autodetect_observer: bool = True,
296-
extra_state: dict[str, Any] | None = None,
300+
extra_state: dict[str, object] | None = None,
297301
no_view: bool = False,
298-
):
302+
) -> None:
299303
self._autodetect_observer = autodetect_observer
300304
self._extra_state = (extra_state or {}).copy()
301305
self._extra_state.setdefault(_ANYWIDGET_ID_KEY, _anywidget_id(obj))
302306
self._no_view = no_view
303307

304308
try:
305-
self._obj: Callable[[], Any] = weakref.ref(obj, self._on_obj_deleted)
309+
self._obj: Callable[[], object] = weakref.ref(obj, self._on_obj_deleted)
306310
except TypeError:
307311
# obj is not weakrefable, so we'll just hold a strong reference to it.
308312
self._obj = lambda: obj
@@ -332,7 +336,7 @@ def _on_change(new_contents: str, key: str = key) -> None:
332336
self._extra_state[key] = new_contents
333337
self.send_state(key)
334338

335-
def _on_obj_deleted(self, ref: weakref.ReferenceType | None = None) -> None:
339+
def _on_obj_deleted(self, ref: weakref.ReferenceType | None = None) -> None: # noqa: ARG002
336340
"""Called when the python object is deleted."""
337341
self.unsync_object_with_view()
338342
self._comm.close()
@@ -363,7 +367,6 @@ def send_state(self, include: str | Iterable[str] | None = None) -> None:
363367
if not state:
364368
return # pragma: no cover
365369

366-
# if self._property_lock: ... # TODO
367370
state, buffer_paths, buffers = remove_buffers(state)
368371
if getattr(self._comm, "kernel", None):
369372
msg = {"method": "update", "state": state, "buffer_paths": buffer_paths}
@@ -373,6 +376,11 @@ def _handle_msg(self, msg: CommMessage) -> None:
373376
"""Called when a msg is received from the front-end.
374377
375378
(assuming `sync_object_with_view` has been called.)
379+
380+
Raises
381+
------
382+
ValueError
383+
If the method in the comm message is not recognized.
376384
"""
377385
obj = self._obj()
378386
if obj is None:
@@ -389,22 +397,23 @@ def _handle_msg(self, msg: CommMessage) -> None:
389397
elif data["method"] == "request_state":
390398
self.send_state()
391399

392-
# elif method == "custom":
400+
# elif method == "custom": # noqa: ERA001
393401
# Handle a custom msg from the front-end.
394402
# if "content" in data:
395-
# self._handle_custom_msg(data["content"], msg["buffers"])
403+
# self._handle_custom_msg(data["content"], msg["buffers"]) # noqa: ERA001
396404
else: # pragma: no cover
397-
raise ValueError(
405+
err_msg = (
398406
f"Unrecognized method: {data['method']}. Please report this at "
399407
"https://github.com/manzt/anywidget/issues"
400408
)
409+
raise ValueError(err_msg)
401410

402-
# def _handle_custom_msg(self, content: Any, buffers: list[memoryview]):
403-
# # TODO: handle custom callbacks
411+
# def _handle_custom_msg(self, content: object, buffers: list[memoryview]):
412+
# # TODO(manzt): handle custom callbacks # noqa: TD003
404413
# # https://github.com/jupyter-widgets/ipywidgets/blob/6547f840edc1884c75e60386ec7fb873ba13f21c/python/ipywidgets/ipywidgets/widgets/widget.py#L662
405414
# ...
406415

407-
def __call__(self, **kwargs: Sequence[str]) -> tuple[dict, dict] | None:
416+
def __call__(self, **kwargs: Sequence[str]) -> tuple[dict, dict] | None: # noqa: ARG002
408417
"""Called when _repr_mimebundle_ is called on the python object."""
409418
# NOTE: this could conceivably be a method on a Comm subclass
410419
# (i.e. the comm knows how to represent itself as a mimebundle)
@@ -413,7 +422,9 @@ def __call__(self, **kwargs: Sequence[str]) -> tuple[dict, dict] | None:
413422
return repr_mimebundle(model_id=self._comm.comm_id, repr_text=repr(self._obj()))
414423

415424
def sync_object_with_view(
416-
self, py_to_js: bool = True, js_to_py: bool = True
425+
self,
426+
py_to_js: bool = True,
427+
js_to_py: bool = True,
417428
) -> None:
418429
"""Connect the front-end to changes in the model, and vice versa.
419430
@@ -425,6 +436,11 @@ def sync_object_with_view(
425436
js_to_py : bool, optional
426437
If True (the default), changes in the front-end will be reflected in the
427438
python model.
439+
440+
Raises
441+
------
442+
RuntimeError
443+
If the object has been deleted.
428444
"""
429445
if js_to_py:
430446
# connect changes in the view to the instance
@@ -435,7 +451,8 @@ def sync_object_with_view(
435451
# connect changes in the instance to the view
436452
obj = self._obj()
437453
if obj is None:
438-
raise RuntimeError("Cannot sync a deleted object")
454+
msg = "Cannot sync a deleted object"
455+
raise RuntimeError(msg)
439456

440457
if self._disconnectors:
441458
warnings.warn("Refusing to re-sync a synced object.", stacklevel=2)
@@ -491,6 +508,11 @@ def determine_state_getter(obj: object) -> _GetState:
491508
-------
492509
state_getter : Callable[[object], dict]
493510
A callable that takes an object and returns a dict of its state.
511+
512+
Raises
513+
------
514+
TypeError
515+
If no state-getting method can be determined.
494516
"""
495517
# check on the class for our special state getter method
496518
if hasattr(type(obj), _STATE_GETTER_NAME):
@@ -501,7 +523,7 @@ def determine_state_getter(obj: object) -> _GetState:
501523
if is_dataclass(obj):
502524
# caveat: if the dict is not JSON serializeable... you still need to
503525
# provide an API for the user to customize serialization
504-
return lambda obj, include: asdict(obj)
526+
return lambda obj, include: asdict(obj) # noqa: ARG005
505527

506528
if _is_traitlets_object(obj):
507529
return _get_traitlets_state
@@ -517,12 +539,13 @@ def determine_state_getter(obj: object) -> _GetState:
517539
# pickle protocol ... probably not type-safe enough for our purposes
518540
# https://docs.python.org/3/library/pickle.html#object.__getstate__
519541
# if hasattr(type(obj), "__getstate__"):
520-
# return type(obj).__getstate__
542+
# return type(obj).__getstate__ # noqa: ERA001
521543

522-
raise TypeError( # pragma: no cover
544+
msg = (
523545
f"Cannot determine a state-getting method for {obj!r}. "
524546
"Please implement a `_get_anywidget_state()` method that returns a dict."
525547
)
548+
raise TypeError(msg)
526549

527550

528551
def _default_set_state(obj: object, state: dict) -> None:
@@ -567,7 +590,9 @@ def _get_psygnal_signal_group(obj: object) -> psygnal.SignalGroup | None:
567590

568591
# try exhaustive search
569592
with contextlib.suppress(
570-
AttributeError, RecursionError, TypeError
593+
AttributeError,
594+
RecursionError,
595+
TypeError,
571596
): # pragma: no cover
572597
for attr in vars(obj).values():
573598
if isinstance(attr, psygnal.SignalGroup):
@@ -603,7 +628,7 @@ def _disconnect() -> None:
603628
# ------------- Traitlets support --------------
604629

605630

606-
def _is_traitlets_object(obj: Any) -> TypeGuard[traitlets.HasTraits]:
631+
def _is_traitlets_object(obj: object) -> TypeGuard[traitlets.HasTraits]:
607632
"""Return `True` if an object is an instance of traitlets.HasTraits."""
608633
traitlets = sys.modules.get("traitlets")
609634
return isinstance(obj, traitlets.HasTraits) if traitlets is not None else False
@@ -614,15 +639,22 @@ def _is_traitlets_object(obj: Any) -> TypeGuard[traitlets.HasTraits]:
614639
_TRAITLETS_SYNC_FLAG = "sync"
615640

616641

617-
# TODO: decide about usage of "sync" being opt-in or opt-out
642+
# TODO(tlambert03): decide about usage of "sync" being opt-in or opt-out # noqa: TD003
618643
# users of traitlets who *don't* use ipywidgets might be surprised when their
619644
# state isn't being synced without opting in.
620645

621646

622647
def _get_traitlets_state(
623-
obj: traitlets.HasTraits, include: set[str] | None
648+
obj: traitlets.HasTraits,
649+
include: set[str] | None, # noqa: ARG001
624650
) -> Serializable:
625-
"""Get the state of a traitlets.HasTraits instance."""
651+
"""Get the state of a traitlets.HasTraits instance.
652+
653+
Returns
654+
-------
655+
state : dict
656+
A dictionary of the state of the traitlets.HasTraits instance.
657+
"""
626658
kwargs = {_TRAITLETS_SYNC_FLAG: True}
627659
return obj.trait_values(**kwargs)
628660

@@ -659,26 +691,38 @@ def _disconnect() -> None:
659691
# ------------- Pydantic support --------------
660692

661693

662-
def _is_pydantic_model(obj: Any) -> TypeGuard[pydantic.BaseModel]:
663-
"""Return `True` if an object is an instance of pydantic.BaseModel."""
694+
def _is_pydantic_model(obj: object) -> TypeGuard[pydantic.BaseModel]:
695+
"""Whether an object is an instance of pydantic.BaseModel.
696+
697+
Returns
698+
-------
699+
`True` if the object is an instance of pydantic.BaseModel, `False` otherwise.
700+
"""
664701
pydantic = sys.modules.get("pydantic")
665702
return isinstance(obj, pydantic.BaseModel) if pydantic is not None else False
666703

667704

668705
def _get_pydantic_state_v1(
669-
obj: pydantic.BaseModel, include: set[str] | None
706+
obj: pydantic.BaseModel,
707+
include: set[str] | None,
670708
) -> Serializable:
671709
"""Get the state of a pydantic BaseModel instance.
672710
673711
To take advantage of pydantic's support for custom encoders (with json_encoders)
674712
we call obj.json() here, and then cast back to a dict (which is what
675713
the comm expects).
714+
715+
Returns
716+
-------
717+
state : dict
718+
A dictionary copy of state from the pydantic BaseModel
676719
"""
677720
return json.loads(obj.json(include=include))
678721

679722

680723
def _get_pydantic_state_v2(
681-
obj: pydantic.BaseModel, include: set[str] | None
724+
obj: pydantic.BaseModel,
725+
include: set[str] | None,
682726
) -> Serializable:
683727
"""Get the state of a pydantic (v2) BaseModel instance."""
684728
return obj.model_dump(mode="json", include=include)
@@ -687,17 +731,16 @@ def _get_pydantic_state_v2(
687731
# ------------- msgspec support --------------
688732

689733

690-
def _is_msgspec_struct(obj: Any) -> TypeGuard[msgspec.Struct]:
734+
def _is_msgspec_struct(obj: object) -> TypeGuard[msgspec.Struct]:
691735
"""Return `True` if an object is an instance of msgspec.Struct."""
692736
msgspec = sys.modules.get("msgspec")
693737
return isinstance(obj, msgspec.Struct) if msgspec is not None else False
694738

695739

696-
def _get_msgspec_state(obj: msgspec.Struct, include: set[str] | None) -> dict:
740+
def _get_msgspec_state(obj: msgspec.Struct, include: set[str] | None) -> Serializable: # noqa: ARG001
697741
"""Get the state of a msgspec.Struct instance."""
698742
import msgspec
699743

700-
# FIXME:
701-
# see discussion here:
702-
# https://github.com/manzt/anywidget/pull/64/files#r1129327721
744+
# TODO(manzt): comm expects a dict. ideally we could serialize with msgspec
745+
# https://github.com/manzt/anywidget/pull/64#discussion_r1128986939
703746
return cast(dict, msgspec.to_builtins(obj))

anywidget/_file_contents.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class VirtualFileContents:
2828

2929
changed = Signal(str)
3030

31-
def __init__(self, contents: str = ""):
31+
def __init__(self, contents: str = "") -> None:
3232
self._contents = contents
3333

3434
@property
@@ -61,10 +61,11 @@ class FileContents:
6161
changed = Signal(str)
6262
deleted = Signal()
6363

64-
def __init__(self, path: str | pathlib.Path, start_thread: bool = True):
64+
def __init__(self, path: str | pathlib.Path, start_thread: bool = True) -> None:
6565
self._path = pathlib.Path(path).expanduser().absolute()
6666
if not self._path.is_file():
67-
raise ValueError("File does not exist: {self._path}")
67+
msg = f"File does not exist: {self._path}"
68+
raise ValueError(msg)
6869
self._contents: str | None = None # cached contents, cleared on change
6970
self._stop_event = threading.Event()
7071
self._background_thread: threading.Thread | None = None
@@ -106,9 +107,12 @@ def watch(self) -> Iterator[tuple[int, str]]:
106107
try:
107108
from watchfiles import Change, watch
108109
except ImportError as exc:
109-
raise ImportError(
110+
msg = (
110111
"watchfiles is required to watch for file changes during development. "
111112
"Install with `pip install watchfiles`."
113+
)
114+
raise ImportError(
115+
msg,
112116
) from exc
113117

114118
for changes in watch(self._path, stop_event=self._stop_event):
@@ -117,7 +121,7 @@ def watch(self) -> Iterator[tuple[int, str]]:
117121
self.deleted.emit()
118122
return
119123
# Only getting Change.added events on macOS so we listen for either
120-
if change == Change.modified or change == Change.added:
124+
if change in (Change.modified, Change.added):
121125
self._contents = None
122126
self.changed.emit(str(self))
123127
yield (change, path)

0 commit comments

Comments
 (0)