Skip to content

Commit 4a40242

Browse files
author
Christopher Doris
committed
add wrapper types to API
1 parent a64236b commit 4a40242

File tree

11 files changed

+201
-179
lines changed

11 files changed

+201
-179
lines changed

src/API/exports.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,13 @@ export PYCONVERT_PRIORITY_ARRAY
109109
export PYCONVERT_PRIORITY_CANONICAL
110110
export PYCONVERT_PRIORITY_NORMAL
111111
export PYCONVERT_PRIORITY_FALLBACK
112+
113+
# Wrap
114+
export PyArray
115+
export PyDict
116+
export PyIO
117+
export PyIterable
118+
export PyList
119+
export PyPandasDataFrame
120+
export PySet
121+
export PyTable

src/API/types.jl

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

src/Wrap/PyArray.jl

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,6 @@ struct UnsafePyObject
22
ptr::C.PyPtr
33
end
44

5-
"""
6-
PyArray{T,N,M,L,R}(x; copy=true, array=true, buffer=true)
7-
8-
Wrap the Python array `x` as a Julia `AbstractArray{T,N}`.
9-
10-
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`).
11-
12-
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.
13-
14-
The type parameters are all optional, and are:
15-
- `T`: The element type.
16-
- `N`: The number of dimensions.
17-
- `M`: True if the array is mutable.
18-
- `L`: True if the array supports fast linear indexing.
19-
- `R`: The element type of the underlying buffer. Often equal to `T`.
20-
"""
21-
struct PyArray{T,N,M,L,R} <: AbstractArray{T,N}
22-
ptr::Ptr{R} # pointer to the data
23-
length::Int # length of the array
24-
size::NTuple{N,Int} # size of the array
25-
strides::NTuple{N,Int} # strides (in bytes) between elements
26-
py::Py # underlying python object
27-
handle::Py # the data in this array is valid as long as this handle is alive
28-
function PyArray{T,N,M,L,R}(
29-
::Val{:new},
30-
ptr::Ptr{R},
31-
size::NTuple{N,Int},
32-
strides::NTuple{N,Int},
33-
py::Py,
34-
handle::Py,
35-
) where {T,N,M,L,R}
36-
T isa Type || error("T must be a Type")
37-
N isa Int || error("N must be an Int")
38-
M isa Bool || error("M must be a Bool")
39-
L isa Bool || error("L must be a Bool")
40-
R isa DataType || error("R must be a DataType")
41-
new{T,N,M,L,R}(ptr, prod(size), size, strides, py, handle)
42-
end
43-
end
44-
export PyArray
455

466
ispy(::PyArray) = true
477
Py(x::PyArray) = x.py

src/Wrap/PyDict.jl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
"""
2-
PyDict{K=Py,V=Py}([x])
3-
4-
Wraps the Python dict `x` (or anything satisfying the mapping interface) as an `AbstractDict{K,V}`.
5-
6-
If `x` is not a Python object, it is converted to one using `pydict`.
7-
"""
8-
struct PyDict{K,V} <: AbstractDict{K,V}
9-
py::Py
10-
PyDict{K,V}(x = pydict()) where {K,V} = new{K,V}(ispy(x) ? Py(x) : pydict(x))
11-
end
12-
export PyDict
131

142
PyDict{K}(x = pydict()) where {K} = PyDict{K,Py}(x)
153
PyDict(x = pydict()) = PyDict{Py,Py}(x)

