Skip to content

Commit e7e7138

Browse files
committed
Release 1.1.0
2 parents e0d76c7 + 857a828 commit e7e7138

File tree

12 files changed

+277
-65
lines changed

12 files changed

+277
-65
lines changed

.github/workflows/on-push.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: on-push
2+
3+
on: [push]
4+
5+
jobs:
6+
unit-tests:
7+
runs-on: ${{ matrix.os }}-latest
8+
strategy:
9+
max-parallel: 5
10+
matrix:
11+
os: [ubuntu]
12+
python: [3.6, 3.7, 3.8]
13+
include:
14+
- os: macos
15+
python: 3.8
16+
- os: windows
17+
python: 3.8
18+
19+
steps:
20+
- uses: actions/checkout@v2
21+
- uses: conda-incubator/setup-miniconda@v2
22+
with:
23+
python-version: ${{ matrix.python }}
24+
activate-environment: ${{ matrix.os }}-${{ matrix.python }}
25+
# environment-file: tests/environment-${{ matrix.os }}-${{ matrix.python }}.yml
26+
environment-file: tests/environment.yml
27+
- name: Test with pytest
28+
shell: bash -l {0}
29+
run: |
30+
conda install pytest pytest-cov
31+
python -m pip install --no-deps -e .
32+
pytest -v --cov=. --cov-report=xml --cov-branch .
33+
- name: Upload coverage to Codecov
34+
uses: codecov/codecov-action@v1
35+
36+
code-quality:
37+
runs-on: ubuntu-latest
38+
39+
steps:
40+
- uses: actions/checkout@v2
41+
- name: Lint with flake8
42+
run: |
43+
$CONDA/bin/conda install flake8
44+
# stop the build if there are Python syntax errors or undefined names
45+
$CONDA/bin/flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
46+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
47+
$CONDA/bin/flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
48+
49+
code-style:
50+
runs-on: ubuntu-latest
51+
52+
steps:
53+
- uses: actions/checkout@v2
54+
- name: Check code style with black
55+
run: |
56+
$CONDA/bin/conda install -c conda-forge black
57+
$CONDA/bin/black --check .
58+
- name: Check code style with isort
59+
run: |
60+
$CONDA/bin/conda install isort
61+
$CONDA/bin/isort .

CHANGELOG.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
Changelog for eccodes-python
33
============================
44

5+
1.1.0 (2021-01-20)
6+
--------------------
7+
8+
- ECC-1171: Performance: Python bindings: remove assert statements
9+
- ECC-1161: Python3 bindings: Do not raise exception on first failed attempt
10+
- ECC-1176: Python3 bindings: float32 recognised as int instead of float
11+
- GitHub pull request #41: Remove the apparent support for Python 2
12+
- GitHub pull request #44: Fix CFFI crash on windows
13+
- GitHub pull request #42: Add unit testing with GitHub actions (linux, macos and windows)
14+
15+
516
1.0.0 (2020-10-14)
617
--------------------
718

