Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

eschnett / ASDF.jl / 79

8 Feb 2020 - 14:23 coverage decreased (-0.3%) to 56.489%
79

Pull #7

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Install TagBot as a GitHub Action
Pull Request #7: Install TagBot as a GitHub Action

74 of 131 relevant lines covered (56.49%)

456.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.49
/src/ASDF.jl
1
module ASDF
2

3
using PyCall
4
using URIParser
5

6
const asdf = PyNULL()
7
function __init__()
8
    # copy!(asdf, pyimport_conda("asdf", "asdf", "conda-forge"))
9
    # copy!(asdf, pyimport_conda("asdf", "asdf", "astropy"))
10
    copy!(asdf, pyimport("asdf"))
12×
11
end
12

13

14

15
################################################################################
16

17
struct Empty end
18
const empty = Empty()
19

20
const Maybe{T} = Union{Nothing, T}
21
nothing2tuple(x) = x
!
22
nothing2tuple(::Nothing) = ()
!
23

24

25

26
################################################################################
27

28
const tag2asdftype = Dict{String, Type}()
29

30
function makeASDFType(pyobj::PyObject)
31
    tag = try
590×
32
        pyobj.yaml_tag
382×
33
    catch
34
        # Convert to a nice Julia type if possible
35
        return convert(PyAny, pyobj)
42×
36
    end
37
    type_ = get(tag2asdftype, tag, empty)
312×
38
    if type_ === empty
312×
39
        @show tag
!
40
        @assert false           # for debugging
!
41
        return pyobj
42
    end
43
    type_(pyobj)::ASDFType
312×
44
end
45

46

47

48
################################################################################
49

50
struct File
51
    pyobj::PyObject
84×
52
end
53

54
function File(dict::Dict)       # for convenience
55
    File(asdf.AsdfFile(dict))
14×
56
end
57

58
function open(filename::AbstractString)::File
59
    File(asdf.open(filename))
182×
60
end
61

62
function close(file::File)::Nothing
63
    file.pyobj.close()
144×
64
end
65

66
function write_to(file::File, filename::AbstractString)::Nothing
67
    file.pyobj.write_to(filename)
12×
68
end
69

70

71

72
function file_format_version(file::File)::VersionNumber
73
    VersionNumber(file.pyobj.file_format_version)
144×
74
end
75

76
function version(file::File)::VersionNumber
77
    VersionNumber(file.pyobj.version)
144×
78
end
79

80
function Base.VersionNumber(obj::PyObject)
81
    VersionNumber(
240×
82
        obj.major, obj.minor, obj.patch, obj.prerelease, obj.build)
83
end
84
function Base.VersionNumber(obj::Dict)
85
    VersionNumber(
!
86
        obj["major"], obj["minor"], obj["patch"],
87
        nothing2tuple(obj["prerelease"]), nothing2tuple(obj["build"]))
88
end
89

90

91

92
function comments(file::File)::Vector{String}
93
    file.pyobj.comments
120×
94
end
95

96

97

98
function tree(file::File)::Tree
99
    makeASDFType(file.pyobj."tree"::PyObject)::Tree
126×
100
end
101

102

103

104
################################################################################
105

106
# id: "http://stsci.edu/schemas/asdf/core/asdf-1.1.0"
107
# tag: "tag:stsci.edu:asdf/core/asdf-1.1.0"
108
# title: Top-level schema for every ASDF file.
109
# additionalProperties: true
110

111
"""Top-level schema for every ASDF file"""
112
struct Tree
113
    pyobj::PyObject
130×
114
end
115
tag2asdftype["tag:stsci.edu:asdf/core/asdf-1.0.0"] = Tree
116
tag2asdftype["tag:stsci.edu:asdf/core/asdf-1.1.0"] = Tree
117
additionalProperties(::Tree) = ()
90×
118

119
# This constructor must come after the type Tree has been defined
120
# TODO: Reverse order of types in this file
121
function File(tree::Tree)
122
    File(asdf.AsdfFile(tree.pyobj))
!
123
end
124

125
function asdf_library(tree::Tree)::Maybe{Software}
126
    software = get(tree.pyobj, PyObject, "asdf_library", empty)
10×
127
    if software === empty
6×
128
        return nothing
!
129
    end
130
    software::PyObject
131
    makeASDFType(software)::Software
6×
132
end
133

134
# History is apparently not supported by the Python ASDF library
135

136

137

138
################################################################################
139

140
# id: "http://stsci.edu/schemas/asdf/core/software-1.0.0"
141
# tag: "tag:stsci.edu:schemas/asdf/core/software-1.0.0"
142
# title: Describes a software package.
143
# required: [name, version]
144
# additionalProperties: true
145

