46
46
Jupyter = get_ipython ()
47
47
"""Current InteractiveShell instance."""
48
48
49
- _HERE = Path (__file__ ).parent
50
-
49
+ _is_notebook = Jupyter and Jupyter .has_trait ('kernel' )
51
50
52
51
class CommError (Exception ):
53
52
"""Base class for Comm related exceptions."""
@@ -64,37 +63,37 @@ def __init__(self, *args, **kwargs):
64
63
65
64
class RequireJS (object ):
66
65
66
+ __instance = None
67
+
67
68
__LIBS = OrderedDict ()
68
69
"""Required libraries."""
69
70
__SHIM = OrderedDict ()
70
71
"""Shim for required libraries."""
71
72
72
- def __init__ (self , required : dict = None , shim : dict = None ):
73
+ # Comms strictly require to be shared between instances
74
+ __config_comm = None
75
+ __execution_comm = None
76
+ __safe_execution_comm = None
77
+
78
+ __is_initialized = False
79
+
80
+ def __new__ (cls , required : dict = None , shim : dict = None ):
73
81
"""Initialize RequireJS."""
74
- # check if running in Jupyter notebook
75
- self ._is_notebook = Jupyter and Jupyter .has_trait ('kernel' )
82
+ if cls .__instance is None :
83
+ # initialize
84
+ cls .__instance = super (RequireJS , cls ).__new__ (cls )
76
85
77
- if not self . _is_notebook :
86
+ if not _is_notebook :
78
87
msg = "Jupyter Require found itself running outside of Jupyter."
79
88
80
89
logger .critical (msg )
81
90
raise EnvironmentError (msg )
82
91
83
- # comm messages
84
- self ._msg = None
85
- self ._msg_received = None
86
-
87
- # comms
88
- self ._config_comm = None
89
- self ._execution_comm = None
90
- self ._safe_execution_comm = None
91
-
92
92
# update with default required libraries
93
- self .__LIBS .update (required or {})
94
- self .__SHIM .update (shim or {})
93
+ cls .__LIBS .update (required or {})
94
+ cls .__SHIM .update (shim or {})
95
95
96
- # initialization check
97
- self ._is_initialized = False
96
+ return cls .__instance
98
97
99
98
def __call__ (self , library : str , path : str , * args , ** kwargs ):
100
99
"""Links JavaScript library to Jupyter Notebook.
@@ -112,15 +111,52 @@ def __call__(self, library: str, path: str, *args, **kwargs):
112
111
"""
113
112
self .config ({library : path }, shim = kwargs .pop ('shim' , {}))
114
113
114
+ @property
115
+ def is_initialized (self ) -> bool :
116
+ """Return whether RequireJS has been initialized."""
117
+ return RequireJS .__is_initialized
118
+
119
+ @is_initialized .setter
120
+ def is_initialized (self , state : bool ):
121
+ """Set whether requireJS has been initialized."""
122
+ if state and any ([
123
+ RequireJS .__config_comm is None ,
124
+ RequireJS .__execution_comm is None ,
125
+ RequireJS .__safe_execution_comm is None
126
+ ]):
127
+ raise ValueError (
128
+ "Some comms have not been initialized yet."
129
+ "Can't set to True."
130
+ )
131
+
132
+ if not state and not any ([
133
+ RequireJS .__config_comm is None ,
134
+ RequireJS .__execution_comm is None ,
135
+ RequireJS .__safe_execution_comm is None
136
+ ]):
137
+ raise ValueError ("All comms are initialized. Can't set to False." )
138
+
139
+ RequireJS .__is_initialized = state
140
+
115
141
@property
116
142
def libs (self ) -> dict :
117
143
"""Get custom loaded libraries."""
118
- return dict (self .__LIBS )
144
+ return dict (RequireJS .__LIBS )
119
145
120
146
@property
121
147
def shim (self ) -> dict :
122
148
"""Get shim defined in requireJS config."""
123
- return dict (self .__SHIM )
149
+ return dict (RequireJS .__SHIM )
150
+
151
+ @property
152
+ def execution_comm (self ) -> Comm :
153
+ """Return execution Comm."""
154
+ return RequireJS .__execution_comm
155
+
156
+ @property
157
+ def safe_execution_comm (self ) -> Comm :
158
+ """Return execution Comm."""
159
+ return RequireJS .__safe_execution_comm
124
160
125
161
def display_context (self ):
126
162
"""Print defined libraries."""
@@ -156,82 +192,92 @@ def config(self, paths: dict, shim: dict = None):
156
192
157
193
Please note that <path> does __NOT__ contain `.js` suffix.
158
194
"""
159
- if not require ._is_initialized :
195
+ logger .debug ("Configuration requested: %s" , {
196
+ "paths" : paths ,
197
+ "shim" : shim
198
+ })
199
+
200
+ if not self .is_initialized :
160
201
raise CommError ("Comms haven't been initialized properly." )
161
202
162
- self .__LIBS .update (paths )
163
- self .__SHIM .update (shim or {})
203
+ RequireJS .__LIBS .update (paths )
204
+ RequireJS .__SHIM .update (shim or {})
164
205
165
206
# data to be passed to require.config()
166
- self . _msg = {'paths' : self .__LIBS , 'shim' : self .__SHIM }
207
+ data = {'paths' : RequireJS .__LIBS , 'shim' : RequireJS .__SHIM }
167
208
168
- if require . _config_comm is None :
209
+ if RequireJS . __config_comm is None :
169
210
raise CommError ("Comm 'config' is not open." )
170
211
171
- require . _config_comm .send (data = self . _msg )
212
+ RequireJS . __config_comm .send (data = data )
172
213
173
214
def pop (self , lib : str ):
174
215
"""Remove JavaScript library from requirements.
175
216
176
217
:param lib: key as passed to `config()`
177
218
"""
178
- self .__LIBS .pop (lib )
179
- self .__SHIM .pop (lib )
219
+ RequireJS .__LIBS .pop (lib )
220
+ RequireJS .__SHIM .pop (lib )
180
221
181
222
@classmethod
182
223
def reload (cls , clear = False ):
183
224
"""Reload and create new require object."""
184
- global require
185
-
186
225
logger .info ("Reloading." )
187
226
227
+ libs = cls .__LIBS if not clear else []
228
+ shim = cls .__SHIM if not clear else []
229
+
188
230
if clear :
189
231
cls .__LIBS .clear ()
190
232
cls .__SHIM .clear ()
191
233
192
- libs = require .libs if not clear else []
193
- shim = require .shim if not clear else []
234
+ cls .__config_comm = None
235
+ cls .__execution_comm = None
236
+ cls .__safe_execution_comm = None
194
237
195
- require = cls ( required = libs , shim = shim )
238
+ cls . __is_initialized = False
196
239
197
- if require ._is_notebook :
198
- require ._initialize_comms ()
240
+ self = cls (required = libs , shim = shim )
199
241
200
- require .__doc__ = RequireJS .__call__ .__doc__
242
+ if _is_notebook :
243
+ self ._initialize_comms ()
201
244
202
245
def _initialize_comms (self ):
203
246
"""Initialize Python-JavaScript comms."""
204
247
logger .info ("Initializing comms." )
205
248
206
- self ._is_initialized = False
207
-
208
249
now = datetime .now ()
209
250
210
- self . _config_comm = create_comm (
251
+ RequireJS . __config_comm = create_comm (
211
252
target = 'config' ,
212
253
comm_id = f'config.JupyterRequire#{ datetime .timestamp (now )} ' ,
213
- callback = self . _store_callback )
254
+ callback = RequireJS . log_callback )
214
255
215
- self . _execution_comm = create_comm (
256
+ RequireJS . __execution_comm = create_comm (
216
257
target = 'execute' ,
217
258
comm_id = f'execute.JupyterRequire#{ datetime .timestamp (now )} ' ,
218
- callback = self . _store_callback )
259
+ callback = RequireJS . log_callback )
219
260
220
- self . _safe_execution_comm = create_comm (
261
+ RequireJS . __safe_execution_comm = create_comm (
221
262
target = 'safe_execute' ,
222
263
comm_id = f'safe_execute.JupyterRequire#{ datetime .timestamp (now )} ' ,
223
- callback = self ._store_callback )
264
+ callback = RequireJS .log_callback )
265
+
266
+ self .is_initialized = True
224
267
225
268
# initial configuration
226
269
self .config (paths = {})
227
270
228
- self ._is_initialized = True
229
271
logger .info ("Comms have been successfully initialized." )
230
272
231
- def _store_callback (self , msg ):
273
+ @classmethod
274
+ def log_callback (cls , msg ):
232
275
"""Store callback from comm."""
233
- logger .debug ("Storing callback for msg: %s" , msg )
234
- self ._msg_received = msg
276
+ logger .debug ("Callback received: %s" , msg )
277
+
278
+
279
+ require = RequireJS ()
280
+ require .__doc__ = RequireJS .__call__ .__doc__
235
281
236
282
237
283
class JSTemplate (string .Template ):
@@ -301,9 +347,11 @@ def execute_with_requirements(script: str, required: Union[list, dict], silent=F
301
347
302
348
:param kwargs: optional keyword arguments for template substitution
303
349
"""
350
+ requirejs = RequireJS ()
351
+
304
352
if not configured :
305
353
if isinstance (required , dict ):
306
- require .config (required , ** kwargs )
354
+ requirejs .config (required , ** kwargs )
307
355
else :
308
356
raise TypeError (
309
357
f"Attribute `required` expected to be dict, got { type (required )} ." )
@@ -323,21 +371,24 @@ def execute_with_requirements(script: str, required: Union[list, dict], silent=F
323
371
'parameters' : params ,
324
372
}
325
373
326
- if require . _safe_execution_comm is None :
374
+ if requirejs . safe_execution_comm is None :
327
375
raise CommError ("Comm 'execute' is not open." )
328
376
329
377
# noinspection PyProtectedAccess
330
- return require . _execution_comm .send (data ) # pylint: disable=protected-access
378
+ return requirejs . execution_comm .send (data ) # pylint: disable=protected-access
331
379
332
380
333
381
def execute (script : str , ** kwargs ):
334
382
"""Execute JS script.
335
383
336
384
This function implicitly loads libraries defined in requireJS config.
337
385
"""
386
+ requirejs = RequireJS ()
387
+
338
388
required = []
389
+
339
390
try :
340
- required = list (require .libs .keys ())
391
+ required = list (requirejs .libs .keys ())
341
392
except NameError : # require has not been defined yet, allowed
342
393
pass
343
394
@@ -355,18 +406,16 @@ def safe_execute(script: str, **kwargs):
355
406
This function is convenient for automatic loading and linking
356
407
of custom CSS and JS files.
357
408
"""
409
+ requirejs = RequireJS ()
410
+
358
411
script = "{ " + script + " }" # provide local scope
359
412
script = JSTemplate (script ).safe_substitute (** kwargs )
360
413
361
- if require . _safe_execution_comm is None :
414
+ if requirejs . safe_execution_comm is None :
362
415
raise CommError ("Comm 'execute' is not open." )
363
416
364
417
# noinspection PyProtectedAccess
365
- return require ._safe_execution_comm .send (data = {'script' : script }) # pylint: disable=protected-access
366
-
367
-
368
- require = RequireJS ()
369
- require .__doc__ = RequireJS .__call__ .__doc__
418
+ return requirejs .safe_execution_comm .send (data = {'script' : script }) # pylint: disable=protected-access
370
419
371
420
372
421
def communicate (comm , open_msg ):
@@ -382,19 +431,16 @@ def handle_msg(msg):
382
431
"Message received: %r" , msg )
383
432
384
433
data = msg ['content' ]['data' ]
385
-
386
434
event = data .get ('event' , None )
387
- event_data = data .get ('event_data' , {})
388
435
389
436
logger .debug (
390
437
"Requested message handler.\n \t Data: %r\n \t Event: %r" , data , event )
391
438
392
439
response = {'resolved' : True , 'value' : None , 'success' : False }
393
440
try :
394
- event_type , namespace = event ['type' ], event ['namespace' ]
441
+ _ , namespace = event ['type' ], event ['namespace' ]
395
442
396
443
if namespace == 'JupyterRequire' :
397
- # response['value'] = ... # TODO
398
444
response ['success' ] = True
399
445
400
446
logger .debug ("Success." )
@@ -405,6 +451,6 @@ def handle_msg(msg):
405
451
response ['value' ] = err
406
452
407
453
finally :
408
- require . _store_callback (msg )
454
+ RequireJS . log_callback (msg )
409
455
410
456
comm .send (response )
0 commit comments