Skip to content

Commit b25f366

Browse files
jjtoltonJames J. Tolton
and
James J. Tolton
authored
make require-python optionally user path informed (#254)
Co-authored-by: James J. Tolton <[email protected]>
1 parent 67b83cc commit b25f366

File tree

2 files changed

+104
-10
lines changed

2 files changed

+104
-10
lines changed

src/libpython_clj2/metadata.clj

+7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
(get-attr % "__doc__")
4242
(catch Exception e
4343
"")))
44+
(def os (import-module "os"))
4445
(def get-pydoc doc)
4546
(def vars (get-attr builtins "vars"))
4647
(def pyclass? (get-attr inspect "isclass"))
@@ -227,6 +228,12 @@
227228
(or (string? att-val)
228229
(number? att-val)))
229230

231+
(defn py-chdir [path]
232+
(py/$a os "chdir" path))
233+
234+
(defn py-getcwd []
235+
(py/$a os "getcwd"))
236+
230237
(defn datafy-module-or-class [item]
231238
(with-gil
232239
(->> (if (or (pyclass? item)

src/libpython_clj2/require.clj

+97-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
[libpython-clj2.metadata :as pymeta]
77
[clojure.datafy :refer [datafy nav]]
88
[clojure.tools.logging :as log]
9-
[clojure.core.protocols :as clj-proto]))
9+
[clojure.core.protocols :as clj-proto])
10+
(:import (java.io File)))
1011

1112

1213
(defn- parse-flags
@@ -19,7 +20,7 @@
1920
;; First attempt is to filter keywords and make sure any keywords are
2021
;; in supported-flags
2122
(let [total-flags (set (concat supported-flags [:as :refer :exclude
22-
:* :all :bind-ns]))]
23+
:* :all :bind-ns :path]))]
2324
(when-let [missing-flags (->> reqs
2425
(filter #(and (not (total-flags %))
2526
(keyword? %)))
@@ -100,13 +101,20 @@
100101
no-arglists? (:no-arglists flags)
101102
bind-ns? (:bind-ns flags)
102103
alias-name (:as etc)
104+
path (:path etc)
103105
exclude (into #{} (:exclude etc))
104-
105106
refer-data (cond
106107
(= :all (:refer etc)) #{:all}
107108
(= :* (:refer etc)) #{:*}
108109
:else (into #{} (:refer etc)))
109-
pyobj (py/path->py-obj (str module-name) :reload? reload?)
110+
pyobj (if path
111+
(let [cwd (pymeta/py-getcwd)]
112+
(try
113+
(pymeta/py-chdir path)
114+
(py/path->py-obj (str module-name) :reload? reload?)
115+
(finally
116+
(pymeta/py-chdir cwd))))
117+
(py/path->py-obj (str module-name) :reload? reload?))
110118
existing-py-ns? (find-ns module-name)]
111119
(create-ns module-name)
112120

@@ -170,6 +178,7 @@
170178
(into [(req-transform prefix base)] reqs))))))
171179

172180

181+
173182
(defn require-python
174183
"## Basic usage ##
175184
@@ -219,9 +228,64 @@
219228
## Setting up classpath for custom modules ##
220229
221230
Note: you may need to setup your PYTHONPATH correctly.
222-
One technique to do this is, if your foo.py lives at
223-
/path/to/foodir/foo.py:
224-
231+
**WARNING**: This is very handy for local REPL development,
232+
..: if you are going to AOT classes,
233+
..: refer to the documentation on codegen
234+
..: or your AOT compilation will fail.
235+
If your foo.py lives at /path/to/foodir/foo.py, the easiest
236+
way to do it is:
237+
238+
(require-python :from \"/path/to/foodir\"
239+
'foo) ;; or
240+
(require-python \"/path/to/foodir\"
241+
'foo) ;; or
242+
(require-python {:from \"/path/to/foodir\"}
243+
'foo)
244+
245+
as you prefer.
246+
247+
Additionally, if you want to keep the namespacing as you have
248+
it in Python, you may prefer to use a relative import
249+
starting from a location of your choosing. If your
250+
os.getcwd() => /some/path/foo,
251+
and your directory structure looks something like:
252+
253+
/some $ tree
254+
.
255+
└── path
256+
├── baz
257+
│ └── quux.py
258+
└── foo
259+
└── bar.py
260+
261+
262+
(require-python :from \"path\"
263+
'[baz.quux :as quux]
264+
:from \"path/foo\"
265+
'bar)
266+
267+
is perfectly acceptable. It probably makes the most
268+
sense to keep you style consistent, but you can mix
269+
and match as you see fit between <path>, :from <path>,
270+
and {:from <path>}. <path> can either be a file or a
271+
directory. If it is a file, the Python path will be
272+
set to the directory containing that file.
273+
274+
You may also stack several require-pythons under one path:
275+
276+
(require-python {:from \"dir-a\"}
277+
'a
278+
'b
279+
'c
280+
{:from \"dir-b\"}
281+
'e.f
282+
'g
283+
{:from \"dir-c}
284+
'hi.there)
285+
286+
Other options more in keeping with traditional PYTHONPATH
287+
management include:
288+
225289
(require-python 'sys)
226290
(py/call-attr (py/get-attr sys \"path\")
227291
\"append\"
@@ -275,9 +339,32 @@
275339
(throw (Exception. "Invalid argument: %s" req))))
276340
:ok)
277341
([req & reqs]
278-
(require-python req)
279-
(when (not-empty reqs)
280-
(apply require-python reqs))
342+
(cond (and (map? req)
343+
(contains? req :from)
344+
(seq reqs))
345+
(apply require-python (:from req) reqs)
346+
(and (keyword? req)
347+
(= :from req)
348+
(string? (first reqs)))
349+
(apply require-python (first reqs) (rest reqs))
350+
(and (string? req)
351+
(.isFile (File. req)))
352+
(let [file (File. req)
353+
cwd (pymeta/py-getcwd)]
354+
(apply require-python (str (.getParent file)) reqs))
355+
(and (string? req)
356+
(.isDirectory (File. req)))
357+
(let [cwd (pymeta/py-getcwd)]
358+
(try
359+
(pymeta/py-chdir req)
360+
(apply require-python reqs)
361+
(finally
362+
(pymeta/py-chdir cwd))))
363+
:else
364+
(do
365+
(require-python req)
366+
(when (not-empty reqs)
367+
(apply require-python reqs))))
281368
:ok))
282369

283370

0 commit comments

Comments
 (0)