Skip to content

Commit 4dec082

Browse files
author
Christopher Doris
committed
patch up remaining refactored modules
1 parent 291d1f4 commit 4dec082

27 files changed

+328
-279
lines changed

src/API/API.jl

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ include("types.jl")
88
include("GIL.jl")
99
include("GC.jl")
1010

11+
"""
12+
pybuiltins
13+
14+
An object whose fields are the Python builtins, of type [`Py`](@ref).
15+
16+
For example `pybuiltins.None`, `pybuiltins.int`, `pybuiltins.ValueError`.
17+
"""
18+
baremodule pybuiltins end
19+
1120
# functions
1221
function python_executable_path end
1322
function python_library_handle end
@@ -118,12 +127,25 @@ function pyall end
118127
function pyany end
119128
function pycallable end
120129
function pycompile end
130+
function pyjl end
131+
function pyjltype end
132+
function pyisjl end
133+
function pyjlvalue end
134+
function pyfunc end
135+
function pyclassmethod end
136+
function pystaticmethod end
137+
function pyproperty end
138+
function pybinaryio end
139+
function pytextio end
140+
function pyjlraw end
141+
function pytable end
121142

122143
# macros
123144
macro pyeval end
124145
macro pyexec end
125146
macro pyconst end
126147
macro pyconvert end
148+
macro py end
127149

128150
# exports
129151
export Py
@@ -225,7 +247,31 @@ export pycompile
225247
export pybuiltins
226248
export @pyconst
227249
export pyconvert
228-
250+
export @pyconvert
251+
export @py
252+
export PyArray
253+
export PyDict
254+
export PyIO
255+
export PyIterable
256+
export PyList
257+
export PySet
258+
export PyTable
259+
export PyPandasDataFrame
260+
export pyjl
261+
export pyjltype
262+
export pyisjl
263+
export pyjlvalue
264+
export pyfunc
265+
export pyclassmethod
266+
export pystaticmethod
267+
export pyproperty
268+
export pybinaryio
269+
export pytextio
270+
export pyjlraw
271+
export PyObjectVector
272+
export PyObjectMatrix
273+
export PyObjectArray
274+
export pytable
229275
# public bindings
230276
if Base.VERSION v"1.11"
231277
eval(
@@ -235,12 +281,12 @@ if Base.VERSION ≥ v"1.11"
235281
:GIL,
236282
:GC,
237283
:CONFIG,
238-
# :PyNULL,
239-
# :PYCONVERT_PRIORITY_WRAP,
240-
# :PYCONVERT_PRIORITY_ARRAY,
241-
# :PYCONVERT_PRIORITY_CANONICAL,
242-
# :PYCONVERT_PRIORITY_NORMAL,
243-
# :PYCONVERT_PRIORITY_FALLBACK,
284+
:PyNULL,
285+
:PYCONVERT_PRIORITY_WRAP,
286+
:PYCONVERT_PRIORITY_ARRAY,
287+
:PYCONVERT_PRIORITY_CANONICAL,
288+
:PYCONVERT_PRIORITY_NORMAL,
289+
:PYCONVERT_PRIORITY_FALLBACK,
244290
:python_executable_path,
245291
:python_library_handle,
246292
:python_library_path,
@@ -260,3 +306,30 @@ if Base.VERSION ≥ v"1.11"
260306
),
261307
)
262308
end
309+
310+
# aliases for special PyArray types
311+
for N in (missing, 1, 2)
312+
for M in (missing, true, false)
313+
for L in (missing, true, false)
314+
for R in (true, false)
315+
name = Symbol(
316+
"Py",
317+
M === missing ? "" : M ? "Mutable" : "Immutable",
318+
L === missing ? "" : L ? "Linear" : "Cartesian",
319+
R ? "Raw" : "",
320+
N === missing ? "Array" : N == 1 ? "Vector" : "Matrix",
321+
)
322+
name == :PyArray && continue
323+
vars = Any[
324+
:T,
325+
N === missing ? :N : N,
326+
M === missing ? :M : M,
327+
L === missing ? :L : L,
328+
R ? :T : :R,
329+
]
330+
@eval const $name{$(unique([v for v in vars if v isa Symbol])...)} = PyArray{$(vars...)}
331+
@eval export $name
332+
end
333+
end
334+
end
335+
end

