57
57
from ._protocols import CommMessage
58
58
59
59
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
61
61
62
62
# catch all for types that can be serialized ... too hard to actually type
63
63
Serializable : TypeAlias = Any
@@ -85,7 +85,8 @@ def __call__(self, obj: Any, include: set[str] | None) -> dict: ...
85
85
86
86
87
87
def open_comm (
88
- target_name : str = _TARGET_NAME , version : str = _PROTOCOL_VERSION
88
+ target_name : str = _TARGET_NAME ,
89
+ version : str = _PROTOCOL_VERSION ,
89
90
) -> comm .base_comm .BaseComm :
90
91
import comm
91
92
@@ -177,7 +178,7 @@ def __init__(
177
178
follow_changes : bool = True ,
178
179
autodetect_observer : bool = True ,
179
180
no_view : bool = False ,
180
- ** extra_state : Any ,
181
+ ** extra_state : object ,
181
182
) -> None :
182
183
extra_state .setdefault (_ESM_KEY , _DEFAULT_ESM )
183
184
self ._extra_state = extra_state
@@ -187,7 +188,8 @@ def __init__(
187
188
self ._no_view = no_view
188
189
189
190
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
191
193
file_contents = try_file_contents (v )
192
194
if file_contents is not None :
193
195
self ._extra_state [k ] = file_contents
@@ -198,7 +200,7 @@ def __set_name__(self, owner: type, name: str) -> None:
198
200
In most cases, we won't *want* `name` to be anything other than
199
201
`'_repr_mimebundle_'`.
200
202
"""
201
- # TODO: conceivably emit a warning if name != '_repr_mimebundle_'
203
+ # TODO(tlambert03) : conceivably emit a warning if name != '_repr_mimebundle_' # noqa: E501, TD003
202
204
self ._name = name
203
205
204
206
@overload
@@ -208,7 +210,9 @@ def __get__(self, instance: None, owner: type) -> MimeBundleDescriptor: ...
208
210
def __get__ (self , instance : object , owner : type ) -> ReprMimeBundle : ...
209
211
210
212
def __get__ (
211
- self , instance : object | None , owner : type
213
+ self ,
214
+ instance : object | None ,
215
+ owner : type ,
212
216
) -> ReprMimeBundle | MimeBundleDescriptor :
213
217
"""Called when this descriptor's name is accessed on a class or instance.
214
218
@@ -293,16 +297,16 @@ def __init__(
293
297
self ,
294
298
obj : object ,
295
299
autodetect_observer : bool = True ,
296
- extra_state : dict [str , Any ] | None = None ,
300
+ extra_state : dict [str , object ] | None = None ,
297
301
no_view : bool = False ,
298
- ):
302
+ ) -> None :
299
303
self ._autodetect_observer = autodetect_observer
300
304
self ._extra_state = (extra_state or {}).copy ()
301
305
self ._extra_state .setdefault (_ANYWIDGET_ID_KEY , _anywidget_id (obj ))
302
306
self ._no_view = no_view
303
307
304
308
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 )
306
310
except TypeError :
307
311
# obj is not weakrefable, so we'll just hold a strong reference to it.
308
312
self ._obj = lambda : obj
@@ -332,7 +336,7 @@ def _on_change(new_contents: str, key: str = key) -> None:
332
336
self ._extra_state [key ] = new_contents
333
337
self .send_state (key )
334
338
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
336
340
"""Called when the python object is deleted."""
337
341
self .unsync_object_with_view ()
338
342
self ._comm .close ()
@@ -363,7 +367,6 @@ def send_state(self, include: str | Iterable[str] | None = None) -> None:
363
367
if not state :
364
368
return # pragma: no cover
365
369
366
- # if self._property_lock: ... # TODO
367
370
state , buffer_paths , buffers = remove_buffers (state )
368
371
if getattr (self ._comm , "kernel" , None ):
369
372
msg = {"method" : "update" , "state" : state , "buffer_paths" : buffer_paths }
@@ -373,6 +376,11 @@ def _handle_msg(self, msg: CommMessage) -> None:
373
376
"""Called when a msg is received from the front-end.
374
377
375
378
(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.
376
384
"""
377
385
obj = self ._obj ()
378
386
if obj is None :
@@ -389,22 +397,23 @@ def _handle_msg(self, msg: CommMessage) -> None:
389
397
elif data ["method" ] == "request_state" :
390
398
self .send_state ()
391
399
392
- # elif method == "custom":
400
+ # elif method == "custom": # noqa: ERA001
393
401
# Handle a custom msg from the front-end.
394
402
# if "content" in data:
395
- # self._handle_custom_msg(data["content"], msg["buffers"])
403
+ # self._handle_custom_msg(data["content"], msg["buffers"]) # noqa: ERA001
396
404
else : # pragma: no cover
397
- raise ValueError (
405
+ err_msg = (
398
406
f"Unrecognized method: { data ['method' ]} . Please report this at "
399
407
"https://github.com/manzt/anywidget/issues"
400
408
)
409
+ raise ValueError (err_msg )
401
410
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
404
413
# # https://github.com/jupyter-widgets/ipywidgets/blob/6547f840edc1884c75e60386ec7fb873ba13f21c/python/ipywidgets/ipywidgets/widgets/widget.py#L662
405
414
# ...
406
415
407
- def __call__ (self , ** kwargs : Sequence [str ]) -> tuple [dict , dict ] | None :
416
+ def __call__ (self , ** kwargs : Sequence [str ]) -> tuple [dict , dict ] | None : # noqa: ARG002
408
417
"""Called when _repr_mimebundle_ is called on the python object."""
409
418
# NOTE: this could conceivably be a method on a Comm subclass
410
419
# (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:
413
422
return repr_mimebundle (model_id = self ._comm .comm_id , repr_text = repr (self ._obj ()))
414
423
415
424
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 ,
417
428
) -> None :
418
429
"""Connect the front-end to changes in the model, and vice versa.
419
430
@@ -425,6 +436,11 @@ def sync_object_with_view(
425
436
js_to_py : bool, optional
426
437
If True (the default), changes in the front-end will be reflected in the
427
438
python model.
439
+
440
+ Raises
441
+ ------
442
+ RuntimeError
443
+ If the object has been deleted.
428
444
"""
429
445
if js_to_py :
430
446
# connect changes in the view to the instance
@@ -435,7 +451,8 @@ def sync_object_with_view(
435
451
# connect changes in the instance to the view
436
452
obj = self ._obj ()
437
453
if obj is None :
438
- raise RuntimeError ("Cannot sync a deleted object" )
454
+ msg = "Cannot sync a deleted object"
455
+ raise RuntimeError (msg )
439
456
440
457
if self ._disconnectors :
441
458
warnings .warn ("Refusing to re-sync a synced object." , stacklevel = 2 )
@@ -491,6 +508,11 @@ def determine_state_getter(obj: object) -> _GetState:
491
508
-------
492
509
state_getter : Callable[[object], dict]
493
510
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.
494
516
"""
495
517
# check on the class for our special state getter method
496
518
if hasattr (type (obj ), _STATE_GETTER_NAME ):
@@ -501,7 +523,7 @@ def determine_state_getter(obj: object) -> _GetState:
501
523
if is_dataclass (obj ):
502
524
# caveat: if the dict is not JSON serializeable... you still need to
503
525
# 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
505
527
506
528
if _is_traitlets_object (obj ):
507
529
return _get_traitlets_state
@@ -517,12 +539,13 @@ def determine_state_getter(obj: object) -> _GetState:
517
539
# pickle protocol ... probably not type-safe enough for our purposes
518
540
# https://docs.python.org/3/library/pickle.html#object.__getstate__
519
541
# if hasattr(type(obj), "__getstate__"):
520
- # return type(obj).__getstate__
542
+ # return type(obj).__getstate__ # noqa: ERA001
521
543
522
- raise TypeError ( # pragma: no cover
544
+ msg = (
523
545
f"Cannot determine a state-getting method for { obj !r} . "
524
546
"Please implement a `_get_anywidget_state()` method that returns a dict."
525
547
)
548
+ raise TypeError (msg )
526
549
527
550
528
551
def _default_set_state (obj : object , state : dict ) -> None :
@@ -567,7 +590,9 @@ def _get_psygnal_signal_group(obj: object) -> psygnal.SignalGroup | None:
567
590
568
591
# try exhaustive search
569
592
with contextlib .suppress (
570
- AttributeError , RecursionError , TypeError
593
+ AttributeError ,
594
+ RecursionError ,
595
+ TypeError ,
571
596
): # pragma: no cover
572
597
for attr in vars (obj ).values ():
573
598
if isinstance (attr , psygnal .SignalGroup ):
@@ -603,7 +628,7 @@ def _disconnect() -> None:
603
628
# ------------- Traitlets support --------------
604
629
605
630
606
- def _is_traitlets_object (obj : Any ) -> TypeGuard [traitlets .HasTraits ]:
631
+ def _is_traitlets_object (obj : object ) -> TypeGuard [traitlets .HasTraits ]:
607
632
"""Return `True` if an object is an instance of traitlets.HasTraits."""
608
633
traitlets = sys .modules .get ("traitlets" )
609
634
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]:
614
639
_TRAITLETS_SYNC_FLAG = "sync"
615
640
616
641
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
618
643
# users of traitlets who *don't* use ipywidgets might be surprised when their
619
644
# state isn't being synced without opting in.
620
645
621
646
622
647
def _get_traitlets_state (
623
- obj : traitlets .HasTraits , include : set [str ] | None
648
+ obj : traitlets .HasTraits ,
649
+ include : set [str ] | None , # noqa: ARG001
624
650
) -> 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
+ """
626
658
kwargs = {_TRAITLETS_SYNC_FLAG : True }
627
659
return obj .trait_values (** kwargs )
628
660
@@ -659,26 +691,38 @@ def _disconnect() -> None:
659
691
# ------------- Pydantic support --------------
660
692
661
693
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
+ """
664
701
pydantic = sys .modules .get ("pydantic" )
665
702
return isinstance (obj , pydantic .BaseModel ) if pydantic is not None else False
666
703
667
704
668
705
def _get_pydantic_state_v1 (
669
- obj : pydantic .BaseModel , include : set [str ] | None
706
+ obj : pydantic .BaseModel ,
707
+ include : set [str ] | None ,
670
708
) -> Serializable :
671
709
"""Get the state of a pydantic BaseModel instance.
672
710
673
711
To take advantage of pydantic's support for custom encoders (with json_encoders)
674
712
we call obj.json() here, and then cast back to a dict (which is what
675
713
the comm expects).
714
+
715
+ Returns
716
+ -------
717
+ state : dict
718
+ A dictionary copy of state from the pydantic BaseModel
676
719
"""
677
720
return json .loads (obj .json (include = include ))
678
721
679
722
680
723
def _get_pydantic_state_v2 (
681
- obj : pydantic .BaseModel , include : set [str ] | None
724
+ obj : pydantic .BaseModel ,
725
+ include : set [str ] | None ,
682
726
) -> Serializable :
683
727
"""Get the state of a pydantic (v2) BaseModel instance."""
684
728
return obj .model_dump (mode = "json" , include = include )
@@ -687,17 +731,16 @@ def _get_pydantic_state_v2(
687
731
# ------------- msgspec support --------------
688
732
689
733
690
- def _is_msgspec_struct (obj : Any ) -> TypeGuard [msgspec .Struct ]:
734
+ def _is_msgspec_struct (obj : object ) -> TypeGuard [msgspec .Struct ]:
691
735
"""Return `True` if an object is an instance of msgspec.Struct."""
692
736
msgspec = sys .modules .get ("msgspec" )
693
737
return isinstance (obj , msgspec .Struct ) if msgspec is not None else False
694
738
695
739
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
697
741
"""Get the state of a msgspec.Struct instance."""
698
742
import msgspec
699
743
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
703
746
return cast (dict , msgspec .to_builtins (obj ))
0 commit comments