├── test └── clojure_python │ ├── example.py │ └── t_core.clj ├── project.clj ├── README.md └── src └── clojure_python └── core.clj /test/clojure_python/example.py: -------------------------------------------------------------------------------- 1 | # python module to import for tests 2 | 3 | def hello(s): 4 | return "hello, " + s + " how are you." 5 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-python "0.4.1" 2 | :description "Improve seamlessness of Clojure Jython interop." 3 | :dependencies [[org.clojure/clojure "1.4.0"] 4 | [org.python/jython-standalone "2.5.3"]] 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :profiles {:dev {:dependencies [[midje "1.4.0"]]}} 8 | :plugins [[lein-midje "2.0.0"]]) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clojure-python is a library for Jython interop in Clojure. 2 | 3 | Overview 4 | ======== 5 | 6 | Python libraries can be used in Clojure by instantiating a Jython interpreter 7 | via Clojure's excellent Java interop. However, the way in which Python code is 8 | wrapped by Jython makes interop with Python clumsy and verbose. This library 9 | aims to make Jython interop in Clojure nearly as seamless as basic java interop. 10 | 11 | Usage and Installation 12 | ====================== 13 | 14 | To include as a dependency: 15 | -------------------------- 16 | 17 | Copy the config section found at http://clojars.org/clojure-python into your 18 | dependencies in your project's project.clj. 19 | 20 | License 21 | ======= 22 | 23 | Copyright (C) 2010-2012 Robert P. Levy 24 | 25 | Distributed under the Eclipse Public License, the same as Clojure. 26 | -------------------------------------------------------------------------------- /test/clojure_python/t_core.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-python.t-core 2 | (:require [midje.sweet :refer :all] 3 | [clojure-python.core :as base])) 4 | 5 | (fact "append-paths adds the path to system path" 6 | (binding [base/*interp* (org.python.util.PythonInterpreter.)] 7 | (-> (#'base/append-paths ["test/clojure_python/"]) 8 | .getLocals 9 | (.__getitem__ "sys") 10 | .path 11 | set 12 | (get "test/clojure_python/"))) 13 | => 14 | "test/clojure_python/") 15 | 16 | (fact "init sets *interp* root binding (but only once)" 17 | (do 18 | (base/init {:libpaths ["test/clojure_python/"]}) 19 | (class base/*interp*)) 20 | => 21 | org.python.util.PythonInterpreter 22 | 23 | (do 24 | (base/init {:libpaths ["test/clojure_python/"]}) 25 | (class base/*interp*)) 26 | => 27 | (do 28 | (base/init {:libpaths ["test/clojure_python/"]}) 29 | (class base/*interp*))) 30 | 31 | 32 | (defmacro with-test-interp [& body] 33 | `(base/with-interpreter 34 | {:libpaths ["test/clojure_python/"]} 35 | ~@body)) 36 | 37 | (fact "with-interpreter dynamically binds a new interpreter environment" 38 | (with-test-interp base/*interp*) 39 | =not=> 40 | (with-test-interp base/*interp*)) 41 | 42 | (fact "importing python modules works" 43 | (with-test-interp 44 | (base/py-import-lib example) 45 | (class example)) 46 | => 47 | org.python.core.PyStringMap) 48 | 49 | (fact "importing python functions works" 50 | (with-test-interp 51 | (base/py-import-lib example) 52 | (base/import-fn example hello) 53 | (fn? hello)) 54 | => 55 | true) 56 | 57 | (fact "calling python functions works" 58 | (with-test-interp 59 | (base/py-import-lib example) 60 | (base/import-fn example hello) 61 | (hello "world")) 62 | => 63 | "hello, world how are you." 64 | 65 | (with-test-interp 66 | (base/py-import-lib example) 67 | ((base/py-fn example hello) 68 | "person")) 69 | => 70 | "hello, person how are you.") 71 | -------------------------------------------------------------------------------- /src/clojure_python/core.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-python.core 2 | (:require [clojure [string :as str]]) 3 | (:import [org.python.util PythonInterpreter] 4 | [org.python.core PyObject Py])) 5 | 6 | (declare ^:dynamic *interp*) 7 | 8 | (defn append-paths 9 | "Appends a vector of paths to the python system path." 10 | [libpaths] 11 | (.exec *interp* "import sys") 12 | (doseq [p libpaths] 13 | (.exec *interp* (str "sys.path.append('" p "')"))) 14 | *interp*) 15 | 16 | (defn init 17 | "Establish a global python interpreter. The init function is only usefully 18 | called once. Alternatively, only use with-interpreter." 19 | [{:keys [libpaths] :as options}] 20 | (defonce ^:dynamic 21 | ^{:doc "root binding serves as global python interpreter"} 22 | *interp* 23 | (PythonInterpreter.)) 24 | (append-paths libpaths)) 25 | 26 | (defmacro with-interpreter 27 | "Dynamically bind a new python interpreter for the calling context." 28 | [{:keys [libpaths] :as options} & body] 29 | `(binding [*interp* (PythonInterpreter.)] 30 | (append-paths ~libpaths) 31 | ~@body)) 32 | 33 | (defmacro py-import-lib 34 | "Import lib. Defaults to use same name it has in python. If it is something 35 | like foo.bar, the name is bar." 36 | [lib & libs] 37 | (let [lib-sym (or (last libs) lib) 38 | lib-strs (map name (cons lib libs)) 39 | py-name (str/join "." lib-strs)] 40 | `(do (.exec *interp* (str "import " ~py-name)) 41 | (def ~lib-sym 42 | (-> *interp* 43 | .getLocals 44 | (.__getitem__ ~(first lib-strs)) 45 | ~@(map (fn [lib#] `(.__getattr__ ~lib#)) 46 | (next lib-strs)) 47 | .__dict__))))) 48 | 49 | (defmacro py-import-obj 50 | "Import objects from lib." 51 | [lib obj & objs] 52 | (cons 'do 53 | (map 54 | (fn [o#] 55 | `(def ~o# (.__finditem__ ~lib ~(name o#))))) 56 | (cons obj objs))) 57 | 58 | (defmacro py-fn 59 | "Create a native clojure function applying the python wrapper calls on a python 60 | function at the top level of the library use this where lambda is preferred 61 | over named function." 62 | [lib fun] 63 | `(let [f# (.__finditem__ 64 | ~lib 65 | ~(name fun))] 66 | (fn [& args#] 67 | (call f# args#)))) 68 | 69 | (defmacro import-fn 70 | "This is like import but it defines the imported item as a native function that 71 | applies the python wrapper calls." 72 | [lib fun & funs] 73 | (cons 'do 74 | (map 75 | (fn [fun] 76 | `(def ~fun (py-fn ~lib ~fun))) 77 | (cons fun funs)))) 78 | 79 | (defmacro __ 80 | "Access attribute of class or attribute of attribute of (and so on) class." 81 | ([class attr] 82 | `(.__findattr__ ~class ~(name attr))) 83 | ([class attr & attrs] 84 | `(__ (__ ~class ~attr) ~@attrs))) 85 | 86 | (defmacro _> 87 | "Call attribute as a method. 88 | Basic usage: (_> [class attrs ...] args ...) 89 | Usage with keyword args: (_> [class attrs ...] args ... :key arg :key arg) 90 | Keyword args must come after any non-keyword args" 91 | ([[class & attrs] & args] 92 | (let [keywords (map name (filter keyword? args)) 93 | non-keywords (filter (fn [a] (not (keyword? a))) args)] 94 | `(call (__ ~class ~@attrs) [~@non-keywords] ~@keywords)))) 95 | 96 | (defn dir 97 | "It's slightly nicer to call the dir method in this way." 98 | [x] (seq (.__dir__ x))) 99 | 100 | (defn pyobj-nth 101 | "Nth item in a 'PyObjectDerived'." 102 | [o i] (.__getitem__ o i)) 103 | 104 | (defn pyobj-range 105 | "Access 'PyObjectDerived' items as non-lazy range." 106 | [o start end] (for [i (range start end)] (pyobj-nth o i))) 107 | 108 | (defn pyobj-iterate 109 | "Access 'PyObjectDerived' items as Lazy Seq." 110 | [pyobj] (lazy-seq (.__iter__ pyobj))) 111 | 112 | (defn java2py 113 | "To wrap java objects for input as jython, and unwrap Jython output as java." 114 | [args] 115 | (into-array 116 | PyObject 117 | (map #(. Py java2py %) args))) 118 | 119 | (defn call 120 | "The first len(args)-len(keywords) members of args[] are plain arguments. The 121 | last len(keywords) arguments are the values of the keyword arguments." 122 | [fun args & key-args] 123 | (.__tojava__ 124 | (if key-args 125 | (.__call__ fun (java2py args) (into-array java.lang.String key-args)) 126 | (.__call__ fun (java2py args))) 127 | Object)) 128 | --------------------------------------------------------------------------------