Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions src/libpython_clj2/metadata.clj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@


(def builtins (import-module "builtins"))
(def py-str (get-attr builtins "str"))
(def inspect (import-module "inspect"))
(def argspec (get-attr inspect "getfullargspec"))
(def py-source (get-attr inspect "getsource"))
Expand Down Expand Up @@ -68,16 +69,38 @@
(catch Exception _
nil)))

(defn- py-default->jvm [x]
(let [jvm-val (->jvm x)]
(if (and (map? jvm-val)
(contains? jvm-val :type)
(contains? jvm-val :value))
(str (py-str x))
jvm-val)))

(defn- py-defaults->jvm [defaults]
(when (->jvm defaults)
(->> defaults
(map py-default->jvm)
(into []))))

(defn- py-kwonlydefaults->jvm [kwonlydefaults]
(when (->jvm kwonlydefaults)
(->> (call-attr kwonlydefaults "items")
(map (fn [entry]
(let [[k v] (seq entry)]
[(->jvm k) (py-default->jvm v)])))
(into {}))))

(defn py-fn-argspec [f]
(if-let [spec (try (when-not (pyclass? f)
(argspec f))
(catch Throwable e nil))]
{:args (->jvm (get-attr spec "args"))
:varargs (->jvm (get-attr spec "varargs"))
:varkw (->jvm (get-attr spec "varkw"))
:defaults (->jvm (get-attr spec "defaults"))
:defaults (py-defaults->jvm (get-attr spec "defaults"))
:kwonlyargs (->jvm (get-attr spec "kwonlyargs"))
:kwonlydefaults (->jvm (get-attr spec "kwonlydefaults"))
:kwonlydefaults (py-kwonlydefaults->jvm (get-attr spec "kwonlydefaults"))
:annotations (->jvm (get-attr spec "annotations"))}
(py-fn-argspec (get-attr f "__init__"))))

Expand Down Expand Up @@ -132,22 +155,16 @@
(map symbol)
(into []))

;;These sometimes have actual python symbols in them so we can't use them
;; or-map (->> (concat
;; (interleave kw-default-args defaults)
;; (flatten (seq kwonlydefaults)))
;; (partition-all 2)
;; (map vec)
;; (map (fn [[k v]] [(symbol k) v]))
;; (into {}))
;; Preserve the default values that inspect returned. These may be nil
;; or non-keyword JVM representations of Python values.
as-varkw (when (not (nil? varkw))
{:as (symbol varkw)})
default-map (->> (concat
(interleave kw-default-args defaults)
(flatten (seq kwonlydefaults)))
(partition-all 2)
(map vec)
(map (fn [[k v]] [(symbol k) (keyword k)]))
(map (fn [[k v]] [(symbol k) v]))
(into {}))

