diff --git a/.gitignore b/.gitignore index d75db893..244f0f60 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ firefox-102.2.0* firefox-102.2.0/* firefox-*/* firefox-*/ -tests/__pycache__/* -tests/python/__pycache__/* +__pycache__ Testing/Temporary -_spidermonkey_install/* \ No newline at end of file +_spidermonkey_install/* +node_modules diff --git a/examples/use-python-module.py b/examples/use-python-module.py index d1f0478f..b64d62f0 100644 --- a/examples/use-python-module.py +++ b/examples/use-python-module.py @@ -1,15 +1,18 @@ ### Loader because Python is awkward +import sys from os import path +import importlib +from importlib import machinery from pathlib import Path def include(relative, filename): - __file__ = str(Path(relative, filename).resolve()) - if (path.exists(__file__)): - fileHnd = open(__file__, "r") - exec(fileHnd.read()) - return locals() - else: - raise Exception('file not found: ' + __file__) + filename = str(Path(relative, filename).resolve()) + name = path.basename(filename) + if name in sys.modules: + return sys.modules[name] + sourceFileLoader = machinery.SourceFileLoader(name, filename) + module = sourceFileLoader.load_module(name) + return module import sys sys.path.append(path.dirname(__file__) + '/..'); @@ -17,5 +20,5 @@ def include(relative, filename): pmr = include(path.dirname(__file__), '../pythonmonkey_require.py'); ### Actual test below -require = pmr['createRequire'](__file__) +require = pmr.createRequire(__file__) require('./use-python-module'); diff --git a/examples/use-python-module/index.js b/examples/use-python-module/index.js index 363c637a..288dc10b 100644 --- a/examples/use-python-module/index.js +++ b/examples/use-python-module/index.js @@ -1,4 +1,3 @@ const { helloWorld } = require('./my-python-module'); - helloWorld() diff --git a/examples/use-require.py b/examples/use-require.py index b6129ff4..4d53919c 100644 --- a/examples/use-require.py +++ b/examples/use-require.py @@ -1,15 +1,17 @@ ### Loader because Python is awkward +import sys from os import path +from importlib import machinery from pathlib import Path def include(relative, filename): - __file__ = str(Path(relative, filename).resolve()) - if (path.exists(__file__)): - fileHnd = open(__file__, "r") - exec(fileHnd.read()) - return locals() - else: - raise Exception('file not found: ' + __file__) + filename = str(Path(relative, filename).resolve()) + name = path.basename(filename) + if name in sys.modules: + return sys.modules[name] + sourceFileLoader = machinery.SourceFileLoader(name, filename) + module = sourceFileLoader.load_module(name) + return module import sys sys.path.append(path.dirname(__file__) + '/..'); @@ -17,6 +19,7 @@ def include(relative, filename): pmr = include(path.dirname(__file__), '../pythonmonkey_require.py'); ### Actual test below -require = pmr['createRequire'](__file__) +require = pmr.createRequire(__file__) require('./use-require/test1'); +print("Done") diff --git a/pythonmonkey_require.py b/pythonmonkey_require.py index 06160be4..3c81a22f 100644 --- a/pythonmonkey_require.py +++ b/pythonmonkey_require.py @@ -24,11 +24,16 @@ # import sys, warnings +import types +from typing import Union, Dict, Callable +import importlib +from importlib import machinery +from os import stat, path, getcwd, getenv + sys.path.append(path.dirname(__file__) + '/build/src') warnings.filterwarnings("ignore", category=DeprecationWarning) import pythonmonkey as pm -from os import stat, path, getcwd, getenv pm.eval(""" globalThis.python = {}; @@ -97,7 +102,14 @@ pm.eval("python.paths = python.paths.split(':'); true"); # fix when pm supports arrays # Implement enough of require('fs') so that ctx-module can find/load files -def statSync_inner(filename): + +def statSync_inner(filename: str) -> Union[Dict[str, int], bool]: + """ + Inner function for statSync. + + Returns: + Union[Dict[str, int], False]: The mode of the file or False if the file doesn't exist. + """ from os import stat if (path.exists(filename)): sb = stat(filename) @@ -105,9 +117,14 @@ def statSync_inner(filename): else: return False -def readFileSync(filename, charset): - fileHnd = open(filename, "r") - return fileHnd.read() +def readFileSync(filename, charset) -> str: + """ + Utility function for reading files. + Returns: + str: The contents of the file + """ + with open(filename, "r", encoding=charset) as fileHnd: + return fileHnd.read() propSet('fsModule', 'statSync_inner', statSync_inner); propSet('fsModule', 'readFileSync', readFileSync) @@ -130,8 +147,9 @@ def readFileSync(filename, charset): # because PythonMonkey current segfaults when return objects. Once that is fixed, we will pass moduleIIFE # parameters which are a python implementation of top-level require(for fs, vm - see top) and an exports # dict to decorate. -ctxModuleSource = open(path.dirname(__file__) + "/node_modules/ctx-module/ctx-module.js", "r") -moduleWrapper = pm.eval("""'use strict'; + +with open(path.dirname(__file__) + "/node_modules/ctx-module/ctx-module.js", "r") as ctxModuleSource: + moduleWrapper = pm.eval("""'use strict'; (function moduleWrapper(require, exports) { exports=exports || globalThis; @@ -148,27 +166,46 @@ def readFileSync(filename, charset): # dict->jsObject in createRequire for every require we create. pm.eval('const __builtinModules = {}; true'); -def load(filename): - __file__ = filename - if (path.exists(__file__)): - exports = {} - fileHnd = open(__file__, "r") - exec(fileHnd.read()) - return exports +def load(filename: str) -> Dict: + """ + Loads a python module using the importlib machinery sourcefileloader, prefills it with an exports object and returns the module. + If the module is already loaded, returns it. + + Args: + filename (str): The filename of the python module to load. + + Returns: + : The loaded python module + """ + + name = path.basename(filename) + if name not in sys.modules: + sourceFileLoader = machinery.SourceFileLoader(name, filename) + spec = importlib.util.spec_from_loader(sourceFileLoader.name, sourceFileLoader) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + module.exports = {} + spec.loader.exec_module(module) else: - raise Exception('file not found: ' + __file__) + module = sys.modules[name] + module_exports = {} + for key in dir(module): + module_exports[key] = getattr(module, key) + return module_exports + propSet('python', 'load', load) -# API - createRequire -# returns a require function that resolves modules relative to the filename argument. -# Conceptually the same as node:module.createRequire(). -# -# example: -# from pythonmonkey import createRequire -# require = createRequire(__file__) -# require('./my-javascript-module') -# -createRequire = pm.eval("""( +""" +API - createRequire +returns a require function that resolves modules relative to the filename argument. +Conceptually the same as node:module.createRequire(). + +example: + from pythonmonkey import createRequire + require = createRequire(__file__) + require('./my-javascript-module') +""" +createRequire: Callable = pm.eval("""( function createRequire(filename) { function loadPythonModule(module, filename)