146
"""Describes a software package"""
147
struct Software
148
    pyobj::PyObject
10×
149
end
150
tag2asdftype["tag:stsci.edu:asdf/core/software-1.0.0"] = Software
151
additionalProperties(::Software) = ()
!
152

153
function name(software::Software)
154
    software.pyobj.get("name")::String
10×
155
end
156

157
function author(software::Software)
158
    software.pyobj.get("author")::Maybe{String}
10×
159
end
160

161
function homepage(software::Software)::Maybe{URI}
162
    homepage = software.pyobj.get("homepage", empty)
10×
163
    if homepage === empty
6×
164
        return nothing
!
165
    end
166
    URI(homepage)::URI
6×
167
end
168

169
function version(software::Software)::Union{VersionNumber, String}
170
    version = software.pyobj.get("version")::String
10×
171
    try
6×
172
        VersionNumber(version)
6×
173
    catch
174
        version
!
175
    end
176
end
177

178

179

180
################################################################################
181

182
# id: "http://stsci.edu/schemas/asdf/core/ndarray-1.0.0"
183
# tag: "tag:stsci.edu:asdf/core/ndarray-1.0.0"
184
# title: An *n*-dimensional array.
185

186
abstract type Datatype end
187

188
@enum ScalarType begin
189
    int8
190
    uint8
191
    int16
192
    uint16
193
    int32
194
    uint32
195
    int64
196
    uint64
197
    float32
198
    float64
199
    complex64
200
    complex128
201
    bool8
202
    ascii
203
    ucs4
204
end
205
const string2scalartype = Dict{String, ScalarType}(
206
    "int8"       => int8,
207
    "uint8"      => uint8,
208
    "int16"      => int16,
209
    "uint16"     => uint16,
210
    "int32"      => int32,
211
    "uint32"     => uint32,
212
    "int64"      => int64,
213
    "uint64"     => uint64,
214
    "float32"    => float32,
215
    "float64"    => float64,
216
    "complex64"  => complex64,
217
    "complex128" => complex128,
218
    "bool8"      => bool8,
219
    "ascii"      => ascii,
220
    "ucs4"       => ucs4)
221
const scalartype2type = Dict{ScalarType, Type}(
222
    int8       => Int8,
223
    uint8      => UInt8,
224
    int16      => Int16,
225
    uint16     => UInt16,
226
    int32      => Int32,
227
    uint32     => UInt32,
228
    int64      => Int64,
229
    uint64     => UInt64,
230
    float32    => Float32,
231
    float64    => Float64,
232
    complex64  => ComplexF32,
233
    complex128 => ComplexF64,
234
    bool8      => Bool,
235
    ascii      => String,
236
    ucs4       => String)
237

238
struct ScalarDatatype <: Datatype
239
    type_::ScalarType
240
    length::Int                 # only for ascii and ucs4; else -1
241
    function ScalarDatatype(type_::ScalarType)
242
        @assert type_ in [
330×
243
            int8, uint8, int16, uint16, int32, uint32, int64, uint64,
244
            float32, float64, complex64, complex128,
245
            bool8]
246
        new(type_, -1)
198×
247
    end
248
    function ScalarDatatype(type_::ScalarType, length::Int)
249
        @assert type_ in [ascii, ucs4]
50×
250
        @assert length >= 0
30×
251
        new(type_, length)
30×
252
    end
253
end
254

255
function ScalarDatatype(type_::String, length...)
256
    ScalarDatatype(string2scalartype[type_], length...)
330×
257
end
258

259
function julia_type(scalardatatype::ScalarDatatype)
260
    scalartype2type[scalardatatype.type_]
304×
261
end
262

263
@enum Byteorder big little
264

265
struct Field
266
    name::Maybe{String}
267
    datatype::Datatype
268
    byteorder::Maybe{Byteorder}
269
    shape::Maybe{Vector{Int}}
270
end
271

272
struct DatatypeList <: Datatype
273
    types::Vector{Field}
274
end
275

276
function Datatype(dtype::PyObject)
277
    if dtype.names !== nothing
380×
278
        # fields = []
279
        # for name in dtype.names:
280
        #     field = dtype.fields[name][0]
281
        #     d = {}
282
        #     d['name'] = name
283
        #     field_dtype, byteorder = numpy_dtype_to_asdf_datatype(field)
284
        #     d['datatype'] = field_dtype
285
        #     if include_byteorder:
286
        #         d['byteorder'] = byteorder
287
        #     if field.shape:
288
        #         d['shape'] = list(field.shape)
289
        #     fields.append(d)
