From 1490d22438759c33133bc6182e4b69ec67212958 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 23 Apr 2024 10:12:44 -0400 Subject: [PATCH] Add `import-path` --- NEWS.rst | 1 + docs/index.rst | 1 + hyrule/misc.hy | 28 ++++++++++++++++++++++++++++ tests/test_misc.hy | 21 ++++++++++++++++++++- 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 550aad5c..8564bbc2 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -16,6 +16,7 @@ New Features * New macro `some->`. * New function `sign`. * New function `thru`. +* New function `import-path`. Removals ------------------------------ diff --git a/docs/index.rst b/docs/index.rst index e32e3785..6dfe0365 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -137,6 +137,7 @@ API .. hy:autofunction:: constantly .. hy:autofunction:: dec .. hy:autofunction:: inc +.. hy:autofunction:: import-path .. hy:automacro:: of .. hy:autofunction:: parse-args .. hy:automacro:: profile/calls diff --git a/hyrule/misc.hy b/hyrule/misc.hy index a328f9a3..3aa7e338 100644 --- a/hyrule/misc.hy +++ b/hyrule/misc.hy @@ -2,6 +2,8 @@ hyrule.macrotools [defmacro!]) (import + sys + importlib.util hy.scoping [ScopeLet] hyrule.collections [by2s]) @@ -52,6 +54,32 @@ (+ n 1)) +(defn import-path [path [name None]] + + #[[Import the Python or Hy source code at ``path`` as a module with + :func:`importlib.util.spec_from_file_location`, per Python's documentation. + Return the new module object. ``name`` defaults to ``(str (hy.gensym + "import-file"))``. :: + + (setv p (hy.I.pathlib.Path "mymodule.hy")) + (.write-text p "(setv foo 3)") + (setv m (import-path p)) + (print m.foo) ; => 3]] + + (when (is name None) + (setv name (str (hy.gensym "import-file")))) + (when (in name sys.modules) + (raise (ValueError f"The name {(hy.repr name)} is already in use in `sys.modules`."))) + + ; Translated from https://github.com/python/cpython/blob/408e127159e54d87bb3464fd8bd60219dc527fac/Doc/library/importlib.rst?plain=1#L1584 + (setv spec (importlib.util.spec-from-file-location name path)) + (setv m (importlib.util.module-from-spec spec)) + (setv (get sys.modules name) m) + (.loader.exec-module spec m) + + m) + + (defmacro of [base #* args] "Shorthand for type annotations with indexing. If only one argument diff --git a/tests/test_misc.hy b/tests/test_misc.hy index 7ffee5d4..b3f4d228 100644 --- a/tests/test_misc.hy +++ b/tests/test_misc.hy @@ -1,9 +1,10 @@ (require hyrule [comment of smacrolet]) (import + sys pytest typing [List Dict] - hyrule [constantly dec inc parse-args sign xor]) + hyrule [constantly dec inc import-path parse-args sign xor]) (defn test-constantly [] @@ -34,6 +35,24 @@ (assert (= (inc (X)) "__add__ got 1"))) +(defn test-import-path [tmp-path] + (setv mp (/ tmp-path "a.hy")) + (.write-text mp "(setv foo 7)") + + (setv m (import-path mp "mymod")) + (assert (= m.foo 7)) + (assert (= m.__name__ "mymod")) + (assert (is (get sys.modules "mymod") m)) + + (setv m2 (import-path mp)) + (assert (= m2.foo 7)) + (assert (is-not m2 m)) + (assert (in m2 (.values sys.modules))) + + (.write-text (/ tmp-path "b.py") "bar = 3") + (assert (= (. (import-path (/ tmp-path "b.py")) bar) 3))) + + (defn test-of [] (assert (= (of str) str)) (assert (= (of List int) (get List int)))