apccapi 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. apccapi-0.2.0/CHANGELOG.md +88 -0
  2. apccapi-0.2.0/MANIFEST.in +3 -0
  3. apccapi-0.2.0/PKG-INFO +367 -0
  4. apccapi-0.2.0/README.md +351 -0
  5. {apccapi-0.1.0 → apccapi-0.2.0}/apccapi/__init__.py +3 -1
  6. apccapi-0.2.0/apccapi/client.py +407 -0
  7. apccapi-0.2.0/apccapi/clipping.py +135 -0
  8. apccapi-0.2.0/apccapi.egg-info/PKG-INFO +367 -0
  9. apccapi-0.2.0/apccapi.egg-info/SOURCES.txt +30 -0
  10. apccapi-0.2.0/example/run_clipping.py +21 -0
  11. apccapi-0.2.0/example/run_clipping_MME.py +30 -0
  12. apccapi-0.2.0/example/run_clipping_MME_MODEL.py +18 -0
  13. apccapi-0.2.0/example/run_download_CMIP5.py +20 -0
  14. apccapi-0.2.0/example/run_download_ERA5.py +28 -0
  15. apccapi-0.2.0/example/run_download_MME_3MONTH.py +27 -0
  16. apccapi-0.2.0/example/run_download_MME_6MONTH.py +27 -0
  17. apccapi-0.2.0/example/run_download_MME_MODEL.py +27 -0
  18. apccapi-0.2.0/example/run_download_NCEP1.py +26 -0
  19. apccapi-0.2.0/example/run_download_NCEP2.py +26 -0
  20. apccapi-0.2.0/example/run_download_S2S_MME.py +27 -0
  21. {apccapi-0.1.0 → apccapi-0.2.0}/setup.py +2 -2
  22. apccapi-0.2.0/tests/__init__.py +33 -0
  23. apccapi-0.2.0/tests/test_client.py +680 -0
  24. apccapi-0.2.0/tests/test_clipping.py +201 -0
  25. apccapi-0.2.0/tests/test_helpers.py +276 -0
  26. apccapi-0.2.0/tests/test_network_guard.py +37 -0
  27. apccapi-0.2.0/tests/test_readme.py +181 -0
  28. apccapi-0.1.0/PKG-INFO +0 -96
  29. apccapi-0.1.0/README.md +0 -66
  30. apccapi-0.1.0/apccapi/client.py +0 -324
  31. apccapi-0.1.0/apccapi/clipping.py +0 -130
  32. apccapi-0.1.0/apccapi.egg-info/PKG-INFO +0 -96
  33. apccapi-0.1.0/apccapi.egg-info/SOURCES.txt +0 -11
  34. {apccapi-0.1.0 → apccapi-0.2.0}/LICENSE +0 -0
  35. {apccapi-0.1.0 → apccapi-0.2.0}/apccapi.egg-info/dependency_links.txt +0 -0
  36. {apccapi-0.1.0 → apccapi-0.2.0}/apccapi.egg-info/requires.txt +0 -0
  37. {apccapi-0.1.0 → apccapi-0.2.0}/apccapi.egg-info/top_level.txt +0 -0
  38. {apccapi-0.1.0 → apccapi-0.2.0}/setup.cfg +0 -0
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 - 2026-11-11
4
+
5
+ ### Added
6
+
7
+ - `download_S2S_MME()` downloads subseasonal-to-seasonal (S2S) multi-model
8
+ ensemble data (`prediction_type`, `mme_type`, `timestep`, `variables`,
9
+ `issued_ymd`). It follows the existing per-dataset helper pattern and
10
+ `Client` is unchanged. See `example/run_download_S2S_MME.py`.
11
+ - `Client(polling_timeout=...)` optionally bounds status polling and raises
12
+ `PollingTimeoutError` (a `ClikApiError` subclass) when its deadline is
13
+ exceeded. The default remains unbounded for backward compatibility.
14
+ - `PollingTimeoutError` and `download_S2S_MME` are exported from `apccapi`.
15
+
16
+ ### Changed
17
+
18
+ - Dataset helpers validate their input: a missing request field raises
19
+ `ClikApiError` instead of `KeyError`.
20
+ - Malformed or unexpected API responses raise `ClikApiError` instead of leaking
21
+ `KeyError`, `TypeError`, or JSON parsing exceptions.
22
+ - `Client.download()` and `ClippingClient.clip()` raise `ClikApiError` for
23
+ expected failures instead of returning `False`. Successful calls still
24
+ return `True`; `Client.retrieve()` and the dataset helpers still return the
25
+ output path.
26
+ - `ClippingClient` no longer prints progress or errors; it reports through
27
+ `logging` (logger `clipsapi`) or raises.
28
+ - `read_config()` raises `ClikApiError` when the properties file cannot be read.
29
+ - Packaging: the wheel contains only the `apccapi` package (the `tests` package
30
+ is excluded), and the source distribution also includes `CHANGELOG.md`,
31
+ `example/`, and `tests/`.
32
+
33
+ ### Fixed
34
+
35
+ - `Client.download()` writes to a temporary file in the destination directory
36
+ and replaces the output file only after a successful, complete download. A
37
+ failed download leaves an existing output file unchanged.
38
+ - `Client.download()` treats a non-2xx HTTP status as a failed attempt; 0.1.1
39
+ saved the response body of any status as the result file.
40
+ - Job-status error handling no longer fails when the error response has no
41
+ `data.info` field.
42
+
43
+ ### Testing
44
+
45
+ - Added a mock-based unit test suite (`tests/`) that blocks real network
46
+ access, and a GitHub Actions CI workflow (Python 3.8-3.12).
47
+ - Added `tests/test_readme.py`, which runs every Python example in `README.md`
48
+ against the real client code with the network mocked, and checks that
49
+ `setup.py`, `apccapi.__version__`, and this changelog agree on the version.
50
+
51
+ ### Compatibility
52
+
53
+ Applications written against 0.1.1 need changes only if they relied on the
54
+ old failure behavior:
55
+
56
+ - Replace `if not client.download(...)` / `if not clipping.clip(...)` checks
57
+ with `try/except apccapi.ClikApiError`.
58
+ - Replace `except KeyError` / `TypeError` / JSON-error handling around helper
59
+ calls with `except apccapi.ClikApiError`.
60
+ - Code that parsed `ClippingClient` console output must read `logging` output
61
+ instead.
62
+ - The properties file location (`~/apccapi.properties`), its keys, and all
63
+ return values on success are unchanged.
64
+
65
+ `download_S2S_MME()` needs a CLIK server that supports the `S2S_MME` job type.
66
+ The development server (`kpaas.apcc21.org`) supports it and was verified
67
+ end-to-end with this release. The production server (`apcc21.org`) rejected the
68
+ job type when checked on 2026-11-11.
69
+
70
+ ## 0.1.1
71
+
72
+ - README expanded with usage examples. No code changes from 0.1.0.
73
+
74
+ ## 0.1.0
75
+
76
+ Relative to the original 0.0.1 release:
77
+
78
+ - Package split from a single `apccapi/apccapi.py` into `client.py`,
79
+ `clipping.py`, and `__init__.py`; importing the package no longer prints its
80
+ version.
81
+ - Added `download_ERA5`, `download_NCEP1`, `download_NCEP2`, and
82
+ `ClippingClient`.
83
+ - Failures of `Client.retrieve()` raise `ClikApiError` instead of printing or
84
+ returning `None`; `retrieve()` returns the output path.
85
+ - HTTP requests use TLS certificate verification (0.0.1 sent `verify=False`)
86
+ and a request timeout.
87
+ - `Client` attributes `requesturl` / `statusurl` were renamed to
88
+ `request_url` / `status_url`.
@@ -0,0 +1,3 @@
1
+ include LICENSE README.md CHANGELOG.md
2
+ recursive-include example *.py
3
+ recursive-include tests *.py
apccapi-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,367 @@
1
+ Metadata-Version: 2.1
2
+ Name: apccapi
3
+ Version: 0.2.0
4
+ Summary: APCC Climate Information toolKit API package
5
+ Home-page: https://www.apcc21.org
6
+ Author: APEC Climate Center
7
+ Author-email: joohyung@apcc21.org
8
+ Project-URL: CLIK, https://apcc21.org/clik/
9
+ Keywords: APCC,apcc,Climate,Climate Information,Climate API
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+
17
+ # apccapi
18
+
19
+ Python client for the Open API of the [APCC Climate Information toolKit
20
+ (CLIK)](https://apcc21.org/clik/). It submits a data request, waits for the job
21
+ to finish, downloads the result file, and raises `apccapi.ClikApiError` if
22
+ anything goes wrong.
23
+
24
+ | Function | Data |
25
+ |---|---|
26
+ | `download_MME_3MONTH`, `download_MME_6MONTH` | APCC multi-model ensemble (MME) forecasts, 3- and 6-month |
27
+ | `download_MME_MODEL` | Individual model forecasts |
28
+ | `download_CMIP5` | CMIP5 |
29
+ | `download_ERA5` | ERA5 |
30
+ | `download_NCEP1`, `download_NCEP2` | NCEP1 / NCEP2 |
31
+ | `download_S2S_MME` | Subseasonal-to-seasonal (S2S) MME forecasts, new in 0.2.0 ([details](#s2s-mme-download)) |
32
+
33
+ Also included: the low-level `Client.retrieve()` for any job type, and
34
+ `ClippingClient` for cropped PNG/NetCDF output.
35
+
36
+ **Requirements:** Python 3.6 or later is declared in `setup.py`. The automated
37
+ tests run on Python 3.8-3.12 in CI and were also run on 3.7; Python 3.6 is not
38
+ covered by tests. `requests` and `urllib3` are installed automatically.
39
+
40
+ ## Installation
41
+
42
+ Install via pip with:
43
+
44
+ ```bash
45
+ $ pip install apccapi
46
+ ```
47
+
48
+ ## Configure (Linux)
49
+
50
+ Obtain your API key from the CLIK (Climate Information toolKit) at https://apcc21.org/clik/ and enter it into the apccapi.properties file, formatting it as follows:
51
+
52
+ ```
53
+ $ vi ~/apccapi.properties
54
+
55
+ key={your API key}
56
+ request_url=https://apcc21.org/clikapi/request/apccdata
57
+ status_url=https://apcc21.org/clikapi/request/status
58
+ ```
59
+
60
+ ## Configure (Windows)
61
+
62
+ Obtain your API key from the CLIK (Climate Information toolKit) at https://apcc21.org/clik/, and create the apccapi.properties file in your Windows Home directory (typically C:\Users\YourUserName) with the following format:
63
+
64
+ ```
65
+ key={your API key}
66
+ request_url=https://apcc21.org/clikapi/request/apccdata
67
+ status_url=https://apcc21.org/clikapi/request/status
68
+ ```
69
+
70
+ ## Service availability
71
+
72
+ The `request_url` / `status_url` above point to the production CLIK API
73
+ (`apcc21.org`), which is what all dataset helpers except S2S MME use.
74
+
75
+ **`download_S2S_MME` is not available on the production API yet.** The
76
+ production server rejected the `S2S_MME` job type when last checked
77
+ (2026-11-11). The CLIK development server supports it and was verified
78
+ end-to-end with this package (downloaded and validated on 2026-11-11). To use
79
+ S2S MME today, point the properties file at the development server:
80
+
81
+ ```
82
+ key={your API key}
83
+ request_url=https://kpaas.apcc21.org/clikapi/request/apccdata
84
+ status_url=https://kpaas.apcc21.org/clikapi/request/status
85
+ ```
86
+
87
+ The development server is not a production service, so availability and
88
+ contents are not guaranteed. Once S2S MME is deployed to production, no package
89
+ update is needed: switch back to the default production URLs.
90
+
91
+ ## Usage
92
+
93
+ ### Quick start: dataset download helpers
94
+
95
+ Each helper submits the request, polls until it's done, downloads the
96
+ result, and returns the output file path (or raises on failure - see
97
+ [Error handling](#error-handling)).
98
+
99
+ ```python
100
+ import apccapi
101
+
102
+ apccapi.download_MME_3MONTH(
103
+ {
104
+ 'type': 'FORECAST',
105
+ 'method': 'SCM',
106
+ 'variable': ['prec', 't2m'],
107
+ 'period': ['Monthly mean'],
108
+ 'yearmonth': ['201909']
109
+ },
110
+ 'mme3.zip'
111
+ )
112
+
113
+ apccapi.download_MME_6MONTH(
114
+ {
115
+ 'type': 'FORECAST',
116
+ 'method': 'SCM',
117
+ 'variable': ['prec', 't2m'],
118
+ 'period': ['Monthly mean'],
119
+ 'yearmonth': ['201909']
120
+ },
121
+ 'mme6.zip'
122
+ )
123
+
124
+ apccapi.download_MME_MODEL(
125
+ {
126
+ 'type': 'FORECAST',
127
+ 'institute': 'APCC',
128
+ 'model': 'SCOPS',
129
+ 'variable': ['prec', 't2m'],
130
+ 'yearmonth': ['201909']
131
+ },
132
+ 'model.zip'
133
+ )
134
+
135
+ apccapi.download_CMIP5(
136
+ {
137
+ 'code': 'AD'
138
+ },
139
+ 'cmip5.zip'
140
+ )
141
+
142
+ apccapi.download_ERA5(
143
+ {
144
+ 'timestep': 'DAILY', # 'DAILY' | 'MONTHLY' | 'HOURLY'
145
+ 'level': 'single', # 'pressure' | 'single'
146
+ 'variable': 't2m',
147
+ 'year': '2020',
148
+ 'month': '12' # omit for 'MONTHLY'
149
+ },
150
+ 't2m_202012.nc'
151
+ )
152
+
153
+ apccapi.download_NCEP1(
154
+ {
155
+ 'timestep': 'DAILY', # 'DAILY' | 'MONTHLY'
156
+ 'level': 'pressure',
157
+ 'variable': 'air',
158
+ 'year': '2022' # only required for 'DAILY'
159
+ },
160
+ 'air.2022.nc'
161
+ )
162
+
163
+ apccapi.download_NCEP2(
164
+ {
165
+ 'timestep': 'DAILY',
166
+ 'level': 'pressure',
167
+ 'variable': 'air',
168
+ 'year': '2022'
169
+ },
170
+ 'air.2022.nc'
171
+ )
172
+
173
+ apccapi.download_S2S_MME(
174
+ {
175
+ 'prediction_type': 'FORECAST',
176
+ 'mme_type': 'DMME',
177
+ 'timestep': 'WEEK_MEAN',
178
+ 'variables': ['prec', 'sst', 't2m'],
179
+ 'issued_ymd': ['20261019']
180
+ },
181
+ 's2s_mme.zip'
182
+ )
183
+ ```
184
+
185
+ More runnable examples (one file per dataset) are in [`example/`](example/).
186
+
187
+ ### S2S MME download
188
+
189
+ `download_S2S_MME(request, output_zipfile_name)` requests subseasonal-to-seasonal
190
+ multi-model ensemble data. It returns `output_zipfile_name` on success and
191
+ raises `apccapi.ClikApiError` on failure. All five request fields are required;
192
+ a missing field raises `ClikApiError`.
193
+
194
+ | Field | Type | Example |
195
+ |---|---|---|
196
+ | `prediction_type` | str | `'FORECAST'` |
197
+ | `mme_type` | str | `'DMME'` |
198
+ | `timestep` | str | `'WEEK_MEAN'` |
199
+ | `variables` | list of str | `['prec', 'sst', 't2m']` |
200
+ | `issued_ymd` | list of str, `'YYYYMMDD'` | `['20261019']` |
201
+
202
+ These field names differ from the other MME helpers (`type`, `method`,
203
+ `variable`, `period`, `yearmonth`), because the helper sends them to the server
204
+ unchanged. The accepted values are defined by the server; the example values
205
+ above are the combination verified with this release. Requires a server that
206
+ supports S2S MME (see [Service availability](#service-availability)).
207
+
208
+ ```python
209
+ import zipfile
210
+ import apccapi
211
+
212
+ try:
213
+ path = apccapi.download_S2S_MME(
214
+ {
215
+ 'prediction_type': 'FORECAST',
216
+ 'mme_type': 'DMME',
217
+ 'timestep': 'WEEK_MEAN',
218
+ 'variables': ['prec', 'sst', 't2m'],
219
+ 'issued_ymd': ['20261019']
220
+ },
221
+ 's2s_mme.zip'
222
+ )
223
+ except apccapi.ClikApiError as e:
224
+ print('Request failed:', e)
225
+ else:
226
+ with zipfile.ZipFile(path) as z:
227
+ z.extractall('s2s_mme')
228
+ print(z.namelist())
229
+ ```
230
+
231
+ For this request the development server returned a zip archive with six
232
+ NetCDF files: `prec`, `sst`, and `t2m`, each as a week 1-4 mean and a week 3-4
233
+ mean (1-degree global grid, one time step per file).
234
+
235
+ ### Low-level usage: `Client.retrieve`
236
+
237
+ The helpers above are thin wrappers around `Client.retrieve()`. Call it
238
+ directly when you need a `jobtype`/`dataset` combination that doesn't have
239
+ a dedicated helper yet, or the clipping jobtypes (`Clipping_MME`,
240
+ `Clipping_Model`):
241
+
242
+ ```python
243
+ import apccapi
244
+
245
+ c = apccapi.Client()
246
+ c.retrieve(
247
+ {
248
+ 'jobtype': 'MME',
249
+ 'dataset': 'MME_3MONTH',
250
+ 'type': 'FORECAST',
251
+ 'method': 'SCM',
252
+ 'variable': ['prec', 't2m'],
253
+ 'period': ['Monthly mean'],
254
+ 'yearmonth': ['201909']
255
+ },
256
+ 'mme3.zip'
257
+ )
258
+ ```
259
+
260
+ ### Clipping
261
+
262
+ `ClippingClient` calls a separate clipping/masking service and returns a
263
+ cropped PNG or NetCDF file for a bounding box. On success, `clip()` returns
264
+ `True` after writing the result file or files to the current directory:
265
+
266
+ ```python
267
+ import apccapi
268
+
269
+ c = apccapi.ClippingClient()
270
+ c.clip(
271
+ {
272
+ 'lead_month': '3-MON', # '3-MON' | '6-MON'
273
+ 'variable': 'prec', # prec, slp, sst, t2m, t850, z500
274
+ 'method': 'SCM', # SCM, GAUS
275
+ 'period': 'Monthly mean', # Monthly mean, Seasonal mean
276
+ 'iyear': '2021', # issued year
277
+ 'imonth': '2', # issued month
278
+ 'cowest': '188',
279
+ 'coeast': '191',
280
+ 'conorth': '-11',
281
+ 'cosouth': '-15',
282
+ 'return_type': 'png' # 'png' | 'nc'
283
+ }
284
+ )
285
+ ```
286
+
287
+ The clipping service determines each result filename. For every result URL,
288
+ `clip()` writes the file using the substring after its final `/` in the current
289
+ working directory (for example, `https://example.test/results/crop.png` is
290
+ saved as `./crop.png`). The name assembled from the request fields is only
291
+ used in the start log; it is not the output filename. `clip()` has no output
292
+ path or custom-filename option, so callers that need a chosen filename should
293
+ run it from the desired directory and rename the downloaded file after it
294
+ returns `True`. This preserves the existing `True` success return type.
295
+
296
+ ### Error handling
297
+
298
+ Every expected failure (invalid helper/clipping input, rejected request,
299
+ malformed API response, job that ends in `Failed`, exhausted retries, or a
300
+ broken download) raises `apccapi.ClikApiError` instead of failing silently.
301
+ This applies to `Client`, all dataset helpers, and `ClippingClient`:
302
+
303
+ ```python
304
+ import apccapi
305
+
306
+ try:
307
+ apccapi.download_ERA5(
308
+ {'timestep': 'MONTHLY', 'level': 'single', 'variable': 't2m', 'year': '2020'},
309
+ 't2m_2020.nc'
310
+ )
311
+ except apccapi.ClikApiError as e:
312
+ print('Request failed:', e)
313
+ ```
314
+
315
+ ### Optional polling deadline
316
+
317
+ `Client` preserves its existing automatic polling behavior by default. To
318
+ bound the time spent waiting for a submitted job, pass `polling_timeout` in
319
+ seconds. Once the deadline expires, `retrieve()` raises
320
+ `apccapi.PollingTimeoutError` (a `ClikApiError` subclass).
321
+
322
+ ```python
323
+ import apccapi
324
+
325
+ c = apccapi.Client(polling_timeout=900)
326
+ c.retrieve(details, 'output.nc') # details: a request dict, as in the low-level example
327
+ ```
328
+
329
+ ### Upgrading from 0.1.1 to 0.2.0
330
+
331
+ New in 0.2.0: `download_S2S_MME`, the `polling_timeout` option, and
332
+ `PollingTimeoutError`. Existing code keeps working unless it relied on the old
333
+ failure behavior:
334
+
335
+ - `Client.download()` and `ClippingClient.clip()` no longer return `False` for
336
+ expected failures; they raise `ClikApiError`. Replace `if not ...` checks
337
+ with `try/except apccapi.ClikApiError`.
338
+ - Missing helper input fields, malformed API responses, and an unreadable
339
+ properties file now raise `ClikApiError` instead of `KeyError`, `TypeError`,
340
+ JSON parsing errors, or `OSError`.
341
+ - `ClippingClient` no longer prints; it reports through `logging` (logger
342
+ `clipsapi`).
343
+ - Unchanged: the properties file (`~/apccapi.properties`) and its keys, the
344
+ return value of `Client.retrieve()` and the dataset helpers (the output
345
+ path), and `True` from a successful `Client.download()` / `clip()`.
346
+
347
+ Downloads are written to a temporary file in the destination directory and
348
+ replace an existing output file only after a successful HTTP response and
349
+ complete write, so a failed download leaves an existing output file unchanged.
350
+ A non-2xx HTTP status counts as a failed attempt (0.1.1 saved its body as the
351
+ result file).
352
+
353
+ See `CHANGELOG.md` in the source repository for the full list.
354
+
355
+ ## License
356
+
357
+ MIT License
358
+
359
+ Copyright (c) 2019 - 2024, APEC Climate Center
360
+
361
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
362
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
363
+
364
+ The above copyright notice and this permission notice shall be included in all
365
+ copies or substantial portions of the Software.
366
+
367
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.