Skip to content

V2-migrate-mypy-to-pyright #1274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,16 @@ serve = [
[tool.hatch.envs.python]
extra-dependencies = [
"reactpy[all]",
"ruff",
"toml",
"mypy==1.8",
"pyright",
"types-toml",
"types-click",
"types-requests",
"types-lxml",
"jsonpointer",
]

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

############################
# >>> Hatch JS Scripts <<< #
Expand Down Expand Up @@ -218,12 +217,8 @@ publish_client = [
# >>> Generic Tools <<< #
#########################

[tool.mypy]
incremental = false
ignore_missing_imports = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
[tool.pyright]
reportIncompatibleVariableOverride = false

[tool.coverage.run]
source_pkgs = ["reactpy"]
Expand Down
1 change: 1 addition & 0 deletions src/reactpy/_console/ast_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pyright: reportAttributeAccessIssue=false
from __future__ import annotations

import ast
Expand Down
4 changes: 2 additions & 2 deletions src/reactpy/_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from functools import wraps
from inspect import currentframe
from types import FrameType
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast
from warnings import warn as _warn


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


if TYPE_CHECKING:
warn = _warn
warn = cast(Any, _warn)


def _frame_depth_in_module() -> int:
Expand Down
8 changes: 6 additions & 2 deletions src/reactpy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ def boolean(value: str | bool | int) -> bool:
- :data:`REACTPY_CHECK_JSON_ATTRS`
"""

REACTPY_CHECK_VDOM_SPEC = Option("REACTPY_CHECK_VDOM_SPEC", parent=REACTPY_DEBUG)
REACTPY_CHECK_VDOM_SPEC = Option(
"REACTPY_CHECK_VDOM_SPEC", parent=REACTPY_DEBUG, validator=boolean
)
"""Checks which ensure VDOM is rendered to spec

For more info on the VDOM spec, see here: :ref:`VDOM JSON Schema`
"""

REACTPY_CHECK_JSON_ATTRS = Option("REACTPY_CHECK_JSON_ATTRS", parent=REACTPY_DEBUG)
REACTPY_CHECK_JSON_ATTRS = Option(
"REACTPY_CHECK_JSON_ATTRS", parent=REACTPY_DEBUG, validator=boolean
)
"""Checks that all VDOM attributes are JSON serializable

The VDOM spec is not able to enforce this on its own since attributes could anything.
Expand Down
20 changes: 7 additions & 13 deletions src/reactpy/core/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ def use_state(initial_value: _Type | Callable[[], _Type]) -> State[_Type]:
A tuple containing the current state and a function to update it.
"""
current_state = _use_const(lambda: _CurrentState(initial_value))
return State(current_state.value, current_state.dispatch)

# FIXME: Not sure why this type hint is not being inferred correctly when using pyright
return State(current_state.value, current_state.dispatch) # type: ignore


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

def dispatch(new: _Type | Callable[[_Type], _Type]) -> None:
if callable(new):
next_value = new(self.value)
else:
next_value = new
next_value = new(self.value) if callable(new) else new # type: ignore
if not strictly_equal(next_value, self.value):
self.value = next_value
hook.schedule_render()
Expand Down Expand Up @@ -338,9 +337,9 @@ def use_connection() -> Connection[Any]:
return conn


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


def use_location() -> Location:
Expand Down Expand Up @@ -511,8 +510,6 @@ def use_memo(
else:
changed = False

setup: Callable[[Callable[[], _Type]], _Type]

if changed:

def setup(function: Callable[[], _Type]) -> _Type:
Expand All @@ -524,10 +521,7 @@ def setup(function: Callable[[], _Type]) -> _Type:
def setup(function: Callable[[], _Type]) -> _Type:
return memo.value

if function is not None:
return setup(function)
else:
return setup
return setup(function) if function is not None else setup


class _Memo(Generic[_Type]):
Expand Down
28 changes: 16 additions & 12 deletions src/reactpy/core/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from collections.abc import Sequence
from contextlib import AsyncExitStack
from logging import getLogger
from types import TracebackType
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -56,13 +57,13 @@ class Layout:
"""Responsible for "rendering" components. That is, turning them into VDOM."""

__slots__: tuple[str, ...] = (
"root",
"_event_handlers",
"_rendering_queue",
"_model_states_by_life_cycle_state_id",
"_render_tasks",
"_render_tasks_ready",
"_rendering_queue",
"_root_life_cycle_state_id",
"_model_states_by_life_cycle_state_id",
"root",
)

if not hasattr(abc.ABC, "__weakref__"): # nocov
Expand All @@ -80,17 +81,17 @@ async def __aenter__(self) -> Layout:
self._event_handlers: EventHandlerDict = {}
self._render_tasks: set[Task[LayoutUpdateMessage]] = set()
self._render_tasks_ready: Semaphore = Semaphore(0)

self._rendering_queue: _ThreadSafeQueue[_LifeCycleStateId] = _ThreadSafeQueue()
root_model_state = _new_root_model_state(self.root, self._schedule_render_task)

self._root_life_cycle_state_id = root_id = root_model_state.life_cycle_state.id
self._model_states_by_life_cycle_state_id = {root_id: root_model_state}
self._schedule_render_task(root_id)

return self

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

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

async def deliver(self, event: LayoutEventMessage) -> None:
async def deliver(self, event: LayoutEventMessage | dict[str, Any]) -> None:
"""Dispatch an event to the targeted handler"""
# It is possible for an element in the frontend to produce an event
# associated with a backend model that has been deleted. We only handle
Expand Down Expand Up @@ -217,7 +218,7 @@ async def _render_component(
parent.children_by_key[key] = new_state
# need to add this model to parent's children without mutating parent model
old_parent_model = parent.model.current
old_parent_children = old_parent_model["children"]
old_parent_children = old_parent_model.setdefault("children", [])
parent.model.current = {
**old_parent_model,
"children": [
Expand Down Expand Up @@ -318,8 +319,11 @@ async def _render_model_children(
new_state: _ModelState,
raw_children: Any,
) -> None:
if not isinstance(raw_children, (list, tuple)):
raw_children = [raw_children]
if not isinstance(raw_children, list):
if isinstance(raw_children, tuple):
raw_children = list(raw_children)
else:
raw_children = [raw_children]

if old_state is None:
if raw_children:
Expand Down Expand Up @@ -609,7 +613,7 @@ def __init__(
parent: _ModelState | None,
index: int,
key: Any,
model: Ref[VdomJson],
model: Ref[VdomJson | dict[str, Any]],
patch_path: str,
children_by_key: dict[Key, _ModelState],
targets_by_event: dict[str, str],
Expand Down Expand Up @@ -656,7 +660,7 @@ def parent(self) -> _ModelState:
return parent

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

def __repr__(self) -> str: # nocov
return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })"
Expand Down
19 changes: 13 additions & 6 deletions src/reactpy/core/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Awaitable
from logging import getLogger
from typing import Callable
from typing import Any, Callable
from warnings import warn

from anyio import create_task_group
Expand All @@ -14,10 +14,10 @@
logger = getLogger(__name__)


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

RecvCoroutine = Callable[[], Awaitable[LayoutEventMessage]]
RecvCoroutine = Callable[[], Awaitable[LayoutEventMessage | dict[str, Any]]]
"""Called by a dispatcher to return a :class:`reactpy.core.layout.LayoutEventMessage`

The event will then trigger an :class:`reactpy.core.proto.EventHandlerType` in a layout.
Expand All @@ -35,7 +35,9 @@ class Stop(BaseException):


async def serve_layout(
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
layout: LayoutType[
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
],
send: SendCoroutine,
recv: RecvCoroutine,
) -> None:
Expand All @@ -55,7 +57,10 @@ async def serve_layout(


async def _single_outgoing_loop(
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], send: SendCoroutine
layout: LayoutType[
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
],
send: SendCoroutine,
) -> None:
while True:
update = await layout.render()
Expand All @@ -74,7 +79,9 @@ async def _single_outgoing_loop(

async def _single_incoming_loop(
task_group: TaskGroup,
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
layout: LayoutType[
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
],
recv: RecvCoroutine,
) -> None:
while True:
Expand Down
45 changes: 3 additions & 42 deletions src/reactpy/core/vdom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
from collections.abc import Mapping, Sequence
from functools import wraps
from typing import Any, Protocol, cast, overload
from typing import Any, Callable, Protocol, cast

from fastjsonschema import compile as compile_json_schema

Expand Down Expand Up @@ -92,7 +92,7 @@


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


def validate_vdom_json(value: Any) -> VdomJson:
Expand Down Expand Up @@ -124,19 +124,7 @@ def is_vdom(value: Any) -> bool:
)


@overload
def vdom(tag: str, *children: VdomChildren) -> VdomDict: ...


@overload
def vdom(tag: str, attributes: VdomAttributes, *children: VdomChildren) -> VdomDict: ...


def vdom(
tag: str,
*attributes_and_children: Any,
**kwargs: Any,
) -> VdomDict:
def vdom(tag: str, *attributes_and_children: VdomAttributes | VdomChildren) -> VdomDict:
"""A helper function for creating VDOM elements.

Parameters:
Expand All @@ -157,33 +145,6 @@ def vdom(
(subject to change) specifies javascript that, when evaluated returns a
React component.
"""
if kwargs: # nocov
if "key" in kwargs:
if attributes_and_children:
maybe_attributes, *children = attributes_and_children
if _is_attributes(maybe_attributes):
attributes_and_children = (
{**maybe_attributes, "key": kwargs.pop("key")},
*children,
)
else:
attributes_and_children = (
{"key": kwargs.pop("key")},
maybe_attributes,
*children,
)
else:
attributes_and_children = ({"key": kwargs.pop("key")},)
warn(
"An element's 'key' must be declared in an attribute dict instead "
"of as a keyword argument. This will error in a future version.",
DeprecationWarning,
)

if kwargs:
msg = f"Extra keyword arguments {kwargs}"
raise ValueError(msg)

model: VdomDict = {"tagName": tag}

if not attributes_and_children:
Expand Down
Loading