290
        # return fields, numpy_byteorder_to_asdf_byteorder(dtype.byteorder)
291
        @assert false
!
292

293
    elseif dtype.subdtype !== nothing
228×
294
        # return numpy_dtype_to_asdf_datatype(dtype.subdtype[0])
295
        @assert false
!
296

297
    elseif dtype.name in keys(string2scalartype)
304×
298
        return ScalarDatatype(dtype.name)
198×
299

300
    elseif dtype.name == "bool"
30×
301
        return ScalarDatatype(bool8)
!
302

303
    elseif startswith(dtype.name, "string") ||
60×
304
            startswith(dtype.name, "bytes")
305
        return ScalarDatatype(ascii, Int(dtype.itemsize))
8×
306

307
    elseif startswith(dtype.name, "unicode") ||
48×
308
            startswith(dtype.name, "str")
309
        return ScalarDatatype(ucs4, Int(dtype.itemsize) รท 4)
32×
310

311
    end
312
    @assert false
!
313
end
314

315

316

317
const PyArrayTypes = Union{
318
    Bool,
319
    Int8, Int16, Int32, Int64, Int128,
320
    UInt8, UInt16, UInt32, UInt64, UInt128,
321
    Float16, Float32, Float64,
322
    ComplexF16, ComplexF32, ComplexF64}
323

324
"""An *n*-dimensional array"""
325
struct NDArray{T, D, Repr <: Union{PyArray, PyObject}} <: DenseArray{T, D}
326
    # An NDArray either uses a PyArray (if possible) or a PyObject
327
    # as internal representation
328
    repr::Repr
329
    function NDArray{T, D, PyArray{T, D}}(pyobj::PyObject) where {T, D}
330
        new{T, D, PyArray{T, D}}(PyArray(pyobj))
!
331
    end
332
    function NDArray{T, D, PyObject}(pyobj::PyObject) where {T, D}
333
        new{T, D, PyObject}(pyobj)
228×
334
    end
335
    function NDArray{T, D}(pyobj::PyObject) where {T, D}
336
        isefficient = T <: PyArrayTypes
380×
337
        if isefficient
208×
338
            # Python 3 requires array-like Python object to support a
339
            # "buffer protocol" as defined in PEP 3118. Apparently,
340
            # asdf.py's "NDArrayType" does not, and the call to
341
            # "PyArray_Info" fails.
342
            isefficient = PyCall.isbuftype(pyobj)
198×
343
        end
344
        if isefficient
208×
345
            info = PyArray_Info(pyobj)
!
346
            isefficient = info.native # byteorder is native
347
        end
348
        if isefficient
208×
349
            # There is an efficient PyArray implementation
350
            NDArray{T, D, PyArray{T, D}}(pyobj)
!
351
        else
352
            # Fallback for other (e.g. string) types
353
            NDArray{T, D, PyObject}(pyobj)
228×
354
        end
355
    end
356
    function NDArray(pyobj::PyObject)
357
        # Determine element type and rank
358
        T = julia_type(Datatype(pyobj.dtype))
380×
359
        D = length(pyobj.shape)
228×
360
        NDArray{T, D}(pyobj)
228×
361
    end
362
end
363
tag2asdftype["tag:stsci.edu:asdf/core/ndarray-1.0.0"] = NDArray
364

365
const FastNDArray = NDArray{T, D, PyArray{T, D}} where {T <: PyArrayTypes, D}
366
const SlowNDArray = NDArray{T, D, PyObject} where {T, D}
UNCOV
367
pyarr(arr::FastNDArray{T, D}) where {T, D} = arr.repr::PyArray{T, D}
!
368
pyobj(arr::SlowNDArray{T, D}) where {T, D} = arr.repr::PyObject
8,334×
369

UNCOV
370
isefficient(::Type{NDArray{T, D, PyArray{T, D}}}) where {T, D} = true
!
371
isefficient(::Type{NDArray{T, D, PyObject}}) where {T, D} = false
10×
372
isefficient(::T) where {T <: NDArray} = isefficient(T)
50×
373

374
# PyArray-based implementation
375
Base.axes(arr::FastNDArray) = axes(pyarr(arr))
!
376
Base.eachindex(ind::IndexCartesian, arr::FastNDArray) =
!
377
    eachindex(ind, pyarr(arr))
378
Base.eachindex(ind::IndexLinear, arr::FastNDArray) = eachindex(ind, pyarr(arr))
!
379
Base.getindex(arr::FastNDArray, i) = pyarr(arr)[i]
!
380
Base.length(arr::FastNDArray) = length(pyarr(arr))
!
381
Base.ndims(arr::FastNDArray) = ndims(pyarr(arr))
!
382
Base.size(arr::FastNDArray) = size(pyarr(arr))
!
383
Base.strides(arr::FastNDArray) = strides(pyarr(arr))
!
384

