Skip to content

Commit 6195625

Browse files
committed
conversion of Julia IO objects -> Python IOBase objects; RawIOBase functionality pending JuliaLang/julia#3878
1 parent 0664691 commit 6195625

File tree

3 files changed

+361
-3
lines changed

3 files changed

+361
-3
lines changed

src/PyCall.jl

+4
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ function pyfinalize()
380380
pydecref(builtin::PyObject)
381381
pydecref(inspect::PyObject)
382382
pyexc_finalize()
383+
pycallback_finalize()
384+
pyio_finalize()
383385
pygc_finalize()
384386
gc() # collect/decref any remaining PyObjects
385387
ccall((@pysym :Py_Finalize), Void, ())
@@ -412,6 +414,8 @@ include("pytype.jl")
412414

413415
include("callback.jl")
414416

417+
include("io.jl")
418+
415419
#########################################################################
416420
# Pretty-printing PyObject
417421

src/io.jl

+353
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
# Julia IO subclasses are converted into Python objects implementing
2+
# the IOBase + RawIOBase interface.
3+
#
4+
# (A useful model here is the Python FileIO class, which is implemented
5+
# in Modules/_io/fileio.c in the Python source code.)
6+
7+
##########################################################################
8+
# IOBase methods:
9+
10+
# IO objects should raise IOError for unsupported operations or failed IO
11+
function ioraise(e)
12+
if isa(e, MethodError) || isa(e, ErrorException)
13+
ccall((@pysym :PyErr_SetString), Void, (PyPtr, Ptr{Uint8}),
14+
(pyexc::Dict)[PyIOError],
15+
bytestring(string("Julia exception: ", e)))
16+
else
17+
pyraise(e)
18+
end
19+
end
20+
21+
function jl_IO_close(self_::PyPtr, noarg_::PyPtr)
22+
try
23+
io = unsafe_pyjlwrap_to_objref(self_)::IO
24+
close(io)
25+
ccall((@pysym :Py_IncRef), Void, (PyPtr,), pynothing::PyPtr)
26+
return pynothing::PyPtr
27+
catch e
28+
ioraise(e)
29+
end
30+
return convert(PyPtr, C_NULL)
31+
end
32+
33+
# "closed" must be an attribute "for backwards compatibility"
34+
function jl_IO_closed(self_::PyPtr, closure::Ptr{Void})
35+
try
36+
io = unsafe_pyjlwrap_to_objref(self_)::IO
37+
# Note: isopen is not defined for all IO subclasses in Julia.
38+
# Should we do something different than throwing a MethodError,
39+
# if isopen is not available?
40+
return pyincref(PyObject(!isopen(io))).o
41+
catch e
42+
ioraise(e)
43+
end
44+
return convert(PyPtr, C_NULL)
45+
end
46+
47+
function jl_IO_fileno(self_::PyPtr, noarg_::PyPtr)
48+
try
49+
io = unsafe_pyjlwrap_to_objref(self_)::IO
50+
return pyincref(PyObject(fd(io))).o
51+
catch e
52+
ioraise(e)
53+
end
54+
return convert(PyPtr, C_NULL)
55+
end
56+
57+
function jl_IO_flush(self_::PyPtr, noarg_::PyPtr)
58+
try
59+
io = unsafe_pyjlwrap_to_objref(self_)::IO
60+
if method_exists(flush, (typeof(io),))
61+
flush(io)
62+
end
63+
ccall((@pysym :Py_IncRef), Void, (PyPtr,), pynothing::PyPtr)
64+
return pynothing::PyPtr
65+
catch e
66+
ioraise(e)
67+
end
68+
return convert(PyPtr, C_NULL)
69+
end
70+
71+
function jl_IO_isatty(self_::PyPtr, noarg_::PyPtr)
72+
try
73+
io = unsafe_pyjlwrap_to_objref(self_)::IO
74+
return pyincref(PyObject(isa(io, Base.TTY))).o
75+
catch e
76+
ioraise(e)
77+
end
78+
return convert(PyPtr, C_NULL)
79+
end
80+
81+
function jl_IO_readable(self_::PyPtr, noarg_::PyPtr)
82+
try
83+
io = unsafe_pyjlwrap_to_objref(self_)::IO
84+
return pyincref(PyObject(isreadable(io))).o
85+
catch e
86+
ioraise(e)
87+
end
88+
return convert(PyPtr, C_NULL)
89+
end
90+
91+
function jl_IO_writable(self_::PyPtr, noarg_::PyPtr)
92+
try
93+
io = unsafe_pyjlwrap_to_objref(self_)::IO
94+
return pyincref(PyObject(iswritable(io))).o
95+
catch e
96+
ioraise(e)
97+
end
98+
return convert(PyPtr, C_NULL)
99+
end
100+
101+
function jl_IO_readline(self_::PyPtr, args_::PyPtr)
102+
try
103+
io = unsafe_pyjlwrap_to_objref(self_)::IO
104+
105+
nargs = ccall((@pysym :PySequence_Size), Int, (PyPtr,), args_)
106+
nb = typemax(Int) # max #bytes to return
107+
if nargs == 1
108+
nb = convert(Int, PyObject(ccall((@pysym :PySequence_GetItem),
109+
PyPtr, (PyPtr, Int), args_, 0)))
110+
if nb < 0
111+
nb = typemax(Int)
112+
end
113+
elseif nargs > 1
114+
throw(ArgumentError("readline cannot accept $nargs arguments"))
115+
end
116+
117+
d = readline(io)
118+
if length(d) > nb
119+
resize!(d, nb)
120+
end
121+
return pyincref(PyObject(d)).o
122+
catch e
123+
ioraise(e)
124+
end
125+
return convert(PyPtr, C_NULL)
126+
end
127+
128+
function jl_IO_readlines(self_::PyPtr, args_::PyPtr)
129+
try
130+
io = unsafe_pyjlwrap_to_objref(self_)::IO
131+
132+
nargs = ccall((@pysym :PySequence_Size), Int, (PyPtr,), args_)
133+
nb = typemax(Int) # max #bytes to return
134+
if nargs == 1
135+
nb = convert(Int, PyObject(ccall((@pysym :PySequence_GetItem),
136+
PyPtr, (PyPtr, Int), args_, 0)))
137+
if nb < 0
138+
nb = typemax(Int)
139+
end
140+
elseif nargs > 1
141+
throw(ArgumentError("readlines cannot accept $nargs arguments"))
142+
end
143+
144+
ret = PyObject[]
145+
nread = 0
146+
while nread < nb
147+
d = readline(io)
148+
nread += length(d)
149+
push!(ret, PyObject(d))
150+
end
151+
return pyincref(PyObject(ret)).o
152+
catch e
153+
ioraise(e)
154+
end
155+
return convert(PyPtr, C_NULL)
156+
end
157+
158+
function jl_IO_seek(self_::PyPtr, args_::PyPtr)
159+
try
160+
io = unsafe_pyjlwrap_to_objref(self_)::IO
161+
162+
nargs = ccall((@pysym :PySequence_Size), Int, (PyPtr,), args_)
163+
if nargs < 1 || nargs > 2
164+
throw(ArgumentError("seek cannot accept $nargs arguments"))
165+
end
166+
offset = convert(FileOffset,
167+
PyObject(ccall((@pysym :PySequence_GetItem),
168+
PyPtr, (PyPtr, Int), args_, 0)))
169+
whence = nargs == 1 ? 0 :
170+
convert(Int, PyObject(ccall((@pysym :PySequence_GetItem),
171+
PyPtr, (PyPtr, Int), args_, 1)))
172+
173+
174+
if whence == 0
175+
seek(io, offset)
176+
elseif whence == 1
177+
skip(io, offset)
178+
elseif whence == 2
179+
seekend(io)
180+
skip(io, offset)
181+
else
182+
throw(ArgumentError("unrecognized whence=$n argument to seek"))
183+
end
184+
return pyincref(PyObject(position(io))).o
185+
catch e
186+
ioraise(e)
187+
end
188+
return convert(PyPtr, C_NULL)
189+
end
190+
191+
isseekable(io) = method_exists(seek, (typeof(io), FileOffset))
192+
isseekable(io::IOBuffer) = io.seekable
193+
194+
function jl_IO_seekable(self_::PyPtr, noarg_::PyPtr)
195+
try
196+
io = unsafe_pyjlwrap_to_objref(self_)::IO
197+
return pyincref(PyObject(isseekable(io))).o
198+
catch e
199+
ioraise(e)
200+
end
201+
return convert(PyPtr, C_NULL)
202+
end
203+
204+
function jl_IO_tell(self_::PyPtr, noarg_::PyPtr)
205+
try
206+
io = unsafe_pyjlwrap_to_objref(self_)::IO
207+
return pyincref(PyObject(position(io))).o
208+
catch e
209+
ioraise(e)
210+
end
211+
return convert(PyPtr, C_NULL)
212+
end
213+
214+
function jl_IO_writelines(self_::PyPtr, arg_::PyPtr)
215+
try
216+
io = unsafe_pyjlwrap_to_objref(self_)::IO
217+
for s in PyVector{String}(PyObject(arg_))
218+
write(io, s)
219+
end
220+
ccall((@pysym :Py_IncRef), Void, (PyPtr,), pynothing::PyPtr)
221+
return pynothing::PyPtr
222+
catch e
223+
ioraise(e)
224+
end
225+
return convert(PyPtr, C_NULL)
226+
end
227+
228+
##########################################################################
229+
# RawIOBase methods:
230+
231+
function jl_IO_read(self_::PyPtr, args_::PyPtr)
232+
try
233+
io = unsafe_pyjlwrap_to_objref(self_)::IO
234+
235+
nargs = ccall((@pysym :PySequence_Size), Int, (PyPtr,), args_)
236+
if nargs > 1
237+
throw(ArgumentError("read cannot accept $nargs arguments"))
238+
end
239+
nb = nargs == 0 ? -1 :
240+
convert(Int, PyObject(ccall((@pysym :PySequence_GetItem),
241+
PyPtr, (PyPtr, Int), args_, 0)))
242+
if nb < 0
243+
nb = typemax(Int)
244+
end
245+
246+
return pyincref(PyObject(readbytes(io, nb))).o
247+
catch e
248+
ioraise(e)
249+
end
250+
return convert(PyPtr, C_NULL)
251+
end
252+
253+
function jl_IO_readall(self_::PyPtr, noarg_::PyPtr)
254+
try
255+
io = unsafe_pyjlwrap_to_objref(self_)::IO
256+
return pyincref(PyObject(readbytes(io))).o
257+
catch e
258+
ioraise(e)
259+
end
260+
return convert(PyPtr, C_NULL)
261+
end
262+
263+
function jl_IO_readinto(self_::PyPtr, arg_::PyPtr)
264+
try
265+
io = unsafe_pyjlwrap_to_objref(self_)::IO
266+
b = convert(Vector{Uint8}, PyObject(arg_))
267+
return pyincref(PyObject(readbytes!(io, b))).o
268+
catch e
269+
ioraise(e)
270+
end
271+
return convert(PyPtr, C_NULL)
272+
end
273+
274+
function jl_IO_write(self_::PyPtr, arg_::PyPtr)
275+
try
276+
io = unsafe_pyjlwrap_to_objref(self_)::IO
277+
b = convert(Vector{Uint8}, arg_)
278+
return pyincref(PyObject(write(io, b))).o
279+
catch e
280+
ioraise(e)
281+
end
282+
return convert(PyPtr, C_NULL)
283+
end
284+
285+
##########################################################################
286+
# TODO: support other Python interfaces (e.g. TextIO) when possible?
287+
288+
##########################################################################
289+
290+
const jl_IO_methods = PyMethodDef[
291+
PyMethodDef("close", jl_IO_close, METH_NOARGS),
292+
PyMethodDef("fileno", jl_IO_fileno, METH_NOARGS),
293+
PyMethodDef("flush", jl_IO_flush, METH_NOARGS),
294+
PyMethodDef("isatty", jl_IO_isatty, METH_NOARGS),
295+
PyMethodDef("readable", jl_IO_readable, METH_NOARGS),
296+
PyMethodDef("writable", jl_IO_writable, METH_NOARGS),
297+
PyMethodDef("readline", jl_IO_readline, METH_VARARGS),
298+
PyMethodDef("readlines", jl_IO_readlines, METH_VARARGS),
299+
PyMethodDef("seek", jl_IO_seek, METH_VARARGS),
300+
PyMethodDef("seekable", jl_IO_seekable, METH_NOARGS),
301+
PyMethodDef("tell", jl_IO_tell, METH_NOARGS),
302+
PyMethodDef("writelines", jl_IO_writelines, METH_O),
303+
PyMethodDef("read", jl_IO_read, METH_VARARGS),
304+
PyMethodDef("readall", jl_IO_readall, METH_NOARGS),
305+
PyMethodDef("readinto", jl_IO_readinto, METH_O),
306+
PyMethodDef("write", jl_IO_write, METH_O),
307+
PyMethodDef() # sentinel
308+
]
309+
310+
const jl_IO_getset = PyGetSetDef[
311+
PyGetSetDef("closed", jl_IO_closed)
312+
PyGetSetDef()
313+
]
314+
315+
function pyio_repr(o::PyPtr)
316+
o = PyObject(try string("<PyCall.io ",unsafe_pyjlwrap_to_objref(o),">")
317+
catch "<PyCall.io NULL>"; end)
318+
oret = o.o
319+
o.o = convert(PyPtr, C_NULL) # don't decref
320+
return oret
321+
end
322+
const pyio_repr_ptr = cfunction(pyio_repr, PyPtr, (PyPtr,))
323+
324+
jl_IOType = PyTypeObject()
325+
function pyio_initialize()
326+
global jl_IOType
327+
if (jl_IOType::PyTypeObject).tp_name == C_NULL
328+
jl_IOType::PyTypeObject =
329+
pyjlwrap_type("PyCall.jl_IO",
330+
t -> begin
331+
t.tp_getattro = pysym(:PyObject_GenericGetAttr)
332+
t.tp_methods = pointer(jl_IO_methods)
333+
t.tp_getset = pointer(jl_IO_getset)
334+
t.tp_repr = pyio_repr_ptr
335+
end)
336+
end
337+
return
338+
end
339+
340+
function pyio_finalize()
341+
global jl_IOType
342+
jl_IOType::PyTypeObject = PyTypeObject()
343+
end
344+
345+
##########################################################################
346+
347+
function PyObject(io::IO)
348+
global jl_IOType
349+
if (jl_IOType::PyTypeObject).tp_name == C_NULL
350+
pyio_initialize()
351+
end
352+
pyjlwrap_new(jl_IOType::PyTypeObject, io)
353+
end

