11from builtins import range
22import traceback
33from io import open
4+ from typing import Union , Any , Dict , List , Set , Tuple , Optional , Callable , TYPE_CHECKING
5+
6+ if TYPE_CHECKING :
7+ from eel .types import OptionsDictT , WebSocketT
8+ else :
9+ WebSocketT = Any
10+ OptionsDictT = Any
411
512from gevent .threading import Timer
613import gevent as gvt
1724import socket
1825import mimetypes
1926
27+
2028mimetypes .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
29+ _eel_js_file : str = pkg .resource_filename ('eel' , 'eel.js' )
30+ _eel_js : str = open (_eel_js_file , encoding = 'utf-8' ).read ()
31+ _websockets : List [Tuple [Any , WebSocketT ]] = []
32+ _call_return_values : Dict [Any , Any ] = {}
33+ _call_return_callbacks : Dict [float , Tuple [Callable [..., Any ], Optional [Callable [..., Any ]]]] = {}
34+ _call_number : int = 0
35+ _exposed_functions : Dict [Any , Any ] = {}
36+ _js_functions : List [Any ] = []
37+ _mock_queue : List [Any ] = []
38+ _mock_queue_done : Set [Any ] = set ()
39+ _shutdown : Optional [gvt .Greenlet ] = None # Later assigned as global by _websocket_close()
40+ root_path : str # Later assigned as global by init()
3241
3342# The maximum time (in milliseconds) that Python will try to retrieve a return value for functions executing in JS
3443# Can be overridden through `eel.init` with the kwarg `js_result_timeout` (default: 10000)
35- _js_result_timeout = 10000
44+ _js_result_timeout : int = 10000
3645
3746# All start() options must provide a default value and explanation here
38- _start_args = {
47+ _start_args : OptionsDictT = {
3948 'mode' : 'chrome' , # What browser is used
4049 'host' : 'localhost' , # Hostname use for Bottle server
4150 'port' : 8000 , # Port used for Bottle server (use 0 for auto)
5160 'disable_cache' : True , # Sets the no-store response header when serving assets
5261 'default_path' : 'index.html' , # The default file to retrieve for the root URL
5362 '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
63+ 'shutdown_delay' : 1.0 # how long to wait after a websocket closes before detecting complete shutdown
5564}
5665
5766# == Temporary (suppressible) error message to inform users of breaking API change for v1.0.0 ===
5867_start_args ['suppress_error' ] = False
59- api_error_message = '''
68+ api_error_message : str = '''
6069----------------------------------------------------------------------------------
6170 'options' argument deprecated in v1.0.0, see https://github.com/ChrisKnott/Eel
6271 To suppress this error, add 'suppress_error=True' to start() call.
6776
6877# Public functions
6978
70- def expose (name_or_function = None ):
79+ def expose (name_or_function : Optional [ Callable [..., Any ]] = None ) -> Callable [..., Any ] :
7180 # Deal with '@eel.expose()' - treat as '@eel.expose'
7281 if name_or_function is None :
7382 return expose
7483
75- if type (name_or_function ) == str : # Called as '@eel.expose("my_name")'
84+ if isinstance (name_or_function , str ) : # Called as '@eel.expose("my_name")'
7685 name = name_or_function
7786
78- def decorator (function ) :
87+ def decorator (function : Callable [..., Any ]) -> Any :
7988 _expose (name , function )
8089 return function
8190 return decorator
@@ -87,7 +96,7 @@ def decorator(function):
8796
8897# PyParsing grammar for parsing exposed functions in JavaScript code
8998# Examples: `eel.expose(w, "func_name")`, `eel.expose(func_name)`, `eel.expose((function (e){}), "func_name")`
90- EXPOSED_JS_FUNCTIONS = pp .ZeroOrMore (
99+ EXPOSED_JS_FUNCTIONS : pp . ZeroOrMore = pp .ZeroOrMore (
91100 pp .Suppress (
92101 pp .SkipTo (pp .Literal ('eel.expose(' ))
93102 + pp .Literal ('eel.expose(' )
@@ -101,8 +110,8 @@ def decorator(function):
101110)
102111
103112
104- def init (path , allowed_extensions = ['.js' , '.html' , '.txt' , '.htm' ,
105- '.xhtml' , '.vue' ], js_result_timeout = 10000 ):
113+ def init (path : str , allowed_extensions : List [ str ] = ['.js' , '.html' , '.txt' , '.htm' ,
114+ '.xhtml' , '.vue' ], js_result_timeout : int = 10000 ) -> None :
106115 global root_path , _js_functions , _js_result_timeout
107116 root_path = _get_real_path (path )
108117
@@ -133,7 +142,7 @@ def init(path, allowed_extensions=['.js', '.html', '.txt', '.htm',
133142 _js_result_timeout = js_result_timeout
134143
135144
136- def start (* start_urls , ** kwargs ) :
145+ def start (* start_urls : str , ** kwargs : Any ) -> None :
137146 _start_args .update (kwargs )
138147
139148 if 'options' in kwargs :
@@ -150,6 +159,8 @@ def start(*start_urls, **kwargs):
150159
151160 if _start_args ['jinja_templates' ] != None :
152161 from jinja2 import Environment , FileSystemLoader , select_autoescape
162+ if not isinstance (_start_args ['jinja_templates' ], str ):
163+ raise TypeError ("'jinja_templates start_arg/option must be of type str'" )
153164 templates_path = os .path .join (root_path , _start_args ['jinja_templates' ])
154165 _start_args ['jinja_env' ] = Environment (loader = FileSystemLoader (templates_path ),
155166 autoescape = select_autoescape (['html' , 'xml' ]))
@@ -162,25 +173,27 @@ def start(*start_urls, **kwargs):
162173 # Launch the browser to the starting URLs
163174 show (* start_urls )
164175
165- def run_lambda ():
176+ def run_lambda () -> None :
166177 if _start_args ['all_interfaces' ] == True :
167178 HOST = '0.0.0.0'
168179 else :
180+ if not isinstance (_start_args ['host' ], str ):
181+ raise TypeError ("'host' start_arg/option must be of type str" )
169182 HOST = _start_args ['host' ]
170183
171- app = _start_args ['app' ] # type: btl.Bottle
184+ app = _start_args ['app' ]
172185
173186 if isinstance (app , btl .Bottle ):
174187 register_eel_routes (app )
175188 else :
176189 register_eel_routes (btl .default_app ())
177190
178- return btl .run (
191+ btl .run (
179192 host = HOST ,
180193 port = _start_args ['port' ],
181194 server = wbs .GeventWebSocketServer ,
182195 quiet = True ,
183- app = app )
196+ app = app ) # Always returns None
184197
185198 # Start the webserver
186199 if _start_args ['block' ]:
@@ -189,20 +202,20 @@ def run_lambda():
189202 spawn (run_lambda )
190203
191204
192- def show (* start_urls ) :
193- brw .open (start_urls , _start_args )
205+ def show (* start_urls : str ) -> None :
206+ brw .open (list ( start_urls ) , _start_args )
194207
195208
196- def sleep (seconds ) :
209+ def sleep (seconds : Union [ int , float ]) -> None :
197210 gvt .sleep (seconds )
198211
199212
200- def spawn (function , * args , ** kwargs ) :
213+ def spawn (function : Callable [..., Any ], * args : Any , ** kwargs : Any ) -> gvt . Greenlet :
201214 return gvt .spawn (function , * args , ** kwargs )
202215
203216# Bottle Routes
204217
205- def _eel ():
218+ def _eel () -> str :
206219 start_geometry = {'default' : {'size' : _start_args ['size' ],
207220 'position' : _start_args ['position' ]},
208221 'pages' : _start_args ['geometry' ]}
@@ -215,16 +228,20 @@ def _eel():
215228 _set_response_headers (btl .response )
216229 return page
217230
218- def _root ():
231+ def _root () -> Optional [btl .Response ]:
232+ if not isinstance (_start_args ['default_path' ], str ):
233+ raise TypeError ("'default_path' start_arg/option must be of type str" )
219234 return _static (_start_args ['default_path' ])
220235
221- def _static (path ) :
236+ def _static (path : str ) -> Optional [ btl . Response ] :
222237 response = None
223238 if 'jinja_env' in _start_args and 'jinja_templates' in _start_args :
239+ if not isinstance (_start_args ['jinja_templates' ], str ):
240+ raise TypeError ("'jinja_templates' start_arg/option must be of type str" )
224241 template_prefix = _start_args ['jinja_templates' ] + '/'
225242 if path .startswith (template_prefix ):
226243 n = len (template_prefix )
227- template = _start_args ['jinja_env' ].get_template (path [n :])
244+ template = _start_args ['jinja_env' ].get_template (path [n :]) # type: ignore # depends on conditional import in start()
228245 response = btl .HTTPResponse (template .render ())
229246
230247 if response is None :
@@ -233,7 +250,7 @@ def _static(path):
233250 _set_response_headers (response )
234251 return response
235252
236- def _websocket (ws ) :
253+ def _websocket (ws : WebSocketT ) -> None :
237254 global _websockets
238255
239256 for js_function in _js_functions :
@@ -259,14 +276,14 @@ def _websocket(ws):
259276 _websocket_close (page )
260277
261278
262- BOTTLE_ROUTES = {
279+ BOTTLE_ROUTES : Dict [ str , Tuple [ Callable [..., Any ], Dict [ Any , Any ]]] = {
263280 "/eel.js" : (_eel , dict ()),
264281 "/" : (_root , dict ()),
265282 "/<path:path>" : (_static , dict ()),
266283 "/eel" : (_websocket , dict (apply = [wbs .websocket ]))
267284}
268285
269- def register_eel_routes (app ) :
286+ def register_eel_routes (app : btl . Bottle ) -> None :
270287 '''
271288 Adds eel routes to `app`. Only needed if you are passing something besides `bottle.Bottle` to `eel.start()`.
272289 Ex:
@@ -281,11 +298,11 @@ def register_eel_routes(app):
281298
282299# Private functions
283300
284- def _safe_json (obj ) :
301+ def _safe_json (obj : Any ) -> str :
285302 return jsn .dumps (obj , default = lambda o : None )
286303
287304
288- def _repeated_send (ws , msg ) :
305+ def _repeated_send (ws : WebSocketT , msg : str ) -> None :
289306 for attempt in range (100 ):
290307 try :
291308 ws .send (msg )
@@ -294,7 +311,7 @@ def _repeated_send(ws, msg):
294311 sleep (0.001 )
295312
296313
297- def _process_message (message , ws ) :
314+ def _process_message (message : Dict [ str , Any ], ws : WebSocketT ) -> None :
298315 if 'call' in message :
299316 error_info = {}
300317 try :
@@ -326,47 +343,48 @@ def _process_message(message, ws):
326343 print ('Invalid message received: ' , message )
327344
328345
329- def _get_real_path (path ) :
346+ def _get_real_path (path : str ) -> str :
330347 if getattr (sys , 'frozen' , False ):
331- return os .path .join (sys ._MEIPASS , path )
348+ return os .path .join (sys ._MEIPASS , path ) # type: ignore # sys._MEIPASS is dynamically added by PyInstaller
332349 else :
333350 return os .path .abspath (path )
334351
335352
336- def _mock_js_function (f ) :
353+ def _mock_js_function (f : str ) -> None :
337354 exec ('%s = lambda *args: _mock_call("%s", args)' % (f , f ), globals ())
338355
339356
340- def _import_js_function (f ) :
357+ def _import_js_function (f : str ) -> None :
341358 exec ('%s = lambda *args: _js_call("%s", args)' % (f , f ), globals ())
342359
343360
344- def _call_object (name , args ) :
361+ def _call_object (name : str , args : Any ) -> Dict [ str , Any ] :
345362 global _call_number
346363 _call_number += 1
347364 call_id = _call_number + rnd .random ()
348365 return {'call' : call_id , 'name' : name , 'args' : args }
349366
350367
351- def _mock_call (name , args ) :
368+ def _mock_call (name : str , args : Any ) -> Callable [[ Optional [ Callable [..., Any ]], Optional [ Callable [..., Any ]]], Any ] :
352369 call_object = _call_object (name , args )
353370 global _mock_queue
354371 _mock_queue += [call_object ]
355372 return _call_return (call_object )
356373
357374
358- def _js_call (name , args ) :
375+ def _js_call (name : str , args : Any ) -> Callable [[ Optional [ Callable [..., Any ]], Optional [ Callable [..., Any ]]], Any ] :
359376 call_object = _call_object (name , args )
360377 for _ , ws in _websockets :
361378 _repeated_send (ws , _safe_json (call_object ))
362379 return _call_return (call_object )
363380
364381
365- def _call_return (call ) :
382+ def _call_return (call : Dict [ str , Any ]) -> Callable [[ Optional [ Callable [..., Any ]], Optional [ Callable [..., Any ]]], Any ] :
366383 global _js_result_timeout
367384 call_id = call ['call' ]
368385
369- def return_func (callback = None , error_callback = None ):
386+ def return_func (callback : Optional [Callable [..., Any ]] = None ,
387+ error_callback : Optional [Callable [..., Any ]] = None ) -> Any :
370388 if callback is not None :
371389 _call_return_callbacks [call_id ] = (callback , error_callback )
372390 else :
@@ -377,33 +395,35 @@ def return_func(callback=None, error_callback=None):
377395 return return_func
378396
379397
380- def _expose (name , function ) :
398+ def _expose (name : str , function : Callable [..., Any ]) -> None :
381399 msg = 'Already exposed function with name "%s"' % name
382400 assert name not in _exposed_functions , msg
383401 _exposed_functions [name ] = function
384402
385403
386- def _detect_shutdown ():
404+ def _detect_shutdown () -> None :
387405 if len (_websockets ) == 0 :
388406 sys .exit ()
389407
390408
391- def _websocket_close (page ) :
409+ def _websocket_close (page : str ) -> None :
392410 global _shutdown
393411
394412 close_callback = _start_args .get ('close_callback' )
395413
396414 if close_callback is not None :
415+ if not callable (close_callback ):
416+ raise TypeError ("'close_callback' start_arg/option must be callable or None" )
397417 sockets = [p for _ , p in _websockets ]
398418 close_callback (page , sockets )
399419 else :
400- if _shutdown :
420+ if isinstance ( _shutdown , gvt . Greenlet ) :
401421 _shutdown .kill ()
402422
403423 _shutdown = gvt .spawn_later (_start_args ['shutdown_delay' ], _detect_shutdown )
404424
405425
406- def _set_response_headers (response ) :
426+ def _set_response_headers (response : btl . Response ) -> None :
407427 if _start_args ['disable_cache' ]:
408428 # https://stackoverflow.com/a/24748094/280852
409429 response .set_header ('Cache-Control' , 'no-store' )
0 commit comments