Skip to content
This repository was archived by the owner on May 3, 2020. It is now read-only.

Commit 1778ce3

Browse files
author
Marek Cermak
committed
chg: dev: Refactor RequireJS to singleton pattern
Signed-off-by: Marek Cermak <[email protected]> modified: jupyter_require/__init__.py modified: jupyter_require/core.py
1 parent 4db00c3 commit 1778ce3

File tree

2 files changed

+110
-66
lines changed

2 files changed

+110
-66
lines changed

jupyter_require/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,10 @@
4141
from .core import execute_with_requirements
4242
from .core import execute
4343
from .core import safe_execute
44-
4544
from .core import require
4645

4746
from IPython import get_ipython
4847

49-
5048
daiquiri.setup(
5149
level=logging.DEBUG,
5250
outputs=[

jupyter_require/core.py

Lines changed: 110 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@
4646
Jupyter = get_ipython()
4747
"""Current InteractiveShell instance."""
4848

49-
_HERE = Path(__file__).parent
50-
49+
_is_notebook = Jupyter and Jupyter.has_trait('kernel')
5150

5251
class CommError(Exception):
5352
"""Base class for Comm related exceptions."""
@@ -64,37 +63,37 @@ def __init__(self, *args, **kwargs):
6463

6564
class RequireJS(object):
6665

66+
__instance = None
67+
6768
__LIBS = OrderedDict()
6869
"""Required libraries."""
6970
__SHIM = OrderedDict()
7071
"""Shim for required libraries."""
7172

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):
7381
"""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)
7685

77-
if not self._is_notebook:
86+
if not _is_notebook:
7887
msg = "Jupyter Require found itself running outside of Jupyter."
7988

8089
logger.critical(msg)
8190
raise EnvironmentError(msg)
8291

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-
9292
# 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 {})
9595

96-
# initialization check
97-
self._is_initialized = False
96+
return cls.__instance
9897

9998
def __call__(self, library: str, path: str, *args, **kwargs):
10099
"""Links JavaScript library to Jupyter Notebook.
@@ -112,15 +111,52 @@ def __call__(self, library: str, path: str, *args, **kwargs):
112111
"""
113112
self.config({library: path}, shim=kwargs.pop('shim', {}))
114113

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+
115141
@property
116142
def libs(self) -> dict:
117143
"""Get custom loaded libraries."""
118-
return dict(self.__LIBS)
144+
return dict(RequireJS.__LIBS)
119145

120146
@property
121147
def shim(self) -> dict:
122148
"""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
124160

125161
def display_context(self):
126162
"""Print defined libraries."""
@@ -156,82 +192,92 @@ def config(self, paths: dict, shim: dict = None):
156192
157193
Please note that <path> does __NOT__ contain `.js` suffix.
158194
"""
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:
160201
raise CommError("Comms haven't been initialized properly.")
161202

162-
self.__LIBS.update(paths)
163-
self.__SHIM.update(shim or {})
203+
RequireJS.__LIBS.update(paths)
204+
RequireJS.__SHIM.update(shim or {})
164205

165206
# data to be passed to require.config()
166-
self._msg = {'paths': self.__LIBS, 'shim': self.__SHIM}
207+
data = {'paths': RequireJS.__LIBS, 'shim': RequireJS.__SHIM}
167208

168-
if require._config_comm is None:
209+
if RequireJS.__config_comm is None:
169210
raise CommError("Comm 'config' is not open.")
170211

171-
require._config_comm.send(data=self._msg)
212+
RequireJS.__config_comm.send(data=data)
172213

173214
def pop(self, lib: str):
174215
"""Remove JavaScript library from requirements.
175216
176217
:param lib: key as passed to `config()`
177218
"""
178-
self.__LIBS.pop(lib)
179-
self.__SHIM.pop(lib)
219+
RequireJS.__LIBS.pop(lib)
220+
RequireJS.__SHIM.pop(lib)
180221

181222
@classmethod
182223
def reload(cls, clear=False):
183224
"""Reload and create new require object."""
184-
global require
185-
186225
logger.info("Reloading.")
187226

227+
libs = cls.__LIBS if not clear else []
228+
shim = cls.__SHIM if not clear else []
229+
188230
if clear:
189231
cls.__LIBS.clear()
190232
cls.__SHIM.clear()
191233

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
194237

195-
require = cls(required=libs, shim=shim)
238+
cls.__is_initialized = False
196239

197-
if require._is_notebook:
198-
require._initialize_comms()
240+
self = cls(required=libs, shim=shim)
199241

200-
require.__doc__ = RequireJS.__call__.__doc__
242+
if _is_notebook:
243+
self._initialize_comms()
201244

202245
def _initialize_comms(self):
203246
"""Initialize Python-JavaScript comms."""
204247
logger.info("Initializing comms.")
205248

206-
self._is_initialized = False
207-
208249
now = datetime.now()
209250

210-
self._config_comm = create_comm(
251+
RequireJS.__config_comm = create_comm(
211252
target='config',
212253
comm_id=f'config.JupyterRequire#{datetime.timestamp(now)}',
213-
callback=self._store_callback)
254+
callback=RequireJS.log_callback)
214255

215-
self._execution_comm = create_comm(
256+
RequireJS.__execution_comm = create_comm(
216257
target='execute',
217258
comm_id=f'execute.JupyterRequire#{datetime.timestamp(now)}',
218-
callback=self._store_callback)
259+
callback=RequireJS.log_callback)
219260

220-
self._safe_execution_comm = create_comm(
261+
RequireJS.__safe_execution_comm = create_comm(
221262
target='safe_execute',
222263
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
224267

225268
# initial configuration
226269
self.config(paths={})
227270

228-
self._is_initialized = True
229271
logger.info("Comms have been successfully initialized.")
230272

231-
def _store_callback(self, msg):
273+
@classmethod
274+
def log_callback(cls, msg):
232275
"""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__
235281

236282

237283
class JSTemplate(string.Template):
@@ -301,9 +347,11 @@ def execute_with_requirements(script: str, required: Union[list, dict], silent=F
301347
302348
:param kwargs: optional keyword arguments for template substitution
303349
"""
350+
requirejs = RequireJS()
351+
304352
if not configured:
305353
if isinstance(required, dict):
306-
require.config(required, **kwargs)
354+
requirejs.config(required, **kwargs)
307355
else:
308356
raise TypeError(
309357
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
323371
'parameters': params,
324372
}
325373

326-
if require._safe_execution_comm is None:
374+
if requirejs.safe_execution_comm is None:
327375
raise CommError("Comm 'execute' is not open.")
328376

329377
# noinspection PyProtectedAccess
330-
return require._execution_comm.send(data) # pylint: disable=protected-access
378+
return requirejs.execution_comm.send(data) # pylint: disable=protected-access
331379

332380

333381
def execute(script: str, **kwargs):
334382
"""Execute JS script.
335383
336384
This function implicitly loads libraries defined in requireJS config.
337385
"""
386+
requirejs = RequireJS()
387+
338388
required = []
389+
339390
try:
340-
required = list(require.libs.keys())
391+
required = list(requirejs.libs.keys())
341392
except NameError: # require has not been defined yet, allowed
342393
pass
343394

@@ -355,18 +406,16 @@ def safe_execute(script: str, **kwargs):
355406
This function is convenient for automatic loading and linking
356407
of custom CSS and JS files.
357408
"""
409+
requirejs = RequireJS()
410+
358411
script = "{ " + script + " }" # provide local scope
359412
script = JSTemplate(script).safe_substitute(**kwargs)
360413

361-
if require._safe_execution_comm is None:
414+
if requirejs.safe_execution_comm is None:
362415
raise CommError("Comm 'execute' is not open.")
363416

364417
# 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
370419

371420

372421
def communicate(comm, open_msg):
@@ -382,19 +431,16 @@ def handle_msg(msg):
382431
"Message received: %r", msg)
383432

384433
data = msg['content']['data']
385-
386434
event = data.get('event', None)
387-
event_data = data.get('event_data', {})
388435

389436
logger.debug(
390437
"Requested message handler.\n\tData: %r\n\tEvent: %r", data, event)
391438

392439
response = {'resolved': True, 'value': None, 'success': False}
393440
try:
394-
event_type, namespace = event['type'], event['namespace']
441+
_, namespace = event['type'], event['namespace']
395442

396443
if namespace == 'JupyterRequire':
397-
# response['value'] = ... # TODO
398444
response['success'] = True
399445

400446
logger.debug("Success.")
@@ -405,6 +451,6 @@ def handle_msg(msg):
405451
response['value'] = err
406452

407453
finally:
408-
require._store_callback(msg)
454+
RequireJS.log_callback(msg)
409455

410456
comm.send(response)

0 commit comments

Comments
 (0)