src/pytype.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ function PyMethodDef(name::String, meth::Function, flags::Integer, doc::String="
4646
end
4747

4848
# used as sentinel value to end method arrays:
49-
PyMethodDef() = PyMethodDef(NULL_Uint8_Ptr, C_NULL, 0, NULL_Uint8_Ptr)
49+
PyMethodDef() = PyMethodDef(NULL_Uint8_Ptr, C_NULL,
50+
convert(Cint, 0), NULL_Uint8_Ptr)
5051

5152
################################################################
5253
# mirror of Python API types and constants from descrobject.h
@@ -60,15 +61,15 @@ immutable PyGetSetDef
6061
end
6162

6263
function PyGetSetDef(name::String, get::Function,set::Function, doc::String="")
63-
PyGetSetDef(gstring_ptr(name),
64+
PyGetSetDef(gstring_ptr(name, name),
6465
cfunction(get, PyPtr, (PyPtr,Ptr{Void})),
6566
cfunction(set, PyPtr, (PyPtr,Ptr{Void})),
6667
isempty(doc) ? NULL_Uint8_Ptr : gstring_ptr(name, doc),
6768
C_NULL)
6869
end
6970

7071
function PyGetSetDef(name::String, get::Function, doc::String="")
71-
PyGetSetDef(gstring_ptr(name),
72+
PyGetSetDef(gstring_ptr(name, name),
7273
cfunction(get, PyPtr, (PyPtr,Ptr{Void})),
7374
C_NULL,
7475
isempty(doc) ? NULL_Uint8_Ptr : gstring_ptr(name, doc),

0 commit comments

Comments
 (0)