src/API/types.jl

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,200 @@ mutable struct PyException <: Exception
2626
_b::Py
2727
_isnormalized::Bool
2828
end
29+
30+
"""
31+
PyArray{T,N,M,L,R}(x; copy=true, array=true, buffer=true)
32+
33+
Wrap the Python array `x` as a Julia `AbstractArray{T,N}`.
34+
35+
The input `x` can be `bytes`, `bytearray`, `array.array`, `numpy.ndarray` or anything satisfying the buffer protocol (if `buffer=true`) or the numpy array interface (if `array=true`).
36+
37+
If `copy=false` then the resulting array is guaranteed to directly wrap the data in `x`. If `copy=true` then a copy is taken if necessary to produce an array.
38+
39+
The type parameters are all optional, and are:
40+
- `T`: The element type.
41+
- `N`: The number of dimensions.
42+
- `M`: True if the array is mutable.
43+
- `L`: True if the array supports fast linear indexing.
44+
- `R`: The element type of the underlying buffer. Often equal to `T`.
45+
"""
46+
struct PyArray{T,N,M,L,R} <: AbstractArray{T,N}
47+
ptr::Ptr{R} # pointer to the data
48+
length::Int # length of the array
49+
size::NTuple{N,Int} # size of the array
50+
strides::NTuple{N,Int} # strides (in bytes) between elements
51+
py::Py # underlying python object
52+
handle::Py # the data in this array is valid as long as this handle is alive
53+
function PyArray{T,N,M,L,R}(
54+
::Val{:new},
55+
ptr::Ptr{R},
56+
size::NTuple{N,Int},
57+
strides::NTuple{N,Int},
58+
py::Py,
59+
handle::Py,
60+
) where {T,N,M,L,R}
61+
T isa Type || error("T must be a Type")
62+
N isa Int || error("N must be an Int")
63+
M isa Bool || error("M must be a Bool")
64+
L isa Bool || error("L must be a Bool")
65+
R isa DataType || error("R must be a DataType")
66+
new{T,N,M,L,R}(ptr, prod(size), size, strides, py, handle)
67+
end
68+
end
69+
70+
"""
71+
PyDict{K=Py,V=Py}([x])
72+
73+
Wraps the Python dict `x` (or anything satisfying the mapping interface) as an `AbstractDict{K,V}`.
74+
75+
If `x` is not a Python object, it is converted to one using `pydict`.
76+
"""
77+
struct PyDict{K,V} <: AbstractDict{K,V}
78+
py::Py
79+
PyDict{K,V}(x = pydict()) where {K,V} = new{K,V}(ispy(x) ? Py(x) : pydict(x))
80+
end
81+
82+
"""
83+
PyIO(x; own=false, text=missing, line_buffering=false, buflen=4096)
84+
85+
Wrap the Python IO stream `x` as a Julia IO stream.
86+
87+
When this goes out of scope and is finalized, it is automatically flushed. If `own=true` then it is also closed.
88+
89+
If `text=false` then `x` must be a binary stream and arbitrary binary I/O is possible.
90+
If `text=true` then `x` must be a text stream and only UTF-8 must be written (i.e. use `print` not `write`).
91+
If `text` is not specified then it is chosen automatically.
92+
If `x` is a text stream and you really need a binary stream, then often `PyIO(x.buffer)` will work.
93+
94+
If `line_buffering=true` then output is flushed at each line.
95+
96+
For efficiency, reads and writes are buffered before being sent to `x`.
97+
The size of the buffers is `buflen`.
98+
The buffers are cleared using `flush`.
99+
"""
100+
mutable struct PyIO <: IO
101+
py::Py
102+
# true to close the file automatically
103+
own::Bool
104+
# true if `o` is text, false if binary
105+
text::Bool
106+
# true to flush whenever '\n' or '\r' is encountered
107+
line_buffering::Bool
108+
# true if we are definitely at the end of the file; false if we are not or don't know
109+
eof::Bool
110+
# input buffer
111+
ibuflen::Int
112+
ibuf::Vector{UInt8}
113+
# output buffer
114+
obuflen::Int
115+
obuf::Vector{UInt8}
116+
117+
function PyIO(
118+
x;
119+
own::Bool = false,
120+
text::Union{Missing,Bool} = missing,
121+
buflen::Integer = 4096,
122+
ibuflen::Integer = buflen,
123+
obuflen::Integer = buflen,
124+
line_buffering::Bool = false,
125+
)
126+
if text === missing
127+
text = pyhasattr(x, "encoding")
128+
end
129+
buflen = convert(Int, buflen)
130+
buflen > 0 || error("buflen must be positive")
131+
ibuflen = convert(Int, ibuflen)
132+
ibuflen > 0 || error("ibuflen must be positive")
133+
obuflen = convert(Int, obuflen)
134+
obuflen > 0 || error("obuflen must be positive")
135+
new(Py(x), own, text, line_buffering, false, ibuflen, UInt8[], obuflen, UInt8[])
136+
end
137+
end
138+
139+
"""
140+
PyIterable{T=Py}(x)
141+
142+
This object iterates over iterable Python object `x`, yielding values of type `T`.
143+
"""
144+
struct PyIterable{T}
145+
py::Py
146+
PyIterable{T}(x) where {T} = new{T}(Py(x))
147+
end
148+
149+
"""
150+
PyList{T=Py}([x])
151+
152+
Wraps the Python list `x` (or anything satisfying the sequence interface) as an `AbstractVector{T}`.
153+
154+
If `x` is not a Python object, it is converted to one using `pylist`.
155+
"""
156+
struct PyList{T} <: AbstractVector{T}
157+
py::Py
158+
PyList{T}(x = pylist()) where {T} = new{T}(ispy(x) ? Py(x) : pylist(x))
159+
end
160+
161+
"""
162+
PySet{T=Py}([x])
163+
164+
Wraps the Python set `x` (or anything satisfying the set interface) as an `AbstractSet{T}`.
165+
166+
If `x` is not a Python object, it is converted to one using `pyset`.
167+
"""
168+
struct PySet{T} <: AbstractSet{T}
169+
py::Py
170+
PySet{T}(x = pyset()) where {T} = new{T}(ispy(x) ? Py(x) : pyset(x))
171+
end
172+
173+
"""
174+
PyTable(x)
175+
176+
Wrap `x` as a Tables.jl-compatible table.
177+
178+
`PyTable` is an abstract type. See [`PyPandasDataFrame`](@ref) for a concrete example.
179+
"""
180+
abstract type PyTable end
181+
182+
"""
183+
PyPandasDataFrame(x; [indexname::Union{Nothing,Symbol}], [columnnames::Function], [columntypes::Function])
184+
185+
Wraps the pandas DataFrame `x` as a Tables.jl-compatible table.
186+
187+
- `indexname`: The name of the column including the index. The default is `nothing`, meaning
188+
to exclude the index.
189+
- `columnnames`: A function mapping the Python column name (a `Py`) to the Julia one (a
190+
`Symbol`). The default is `x -> Symbol(x)`.
191+
- `columntypes`: A function taking the column name (a `Symbol`) and returning either the
192+
desired element type of the column, or `nothing` to indicate automatic inference.
193+
"""
194+
struct PyPandasDataFrame <: PyTable
195+
py::Py
196+
indexname::Union{Symbol,Nothing}
197+
columnnames::Function # Py -> Symbol
198+
columntypes::Function # Symbol -> Union{Type,Nothing}
199+
function PyPandasDataFrame(
200+
x;
201+
indexname::Union{Symbol,Nothing} = nothing,
202+
columnnames::Function = x -> Symbol(x),
203+
columntypes::Function = x -> nothing,
204+
)
205+
new(Py(x), indexname, columnnames, columntypes)
206+
end
207+
end
208+
209+
"""
210+
PyObjectArray(undef, dims...)
211+
PyObjectArray(array)
212+
213+
An array of `Py`s which supports the Python buffer protocol.
214+
215+
Internally, the objects are stored as an array of pointers.
216+
"""
217+
mutable struct PyObjectArray{N} <: AbstractArray{Py,N}
218+
ptrs::Array{Ptr{Cvoid},N}
219+
function PyObjectArray{N}(::UndefInitializer, dims::NTuple{N,Integer}) where {N}
220+
x = new{N}(fill(C_NULL, dims))
221+
finalizer(Internals.JlWrap.pyobjectarray_finalizer, x)
222+
end
223+
end
224+
const PyObjectVector = PyObjectArray{1}
225+
const PyObjectMatrix = PyObjectArray{2}