kwargs-map (merge default-map
Expand Down
10 changes: 9 additions & 1 deletion src/libpython_clj2/python.clj
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@ user> (py/py. np linspace 2 3 :num 10)
#'~varname))


(defn ^:no-doc py-var-metadata [var-name var-data]
(try
(let [metadata-fn (requiring-resolve 'libpython-clj2.metadata/py-fn-metadata)]
(select-keys (metadata-fn var-name var-data {}) [:doc :arglists]))
(catch Throwable _
{:doc (get-attr var-data "__doc__")})))


(defmacro from-import
"Support for the from a import b,c style of importing modules and symbols in python.
Documentation is included."
Expand All @@ -390,7 +398,7 @@ user> (py/py. np linspace 2 3 :num 10)
~@(map (fn [varname]
`(let [~'var-data (get-attr ~'mod-data ~(name varname))]
(def ~varname ~'var-data)
(alter-meta! #'~varname assoc :doc (get-attr ~'var-data "__doc__"))
(alter-meta! #'~varname merge (py-var-metadata ~(name varname) ~'var-data))
#'~varname))
(concat [item] args)))))

Expand Down
45 changes: 45 additions & 0 deletions test/libpython_clj2/metadata_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(ns libpython-clj2.metadata-test
(:require [clojure.test :refer :all]
[libpython-clj2.python :as py]
[libpython-clj2.metadata :as metadata]))

(deftest pyarglists-preserves-default-values
(let [argspec {:args ["top" "topdown" "onerror"]
:varargs nil
:varkw nil
:defaults ["." true nil]
:kwonlyargs ["follow_symlinks" "dir_fd"]
:kwonlydefaults (array-map "follow_symlinks" false
"dir_fd" nil)}]
(is (= '([& [{top "."
topdown true
onerror nil
follow_symlinks false
dir_fd nil}]]
[& [{top "."
topdown true
follow_symlinks false
dir_fd nil}]]
[& [{top "."
follow_symlinks false
dir_fd nil}]]
[& [{follow_symlinks false
dir_fd nil}]])
(metadata/pyarglists argspec)))))

(deftest py-fn-argspec-stringifies-python-object-defaults
(let [testcode (py/import-module "testcode")
default-type-fn (py/get-attr testcode "default_type_fn")]
(is (= '([& [{dtype "<class 'int'>"}]]
[])
(-> default-type-fn
metadata/py-fn-argspec
metadata/pyarglists)))))

(deftest py-fn-argspec-stringifies-kwonly-python-object-defaults
(let [testcode (py/import-module "testcode")
kw-default-type-fn (py/get-attr testcode "kw_default_type_fn")]
(is (= '([& [{dtype "<class 'int'>"}]])
(-> kw-default-type-fn
metadata/py-fn-argspec
metadata/pyarglists)))))
26 changes: 26 additions & 0 deletions test/libpython_clj2/python_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[tech.v3.datatype.ffi :as dt-ffi]
[tech.v3.tensor :as dtt]
[clojure.test :refer :all]
[clojure.repl :refer [doc]]
libpython-clj2.python.bridge-as-python)
(:import [java.io StringWriter]
[java.util Map List]
Expand Down Expand Up @@ -153,6 +154,31 @@
a)
vec)))))

(py/from-import testcode defaults_fn)

(deftest from-import-adds-arglists-metadata
(is (= '([& [{top "."
topdown true
onerror nil
follow_symlinks false
dir_fd nil}]]
[& [{top "."
topdown true
follow_symlinks false
dir_fd nil}]]
[& [{top "."
follow_symlinks false
dir_fd nil}]]
[& [{follow_symlinks false
dir_fd nil}]])
(:arglists (meta #'defaults_fn)))))

(deftest from-import-doc-renders-arglists
(let [doc-output (with-out-str (doc defaults_fn))]
(is (re-find #"defaults_fn" doc-output))
(is (re-find #"\[\[& \[\{top \"\.\"" doc-output))
(is (re-find #"Function with Python defaults for metadata tests\." doc-output))))

(deftest aspy-iter
(let [testcode-module (py/import-module "testcode")]
(is (= [1 2 3 4 5]
Expand Down
15 changes: 15 additions & 0 deletions testcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ def complex_fn(a, b, c: str = 5, *args, d=10, **kwargs):
return {"a": a, "b": b, "c": c, "args": args, "d": d, "kwargs": kwargs}


def defaults_fn(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=None):
"""Function with Python defaults for metadata tests."""
return top, topdown, onerror, follow_symlinks, dir_fd


def default_type_fn(dtype=int):
"""Function with a Python object default for metadata tests."""
return dtype


def kw_default_type_fn(*, dtype=int):
"""Function with a keyword-only Python object default for metadata tests."""
return dtype


complex_fn_testcases = {
"complex_fn(1, 2, c=10, d=10, e=10)": complex_fn(1, 2, c=10, d=10, e=10),
"complex_fn(1, 2, 10, 11, 12, d=10, e=10)": complex_fn(
Expand Down