Skip to content

Commit

Permalink
Refined inline types and added type guards to pass mypy --strict type…
Browse files Browse the repository at this point in the history
…checking
  • Loading branch information
thatfloflo committed Feb 12, 2023
1 parent 3ca09bd commit 65c1fe3
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 19 deletions.
23 changes: 17 additions & 6 deletions eel/__init__.py
Expand Up @@ -6,6 +6,7 @@

if TYPE_CHECKING:
from geventwebsocket.websocket import WebSocket
from eel.types import OptionsDictT

from gevent.threading import Timer
import gevent as gvt
Expand Down Expand Up @@ -41,7 +42,7 @@
_js_result_timeout: int = 10000

# All start() options must provide a default value and explanation here
_start_args: dict[str, Any] = {
_start_args: OptionsDictT = {
'mode': 'chrome', # What browser is used
'host': 'localhost', # Hostname use for Bottle server
'port': 8000, # Port used for Bottle server (use 0 for auto)
Expand Down Expand Up @@ -156,6 +157,8 @@ def start(*start_urls: str, **kwargs: Any) -> None:

if _start_args['jinja_templates'] != None:
from jinja2 import Environment, FileSystemLoader, select_autoescape
if not isinstance(_start_args['jinja_templates'], str):
raise TypeError("'jinja_templates start_arg/option must be of type str'")
templates_path = os.path.join(root_path, _start_args['jinja_templates'])
_start_args['jinja_env'] = Environment(loader=FileSystemLoader(templates_path),
autoescape=select_autoescape(['html', 'xml']))
Expand All @@ -172,6 +175,8 @@ def run_lambda() -> None:
if _start_args['all_interfaces'] == True:
HOST = '0.0.0.0'
else:
if not isinstance(_start_args['host'], str):
raise TypeError("'host' start_arg/option must be of type str")
HOST = _start_args['host']

app = _start_args['app']
Expand Down Expand Up @@ -200,7 +205,7 @@ def show(*start_urls: str) -> None:


def sleep(seconds: int | float) -> None:
gvt.sleep(seconds) # type: ignore # gevent docs specify int | float, available stubs are wrong
gvt.sleep(seconds)


def spawn(function: Callable[..., Any], *args: Any, **kwargs: Any) -> gvt.Greenlet:
Expand All @@ -222,15 +227,19 @@ def _eel() -> str:
return page

def _root() -> Optional[btl.Response]:
if not isinstance(_start_args['default_path'], str):
raise TypeError("'default_path' start_arg/option must be of type str")
return _static(_start_args['default_path'])

def _static(path: str) -> Optional[btl.HTTPResponse | btl.HTTPError]:
response = None
if 'jinja_env' in _start_args and 'jinja_templates' in _start_args:
if not isinstance(_start_args['jinja_templates'], str):
raise TypeError("'jinja_templates' start_arg/option must be of type str")
template_prefix = _start_args['jinja_templates'] + '/'
if path.startswith(template_prefix):
n = len(template_prefix)
template = _start_args['jinja_env'].get_template(path[n:])
template = _start_args['jinja_env'].get_template(path[n:]) # type: ignore # depends on conditional import in start()
response = btl.HTTPResponse(template.render())

if response is None:
Expand Down Expand Up @@ -291,7 +300,7 @@ def _safe_json(obj: Any) -> str:
return jsn.dumps(obj, default=lambda o: None)


def _repeated_send(ws: WebSocket, msg: str):
def _repeated_send(ws: WebSocket, msg: str) -> None:
for attempt in range(100):
try:
ws.send(msg)
Expand Down Expand Up @@ -401,16 +410,18 @@ def _websocket_close(page: str) -> None:
close_callback = _start_args.get('close_callback')

if close_callback is not None:
if not callable(close_callback):
raise TypeError("'close_callback' start_arg/option must be callable or None")
sockets = [p for _, p in _websockets]
close_callback(page, sockets)
else:
if _shutdown:
if isinstance(_shutdown, gvt.Greenlet):
_shutdown.kill()

_shutdown = gvt.spawn_later(_start_args['shutdown_delay'], _detect_shutdown)


def _set_response_headers(response: btl.Response):
def _set_response_headers(response: btl.Response) -> None:
if _start_args['disable_cache']:
# https://stackoverflow.com/a/24748094/280852
response.set_header('Cache-Control', 'no-store')
19 changes: 14 additions & 5 deletions eel/browsers.py
Expand Up @@ -4,6 +4,7 @@
from typing import List, Dict, Iterable, Optional
from types import ModuleType

from eel.types import OptionsDictT
import eel.chrome as chm
import eel.electron as ele
import eel.edge as edge
Expand All @@ -16,20 +17,24 @@
'edge': edge}


def _build_url_from_dict(page: Dict[str, str], options: Dict[str, str]) -> str:
def _build_url_from_dict(page: Dict[str, str], options: OptionsDictT) -> str:
scheme = page.get('scheme', 'http')
host = page.get('host', 'localhost')
port = page.get('port', options["port"])
path = page.get('path', '')
if not isinstance(port, (int, str)):
raise TypeError("'port' option must be an integer")
return '%s://%s:%d/%s' % (scheme, host, int(port), path)


def _build_url_from_string(page: str, options: Dict[str, str]) -> str:
def _build_url_from_string(page: str, options: OptionsDictT) -> str:
if not isinstance(options['port'], (int, str)):
raise TypeError("'port' option must be an integer")
base_url = 'http://%s:%d/' % (options['host'], int(options['port']))
return base_url + page


def _build_urls(start_pages: Iterable[str | Dict[str, str]], options: Dict[str, str]) -> List[str]:
def _build_urls(start_pages: Iterable[str | Dict[str, str]], options: OptionsDictT) -> List[str]:
urls: List[str] = []

for page in start_pages:
Expand All @@ -42,16 +47,20 @@ def _build_urls(start_pages: Iterable[str | Dict[str, str]], options: Dict[str,
return urls


def open(start_pages: Iterable[str | Dict[str, str]], options: Dict[str, str]) -> None:
def open(start_pages: Iterable[str | Dict[str, str]], options: OptionsDictT) -> None:
# Build full URLs for starting pages (including host and port)
start_urls = _build_urls(start_pages, options)

mode = options.get('mode')
if mode in [None, False]:
if not isinstance(mode, (str, bool, type(None))) or mode is True:
raise TypeError("'mode' option must by either a string, False, or None")
if mode is None or mode is False:
# Don't open a browser
pass
elif mode == 'custom':
# Just run whatever command the user provided
if not isinstance(options['cmdline_args'], list):
raise TypeError("'cmdline_args' option must be of type List[str]")
sps.Popen(options['cmdline_args'],
stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE)
elif mode in _browser_modules:
Expand Down
10 changes: 7 additions & 3 deletions eel/chrome.py
@@ -1,12 +1,16 @@
from __future__ import annotations
import sys, subprocess as sps, os
from typing import Dict, List, Any, Optional
from typing import List, Optional

from eel.types import OptionsDictT

# Every browser specific module must define run(), find_path() and name like this

name: str = 'Google Chrome/Chromium'

def run(path: str, options: Dict[str, Any], start_urls: List[str]) -> None:
def run(path: str, options: OptionsDictT, start_urls: List[str]) -> None:
if not isinstance(options['cmdline_args'], list):
raise TypeError("'cmdline_args' option must be of type List[str]")
if options['app_mode']:
for url in start_urls:
sps.Popen([path, '--app=%s' % url] +
Expand Down Expand Up @@ -63,7 +67,7 @@ def _find_chrome_linux() -> Optional[str]:
for name in chrome_names:
chrome = wch.which(name)
if chrome is not None:
return chrome
return chrome # type: ignore # whichcraft doesn't currently have type hints
return None


Expand Down
6 changes: 4 additions & 2 deletions eel/edge.py
Expand Up @@ -2,12 +2,14 @@
import platform
import subprocess as sps
import sys
from typing import List, Dict, Any
from typing import List

from eel.types import OptionsDictT

name: str = 'Edge'


def run(_path: str, options: Dict[str, Any], start_urls: List[str]) -> None:
def run(_path: str, options: OptionsDictT, start_urls: List[str]) -> None:
cmd = 'start microsoft-edge:{}'.format(start_urls[0])
sps.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sps.PIPE, shell=True)

Expand Down
10 changes: 7 additions & 3 deletions eel/electron.py
Expand Up @@ -3,11 +3,15 @@
import os
import subprocess as sps
import whichcraft as wch
from typing import List, Dict, Any, Optional
from typing import List, Optional

from eel.types import OptionsDictT

name: str = 'Electron'

def run(path: str, options: Dict[str, Any], start_urls: List[str]):
def run(path: str, options: OptionsDictT, start_urls: List[str]) -> None:
if not isinstance(options['cmdline_args'], list):
raise TypeError("'cmdline_args' option must be of type List[str]")
cmd = [path] + options['cmdline_args']
cmd += ['.', ';'.join(start_urls)]
sps.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sps.PIPE)
Expand All @@ -20,7 +24,7 @@ def find_path() -> Optional[str]:
return os.path.join(bat_path, r'..\node_modules\electron\dist\electron.exe')
elif sys.platform in ['darwin', 'linux']:
# This should work find...
return wch.which('electron')
return wch.which('electron') # type: ignore # whichcraft doesn't currently have type hints
else:
return None

Empty file added eel/py.typed
Empty file.
15 changes: 15 additions & 0 deletions eel/types.py
@@ -0,0 +1,15 @@
from __future__ import annotations
from typing import Dict, List, Tuple, Callable, Optional, Any, TYPE_CHECKING

if TYPE_CHECKING:
from jinja2 import Environment
Jinja2Environment = Environment
else:
Jinja2Environment = None

OptionsDictT = Dict[
str,
Optional[
str | bool | int | float | List[str] | Tuple[int, int] | Dict[str, Tuple[int, int]] | Callable[..., Any] | Jinja2Environment
]
]

0 comments on commit 65c1fe3

Please sign in to comment.