src/Compat/Compat.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Misc bits and bobs for compatibility.
55
"""
66
module Compat
7-
using ..PythonCall: PythonCall # needed for docstring cross-refs
7+
using ..PythonCall
88
using ..Core
99
using ..Core:
1010
Core,
@@ -25,6 +25,7 @@ using ..Wrap: PyArray, PyPandasDataFrame
2525
using Serialization: Serialization, AbstractSerializer, serialize, deserialize
2626
using Tables: Tables
2727
using Requires: @require
28+
import ..PythonCall: pytable
2829

2930
include("gui.jl")
3031
include("ipython.jl")

src/Compat/gui.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,11 @@ function init_gui()
133133
pycopy!(new_event_loop_callback, g["new_event_loop_callback"])
134134

135135
# add a hook to automatically call fix_qt_plugin_path()
136-
fixqthook =
137-
Py(() -> (Core.CONFIG.auto_fix_qt_plugin_path && fix_qt_plugin_path(); nothing))
136+
fixqthook = Py(
137+
() -> (
138+
PythonCall.CONFIG.auto_fix_qt_plugin_path && fix_qt_plugin_path(); nothing
139+
),
140+
)
138141
pymodulehooks.add_hook("PyQt4", fixqthook)
139142
pymodulehooks.add_hook("PyQt5", fixqthook)
140143
pymodulehooks.add_hook("PySide", fixqthook)

src/Compat/tables.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ function pytable(src, format = :pandas; opts...)
2626
error("invalid format")
2727
end
2828
end
29-
export pytable
3029

3130
function _pytable_columns(src, cols = Tables.columns(src))
3231
pydict(

src/Convert/pyconvert.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,6 @@ macro pyconvert(T, x, onfail = :(return $pyconvert_unconverted()))
387387
end
388388
end
389389
end
390-
export @pyconvert
391390

392391
"""
393392
pyconvert(T, x, [d])
@@ -400,7 +399,6 @@ pyconvert(::Type{T}, x) where {T} = @autopy x @pyconvert T x_ error(
400399
"cannot convert this Python '$(pytype(x_).__name__)' to a Julia '$T'",
401400
)
402401
pyconvert(::Type{T}, x, d) where {T} = @autopy x @pyconvert T x_ d
403-
export pyconvert
404402

405403
"""
406404
pyconvertarg(T, x, name)

0 commit comments

Comments
 (0)