Skip to content

Commit 248d543

Browse files
committed
Moved type hints into .py and narrowed further
1 parent 88527c5 commit 248d543

2 files changed

Lines changed: 60 additions & 202 deletions

File tree

‎eel/__init__.py‎

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from __future__ import annotations
12
from builtins import range
23
import traceback
34
from io import open
5+
from typing import Any, Dict, List, Set, Tuple, Optional, Callable, TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from geventwebsocket.websocket import WebSocket
49

510
from gevent.threading import Timer
611
import gevent as gvt
@@ -18,24 +23,25 @@
1823
import mimetypes
1924

2025
mimetypes.add_type('application/javascript', '.js')
21-
_eel_js_file = pkg.resource_filename('eel', 'eel.js')
22-
_eel_js = open(_eel_js_file, encoding='utf-8').read()
23-
_websockets = []
24-
_call_return_values = {}
25-
_call_return_callbacks = {}
26-
_call_number = 0
27-
_exposed_functions = {}
28-
_js_functions = []
29-
_mock_queue = []
30-
_mock_queue_done = set()
31-
_shutdown = None
26+
_eel_js_file: str = pkg.resource_filename('eel', 'eel.js')
27+
_eel_js: str = open(_eel_js_file, encoding='utf-8').read()
28+
_websockets: List[Tuple[Any, WebSocket]] = []
29+
_call_return_values: Dict[Any, Any] = {}
30+
_call_return_callbacks: Dict[float, Tuple[Callable[..., Any], Optional[Callable[..., Any]]]] = {}
31+
_call_number: int = 0
32+
_exposed_functions: Dict[Any, Any] = {}
33+
_js_functions: List[Any] = []
34+
_mock_queue: List[Any] = []
35+
_mock_queue_done: Set[Any] = set()
36+
_shutdown: Optional[gvt.Greenlet] = None # Later assigned as global by _websocket_close()
37+
root_path: str # Later assigned as global by init()
3238

3339
# The maximum time (in milliseconds) that Python will try to retrieve a return value for functions executing in JS
3440
# Can be overridden through `eel.init` with the kwarg `js_result_timeout` (default: 10000)
35-
_js_result_timeout = 10000
41+
_js_result_timeout: int = 10000
3642