src/Wrap/PyIO.jl

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,3 @@
1-
"""
2-
PyIO(x; own=false, text=missing, line_buffering=false, buflen=4096)
3-
4-
Wrap the Python IO stream `x` as a Julia IO stream.
5-
6-
When this goes out of scope and is finalized, it is automatically flushed. If `own=true` then it is also closed.
7-
8-
If `text=false` then `x` must be a binary stream and arbitrary binary I/O is possible.
9-
If `text=true` then `x` must be a text stream and only UTF-8 must be written (i.e. use `print` not `write`).
10-
If `text` is not specified then it is chosen automatically.
11-
If `x` is a text stream and you really need a binary stream, then often `PyIO(x.buffer)` will work.
12-
13-
If `line_buffering=true` then output is flushed at each line.
14-
15-
For efficiency, reads and writes are buffered before being sent to `x`.
16-
The size of the buffers is `buflen`.
17-
The buffers are cleared using `flush`.
18-
"""
19-
mutable struct PyIO <: IO
20-
py::Py
21-
# true to close the file automatically
22-
own::Bool
23-
# true if `o` is text, false if binary
24-
text::Bool
25-
# true to flush whenever '\n' or '\r' is encountered
26-
line_buffering::Bool
27-
# true if we are definitely at the end of the file; false if we are not or don't know
28-
eof::Bool
29-
# input buffer
30-
ibuflen::Int
31-
ibuf::Vector{UInt8}
32-
# output buffer
33-
obuflen::Int
34-
obuf::Vector{UInt8}
35-
36-
function PyIO(
37-
x;
38-
own::Bool = false,
39-
text::Union{Missing,Bool} = missing,
40-
buflen::Integer = 4096,
41-
ibuflen::Integer = buflen,
42-
obuflen::Integer = buflen,
43-
line_buffering::Bool = false,
44-
)
45-
if text === missing
46-
text = pyhasattr(x, "encoding")
47-
end
48-
buflen = convert(Int, buflen)
49-
buflen > 0 || error("buflen must be positive")
50-
ibuflen = convert(Int, ibuflen)
51-
ibuflen > 0 || error("ibuflen must be positive")
52-
obuflen = convert(Int, obuflen)
53-
obuflen > 0 || error("obuflen must be positive")
54-
new(Py(x), own, text, line_buffering, false, ibuflen, UInt8[], obuflen, UInt8[])
55-
end
56-
end
57-
export PyIO
581

592
pyio_finalize!(io::PyIO) = begin
603
C.CTX.is_initialized || return

src/Wrap/PyIterable.jl

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
"""
2-
PyIterable{T=Py}(x)
3-
4-
This object iterates over iterable Python object `x`, yielding values of type `T`.
5-
"""
6-
struct PyIterable{T}
7-
py::Py
8-
PyIterable{T}(x) where {T} = new{T}(Py(x))
9-
end
10-
export PyIterable
111

122
PyIterable(x) = PyIterable{Py}(x)
133

src/Wrap/PyList.jl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
"""
2-
PyList{T=Py}([x])
3-
4-
Wraps the Python list `x` (or anything satisfying the sequence interface) as an `AbstractVector{T}`.
5-
6-
If `x` is not a Python object, it is converted to one using `pylist`.
7-
"""
8-
struct PyList{T} <: AbstractVector{T}
9-
py::Py
10-
PyList{T}(x = pylist()) where {T} = new{T}(ispy(x) ? Py(x) : pylist(x))
11-
end
12-
export PyList
131

142
PyList(x = pylist()) = PyList{Py}(x)
153

src/Wrap/PyPandasDataFrame.jl

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,3 @@
1-
"""
2-
PyPandasDataFrame(x; [indexname::Union{Nothing,Symbol}], [columnnames::Function], [columntypes::Function])
3-
4-
Wraps the pandas DataFrame `x` as a Tables.jl-compatible table.
5-
6-
- `indexname`: The name of the column including the index. The default is `nothing`, meaning
7-
to exclude the index.
8-
- `columnnames`: A function mapping the Python column name (a `Py`) to the Julia one (a
9-
`Symbol`). The default is `x -> Symbol(x)`.
10-
- `columntypes`: A function taking the column name (a `Symbol`) and returning either the
11-
desired element type of the column, or `nothing` to indicate automatic inference.
12-
"""
13-
struct PyPandasDataFrame <: PyTable
14-
py::Py
15-
indexname::Union{Symbol,Nothing}
16-
columnnames::Function # Py -> Symbol
17-
columntypes::Function # Symbol -> Union{Type,Nothing}
18-
function PyPandasDataFrame(
19-
x;
20-
indexname::Union{Symbol,Nothing} = nothing,
21-
columnnames::Function = x -> Symbol(x),
22-
columntypes::Function = x -> nothing,
23-
)
24-
new(Py(x), indexname, columnnames, columntypes)
25-
end
26-
end
27-
export PyPandasDataFrame
281

292
ispy(x::PyPandasDataFrame) = true
303
Py(x::PyPandasDataFrame) = x.py

src/Wrap/PySet.jl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
"""
2-
PySet{T=Py}([x])
3-
4-
Wraps the Python set `x` (or anything satisfying the set interface) as an `AbstractSet{T}`.
5-
6-
If `x` is not a Python object, it is converted to one using `pyset`.
7-
"""
8-
struct PySet{T} <: AbstractSet{T}
9-
py::Py
10-
PySet{T}(x = pyset()) where {T} = new{T}(ispy(x) ? Py(x) : pyset(x))
11-
end
12-
export PySet
131

142
PySet(x = pyset()) = PySet{Py}(x)
153

0 commit comments

Comments
 (0)