Skip to content

Commit 6b5be92

Browse files
ghyatzojuliohm
andauthored
allow for passing directly the python dict string as response body (#59)
* allow for passing directly the python dict string as response body * fix: renaming and type widening * feat: updated README with new examples of usage and added a depwarning to the `py2ju` method. * fix: wrong signature for depwarn * Improve docstring * remove short description of the code snippet. Co-authored-by: Júlio Hoffimann <[email protected]> * Update README.md * Minor adjustments * Refactor tests * add small comment suggesting manipulation of dict if needed. * Deprecate py2ju * fix missing endpoint variable * Minor doc adjustments * Adjust tests * fix test request strings * Minor adjustments in runtests.jl --------- Co-authored-by: Júlio Hoffimann <[email protected]>
1 parent aea09f3 commit 6b5be92

File tree

5 files changed

+135
-187
lines changed

5 files changed

+135
-187
lines changed

README.md

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,51 @@ Suppose that the `Show API request` button generated the following Python code:
2626
```python
2727
#!/usr/bin/env python
2828
import cdsapi
29-
c = cdsapi.Client()
30-
c.retrieve("insitu-glaciers-elevation-mass",
31-
{
32-
"variable": "all",
33-
"product_type": "elevation_change",
34-
"file_version": "20170405",
35-
"format": "tgz"
36-
},
37-
"download.tar.gz")
29+
30+
dataset = "reanalysis-era5-single-levels"
31+
request = {
32+
"product_type": ["reanalysis"],
33+
"variable": [
34+
"10m_u_component_of_wind",
35+
"10m_v_component_of_wind"
36+
],
37+
"year": ["2024"],
38+
"month": ["12"],
39+
"day": ["06"],
40+
"time": ["16:00"],
41+
"data_format": "netcdf",
42+
"download_format": "unarchived",
43+
"area": [58, 6, 55, 9]
44+
}
45+
46+
client = cdsapi.Client()
47+
client.retrieve(dataset, request).download()
3848
```
3949

40-
You can obtain the same results in Julia with the following code:
50+
You can obtain the same results in Julia:
4151

4252
```julia
4353
using CDSAPI
4454

45-
CDSAPI.retrieve("insitu-glaciers-elevation-mass",
46-
CDSAPI.py2ju("""
47-
{
48-
"variable": "all",
49-
"product_type": "elevation_change",
50-
"file_version": "20170405",
51-
"format": "tgz"
52-
}
53-
"""),
54-
"download.tar.gz")
55+
dataset = "reanalysis-era5-single-levels"
56+
request = """{
57+
"product_type": ["reanalysis"],
58+
"variable": [
59+
"10m_u_component_of_wind",
60+
"10m_v_component_of_wind"
61+
],
62+
"year": ["2024"],
63+
"month": ["12"],
64+
"day": ["06"],
65+
"time": ["16:00"],
66+
"data_format": "netcdf",
67+
"download_format": "unarchived",
68+
"area": [58, 6, 55, 9]
69+
}""" # <- notice the multiline string.
70+
71+
CDSAPI.retrieve(dataset, request, "download.nc")
5572
```
5673

57-
We've copied/pasted the code and called the `py2ju` function on the second argument of the `retrieve` function.
58-
The `py2ju` function simply converts the string containing a Python dictionary to an actual Julia dictionary.
59-
6074
Besides the downloaded file, the `retrieve` function also returns a dictionary with metadata:
6175

6276
```

src/CDSAPI.jl

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@ using JSON
66
"""
77
retrieve(name, params, filename; wait=1.0)
88
9-
Retrieves data for `name` from the Climate Data Store
10-
with the specified `params` and stores it in the current
11-
directory as `filename`.
9+
Retrieves dataset with given `name` from the Climate Data Store
10+
with the specified `params` (JSON string) and stores it in the
11+
given `filename`.
1212
13-
The client periodically requests the status of the retrieve request.
14-
`wait` is the maximum time (in seconds) between status updates.
13+
The client periodically checks the status of the request and one
14+
can specify the maximum time in seconds to `wait` between updates.
1515
"""
16-
function retrieve(name, params, filename; wait=1.0)
16+
retrieve(name, params::AbstractString, filename; wait=1.0) =
17+
retrieve(name, JSON.parse(params), filename; wait)
18+
19+
# CDSAPI.parse can be used to convert the request params into a
20+
# Julia dictionary for additional manipulation before retrieval
21+
function retrieve(name, params::AbstractDict, filename; wait=1.0)
1722
creds = Dict()
1823
open(joinpath(homedir(), ".cdsapirc")) do f
1924
for line in readlines(f)
@@ -44,12 +49,11 @@ function retrieve(name, params, filename; wait=1.0)
4449
throw(e)
4550
end
4651

47-
body = JSON.parse(String(response.body))
48-
data = Dict("status" => "queued")
52+
data = JSON.parse(String(response.body))
53+
endpoint = Dict(response.headers)["location"]
4954

5055
while data["status"] != "successful"
51-
data = HTTP.request("GET",
52-
creds["url"] * "/retrieve/v1/jobs/" * string(body["jobID"]),
56+
data = HTTP.request("GET", endpoint,
5357
["PRIVATE-TOKEN" => creds["key"]]
5458
)
5559
data = JSON.parse(String(data.body))
@@ -70,7 +74,7 @@ function retrieve(name, params, filename; wait=1.0)
7074
end
7175

7276
response = HTTP.request("GET",
73-
creds["url"] * "/retrieve/v1/jobs/" * string(body["jobID"]) * "/results",
77+
endpoint * "/results",
7478
["PRIVATE-TOKEN" => creds["key"]]
7579
)
7680
body = JSON.parse(String(response.body))
@@ -80,45 +84,12 @@ function retrieve(name, params, filename; wait=1.0)
8084
end
8185

8286
"""
83-
py2ju(dictstr)
84-
85-
Takes a Python dictionary as string and converts it into Julia's `Dict`
86-
87-
# Examples
88-
```julia-repl
89-
julia> str = \"""{
90-
'format': 'zip',
91-
'variable': 'surface_air_temperature',
92-
'product_type': 'climatology',
93-
'month': '08',
94-
'origin': 'era_interim',
95-
}\""";
96-
97-
julia> CDSAPI.py2ju(str)
98-
Dict{String,Any} with 5 entries:
99-
"format" => "zip"
100-
"month" => "08"
101-
"product_type" => "climatology"
102-
"variable" => "surface_air_temperature"
103-
"origin" => "era_interim"
104-
105-
```
106-
"""
107-
function py2ju(dictstr)
108-
dictstr_cpy = replace(dictstr, "'" => "\"")
109-
lastcomma_pos = findlast(",", dictstr_cpy).start
110-
111-
# if there's no pair after the last comma
112-
if findnext(":", dictstr_cpy, lastcomma_pos) == nothing
113-
# remove the comma
114-
dictstr_cpy = dictstr_cpy[firstindex(dictstr_cpy):(lastcomma_pos-1)] * dictstr_cpy[(lastcomma_pos+1):lastindex(dictstr_cpy)]
115-
end
87+
parse(string)
11688
117-
# removes trailing comma from a list
118-
rx = r",[ \n\r\t]*\]"
119-
dictstr_cpy = replace(dictstr_cpy, rx => "]")
89+
Equivalent to `JSON.parse(string)`.
90+
"""
91+
parse(string) = JSON.parse(string)
12092

121-
return JSON.parse(dictstr_cpy)
122-
end
93+
@deprecate py2ju(string) parse(string)
12394

12495
end # module

test/py2ju.jl

Lines changed: 0 additions & 27 deletions
This file was deleted.

test/retrieve.jl

Lines changed: 0 additions & 81 deletions
This file was deleted.

test/runtests.jl

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,85 @@ using GRIB, NetCDF
44

55
using Test
66

7-
# list of tests
8-
testfiles = [
9-
"py2ju.jl",
10-
"retrieve.jl",
11-
]
7+
datadir = joinpath(@__DIR__, "data")
128

139
@testset "CDSAPI.jl" begin
14-
for testfile in testfiles
15-
include(testfile)
10+
@testset "ERA5 monthly preasure data" begin
11+
filename = joinpath(datadir, "era5.grib")
12+
response = CDSAPI.retrieve(
13+
"reanalysis-era5-pressure-levels-monthly-means",
14+
"""{
15+
"data_format": "grib",
16+
"product_type": "monthly_averaged_reanalysis",
17+
"variable": "divergence",
18+
"pressure_level": "1",
19+
"year": "2020",
20+
"month": "06",
21+
"area": [90,-180,-90,180],
22+
"time": "00:00"
23+
}""",
24+
filename
25+
)
26+
27+
@test typeof(response) <: Dict
28+
@test isfile(filename)
29+
30+
GribFile(filename) do datafile
31+
data = Message(datafile)
32+
@test data["name"] == "Divergence"
33+
@test data["level"] == 1
34+
@test data["year"] == 2020
35+
@test data["month"] == 6
36+
end
37+
rm(filename)
38+
end
39+
40+
@testset "Sea ice type data" begin
41+
filename = joinpath(datadir, "sea_ice_type.zip")
42+
response = CDSAPI.retrieve(
43+
"satellite-sea-ice-edge-type",
44+
"""{
45+
"variable": "sea_ice_type",
46+
"region": "northern_hemisphere",
47+
"cdr_type": "cdr",
48+
"year": "1979",
49+
"month": "01",
50+
"day": "02",
51+
"version": "3_0",
52+
"data_format": "zip"
53+
}""",
54+
filename
55+
)
56+
57+
@test typeof(response) <: Dict
58+
@test isfile(filename)
59+
60+
# extract contents
61+
zip_reader = ZipFile.Reader(filename)
62+
ewq_fileio = zip_reader.files[1]
63+
ewq_file = joinpath(datadir, ewq_fileio.name)
64+
write(ewq_file, read(ewq_fileio))
65+
close(zip_reader)
66+
67+
# test file contents
68+
@test ncgetatt(ewq_file, "Global", "time_coverage_start") == "19790102T000000Z"
69+
@test ncgetatt(ewq_file, "Global", "time_coverage_end") == "19790103T000000Z"
70+
71+
# cleanup
72+
rm(filename)
73+
rm(ewq_file)
74+
end
75+
76+
@testset "Bad requests errors are catched" begin
77+
goodname = "reanalysis-era5-single-levels"
78+
badname = "bad-dataset"
79+
badrequest = """{
80+
"this": "is",
81+
"a": "bad",
82+
"re": ["quest"]
83+
}"""
84+
85+
@test_throws ArgumentError CDSAPI.retrieve(goodname, badrequest, "unreachable")
86+
@test_throws ArgumentError CDSAPI.retrieve(badname, badrequest, "unreachable")
1687
end
1788
end

0 commit comments

Comments
 (0)