3743
# All start() options must provide a default value and explanation here
38-
_start_args = {
44+
_start_args: dict[str, Any] = {
3945
'mode': 'chrome', # What browser is used
4046
'host': 'localhost', # Hostname use for Bottle server
4147
'port': 8000, # Port used for Bottle server (use 0 for auto)
@@ -51,12 +57,12 @@
5157
'disable_cache': True, # Sets the no-store response header when serving assets
5258
'default_path': 'index.html', # The default file to retrieve for the root URL
5359
'app': btl.default_app(), # Allows passing in a custom Bottle instance, e.g. with middleware
54-
'shutdown_delay': 1.0 # how long to wait after a websocket closes before detecting complete shutdown
60+
'shutdown_delay': 1.0 # how long to wait after a websocket closes before detecting complete shutdown
5561
}
5662

5763
# == Temporary (suppressible) error message to inform users of breaking API change for v1.0.0 ===
5864
_start_args['suppress_error'] = False
59-
api_error_message = '''
65+
api_error_message: str = '''
6066
----------------------------------------------------------------------------------
6167
'options' argument deprecated in v1.0.0, see https://github.com/ChrisKnott/Eel
6268
To suppress this error, add 'suppress_error=True' to start() call.
@@ -67,15 +73,15 @@
6773

6874
# Public functions
6975

70-
def expose(name_or_function=None):
76+
def expose(name_or_function: Optional[Callable[..., Any]] = None) -> Callable[..., Any]:
7177
# Deal with '@eel.expose()' - treat as '@eel.expose'
7278
if name_or_function is None:
7379
return expose
7480

75-
if type(name_or_function) == str: # Called as '@eel.expose("my_name")'
81+
if isinstance(name_or_function, str): # Called as '@eel.expose("my_name")'
7682
name = name_or_function
7783

78-
def decorator(function):
84+
def decorator(function: Callable[..., Any]) -> Any:
7985
_expose(name, function)
8086
return function
8187
return decorator
@@ -87,7 +93,7 @@ def decorator(function):
8793

8894
# PyParsing grammar for parsing exposed functions in JavaScript code
8995
# Examples: `eel.expose(w, "func_name")`, `eel.expose(func_name)`, `eel.expose((function (e){}), "func_name")`
90-
EXPOSED_JS_FUNCTIONS = pp.ZeroOrMore(
96+
EXPOSED_JS_FUNCTIONS: pp.ZeroOrMore = pp.ZeroOrMore(
9197
pp.Suppress(
9298
pp.SkipTo(pp.Literal('eel.expose('))
9399
+ pp.Literal('eel.expose(')
@@ -101,8 +107,8 @@ def decorator(function):
101107
)
102108

103109

104-
def init(path, allowed_extensions=['.js', '.html', '.txt', '.htm',
105-
'.xhtml', '.vue'], js_result_timeout=10000):
110+
def init(path: str, allowed_extensions: List[str] = ['.js', '.html', '.txt', '.htm',
111+
'.xhtml', '.vue'], js_result_timeout: int = 10000) -> None:
106112
global root_path, _js_functions, _js_result_timeout
107113
root_path = _get_real_path(path)
108114

@@ -133,7 +139,7 @@ def init(path, allowed_extensions=['.js', '.html', '.txt', '.htm',
133139
_js_result_timeout = js_result_timeout
134140

135141

136-
def start(*start_urls, **kwargs):
142+
def start(*start_urls: str, **kwargs: Any) -> None:
137143
_start_args.update(kwargs)
138144

139145
if 'options' in kwargs:
@@ -162,25 +168,25 @@ def start(*start_urls, **kwargs):
162168
# Launch the browser to the starting URLs
163169
show(*start_urls)
164170

165-
def run_lambda():
171+
def run_lambda() -> None:
166172
if _start_args['all_interfaces'] == True:
167173
HOST = '0.0.0.0'
168174
else:
169175
HOST = _start_args['host']
170176

171-
app = _start_args['app'] # type: btl.Bottle
177+
app = _start_args['app']
172178

173179
if isinstance(app, btl.Bottle):
174180
register_eel_routes(app)
175181
else:
176182
register_eel_routes(btl.default_app())
177183

178-
return btl.run(
184+
btl.run(
179185
host=HOST,
180186
port=_start_args['port'],
181187
server=wbs.GeventWebSocketServer,
182188
quiet=True,
183-
app=app)
189+
app=app) # Always returns None
184190

185191
# Start the webserver
186192
if _start_args['block']:
@@ -189,20 +195,20 @@ def run_lambda():
189195
spawn(run_lambda)
190196

191197

192-
def show(*start_urls):
193-
brw.open(start_urls, _start_args)
198+
def show(*start_urls: str) -> None:
199+
brw.open(list(start_urls), _start_args)
194200

195201

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

199205

200-
def spawn(function, *args, **kwargs):
206+
def spawn(function: Callable[..., Any], *args: Any, **kwargs: Any) -> gvt.Greenlet:
201207
return gvt.spawn(function, *args, **kwargs)
202208

203209
# Bottle Routes
204210

205-
def _eel():
211+
def _eel() -> str:
206212
start_geometry = {'default': {'size': _start_args['size'],
207213
'position': _start_args['position']},
208214
'pages': _start_args['geometry']}
@@ -215,10 +221,10 @@ def _eel():
215221
_set_response_headers(btl.response)
216222
return page
217223

218-
def _root():
224+
def _root() -> Optional[btl.Response]:
219225
return _static(_start_args['default_path'])
220226

221-
def _static(path):
227+
def _static(path: str) -> Optional[btl.HTTPResponse | btl.HTTPError]:
222228
response = None
223229
if 'jinja_env' in _start_args and 'jinja_templates' in _start_args:
224230
template_prefix = _start_args['jinja_templates'] + '/'
@@ -233,7 +239,7 @@ def _static(path):
233239
_set_response_headers(response)
234240
return response
235241

236-
def _websocket(ws):
242+
def _websocket(ws: WebSocket) -> None:
237243
global _websockets
238244

239245
for js_function in _js_functions:
@@ -259,14 +265,14 @@ def _websocket(ws):
259265
_websocket_close(page)
260266

261267

262-
BOTTLE_ROUTES = {
268+
BOTTLE_ROUTES: Dict[str, Tuple[Callable[..., Any], dict[Any, Any]]] = {
263269
"/eel.js": (_eel, dict()),
264270
"/": (_root, dict()),
265271
"/<path:path>": (_static, dict()),
266272
"/eel": (_websocket, dict(apply=[wbs.websocket]))
267273
}
268274

269-
def register_eel_routes(app):
275+
def register_eel_routes(app: btl.Bottle) -> None:
270276
'''
271277
Adds eel routes to `app`. Only needed if you are passing something besides `bottle.Bottle` to `eel.start()`.
272278
Ex:
@@ -281,11 +287,11 @@ def register_eel_routes(app):
281287

282288
# Private functions
283289

284-
def _safe_json(obj):
290+
def _safe_json(obj: Any) -> str:
285291
return jsn.dumps(obj, default=lambda o: None)
286292

287293

288-
def _repeated_send(ws, msg):
294+
def _repeated_send(ws: WebSocket, msg: str):
289295
for attempt in range(100):
290296
try:
291297
ws.send(msg)
@@ -294,7 +300,7 @@ def _repeated_send(ws, msg):
294300
sleep(0.001)
295301

296302

297-
def _process_message(message, ws):
303+
def _process_message(message: dict[str, Any], ws: WebSocket) -> None:
298304
if 'call' in message:
299305
error_info = {}
300306
try:
@@ -326,47 +332,48 @@ def _process_message(message, ws):
326332
print('Invalid message received: ', message)
327333

328334

329-
def _get_real_path(path):
335+
def _get_real_path(path: str) -> str:
330336
if getattr(sys, 'frozen', False):
331-
return os.path.join(sys._MEIPASS, path)
337+
return os.path.join(sys._MEIPASS, path) # type: ignore # sys._MEIPASS is dynamically added by PyInstaller
332338
else:
333339
return os.path.abspath(path)
334340

335341

336-
def _mock_js_function(f):
342+
def _mock_js_function(f: str) -> None:
337343
exec('%s = lambda *args: _mock_call("%s", args)' % (f, f), globals())
338344

339345

340-
def _import_js_function(f):
346+
def _import_js_function(f: str) -> None:
341347
exec('%s = lambda *args: _js_call("%s", args)' % (f, f), globals())
342348

343349

344-
def _call_object(name, args):
350+
def _call_object(name: str, args: Any) -> Dict[str, Any]:
345351
global _call_number
346352
_call_number += 1
347353
call_id = _call_number + rnd.random()
348354
return {'call': call_id, 'name': name, 'args': args}
349355

350356

351-
def _mock_call(name, args):
357+
def _mock_call(name: str, args: Any) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
352358
call_object = _call_object(name, args)
353359
global _mock_queue
354360
_mock_queue += [call_object]
355361
return _call_return(call_object)
356362

357363

358-
def _js_call(name, args):
364+
def _js_call(name: str, args: Any) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
359365
call_object = _call_object(name, args)
360366
for _, ws in _websockets:
361367
_repeated_send(ws, _safe_json(call_object))
362368
return _call_return(call_object)
363369

364370

365-
def _call_return(call):
371+
def _call_return(call: dict[str, Any]) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
366372
global _js_result_timeout
367373
call_id = call['call']
368374

369-
def return_func(callback=None, error_callback=None):
375+
def return_func(callback: Optional[Callable[..., Any]] = None,
376+
error_callback: Optional[Callable[..., Any]] = None) -> Any:
370377
if callback is not None:
371378
_call_return_callbacks[call_id] = (callback, error_callback)
372379
else:
@@ -377,18 +384,18 @@ def return_func(callback=None, error_callback=None):
377384
return return_func
378385

379386

380-
def _expose(name, function):
387+
def _expose(name: str, function: Callable[..., Any]) -> None:
381388
msg = 'Already exposed function with name "%s"' % name
382389
assert name not in _exposed_functions, msg
383390
_exposed_functions[name] = function
384391

385392

386-
def _detect_shutdown():
393+
def _detect_shutdown() -> None:
387394
if len(_websockets) == 0:
388395
sys.exit()
389396

390397

391-
def _websocket_close(page):
398+
def _websocket_close(page: str) -> None:
392399
global _shutdown
393400

394401
close_callback = _start_args.get('close_callback')
@@ -403,7 +410,7 @@ def _websocket_close(page):
403410
_shutdown = gvt.spawn_later(_start_args['shutdown_delay'], _detect_shutdown)
404411

405412

406-
def _set_response_headers(response):
413+
def _set_response_headers(response: btl.Response):
407414
if _start_args['disable_cache']:
408415
# https://stackoverflow.com/a/24748094/280852
409416
response.set_header('Cache-Control', 'no-store')

0 commit comments

Comments
 (0)