Skip to content

Commit 8da4fa4

Browse files
committed
Some preliminary 'with' support.
1 parent a2302ec commit 8da4fa4

File tree

5 files changed

+89
-8
lines changed

5 files changed

+89
-8
lines changed

src/libpython_clj/jna/concrete/err.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
195195
This function is normally only used by code that needs to save and restore the error
196196
indicator temporarily. Use PyErr_Fetch() to save the current error indicator."
197+
nil
197198
[type ensure-pyobj]
198199
[value ensure-pyobj]
199200
[traceback ensure-pyobj])

src/libpython_clj/python.clj

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
[libpython-clj.python.interop :as pyinterop]
44
[libpython-clj.python.interpreter :as pyinterp
55
:refer [with-gil with-interpreter]]
6-
[libpython-clj.python.bridge])
6+
[libpython-clj.python.object :as pyobj]
7+
[libpython-clj.python.bridge]
8+
[libpython-clj.jna :as pyjna]
9+
[tech.jna :as jna]
10+
[libpython-clj.jna.concrete.err :as py-err])
711
(:import [com.sun.jna Pointer]
12+
[com.sun.jna.ptr PointerByReference]
813
[java.io Writer]
914
[libpython_clj.jna PyObject]))
1015

@@ -146,3 +151,59 @@
146151
global interpreter and reinitialization is unsupported cpython."
147152
[]
148153
(pyinterp/finalize!))
154+
155+
156+
(defmacro with
157+
"Support for the 'with' statement in python."
158+
[bind-vec & body]
159+
(when-not (= 2 (count bind-vec))
160+
(throw (Exception. "Bind vector must have 2 items")))
161+
(let [varname (first bind-vec)]
162+
`(with-gil
163+
(let [~@bind-vec]
164+
(try
165+
(with-bindings
166+
{#'libpython-clj.python.interpreter/*python-error-handler*
167+
(fn []
168+
(let [ptype# (PointerByReference.)
169+
pvalue# (PointerByReference.)
170+
ptraceback# (PointerByReference.)
171+
_# (pyjna/PyErr_Fetch ptype# pvalue# ptraceback#)
172+
ptype# (-> (jna/->ptr-backing-store ptype#)
173+
(pyobj/wrap-pyobject true))
174+
pvalue# (-> (jna/->ptr-backing-store pvalue#)
175+
(pyobj/wrap-pyobject true))
176+
ptraceback# (-> (jna/->ptr-backing-store ptraceback#)
177+
(pyobj/wrap-pyobject true))]
178+
;;We own the references so they have to be released.
179+
(throw (ex-info "python error in flight"
180+
{:ptype ptype#
181+
:pvalue pvalue#
182+
:ptraceback ptraceback#}))))}
183+
(call-attr ~varname "__enter__")
184+
(let [retval#
185+
(do
186+
~@body)]
187+
(call-attr ~varname "__exit__" nil nil nil)
188+
retval#))
189+
(catch Throwable e#
190+
(let [einfo# (ex-data e#)]
191+
(if (= #{:ptype :pvalue :ptraceback} (set (keys einfo#)))
192+
(let [{ptype# :ptype
193+
pvalue# :pvalue
194+
ptraceback# :ptraceback} einfo#
195+
suppress-error?# (call-attr ~varname "__exit__"
196+
ptype#
197+
pvalue#
198+
ptraceback#)]
199+
(when (and ptype# pvalue# ptraceback#
200+
(not suppress-error?#))
201+
(do
202+
;;MAnuall incref here because we cannot detach the object
203+
;;from our gc decref hook added above.
204+
(pyjna/Py_IncRef ptype#)
205+
(pyjna/Py_IncRef pvalue#)
206+
(pyjna/Py_IncRef ptraceback#)
207+
(pyjna/PyErr_Restore ptype# pvalue# ptraceback#)
208+
(pyinterp/check-error-throw))))
209+
(throw e#)))))))))

src/libpython_clj/python/interpreter.clj

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
JVMBridge
99
PyObject]
1010
[com.sun.jna Pointer]
11+
[com.sun.jna.ptr PointerByReference]
1112
[java.io StringWriter]))
1213

1314

@@ -247,21 +248,26 @@
247248
(construct-main-interpreter! (libpy/PyEval_SaveThread) type-symbols))))
248249

249250

251+
(def ^:dynamic *python-error-handler* nil)
252+
253+
250254
(defn check-error-str
251255
"Function assumes python stdout and stderr have been redirected"
252256
[]
253257
(with-gil
254258
(when-not (= nil (libpy/PyErr_Occurred))
255-
(let [custom-writer (StringWriter.)]
256-
(with-bindings {#'*err* custom-writer}
257-
(libpy/PyErr_Print))
258-
(.toString custom-writer)))))
259+
(if-not *python-error-handler*
260+
(let [custom-writer (StringWriter.)]
261+
(with-bindings {#'*err* custom-writer}
262+
(libpy/PyErr_Print))
263+
(.toString custom-writer))
264+
(*python-error-handler*)))))
259265

260266

261267
(defn check-error-throw
262268
[]
263269
(when-let [error-str (check-error-str)]
264-
(throw (ex-info error-str {}))))
270+
(throw (Exception. ^String error-str))))
265271

266272

267273
(defn check-error-log

src/libpython_clj/python/object.clj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@
102102
called. Used for new references. This is some of the meat of the issue, however,
103103
in that getting the two system's garbage collectors to play nice is kind
104104
of tough."
105-
[pyobj]
106-
(check-error-throw)
105+
[pyobj & [skip-check-error?]]
106+
(when-not skip-check-error?
107+
(check-error-throw))
107108
(when pyobj
108109
(let [interpreter (ensure-bound-interpreter)
109110
pyobj-value (Pointer/nativeValue (jna/as-ptr pyobj))

testcode/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class WithObjClass:
2+
def __init__(self, suppress):
3+
self.suppress = suppress
4+
def __enter__(self):
5+
print( "Entering test obj")
6+
def doit_noerr(self):
7+
return 1
8+
def doit_err(self):
9+
raise Exception("Spam", "Eggs")
10+
def __exit__(self, ex_type, ex_val, ex_traceback):
11+
print( "Exiting: " + str(ex_type) + str(ex_val) + str(ex_traceback))
12+
return self.suppress

0 commit comments

Comments
 (0)