diff --git a/src/libpython_clj/metadata.clj b/src/libpython_clj/metadata.clj index 947f59c..4f5f7c7 100644 --- a/src/libpython_clj/metadata.clj +++ b/src/libpython_clj/metadata.clj @@ -17,6 +17,8 @@ (def inspect (as-jvm (import-module "inspect") {})) (def argspec (get-attr inspect "getfullargspec")) (def py-source (get-attr inspect "getsource")) +(def py-sourcelines (get-attr inspect "getsourcelines")) +(def py-file (get-attr inspect "getfile")) (def types (import-module "types")) (def fn-type (call-attr builtins "tuple" @@ -42,11 +44,25 @@ (def importlib (py/import-module "importlib")) (def importlib_util (import-module "importlib.util")) (def reload-module (py/get-attr importlib "reload")) + (defn findspec [x] (let [-findspec (-> importlib_util (get-attr "find_spec"))] (-findspec x))) + +(defn find-lineno [x] + (try + (-> x py-sourcelines last) + (catch Exception _ + nil))) + +(defn find-file [x] + (try + (py-file x) + (catch Exception _ + nil))) + (defn py-fn-argspec [f] (if-let [spec (try (when-not (pyclass? f) (argspec f)) @@ -155,10 +171,6 @@ (recur argspec' defaults' arglists)))))) -(defn py-class-argspec [class] - (let [constructor (py/get-attr class "__init__")] - (py-fn-argspec constructor))) - (defn py-fn-metadata [fn-name x {:keys [no-arglists?]}] (let [fn-argspec (pyargspec x) @@ -190,14 +202,18 @@ (defn base-pyobj-map [item] - (merge {:type (py/python-type item) - :doc (doc item) - :str (.toString item) - :flags (pyobj-flags item)} - (when (has-attr? item "__module__") - {:module (get-attr item "__module__")}) - (when (has-attr? item "__name__") - {:name (get-attr item "__name__")}))) + (cond-> {:type (py/python-type item) + :doc (doc item) + :str (.toString item) + :flags (pyobj-flags item) + :line (find-lineno item) + :file (find-file item)} + (has-attr? item "__module__") + (assoc :module (get-attr item "__module__")) + (has-attr? item "__name__") + (assoc :name (get-attr item "__name__")) + (and (find-lineno item) (find-file item)) + (assoc :line (find-lineno item) :file (find-file item)))) (defn scalar? @@ -280,10 +296,15 @@ (defn metadata-map->py-obj [metadata-map] - (case (:type metadata-map) - :module (import-module (:name metadata-map)) - :type (-> (import-module (:module metadata-map)) - (get-attr (:name metadata-map))))) + (try + (case (:type metadata-map) + :module (import-module (:name metadata-map)) + :type (-> (import-module (:module metadata-map)) + (get-attr (:name metadata-map)))) + (catch Exception _ + ;; metatypes -- e.g. socket.SocketIO + (-> (import-module (:module metadata-map)) + (get-attr (:name metadata-map)))))) (defn get-or-create-namespace! diff --git a/src/libpython_clj/require.clj b/src/libpython_clj/require.clj index 0513309..3cc4cca 100644 --- a/src/libpython_clj/require.clj +++ b/src/libpython_clj/require.clj @@ -127,7 +127,9 @@ " to something without periods.")))) (intern (symbol (str *ns*)) - (symbol import-name) + (with-meta (symbol import-name) + {:file (metadata/find-file pyobj) + :line 1}) pyobj))) (when (or (not existing-py-ns?) reload?) diff --git a/test/libpython_clj/require_python_test.clj b/test/libpython_clj/require_python_test.clj index a79b39b..42afbea 100644 --- a/test/libpython_clj/require_python_test.clj +++ b/test/libpython_clj/require_python_test.clj @@ -114,7 +114,35 @@ (is (set? (datafy (python/frozenset [1 2 3]))))) - - (deftest import-python-test (is (= :ok (libpython-clj.require/import-python)))) + +(require-python '[socket :bind-ns true]) +(require-python 'socket.SocketIO) +(deftest metadata-test + (testing "metadata generation" + ;; module meta + (let [{line :line file :file} (meta #'socket)] + (is (= 1 line) + "Modules have line numbers") + (is (string? file) + "Modules have file paths")) + ;; class meta + (let [{line :line file :file} (meta #'socket/SocketIO)] + (is (int? line) + "Classes have line numbers") + (is (string? file) + "Classes have file paths")) + ;; function meta + (let [{line :line file :file} (meta #'socket/_intenum_converter)] + (is (int? line) + "Functions have line numbers") + (is (string? file) + "Functions have file paths")) + ;; method meta + (let [{line :line file :file} (meta #'socket.SocketIO/readable)] + (is (int? line) + "Methods have line numbers") + (is (string? file) + "Methods have file paths")))) +