Skip to content

Commit 3b821a7

Browse files
committed
fix misc
- various type annotation fixes - servers do not have a general _run_dispatcher anymore as a result we need a new way to capture the last_server_error - the solution is to use logs
1 parent 7ceb3c7 commit 3b821a7

File tree

13 files changed

+80
-111
lines changed

13 files changed

+80
-111
lines changed

idom/dialect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pyalect import DialectError, Dialect
66

77

8-
class HtmlDialectTranspiler(Dialect, name="html"):
8+
class HtmlDialectTranspiler(Dialect, name="html"): # type: ignore
99
"""An HTML dialect transpiler for Python."""
1010

1111
def __init__(self, filename: Optional[str] = None):

idom/server/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def register(self: _Self, app: Optional[_App]) -> _Self:
7474
self._app = app
7575
return self
7676

77-
def wait_until_server_start(self, timeout: float = 3.0):
77+
def wait_until_server_start(self, timeout: float = 3.0) -> None:
7878
"""Block until the underlying application has started"""
7979
if not self._server_did_start.wait(timeout=timeout):
8080
raise RuntimeError( # pragma: no cover

idom/server/flask.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,17 @@ def stop(self, timeout: Optional[float] = None) -> None:
5050
server.stop(timeout)
5151

5252
def _create_config(self, config: Optional[Config]) -> Config:
53-
return Config(
54-
{
55-
"import_name": __name__,
56-
"url_prefix": "",
57-
"cors": False,
58-
"serve_static_files": True,
59-
"redirect_root_to_index": True,
60-
**(config or {}),
61-
}
62-
)
53+
new_config: Config = {
54+
"import_name": __name__,
55+
"url_prefix": "",
56+
"cors": False,
57+
"serve_static_files": True,
58+
"redirect_root_to_index": True,
59+
}
60+
if config is not None:
61+
# BUG: https://github.com/python/mypy/issues/6462
62+
new_config.update(config) # type: ignore
63+
return new_config
6364

6465
def _default_application(self, config: Config) -> Flask:
6566
return Flask(config["import_name"])

idom/server/prefab.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def run(
5656
server.register(app)
5757

5858
run_server = server.run if not daemon else server.daemon
59-
run_server(host, port, **(run_kwargs or {}))
59+
run_server(host, port, **(run_kwargs or {})) # type: ignore
6060

6161
return server
6262

idom/server/sanic.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Config(TypedDict, total=False):
2828

2929
cors: Union[bool, Dict[str, Any]]
3030
url_prefix: str
31-
server_static_files: bool
31+
serve_static_files: bool
3232
redirect_root_to_index: bool
3333

3434

@@ -43,15 +43,15 @@ def stop(self) -> None:
4343
self._loop.call_soon_threadsafe(self.application.stop)
4444

4545
def _create_config(self, config: Optional[Config]) -> Config:
46-
return Config(
47-
{
48-
"cors": False,
49-
"url_prefix": "",
50-
"serve_static_files": True,
51-
"redirect_root_to_index": True,
52-
**(config or {}),
53-
}
54-
)
46+
new_config: Config = {
47+
"cors": False,
48+
"url_prefix": "",
49+
"serve_static_files": True,
50+
"redirect_root_to_index": True,
51+
}
52+
if config is not None:
53+
new_config.update(config)
54+
return new_config
5555

5656
def _default_application(self, config: Config) -> Sanic:
5757
return Sanic()

idom/server/tornado.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from asyncio import Queue as AsyncQueue
66
from typing import Optional, Type, Any, List, Tuple, Dict
77

8-
from tornado.web import Application, StaticFileHandler, RedirectHandler
8+
from tornado.web import Application, StaticFileHandler, RedirectHandler, RequestHandler
99
from tornado.websocket import WebSocketHandler
1010
from tornado.platform.asyncio import AsyncIOMainLoop
1111
from typing_extensions import TypedDict
@@ -18,7 +18,10 @@
1818
from .base import AbstractRenderServer
1919

2020

21-
class Config(TypedDict):
21+
_RouteHandlerSpecs = List[Tuple[str, Type[RequestHandler], Any]]
22+
23+
24+
class Config(TypedDict, total=False):
2225
"""Render server config for :class:`TornadoRenderServer` subclasses"""
2326

2427
base_url: str
@@ -31,7 +34,7 @@ class TornadoRenderServer(AbstractRenderServer[Application, Config]):
3134

3235
_model_stream_handler_type: Type[WebSocketHandler]
3336

34-
def stop(self):
37+
def stop(self) -> None:
3538
try:
3639
loop = self._loop
3740
except AttributeError: # pragma: no cover
@@ -42,14 +45,14 @@ def stop(self):
4245
loop.call_soon_threadsafe(self._loop.stop)
4346

4447
def _create_config(self, config: Optional[Config]) -> Config:
45-
return Config(
46-
{
47-
"base_url": "",
48-
"serve_static_files": True,
49-
"redirect_root_to_index": True,
50-
**(config or {}),
51-
}
52-
)
48+
new_config: Config = {
49+
"base_url": "",
50+
"serve_static_files": True,
51+
"redirect_root_to_index": True,
52+
}
53+
if config is not None:
54+
new_config.update(config)
55+
return new_config
5356

5457
def _default_application(self, config: Config) -> Application:
5558
return Application()
@@ -73,8 +76,8 @@ def _setup_application_did_start_event(
7376
) -> None:
7477
pass
7578

76-
def _create_route_handlers(self, config: Config) -> List[Tuple[Any, ...]]:
77-
handlers = [
79+
def _create_route_handlers(self, config: Config) -> _RouteHandlerSpecs:
80+
handlers: _RouteHandlerSpecs = [
7881
(
7982
"/stream",
8083
self._model_stream_handler_type,
@@ -119,18 +122,18 @@ def _run_application_in_thread(
119122
self._run_application(config, app, host, port, args, kwargs)
120123

121124

122-
class PerClientStateModelStreamHandler(WebSocketHandler):
125+
class PerClientStateModelStreamHandler(WebSocketHandler): # type: ignore
123126
"""A web-socket handler that serves up a new model stream to each new client"""
124127

125128
_dispatcher_type: Type[AbstractDispatcher] = SingleViewDispatcher
126129
_dispatcher_inst: AbstractDispatcher
127-
_message_queue: AsyncQueue
130+
_message_queue: "AsyncQueue[str]"
128131

129132
def initialize(self, component_constructor: ComponentConstructor) -> None:
130133
self._component_constructor = component_constructor
131134

132-
async def open(self):
133-
message_queue = AsyncQueue()
135+
async def open(self) -> None:
136+
message_queue: "AsyncQueue[str]" = AsyncQueue()
134137
query_params = {k: v[0].decode() for k, v in self.request.arguments.items()}
135138
dispatcher = self._dispatcher_type(
136139
Layout(self._component_constructor(**query_params))
@@ -154,7 +157,7 @@ async def run() -> None:
154157
async def on_message(self, message: str) -> None:
155158
await self._message_queue.put(message)
156159

157-
def on_close(self):
160+
def on_close(self) -> None:
158161
asyncio.ensure_future(self._dispatcher_inst.__aexit__(None, None, None))
159162

160163

idom/server/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def find_builtin_server_type(type_name: str) -> Type[Any]:
2424

2525
for builtin_module in installed_builtin_modules:
2626
try:
27-
return getattr(builtin_module, type_name)
27+
return getattr(builtin_module, type_name) # type: ignore
2828
except AttributeError: # pragma: no cover
2929
pass
3030
else: # pragma: no cover

idom/testing.py

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from urllib.parse import urlunparse, urlencode
2-
from contextlib import contextmanager
32
from typing import (
43
Callable,
54
Tuple,
6-
Iterator,
75
Type,
86
Optional,
97
Any,
@@ -20,7 +18,6 @@
2018
from idom.server.base import AbstractRenderServer
2119
from idom.server.prefab import hotswap_server
2220
from idom.server.utils import find_available_port
23-
from idom.utils import Ref
2421

2522

2623
__all__ = [
@@ -55,7 +52,7 @@ def create_simple_selenium_web_driver(
5552

5653
class ServerMountPoint(Generic[_Mount, _Server]):
5754

58-
__slots__ = "server", "host", "port", "_mount", "__weakref__"
55+
__slots__ = "server", "host", "port", "mount", "__weakref__"
5956

6057
def __init__(
6158
self,
@@ -64,32 +61,23 @@ def __init__(
6461
port: Optional[int] = None,
6562
server_config: Optional[Any] = None,
6663
run_kwargs: Optional[Dict[str, Any]] = None,
67-
mount_and_server_constructor: "Callable[..., Tuple[_Mount, _Server]]" = hotswap_server,
64+
mount_and_server_constructor: "Callable[..., Tuple[_Mount, _Server]]" = hotswap_server, # type: ignore
6865
app: Optional[Any] = None,
6966
**other_options: Any,
7067
):
7168
self.host = host
7269
self.port = port or find_available_port(host)
73-
self._mount, server = mount_and_server_constructor(
74-
(
75-
server_type
76-
if issubclass(server_type, _RenderServerWithLastError)
77-
else type(
78-
server_type.__name__,
79-
(_RenderServerWithLastError, server_type),
80-
{"last_server_error_for_idom_testing": Ref(None)},
81-
)
82-
),
70+
self.mount, self.server = mount_and_server_constructor(
71+
server_type,
8372
self.host,
8473
self.port,
8574
server_config,
8675
run_kwargs,
8776
app,
8877
**other_options,
8978
)
90-
self.server = server
9179
# stop server once mount is done being used
92-
finalize(self, server.stop)
80+
finalize(self, self.server.stop)
9381

9482
def url(self, path: str = "", query: Optional[Any] = None) -> str:
9583
return urlunparse(
@@ -102,25 +90,3 @@ def url(self, path: str = "", query: Optional[Any] = None) -> str:
10290
"",
10391
]
10492
)
105-
106-
@contextmanager
107-
def open_mount_function(self) -> Iterator[_Mount]:
108-
self.server.last_server_error_for_idom_testing.current = None
109-
try:
110-
yield self._mount
111-
finally:
112-
if self.server.last_server_error_for_idom_testing.current is not None:
113-
raise self.server.last_server_error_for_idom_testing.current # pragma: no cover
114-
115-
116-
class _RenderServerWithLastError(AnyRenderServer):
117-
"""A server that updates the ``last_server_error`` fixture"""
118-
119-
last_server_error_for_idom_testing: Ref[Optional[Exception]]
120-
121-
async def _run_dispatcher(self, *args: Any, **kwargs: Any) -> None:
122-
self.last_server_error_for_idom_testing.current = None
123-
try:
124-
await super()._run_dispatcher(*args, **kwargs)
125-
except Exception as e:
126-
self.last_server_error_for_idom_testing.current = e

noxfile.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ def test_with_coverage(session: Session) -> None:
2626
@nox.session
2727
def check_types(session: Session) -> None:
2828
session.install("-r", "requirements/check-types.txt")
29+
session.log("Check Packages (strict)")
2930
session.run("mypy", "--strict", "idom")
31+
session.log("Check Tests (lenient)")
3032
session.run("mypy", "tests")
3133

3234

tests/conftest.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ def pytest_addoption(parser: Parser) -> None:
4040

4141

4242
@pytest.fixture
43-
def display(driver, server_mount_point, mount):
43+
def display(driver, server_mount_point):
4444
display_id = idom.Ref(0)
4545

4646
def mount_and_display(component_constructor, query=None, check_mount=True):
4747
component_id = f"display-{display_id.set_current(display_id.current + 1)}"
48-
mount(lambda: idom.html.div({"id": component_id}, component_constructor()))
48+
server_mount_point.mount(
49+
lambda: idom.html.div({"id": component_id}, component_constructor())
50+
)
4951
driver.get(server_mount_point.url(query=query))
5052
if check_mount:
5153
driver.find_element_by_id(component_id)
@@ -59,17 +61,6 @@ def driver_get(driver, server_mount_point):
5961
return lambda query=None: driver.get(server_mount_point.url(query=query))
6062

6163

62-
@pytest.fixture
63-
def mount(server_mount_point):
64-
with server_mount_point.open_mount_function() as mount:
65-
yield mount
66-
67-
68-
@pytest.fixture
69-
def last_server_error(server_mount_point):
70-
return server_mount_point.server.last_server_error_for_idom_testing
71-
72-
7364
@pytest.fixture
7465
def server_mount_point():
7566
"""An IDOM layout mount function and server as a tuple
@@ -115,11 +106,13 @@ def driver_is_headless(pytestconfig: Config):
115106

116107
@pytest.fixture(autouse=True)
117108
def caplog(_caplog: LogCaptureFixture) -> Iterator[LogCaptureFixture]:
118-
119109
handler_id = logger.add(_PropogateHandler(), format="{message}")
120110
yield _caplog
121111
logger.remove(handler_id)
122-
assert not _caplog.record_tuples
112+
for record in _caplog.records:
113+
if record.exc_info:
114+
raise record.exc_info[1]
115+
assert record.levelno < logging.ERROR
123116

124117

125118
class _PropogateHandler(logging.Handler):
File renamed without changes.

tests/test_server/test_common/test_shared_state_client.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def server_mount_point(request):
2121
return ServerMountPoint(request.param, sync_views=True)
2222

2323

24-
def test_shared_client_state(create_driver, mount, server_mount_point):
24+
def test_shared_client_state(create_driver, server_mount_point):
2525
driver_1 = create_driver()
2626
driver_2 = create_driver()
2727
was_garbage_collected = Event()
@@ -46,7 +46,7 @@ def Counter(count):
4646
finalize(component, was_garbage_collected.set)
4747
return idom.html.div({"id": f"count-is-{count}"}, count)
4848

49-
mount(IncrCounter)
49+
server_mount_point.mount(IncrCounter)
5050

5151
driver_1.get(server_mount_point.url())
5252
driver_2.get(server_mount_point.url())
@@ -71,15 +71,17 @@ def Counter(count):
7171

7272

7373
def test_shared_client_state_server_does_not_support_per_client_parameters(
74-
driver_get, last_server_error
74+
driver_get, caplog
7575
):
7676
driver_get({"per_client_param": 1})
7777

78-
error = last_server_error.current
78+
for record in caplog.records:
79+
if record.exc_info and isinstance(record.exc_info[1], ValueError):
80+
assert "does not support per-client view parameters" in str(
81+
record.exc_info[1]
82+
)
83+
break
84+
else:
85+
assert False, "did not log error"
7986

80-
assert error is not None
81-
82-
assert isinstance(error, ValueError)
83-
assert "does not support per-client view parameters" in str(error)
84-
85-
last_server_error.current = None
87+
caplog.clear()

0 commit comments

Comments
 (0)