UNCOV
385
Base.IteratorSize(::Type{<:NDArray{T, D, PyArray{T, D}}}) where {T, D} =
!
386
    HasShape{D}()
387
Base.iterate(arr::FastNDArray, state...) = iterate(pyarr(arr), state...)
!
388

389
# PyObject-based implementation
390
function Base.axes(arr::SlowNDArray{T, D}) where {T, D}
391
    map(sz -> Base.Slice(0:sz-1),
13,312×
392
        size(arr))::NTuple{D, Base.Slice{UnitRange{Int}}}
393
end
394
function Base.eachindex(::IndexCartesian, arr::SlowNDArray)
395
    CartesianIndices(axes(arr))
!
396
end
397
function Base.eachindex(::IndexLinear, arr::SlowNDArray)
398
    axes(arr, 1)
!
399
end
400
function Base.getindex(arr::SlowNDArray{T, D}, i::NTuple{D, Int}) where {T, D}
401
    @boundscheck @assert all(checkindex(Bool, axes(arr)[d], i[d]) for d in 1:D)
11,568×
402
    pycall(pyobj(arr).__getitem__, T, i)::T
5,784×
403
end
404
function Base.getindex(arr::SlowNDArray{T, D}, i::NTuple{D, I}) where {T, D, I}
405
    arr[NTuple{D, Int}(i)]
!
406
end
407
function Base.getindex(arr::SlowNDArray{T, D}, i::CartesianIndex{D}) where {
408
        T, D}
409
    arr[Tuple(i)]
!
410
end
411
function Base.getindex(arr::SlowNDArray, i...)
412
    arr[i]
3,856×
413
end
414
function Base.ndims(arr::SlowNDArray{T, D}) where {T, D}
415
    D
!
416
end
417
function Base.size(arr::SlowNDArray{T, D}) where {T, D}
418
    NTuple{D, Int}(pyobj(arr).shape::NTuple{D, Int64})
7,488×
419
end
420
function Base.strides(arr::SlowNDArray)
421
    Base.size_to_strides(1, reverse(size(arr))...)
!
422
end
423

UNCOV
424
Base.IteratorSize(::Type{<:SlowNDArray{T, D}}) where {T, D} = HasShape{D}()
!
425
function Base.iterate(arr::SlowNDArray, state...)
426
    iter = eachindex(arr)
!
427
    res = iterate(iter, state...)
!
428
    if res === nothing
!
429
        return nothing
!
430
    end
431
    idx, state = res
!
432
    arr[idx], state
!
433
end
434

435

436

437
################################################################################
438

439
const ASDFType = Union{Tree, Software, NDArray}
440

441
function Base.getindex(obj::ASDFType, key::String)
442
    additionalProperties(obj)   # check
234×
443
    makeASDFType(get(obj.pyobj, PyObject, key))
270×
444
end
445
function Base.get(obj::ASDFType, key::String, default)
446
    additionalProperties(obj)   # check
!
447
    value = get(obj.pyobj, PyObject, key, empty)
!
448
    if value === empty
!
449
        return default
!
450
    end
451
    makeASDFType(value)::ASDFType
!
452
end
453
function Base.setindex!(obj::ASDFType, value::ASDFType, key::String)
454
    additionalProperties(obj)   # check
!
455
    obj.pyobj[key] = value.pyobj
!
456
end
457
function Base.delete!(obj::ASDFType, key::String)
458
    additionalProperties(obj)   # check
!
459
    delete!(obj.pyobj, key)
!
460
    obj
!
461
end
462

463
function Base.length(obj::ASDFType)
464
    Int(obj.pyobj.__len__())
!
465
end
466
function Base.keys(obj::ASDFType)
467
    iter = obj.pyobj.keys().__iter__()
10×
468
    keys = String[]
469
    while true
72×
470
        try
72×
471
            key = iter.__next__()
72×
472
            push!(keys, key)
72×
473
        catch
474
            # TODO: Check for Python StopIteration exception
475
            break
70×
476
        end
477
    end
478
    Set(keys)
8×
479
end
480
function Base.iterate(obj::ASDFType)
481
    Base.iterate(obj, obj.pyobj.__iter__())
!
482
end
483
function Base.iterate(obj::ASDFType, iter)
484
    try
!
485
        iter.__next__(), iter
!
486
    catch
487
        # TODO: Check for Python StopIteration exception
488
        nothing
!
489
    end
490
end
491

492
end
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2022 Coveralls, Inc