@@ -24,7 +35,7 @@ Changelog for eccodes-python
2435
- Fix codes_set_definitions_path() typo
2536
- Fix grib_get_double_element(). Missing last argument
2637
- Add more tests to increase coverage
27-
- Add .__next__() method to eccodes.CodesFile class (pull request #15)
38+
- GitHub pull request #15: Add .__next__() method to eccodes.CodesFile class
2839
- ECC-1113: Python3 bindings under Windows: codes_get_long_array returns incorrect values
2940
- ECC-1108: Python3 bindings under Windows: use of handle causes crash
3041
- ECC-1121: Segfault when closing GribFile if messages are closed manually

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ https://software.ecmwf.int/wiki/display/ECC/ecCodes+installation
4747
You may run a simple selfcheck command to ensure that your system is set up correctly::
4848

4949
$ python -m eccodes selfcheck
50-
Found: ecCodes v2.19.0.
50+
Found: ecCodes v2.20.0.
5151
Your system is ready.
5252

5353

eccodes/eccodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,8 @@
129129
from gribapi import codes_bufr_multi_element_constant_arrays_off
130130
from gribapi import codes_bufr_extract_headers
131131

132+
from gribapi import codes_bufr_key_is_header
133+
from gribapi import codes_extract_offsets
134+
132135
from gribapi.errors import GribInternalError as CodesInternalError
133136
from gribapi.errors import *

gribapi/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from .gribapi import bindings_version
1616

1717
# The minimum required version for the ecCodes package
18-
min_reqd_version_str = "2.19.0"
19-
min_reqd_version_int = 21900
18+
min_reqd_version_str = "2.20.0"
19+
min_reqd_version_int = 22000
2020

2121
if lib.grib_get_api_version() < min_reqd_version_int:
2222
print(

gribapi/bindings.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@
2121

2222
import cffi
2323

24-
__version__ = "1.0.0"
24+
__version__ = "1.1.0"
2525

2626
LOG = logging.getLogger(__name__)
2727

2828
try:
2929
from ._bindings import ffi, lib
3030
except ModuleNotFoundError:
3131
ffi = cffi.FFI()
32-
ffi.cdef(
33-
pkgutil.get_data(__name__, "grib_api.h").decode("utf-8")
34-
+ pkgutil.get_data(__name__, "eccodes.h").decode("utf-8")
35-
)
32+
CDEF = pkgutil.get_data(__name__, "grib_api.h")
33+
CDEF += pkgutil.get_data(__name__, "eccodes.h")
34+
ffi.cdef(CDEF.decode("utf-8").replace("\r", "\n"))
3635

3736
LIBNAMES = ["eccodes", "libeccodes.so", "libeccodes"]
3837

@@ -46,18 +45,19 @@
4645
if os.environ.get("ECCODES_DIR"):
4746
eccdir = os.environ["ECCODES_DIR"]
4847
LIBNAMES.insert(0, os.path.join(eccdir, "lib/libeccodes.so"))
49-
LIBNAMES.insert(0, os.path.join(eccdir, "lib64/libeccodes.so"))
48+
LIBNAMES.insert(1, os.path.join(eccdir, "lib64/libeccodes.so"))
5049

50+
lib = None
5151
for libname in LIBNAMES:
5252
try:
5353
lib = ffi.dlopen(libname)
5454
LOG.info("ecCodes library found using name '%s'.", libname)
5555
break
5656
except OSError:
57-
# lazy exception
58-
lib = None
5957
LOG.info("ecCodes library not found using name '%s'.", libname)
60-
raise RuntimeError(f"ecCodes library not found using {LIBNAMES}")
58+
pass
59+
if lib is None:
60+
raise RuntimeError(f"ecCodes library not found using {LIBNAMES}")
6161

6262
# default encoding for ecCodes strings
6363
ENC = "ascii"

gribapi/eccodes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@ int codes_bufr_copy_data(grib_handle* hin, grib_handle* hout);
2020
void codes_bufr_multi_element_constant_arrays_on(codes_context* c);
2121
void codes_bufr_multi_element_constant_arrays_off(codes_context* c);
2222
int codes_bufr_extract_headers_malloc(codes_context* c, const char* filename, codes_bufr_header** result, int* num_messages, int strict_mode);
23+
int codes_extract_offsets_malloc(grib_context* c, const char* filename, ProductKind product, long int** offsets, int* num_messages, int strict_mode);
24+
int codes_bufr_key_is_header(const codes_handle* h, const char* key, int* err);
25+
2326
char* codes_samples_path(const codes_context *c);
2427
char* codes_definition_path(const codes_context *c);

gribapi/grib_api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ typedef struct codes_bufr_header {
220220
long rectimeHour;
221221
long rectimeMinute;
222222
long rectimeSecond;
223+
long restricted;
223224

224225
long isSatellite;
225226
double localLongitude1;

gribapi/gribapi.py

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ def wrapper(*args):
155155

156156

157157
def get_handle(msgid):
158-
assert isinstance(msgid, int)
159158
h = ffi.cast("grib_handle*", msgid)
160159
if h == ffi.NULL:
161160
raise errors.InvalidGribError(f"get_handle: Bad message ID {msgid}")
@@ -169,7 +168,6 @@ def put_handle(handle):
169168

170169

171170
def get_multi_handle(msgid):
172-
assert isinstance(msgid, int)
173171
return ffi.cast("grib_multi_handle*", msgid)
174172

175173

@@ -178,7 +176,6 @@ def put_multi_handle(handle):
178176

179177

180178
def get_index(indexid):
181-
assert isinstance(indexid, int)
182179
return ffi.cast("grib_index*", indexid)
183180

184181

@@ -187,7 +184,6 @@ def put_index(indexh):
187184

188185

189186
def get_iterator(iterid):
190-
assert isinstance(iterid, int)
191187
return ffi.cast("grib_iterator*", iterid)
192188

193189

@@ -196,7 +192,6 @@ def put_iterator(iterh):
196192

197193

198194
def get_grib_keys_iterator(iterid):
199-
assert isinstance(iterid, int)
200195
return ffi.cast("grib_keys_iterator*", iterid)
201196

202197

@@ -205,7 +200,6 @@ def put_grib_keys_iterator(iterh):
205200

206201

207202
def get_bufr_keys_iterator(iterid):
208-
assert isinstance(iterid, int)
209203
return ffi.cast("bufr_keys_iterator*", iterid)
210204

211205

@@ -314,7 +308,7 @@ def codes_new_from_file(fileobj, product_kind, headers_only=False):
314308
return gts_new_from_file(fileobj, headers_only)
315309
if product_kind == CODES_PRODUCT_ANY:
316310
return any_new_from_file(fileobj, headers_only)
317-
raise Exception("Invalid product kind: " + product_kind)
311+
raise ValueError("Invalid product kind %d" % product_kind)
318312

319313

320314
@require(fileobj=file)
@@ -456,7 +450,7 @@ def grib_multi_support_reset_file(fileobj):
456450
@brief Reset file handle in multiple field support mode
457451
"""
458452
context = lib.grib_context_get_default()
459-
GRIB_CHECK(lib.grib_multi_support_reset_file(context, fileobj))
453+
lib.grib_multi_support_reset_file(context, fileobj)
460454

461455

462456
@require(msgid=int)
@@ -971,7 +965,7 @@ def grib_get_double(msgid, key):
971965
return value_p[0]
972966

973967

974-
@require(msgid=int, key=str, value=(int, float, str))
968+
@require(msgid=int, key=str, value=(int, float, np.float16, np.float32, str))
975969
def grib_set_long(msgid, key, value):
976970
"""
977971
@brief Set the integer value for a key in a message.
@@ -990,13 +984,13 @@ def grib_set_long(msgid, key, value):
990984
raise TypeError("Invalid type")
991985

992986
if value > sys.maxsize:
993-
raise TypeError("Invalid type")
987+
raise ValueError("Value too large")
994988

995989
h = get_handle(msgid)
996990
GRIB_CHECK(lib.grib_set_long(h, key.encode(ENC), value))
997991

998992

999-
@require(msgid=int, key=str, value=(int, float, str))
993+
@require(msgid=int, key=str, value=(int, float, np.float16, np.float32, str))
1000994
def grib_set_double(msgid, key, value):
1001995
"""
1002996
@brief Set the double value for a key in a message.
@@ -1037,7 +1031,7 @@ def codes_new_from_samples(samplename, product_kind):
10371031
return grib_new_from_samples(samplename)
10381032
if product_kind == CODES_PRODUCT_BUFR:
10391033
return codes_bufr_new_from_samples(samplename)
1040-
raise Exception("Invalid product kind: " + product_kind)
1034+
raise ValueError("Invalid product kind %d" % product_kind)
10411035

10421036

10431037
@require(samplename=str)
@@ -2039,7 +2033,7 @@ def grib_set(msgid, key, value):
20392033
"""
20402034
if isinstance(value, int):
20412035
grib_set_long(msgid, key, value)
2042-
elif isinstance(value, float):
2036+
elif isinstance(value, (float, np.float16, np.float32, np.float64)):
20432037
grib_set_double(msgid, key, value)
20442038
elif isinstance(value, str):
20452039
grib_set_string(msgid, key, value)
@@ -2075,12 +2069,11 @@ def grib_set_array(msgid, key, value):
20752069
except TypeError:
20762070
pass
20772071

2078-
if isinstance(val0, float):
2072+
if isinstance(val0, (float, np.float16, np.float32, np.float64)):
20792073
grib_set_double_array(msgid, key, value)
20802074
elif isinstance(val0, str):
20812075
grib_set_string_array(msgid, key, value)
20822076
else:
2083-
# Note: Cannot do isinstance(val0,int) for numpy.int64
20842077
try:
20852078
int(val0)
20862079
except (ValueError, TypeError):
@@ -2392,3 +2385,48 @@ def codes_bufr_extract_headers(filepath, is_strict=True):
23922385
while i < num_messages:
23932386
yield _convert_struct_to_dict(headers[i])
23942387
i += 1
2388+
2389+
2390+
def codes_bufr_key_is_header(msgid, key):
2391+
"""
2392+
@brief Check if the BUFR key is in the header or in the data section.
2393+
2394+
If the data section has not been unpacked, then passing in a key from
2395+
the data section will throw KeyValueNotFoundError.
2396+
2397+
@param msgid id of the BUFR message loaded in memory
2398+
@param key key name
2399+
@return 1->header, 0->data section
2400+
@exception CodesInternalError
2401+
"""
2402+
h = get_handle(msgid)
2403+
err, value = err_last(lib.codes_bufr_key_is_header)(h, key.encode(ENC))
2404+
GRIB_CHECK(err)
2405+
return value
2406+
2407+
2408+
def codes_extract_offsets(filepath, product_kind, is_strict=True):
2409+
"""
2410+
@brief Message offset extraction (EXPERIMENTAL FEATURE)
2411+
2412+
@param filepath path of input file
2413+
@param is_strict fail as soon as any invalid message is encountered
2414+
@return a list of offsets
2415+
@exception CodesInternalError
2416+
"""
2417+
context = lib.grib_context_get_default()
2418+
offsets_p = ffi.new("long int**")
2419+
num_message_p = ffi.new("int*")
2420+
2421+
err = lib.codes_extract_offsets_malloc(
2422+
context, filepath.encode(ENC), product_kind, offsets_p, num_message_p, is_strict
2423+
)
2424+
GRIB_CHECK(err)
2425+
2426+
num_messages = num_message_p[0]
2427+
offsets = offsets_p[0]
2428+
2429+
i = 0
2430+
while i < num_messages:
2431+
yield offsets[i]
2432+
i += 1

setup.cfg

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
[bdist_wheel]
2-
universal = 1
3-
41
[aliases]
52
test = pytest
63

0 commit comments

Comments
 (0)