Skip to content
This repository was archived by the owner on Jun 22, 2025. It is now read-only.
Open
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
25 changes: 16 additions & 9 deletions eel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from builtins import range
import traceback
from io import open
from typing import Union, Any, Dict, List, Set, Tuple, Optional, Callable, TYPE_CHECKING
from typing import Union, Any, Dict, List, Set, Tuple, Optional, Callable, TYPE_CHECKING, cast, Type

if TYPE_CHECKING:
from eel.types import OptionsDictT, WebSocketT
Expand All @@ -28,6 +28,7 @@
mimetypes.add_type('application/javascript', '.js')
_eel_js_file: str = pkg.resource_filename('eel', 'eel.js')
_eel_js: str = open(_eel_js_file, encoding='utf-8').read()
_eel_json_dumps_default_function: Callable[[Any], Any] = lambda o: None
_websockets: List[Tuple[Any, WebSocketT]] = []
_call_return_values: Dict[Any, Any] = {}
_call_return_callbacks: Dict[float, Tuple[Callable[..., Any], Optional[Callable[..., Any]]]] = {}
Expand Down Expand Up @@ -55,6 +56,8 @@
'position': None, # (left, top) of main window
'geometry': {}, # Dictionary of size/position for all windows
'close_callback': None, # Callback for when all windows have closed
'json_encoder': None, # Custom JSONEncoder to customize json data dumping
'json_decoder': None, # Custom JSONDecoder to customize json data loading
'app_mode': True, # (Chrome specific option)
'all_interfaces': False, # Allow bottle server to listen for connections on all interfaces
'disable_cache': True, # Sets the no-store response header when serving assets
Expand Down Expand Up @@ -223,7 +226,7 @@ def _eel() -> str:
page = _eel_js.replace('/** _py_functions **/',
'_py_functions: %s,' % list(_exposed_functions.keys()))
page = page.replace('/** _start_geometry **/',
'_start_geometry: %s,' % _safe_json(start_geometry))
'_start_geometry: %s,' % _safe_json_dumps(start_geometry))
btl.response.content_type = 'application/javascript'
_set_response_headers(btl.response)
return page
Expand Down Expand Up @@ -259,15 +262,15 @@ def _websocket(ws: WebSocketT) -> None:
page = btl.request.query.page
if page not in _mock_queue_done:
for call in _mock_queue:
_repeated_send(ws, _safe_json(call))
_repeated_send(ws, _safe_json_dumps(call))
_mock_queue_done.add(page)

_websockets += [(page, ws)]

while True:
msg = ws.receive()
if msg is not None:
message = jsn.loads(msg)
message = _safe_json_loads(msg)
spawn(_process_message, message, ws)
else:
_websockets.remove((page, ws))
Expand Down Expand Up @@ -298,8 +301,12 @@ def register_eel_routes(app: btl.Bottle) -> None:

# Private functions

def _safe_json(obj: Any) -> str:
return jsn.dumps(obj, default=lambda o: None)
def _safe_json_loads(obj: str) -> Any:
return jsn.loads(obj, cls=cast(Optional[Type[jsn.JSONDecoder]], _start_args['json_decoder']))

def _safe_json_dumps(obj: Any) -> str:
return jsn.dumps(obj, cls=cast(Optional[Type[jsn.JSONEncoder]], _start_args['json_encoder']),
default=_eel_json_dumps_default_function if not _start_args['json_encoder'] else None)


def _repeated_send(ws: WebSocketT, msg: str) -> None:
Expand All @@ -324,7 +331,7 @@ def _process_message(message: Dict[str, Any], ws: WebSocketT) -> None:
status = 'error'
error_info['errorText'] = repr(e)
error_info['errorTraceback'] = err_traceback
_repeated_send(ws, _safe_json({ 'return': message['call'],
_repeated_send(ws, _safe_json_dumps({ 'return': message['call'],
'status': status,
'value': return_val,
'error': error_info,}))
Expand Down Expand Up @@ -375,7 +382,7 @@ def _mock_call(name: str, args: Any) -> Callable[[Optional[Callable[..., Any]],
def _js_call(name: str, args: Any) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
call_object = _call_object(name, args)
for _, ws in _websockets:
_repeated_send(ws, _safe_json(call_object))
_repeated_send(ws, _safe_json_dumps(call_object))
return _call_return(call_object)


Expand Down Expand Up @@ -409,7 +416,7 @@ def _detect_shutdown() -> None:
def _websocket_close(page: str) -> None:
global _shutdown

close_callback = _start_args.get('close_callback')
close_callback = cast(Callable[..., Any], _start_args.get('close_callback'))

if close_callback is not None:
if not callable(close_callback):
Expand Down
7 changes: 5 additions & 2 deletions eel/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Union, Dict, List, Tuple, Callable, Optional, Any, TYPE_CHECKING
from typing import Union, Dict, List, Tuple, Callable, Optional, Any, TYPE_CHECKING, Type

# This business is slightly awkward, but needed for backward compatibility,
# because Python < 3.7 doesn't have __future__/annotations, and <3.10 doesn't
Expand All @@ -12,17 +12,20 @@
JinjaEnvironmentT = Environment # type: ignore
from geventwebsocket.websocket import WebSocket
WebSocketT = WebSocket
from json import JSONDecoder, JSONEncoder
else:
JinjaEnvironmentT = None
WebSocketT = Any
JSONEncoder = Any
JSONDecoder = Any

OptionsDictT = Dict[
str,
Optional[
Union[
str, bool, int, float,
List[str], Tuple[int, int], Dict[str, Tuple[int, int]],
Callable[..., Any], JinjaEnvironmentT
Callable[..., Any], JinjaEnvironmentT, Type[JSONEncoder], Type[JSONDecoder]
]
]
]
1 change: 1 addition & 0 deletions examples/10 - custom_json_encoder_decoder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import eel
import json
import datetime

eel.init('web')

@eel.expose
def py_json_data_sender():
return {
'datetime': datetime.datetime.now()
}

@eel.expose
def py_json_data_loader():
return eel.js_json_data_sender()(print_value)

def print_value(v):
print('Got this value from javascript:')
print(v)

# Custom Json Encoder.
class EelJsonEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime.date):
return o.__str__()


# Custom Json Decoder.
class CustomDatetimeObj(object):
def __init__(self, datetime):
self.datetime = datetime

def __str__(self):
return 'CustomDatetimeObj: %s' % self.datetime.__str__()

def decoder_object_hook(o):
if 'datetime' in o:
return CustomDatetimeObj(datetime=o['datetime'])
else:
return o

class EelJsonDecoder(json.JSONDecoder):
def __init__(self, object_hook=decoder_object_hook, *args, **kwargs):
super().__init__(object_hook=object_hook, *args, **kwargs)


eel.start('custom_json_encoder_decoder.html', json_encoder=EelJsonEncoder,
json_decoder=EelJsonDecoder, size=(400, 300))
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom JSON Encoder/Decoder Eel Example</title>

<!-- Include eel.js - note this file doesn't exist in the 'web' directory -->
<script type="text/javascript" src="/eel.js"></script>
<script type="text/javascript" src="custom_json_encoder_decoder.js"></script>
</head>

<body>
Hello World!
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
eel.expose(js_json_data_sender);
function js_json_data_sender() {
return {
datetime: new Date()
}
}

function print_value(v) {
console.log('Got this value from python:');
console.log(v);
}

// Call Python function.
eel.py_json_data_sender()(print_value);

// Call JS function from python.
eel.py_json_data_loader();
Binary file not shown.