Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d5a897e

Browse filesBrowse files
authored
V2-migrate-mypy-to-pyright (#1274)
- Change our preferred type checker from MyPy to Pyright - Don't rely on `asgiref.types` as much, since they're a bit too strict and cause a lot of type errors in use code.
1 parent babc2de commit d5a897e
Copy full SHA for d5a897e

File tree

Expand file treeCollapse file tree

16 files changed

+221
-233
lines changed
Filter options
Expand file treeCollapse file tree

16 files changed

+221
-233
lines changed

‎pyproject.toml

Copy file name to clipboardExpand all lines: pyproject.toml
+6-11Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,16 @@ serve = [
153153
[tool.hatch.envs.python]
154154
extra-dependencies = [
155155
"reactpy[all]",
156-
"ruff",
157-
"toml",
158-
"mypy==1.8",
156+
"pyright",
159157
"types-toml",
160158
"types-click",
161159
"types-requests",
160+
"types-lxml",
161+
"jsonpointer",
162162
]
163163

164164
[tool.hatch.envs.python.scripts]
165-
# TODO: Replace mypy with pyright
166-
type_check = ["mypy --strict src/reactpy"]
165+
type_check = ["pyright src/reactpy"]
167166

168167
############################
169168
# >>> Hatch JS Scripts <<< #
@@ -218,12 +217,8 @@ publish_client = [
218217
# >>> Generic Tools <<< #
219218
#########################
220219

221-
[tool.mypy]
222-
incremental = false
223-
ignore_missing_imports = true
224-
warn_unused_configs = true
225-
warn_redundant_casts = true
226-
warn_unused_ignores = true
220+
[tool.pyright]
221+
reportIncompatibleVariableOverride = false
227222

228223
[tool.coverage.run]
229224
source_pkgs = ["reactpy"]

‎src/reactpy/_console/ast_utils.py

Copy file name to clipboardExpand all lines: src/reactpy/_console/ast_utils.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# pyright: reportAttributeAccessIssue=false
12
from __future__ import annotations
23

34
import ast

‎src/reactpy/_warnings.py

Copy file name to clipboardExpand all lines: src/reactpy/_warnings.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from functools import wraps
33
from inspect import currentframe
44
from types import FrameType
5-
from typing import TYPE_CHECKING, Any
5+
from typing import TYPE_CHECKING, Any, cast
66
from warnings import warn as _warn
77

88

@@ -13,7 +13,7 @@ def warn(*args: Any, **kwargs: Any) -> Any:
1313

1414

1515
if TYPE_CHECKING:
16-
warn = _warn
16+
warn = cast(Any, _warn)
1717

1818

1919
def _frame_depth_in_module() -> int:

‎src/reactpy/config.py

Copy file name to clipboardExpand all lines: src/reactpy/config.py
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,17 @@ def boolean(value: str | bool | int) -> bool:
4242
- :data:`REACTPY_CHECK_JSON_ATTRS`
4343
"""
4444

45-
REACTPY_CHECK_VDOM_SPEC = Option("REACTPY_CHECK_VDOM_SPEC", parent=REACTPY_DEBUG)
45+
REACTPY_CHECK_VDOM_SPEC = Option(
46+
"REACTPY_CHECK_VDOM_SPEC", parent=REACTPY_DEBUG, validator=boolean
47+
)
4648
"""Checks which ensure VDOM is rendered to spec
4749
4850
For more info on the VDOM spec, see here: :ref:`VDOM JSON Schema`
4951
"""
5052

51-
REACTPY_CHECK_JSON_ATTRS = Option("REACTPY_CHECK_JSON_ATTRS", parent=REACTPY_DEBUG)
53+
REACTPY_CHECK_JSON_ATTRS = Option(
54+
"REACTPY_CHECK_JSON_ATTRS", parent=REACTPY_DEBUG, validator=boolean
55+
)
5256
"""Checks that all VDOM attributes are JSON serializable
5357
5458
The VDOM spec is not able to enforce this on its own since attributes could anything.

‎src/reactpy/core/hooks.py

Copy file name to clipboardExpand all lines: src/reactpy/core/hooks.py
+7-13Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def use_state(initial_value: _Type | Callable[[], _Type]) -> State[_Type]:
6666
A tuple containing the current state and a function to update it.
6767
"""
6868
current_state = _use_const(lambda: _CurrentState(initial_value))
69-
return State(current_state.value, current_state.dispatch)
69+
70+
# FIXME: Not sure why this type hint is not being inferred correctly when using pyright
71+
return State(current_state.value, current_state.dispatch) # type: ignore
7072

7173

7274
class _CurrentState(Generic[_Type]):
@@ -84,10 +86,7 @@ def __init__(
8486
hook = current_hook()
8587

8688
def dispatch(new: _Type | Callable[[_Type], _Type]) -> None:
87-
if callable(new):
88-
next_value = new(self.value)
89-
else:
90-
next_value = new
89+
next_value = new(self.value) if callable(new) else new # type: ignore
9190
if not strictly_equal(next_value, self.value):
9291
self.value = next_value
9392
hook.schedule_render()
@@ -338,9 +337,9 @@ def use_connection() -> Connection[Any]:
338337
return conn
339338

340339

341-
def use_scope() -> asgi_types.HTTPScope | asgi_types.WebSocketScope:
340+
def use_scope() -> dict[str, Any] | asgi_types.HTTPScope | asgi_types.WebSocketScope:
342341
"""Get the current :class:`~reactpy.types.Connection`'s scope."""
343-
return use_connection().scope # type: ignore
342+
return use_connection().scope
344343

345344

346345
def use_location() -> Location:
@@ -511,8 +510,6 @@ def use_memo(
511510
else:
512511
changed = False
513512

514-
setup: Callable[[Callable[[], _Type]], _Type]
515-
516513
if changed:
517514

518515
def setup(function: Callable[[], _Type]) -> _Type:
@@ -524,10 +521,7 @@ def setup(function: Callable[[], _Type]) -> _Type:
524521
def setup(function: Callable[[], _Type]) -> _Type:
525522
return memo.value
526523

527-
if function is not None:
528-
return setup(function)
529-
else:
530-
return setup
524+
return setup(function) if function is not None else setup
531525

532526

533527
class _Memo(Generic[_Type]):

‎src/reactpy/core/layout.py

Copy file name to clipboardExpand all lines: src/reactpy/core/layout.py
+16-12Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from collections.abc import Sequence
1515
from contextlib import AsyncExitStack
1616
from logging import getLogger
17+
from types import TracebackType
1718
from typing import (
1819
Any,
1920
Callable,
@@ -56,13 +57,13 @@ class Layout:
5657
"""Responsible for "rendering" components. That is, turning them into VDOM."""
5758

5859
__slots__: tuple[str, ...] = (
59-
"root",
6060
"_event_handlers",
61-
"_rendering_queue",
61+
"_model_states_by_life_cycle_state_id",
6262
"_render_tasks",
6363
"_render_tasks_ready",
64+
"_rendering_queue",
6465
"_root_life_cycle_state_id",
65-
"_model_states_by_life_cycle_state_id",
66+
"root",
6667
)
6768

6869
if not hasattr(abc.ABC, "__weakref__"): # nocov
@@ -80,17 +81,17 @@ async def __aenter__(self) -> Layout:
8081
self._event_handlers: EventHandlerDict = {}
8182
self._render_tasks: set[Task[LayoutUpdateMessage]] = set()
8283
self._render_tasks_ready: Semaphore = Semaphore(0)
83-
8484
self._rendering_queue: _ThreadSafeQueue[_LifeCycleStateId] = _ThreadSafeQueue()
8585
root_model_state = _new_root_model_state(self.root, self._schedule_render_task)
86-
8786
self._root_life_cycle_state_id = root_id = root_model_state.life_cycle_state.id
8887
self._model_states_by_life_cycle_state_id = {root_id: root_model_state}
8988
self._schedule_render_task(root_id)
9089

9190
return self
9291

93-
async def __aexit__(self, *exc: object) -> None:
92+
async def __aexit__(
93+
self, exc_type: type[Exception], exc_value: Exception, traceback: TracebackType
94+
) -> None:
9495
root_csid = self._root_life_cycle_state_id
9596
root_model_state = self._model_states_by_life_cycle_state_id[root_csid]
9697

@@ -109,7 +110,7 @@ async def __aexit__(self, *exc: object) -> None:
109110
del self._root_life_cycle_state_id
110111
del self._model_states_by_life_cycle_state_id
111112

112-
async def deliver(self, event: LayoutEventMessage) -> None:
113+
async def deliver(self, event: LayoutEventMessage | dict[str, Any]) -> None:
113114
"""Dispatch an event to the targeted handler"""
114115
# It is possible for an element in the frontend to produce an event
115116
# associated with a backend model that has been deleted. We only handle
@@ -217,7 +218,7 @@ async def _render_component(
217218
parent.children_by_key[key] = new_state
218219
# need to add this model to parent's children without mutating parent model
219220
old_parent_model = parent.model.current
220-
old_parent_children = old_parent_model["children"]
221+
old_parent_children = old_parent_model.setdefault("children", [])
221222
parent.model.current = {
222223
**old_parent_model,
223224
"children": [
@@ -318,8 +319,11 @@ async def _render_model_children(
318319
new_state: _ModelState,
319320
raw_children: Any,
320321
) -> None:
321-
if not isinstance(raw_children, (list, tuple)):
322-
raw_children = [raw_children]
322+
if not isinstance(raw_children, list):
323+
if isinstance(raw_children, tuple):
324+
raw_children = list(raw_children)
325+
else:
326+
raw_children = [raw_children]
323327

324328
if old_state is None:
325329
if raw_children:
@@ -609,7 +613,7 @@ def __init__(
609613
parent: _ModelState | None,
610614
index: int,
611615
key: Any,
612-
model: Ref[VdomJson],
616+
model: Ref[VdomJson | dict[str, Any]],
613617
patch_path: str,
614618
children_by_key: dict[Key, _ModelState],
615619
targets_by_event: dict[str, str],
@@ -656,7 +660,7 @@ def parent(self) -> _ModelState:
656660
return parent
657661

658662
def append_child(self, child: Any) -> None:
659-
self.model.current["children"].append(child)
663+
self.model.current.setdefault("children", []).append(child)
660664

661665
def __repr__(self) -> str: # nocov
662666
return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })"

‎src/reactpy/core/serve.py

Copy file name to clipboardExpand all lines: src/reactpy/core/serve.py
+13-6Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from collections.abc import Awaitable
44
from logging import getLogger
5-
from typing import Callable
5+
from typing import Any, Callable
66
from warnings import warn
77

88
from anyio import create_task_group
@@ -14,10 +14,10 @@
1414
logger = getLogger(__name__)
1515

1616

17-
SendCoroutine = Callable[[LayoutUpdateMessage], Awaitable[None]]
17+
SendCoroutine = Callable[[LayoutUpdateMessage | dict[str, Any]], Awaitable[None]]
1818
"""Send model patches given by a dispatcher"""
1919

20-
RecvCoroutine = Callable[[], Awaitable[LayoutEventMessage]]
20+
RecvCoroutine = Callable[[], Awaitable[LayoutEventMessage | dict[str, Any]]]
2121
"""Called by a dispatcher to return a :class:`reactpy.core.layout.LayoutEventMessage`
2222
2323
The event will then trigger an :class:`reactpy.core.proto.EventHandlerType` in a layout.
@@ -35,7 +35,9 @@ class Stop(BaseException):
3535

3636

3737
async def serve_layout(
38-
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
38+
layout: LayoutType[
39+
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
40+
],
3941
send: SendCoroutine,
4042
recv: RecvCoroutine,
4143
) -> None:
@@ -55,7 +57,10 @@ async def serve_layout(
5557

5658

5759
async def _single_outgoing_loop(
58-
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], send: SendCoroutine
60+
layout: LayoutType[
61+
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
62+
],
63+
send: SendCoroutine,
5964
) -> None:
6065
while True:
6166
update = await layout.render()
@@ -74,7 +79,9 @@ async def _single_outgoing_loop(
7479

7580
async def _single_incoming_loop(
7681
task_group: TaskGroup,
77-
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
82+
layout: LayoutType[
83+
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
84+
],
7885
recv: RecvCoroutine,
7986
) -> None:
8087
while True:

‎src/reactpy/core/vdom.py

Copy file name to clipboardExpand all lines: src/reactpy/core/vdom.py
+3-42Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
from collections.abc import Mapping, Sequence
55
from functools import wraps
6-
from typing import Any, Protocol, cast, overload
6+
from typing import Any, Callable, Protocol, cast
77

88
from fastjsonschema import compile as compile_json_schema
99

@@ -92,7 +92,7 @@
9292

9393

9494
# we can't add a docstring to this because Sphinx doesn't know how to find its source
95-
_COMPILED_VDOM_VALIDATOR = compile_json_schema(VDOM_JSON_SCHEMA)
95+
_COMPILED_VDOM_VALIDATOR: Callable = compile_json_schema(VDOM_JSON_SCHEMA) # type: ignore
9696

9797

9898
def validate_vdom_json(value: Any) -> VdomJson:
@@ -124,19 +124,7 @@ def is_vdom(value: Any) -> bool:
124124
)
125125

126126

127-
@overload
128-
def vdom(tag: str, *children: VdomChildren) -> VdomDict: ...
129-
130-
131-
@overload
132-
def vdom(tag: str, attributes: VdomAttributes, *children: VdomChildren) -> VdomDict: ...
133-
134-
135-
def vdom(
136-
tag: str,
137-
*attributes_and_children: Any,
138-
**kwargs: Any,
139-
) -> VdomDict:
127+
def vdom(tag: str, *attributes_and_children: VdomAttributes | VdomChildren) -> VdomDict:
140128
"""A helper function for creating VDOM elements.
141129
142130
Parameters:
@@ -157,33 +145,6 @@ def vdom(
157145
(subject to change) specifies javascript that, when evaluated returns a
158146
React component.
159147
"""
160-
if kwargs: # nocov
161-
if "key" in kwargs:
162-
if attributes_and_children:
163-
maybe_attributes, *children = attributes_and_children
164-
if _is_attributes(maybe_attributes):
165-
attributes_and_children = (
166-
{**maybe_attributes, "key": kwargs.pop("key")},
167-
*children,
168-
)
169-
else:
170-
attributes_and_children = (
171-
{"key": kwargs.pop("key")},
172-
maybe_attributes,
173-
*children,
174-
)
175-
else:
176-
attributes_and_children = ({"key": kwargs.pop("key")},)
177-
warn(
178-
"An element's 'key' must be declared in an attribute dict instead "
179-
"of as a keyword argument. This will error in a future version.",
180-
DeprecationWarning,
181-
)
182-
183-
if kwargs:
184-
msg = f"Extra keyword arguments {kwargs}"
185-
raise ValueError(msg)
186-
187148
model: VdomDict = {"tagName": tag}
188149

189150
if not attributes_and_children:

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.