Skip to content

Commit b25f366

Browse files
jjtoltonJames J. Tolton
andauthored
make require-python optionally user path informed (#254)
Co-authored-by: James J. Tolton <jay@toltontechnology.ai>
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 97 additions & 10 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy