Skip to content

Commit

Permalink
refactor(gbif): use testitem
Browse files Browse the repository at this point in the history
  • Loading branch information
tpoisot committed Oct 16, 2023
1 parent f547fbd commit 60077fc
Show file tree
Hide file tree
Showing 21 changed files with 225 additions and 271 deletions.
3 changes: 2 additions & 1 deletion GBIF/Project.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
name = "GBIF"
uuid = "ee291a33-5a6c-5552-a3c8-0f29a1181037"
authors = ["Timothée Poisot <[email protected]>"]
version = "0.4.4"
version = "0.4.5"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe"

[compat]
HTTP = "0.8, 0.9, 1"
Expand Down
17 changes: 17 additions & 0 deletions GBIF/src/GBIF.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module GBIF

using TestItems

using HTTP
using JSON
using Dates
Expand Down Expand Up @@ -78,4 +80,19 @@ include("paging.jl")
export occurrence, occurrences
export occurrences!

@testitem "We can use the Query package" begin
using Query
using DataFrames
t = taxon("Mammalia", strict=false)
set = occurrences(t)
[occurrences!(set) for i in 1:10]

tdf = view(set) |>
@filter(_.rank == "SPECIES") |>
@map({_.key, _.taxon.name, _.country}) |>
DataFrame

@test typeof(tdf) <: DataFrame
end

end # module
88 changes: 88 additions & 0 deletions GBIF/src/occurrence.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ function occurrence(key::Integer)::GBIFRecord
return occurrence(string(key))
end

@testitem "We can get an occurrence by its ID" begin
k = 3986160931
o = occurrence(k)
@test typeof(o) == GBIFRecord
@test o.key == k
end

@testitem "We can get a malformed occurrence" begin
k = 1039645472
o = occurrence(k)
@test typeof(o) == GBIFRecord
@test o.key == k
end

function _internal_occurrences_getter(query::Pair...)
validate_occurrence_query.(query)
occ_s_url = gbifurl * "occurrence/search"
Expand Down Expand Up @@ -112,6 +126,20 @@ function occurrences(t::GBIFTaxon, query::Pair...)
return occurrences(taxon_query, query...)
end

@testitem "Getting occurrences by taxon returns a GBIFRecords" begin
iver_id = taxon(5298019)
@test typeof(occurrences(iver_id)) <: GBIFRecords
end

@testitem "Getting occurrences by taxon returns the correct taxa" begin
t = taxon(5298019)
occ = occurrences(t)
@test typeof(occ) <: GBIFRecords
for o in occ
@test o.taxon.species == Pair("Iris versicolor", 5298019)
end
end

"""
occurrences(t::Vector{GBIFTaxon}, query::Pair...)
Expand All @@ -126,3 +154,63 @@ function occurrences(ts::Vector{GBIFTaxon}, query::Pair...)
end
return occurrences(taxon_query..., query...)
end

@testitem "We get the correct country when looking for occurrences by country" begin
occ = occurrences("country" => "ES")
@test typeof(occ) <: GBIFRecords
for o in occ
@test o.country == "Spain"
end
end

@testitem "We can pass the taxon data manually" begin
set1 = occurrences("scientificName" => "Mus musculus", "year" => 1999, "hasCoordinate" => true)
@test typeof(set1) == GBIFRecords
@test length(set1) == 20
end

@testitem "We can get the latest occurrences" begin
set2 = occurrences()
@test typeof(set2) == GBIFRecords
@test length(set2) == 20
end

@testitem "We can use tuples to show intervals" begin
set3 = occurrences("scientificName" => "Mus musculus", "year" => 1999, "hasCoordinate" => true, "decimalLatitude" => (0.0, 50.0))
@test typeof(set3) == GBIFRecords
@test length(set3) == 20
end

@testitem "We can complete a query using a while loop" begin
serval = GBIF.taxon("Leptailurus serval", strict=true)
obs = occurrences(serval, "hasCoordinate" => "true", "continent" => "AFRICA", "decimalLongitude" => (-30, 40))
while length(obs) < count(obs)
occurrences!(obs)
end
@test length(obs) == count(obs)
end

@testitem "We can query multiple taxa at once" begin
serval = GBIF.taxon("Leptailurus serval", strict=true)
leopard = GBIF.taxon("Panthera pardus", strict=true)
obs = occurrences([leopard, serval], "hasCoordinate" => true, "occurrenceStatus" => "PRESENT")
@test typeof(obs) == GBIFRecords
end

@testitem "We can complete a query with a specific page size" begin
obs = occurrences(serval, "hasCoordinate" => "true", "continent" => "AFRICA", "decimalLongitude" => (-30, 40), "limit" => 45)
while length(obs) < count(obs)
occurrences!(obs)
end
@test length(obs) == count(obs)
end

@testitem "Records of absence are correctly represented" begin
o = occurrences("occurrenceStatus" => "ABSENT")
@test o[1].presence == false
end

@testitem "Records of presence are correctly represented" begin
o = occurrences("occurrenceStatus" => "PRESENT")
@test o[1].presence == true
end
21 changes: 21 additions & 0 deletions GBIF/src/paging.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,24 @@ function occurrences!(o::GBIFRecords)
o.occurrences[start:stop] = retrieved
end
end

@testitem "We always get one page by default" begin
set = occurrences()
occurrences!(set)
@test length(set) == 40
end

@testitem "We can get multiple pages on repeated calls" begin
set = occurrences("limit" => 40)
occurrences!(set)
@test length(set) == 80
@test length(set) == length(unique([o.key for o in set]))
end

@testitem "We can page over queries" begin
setQ = occurrences(taxon("Iris versicolor", rank=:SPECIES), "limit" => 10)
occurrences!(setQ)
@test length(setQ) == 20
occurrences!(setQ)
@test length(setQ) == length(unique([o.key for o in setQ]))
end
23 changes: 23 additions & 0 deletions GBIF/src/query.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,26 @@ function validate_occurrence_query(query::Pair)
end
end
end

@testitem "Using a single argument does not change the type of the argument" begin
queryset = ["hasCoordinate" => true]
@test length(occurrences(queryset...)) > 0

queryset = ["hasCoordinate" => true]
@test length(occurrences(taxon("Alces alces"), queryset...)) > 0
end

@testitem "We cannot search for a wrong country code" begin
qpars = Pair("country", "ABC")
@test_warn "country code" GBIF.validate_occurrence_query(qpars)
end

@testitem "We cannot search with an unsupported keyword" begin
qpars = Pair("years", "1234")
@test_warn "parameter is not allowed" GBIF.validate_occurrence_query(qpars)
end

@testitem "We cannot search with an unsupported enumerated value" begin
qpars = Pair("establishmentMeans", "just vibes")
@test_warn "is not a valid value" GBIF.validate_occurrence_query(qpars)
end
56 changes: 38 additions & 18 deletions GBIF/src/taxon.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ reference taxonomy.
Optional arguments are
- `rank::Union{Symbol,Nothing}=:SPECIES` -- the rank of the taxon you want. This
is part of a controlled vocabulary, and can only be one of `:DOMAIN`,
`:CLASS`, `:CULTIVAR`, `:FAMILY`, `:FORM`, `:GENUS`, `:INFORMAL`, `:ORDER`,
`:PHYLUM,`, `:SECTION`, `:SUBCLASS`, `:VARIETY`, `:TRIBE`, `:KINGDOM`,
`:SUBFAMILY`, `:SUBFORM`, `:SUBGENUS`, `:SUBKINGDOM`, `:SUBORDER`,
`:SUBPHYLUM`, `:SUBSECTION`, `:SUBSPECIES`, `:SUBTRIBE`, `:SUBVARIETY`,
`:SUPERCLASS`, `:SUPERFAMILY`, `:SUPERORDER`, and `:SPECIES`
- `rank::Union{Symbol,Nothing}=:SPECIES` -- the rank of the taxon you want. This
is part of a controlled vocabulary, and can only be one of `:DOMAIN`,
`:CLASS`, `:CULTIVAR`, `:FAMILY`, `:FORM`, `:GENUS`, `:INFORMAL`, `:ORDER`,
`:PHYLUM,`, `:SECTION`, `:SUBCLASS`, `:VARIETY`, `:TRIBE`, `:KINGDOM`,
`:SUBFAMILY`, `:SUBFORM`, `:SUBGENUS`, `:SUBKINGDOM`, `:SUBORDER`,
`:SUBPHYLUM`, `:SUBSECTION`, `:SUBSPECIES`, `:SUBTRIBE`, `:SUBVARIETY`,
`:SUPERCLASS`, `:SUPERFAMILY`, `:SUPERORDER`, and `:SPECIES`
- `strict::Bool=true` -- whether the match should be strict, or fuzzy
- `strict::Bool=true` -- whether the match should be strict, or fuzzy
Finally, one can also specify other levels of the taxonomy, using `kingdom`,
`phylum`, `class`, `order`, `family`, and `genus`, all of which can either be
Expand All @@ -26,17 +26,19 @@ If a match is found, the result will be given as a `GBIFTaxon`. If not, this
function will return `nothing` and give a warning.
"""
function taxon(name::String;
rank::Union{Symbol,Nothing}=:SPECIES, strict::Bool=true,
kingdom::Union{String,Nothing}=nothing, phylum::Union{String,Nothing}=nothing, class::Union{String,Nothing}=nothing,
order::Union{String,Nothing}=nothing, family::Union{String,Nothing}=nothing, genus::Union{String,Nothing}=nothing)
rank::Union{Symbol, Nothing} = :SPECIES, strict::Bool = true,
kingdom::Union{String, Nothing} = nothing, phylum::Union{String, Nothing} = nothing,
class::Union{String, Nothing} = nothing,
order::Union{String, Nothing} = nothing, family::Union{String, Nothing} = nothing,
genus::Union{String, Nothing} = nothing)
@assert rank [
:DOMAIN, :CLASS, :CULTIVAR, :FAMILY, :FORM, :GENUS, :INFORMAL, :ORDER, :PHYLUM,
:SECTION, :SUBCLASS, :VARIETY, :TRIBE, :KINGDOM, :SUBFAMILY, :SUBFORM,
:SUBGENUS, :SUBKINGDOM, :SUBORDER, :SUBPHYLUM, :SUBSECTION, :SUBSERIES,
:SUBSPECIES, :SUBTRIBE, :SUBVARIETY, :SUPERCLASS, :SUPERFAMILY, :SUPERORDER,
:SPECIES
:SPECIES,
]
args = Dict{String,Any}("name" => name, "strict" => strict)
args = Dict{String, Any}("name" => name, "strict" => strict)

isnothing(rank) || (args["rank"] = String(rank))
isnothing(kingdom) || (args["kingdom"] = String(kingdom))
Expand All @@ -47,7 +49,7 @@ function taxon(name::String;
isnothing(genus) || (args["genus"] = String(genus))

sp_s_url = gbifurl * "species/match"
sp_s_req = HTTP.get(sp_s_url, query=args)
sp_s_req = HTTP.get(sp_s_url; query = args)
if sp_s_req.status == 200
body = JSON.parse(String(sp_s_req.body))
# This will throw warnings for various reasons related to matchtypes
Expand Down Expand Up @@ -78,17 +80,35 @@ This function will look for a taxon by its taxonID in the GBIF
reference taxonomy.
"""
function taxon(id::Int)
args = Dict{String,Any}("id" => id)
args = Dict{String, Any}("id" => id)

sp_s_url = gbifurl * "species/$id"
sp_s_req = HTTP.get(sp_s_url, query=args)
sp_s_req = HTTP.get(sp_s_url; query = args)
if sp_s_req.status == 200
body = JSON.parse(String(sp_s_req.body))
return GBIFTaxon(body)
else
throw(ErrorException("Impossible to retrieve information for taxonID $(id) -- HTML error code $(sp_s_req.status)"))
throw(
ErrorException(
"Impossible to retrieve information for taxonID $(id) -- HTML error code $(sp_s_req.status)",
),
)
end

end

taxon(t::Pair) = taxon(t.second)

@testitem "We can get a taxon by its name" begin
iver = taxon("Iris versicolor"; rank = :SPECIES)
@test iver.species == Pair("Iris versicolor", 5298019)
end

@testitem "We can get a taxon by its GBIF ID" begin
iver_id = taxon(5298019)
@test iver_id.species == ("Iris versicolor" => 5298019)
end

@testitem "We do not get a species field when looking for a genus" begin
i_sp = taxon("Lamellodiscus"; rank = :GENUS, strict = true)
@test ismissing(i_sp.species)
end
31 changes: 30 additions & 1 deletion GBIF/src/types/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ function getindex(o::GBIFRecords, r::UnitRange{Int64})
return view(o)[r]
end

@testitem "Occurrences have a length and a typeof" begin
set = occurrences()
@test typeof(set[1]) == GBIFRecord
@test length(set[1:4]) == 4
end

function iterate(o::GBIFRecords)
return iterate(collect(view(o)))
end
Expand All @@ -29,7 +35,30 @@ function iterate(o::GBIFRecords, t::Union{Int64, Nothing})
return iterate(collect(view(o)), t)
end

@testitem "We can iterate over records" begin
set = occurrences()
@test iterate(set) == (set[1], 2)
@test iterate(set, 2) == (set[2], 3)
end

@testitem "We can iterate over records made with a non-empty query" begin
plotor = taxon("Procyon lotor")
plotor_occ = occurrences(plotor)
occurrences!(plotor_occ)
for o in plotor_occ
@test typeof(o) <: GBIFRecord
@test o.taxon.species.second == plotor.species.second
end
end

# Tables.jl interface
Tables.istable(::Type{GBIFRecords}) = true
Tables.rowaccess(::Type{GBIFRecords}) = true
Tables.rows(records::GBIFRecords) = view(records)
Tables.rows(records::GBIFRecords) = view(records)

@testitem "We can convert records to a data frame" begin
using DataFrames

df = DataFrame(occurrences())
@test typeof(df) <: DataFrame
end
1 change: 1 addition & 0 deletions GBIF/test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Query = "1a8c2f83-1ff3-5112-b086-8aa67b057ba1"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
13 changes: 0 additions & 13 deletions GBIF/test/edgecases.jl

This file was deleted.

19 changes: 0 additions & 19 deletions GBIF/test/iteration.jl

This file was deleted.

11 changes: 0 additions & 11 deletions GBIF/test/methods.jl

This file was deleted.

Loading

0 comments on commit 60077fc

Please sign in to comment.