Skip to content

Commit

Permalink
Merge branch 'release/1.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
shahramn committed Aug 22, 2022
2 parents 063cb6d + 0205e72 commit 08d366d
Show file tree
Hide file tree
Showing 17 changed files with 599 additions and 416 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Changelog for eccodes-python
============================

1.5.0 (2022-08-25)
--------------------

- ECC-1404: Add the grib_get_gaussian_latitudes() function
- ECC-1405: Add new function: codes_any_new_from_samples
- ECC-1415: Implement a higher-level Python interface (still experimental)
- ECC-1429: Remove the file 'eccodes/messages.py'
- GitHub pull request #62: add pypi badge

1.4.2 (2022-05-20)
--------------------

Expand Down
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. image:: https://img.shields.io/pypi/v/eccodes.svg
:target: https://pypi.python.org/pypi/eccodes/

Python 3 interface to decode and encode GRIB and BUFR files via the
`ECMWF ecCodes library <https://confluence.ecmwf.int/display/ECC/>`_.
Expand Down
6 changes: 0 additions & 6 deletions docs/messages.rst

This file was deleted.

1 change: 1 addition & 0 deletions eccodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
#

from .eccodes import * # noqa
from .highlevel import * # noqa
4 changes: 4 additions & 0 deletions eccodes/eccodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from gribapi import bindings_version
from gribapi import bufr_new_from_file as codes_bufr_new_from_file
from gribapi import (
codes_any_new_from_samples,
codes_bufr_copy_data,
codes_bufr_extract_headers,
codes_bufr_key_is_header,
Expand All @@ -41,6 +42,7 @@
codes_definition_path,
codes_dump,
codes_extract_offsets,
codes_get_gaussian_latitudes,
codes_get_library_path,
codes_get_version_info,
codes_new_from_file,
Expand Down Expand Up @@ -233,6 +235,7 @@
"codes_bufr_multi_element_constant_arrays_on",
"codes_bufr_new_from_file",
"codes_bufr_new_from_samples",
"codes_any_new_from_samples",
"CODES_CHECK",
"codes_clone",
"codes_copy_namespace",
Expand All @@ -246,6 +249,7 @@
"codes_get_double_elements",
"codes_get_double",
"codes_get_elements",
"codes_get_gaussian_latitudes",
"codes_get_library_path",
"codes_get_long_array",
"codes_get_long",
Expand Down
2 changes: 2 additions & 0 deletions eccodes/highlevel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .message import GRIBMessage, Message # noqa
from .reader import FileReader, MemoryReader, StreamReader # noqa
228 changes: 228 additions & 0 deletions eccodes/highlevel/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import io
from contextlib import contextmanager

import eccodes

_TYPES_MAP = {
"float": float,
"int": int,
"str": str,
}


@contextmanager
def raise_keyerror(name):
"""Make operations on a key raise a KeyError if not found"""
try:
yield
except eccodes.KeyValueNotFoundError:
raise KeyError(name)


class Message:
def __init__(self, handle):
self._handle = handle

def __del__(self):
try:
eccodes.codes_release(self._handle)
except Exception:
pass

def copy(self):
"""Create a copy of the current message"""
return Message(eccodes.codes_clone(self._handle))

def __copy__(self):
return self.copy()

def _get(self, name, ktype=None):
name, sep, stype = name.partition(":")
if sep and ktype is None:
try:
ktype = _TYPES_MAP[stype]
except KeyError:
raise ValueError(f"Unknown key type {stype!r}")
with raise_keyerror(name):
if eccodes.codes_is_missing(self._handle, name):
raise KeyError(name)
if eccodes.codes_get_size(self._handle, name) > 1:
return eccodes.codes_get_array(self._handle, name, ktype=ktype)
return eccodes.codes_get(self._handle, name, ktype=ktype)

def get(self, name, default=None, ktype=None):
"""Get the value of a key
Parameters
----------
name: str
Name of the key. Can be suffixed with ":str", ":int", or ":float" to
request a specific type.
default: any, optional
Value if the key is not Found, or ``None`` if not specified.
ktype: type
Request a specific type for the value. Overrides the suffix in ``name``"""
try:
return self._get(name, ktype=ktype)
except KeyError:
return default

def set(self, name, value):
"""Set the value of the given key
Raises
------
KeyError
If the key does not exist
"""
with raise_keyerror(name):
return eccodes.codes_set(self._handle, name, value)

def get_array(self, name):
"""Get the value of the given key as an array
Raises
------
KeyError
If the key is not set
"""
with raise_keyerror(name):
return eccodes.codes_get_array(self._handle, name)

def get_size(self, name):
"""Get the size of the given key
Raises
------
KeyError
If the key is not set
"""
with raise_keyerror(name):
return eccodes.codes_get_size(self._handle, name)

def get_data_points(self):
raise NotImplementedError

def is_missing(self, name):
"""Check whether the key is set to a missing value
Raises
------
KeyError
If the key is not set
"""
with raise_keyerror(name):
return bool(eccodes.codes_is_missing(self._handle, name))

def set_array(self, name, value):
"""Set the value of the given key
Raises
------
KeyError
If the key does not exist
"""
with raise_keyerror(name):
return eccodes.codes_set_array(self._handle, name, value)

def set_missing(self, name):
"""Set the given key as missing
Raises
------
KeyError
If the key does not exist
"""
with raise_keyerror(name):
return eccodes.codes_set_missing(self._handle, name)

def __getitem__(self, name):
return self._get(name)

def __setitem__(self, name, value):
self.set(name, value)

def __contains__(self, name):
return bool(eccodes.codes_is_defined(self._handle, name))

class _KeyIterator:
def __init__(self, message, namespace=None, iter_keys=True, iter_values=False):
self._message = message
self._iterator = eccodes.codes_keys_iterator_new(message._handle, namespace)
self._iter_keys = iter_keys
self._iter_values = iter_values

def __del__(self):
try:
eccodes.codes_keys_iterator_delete(self._iterator)
except Exception:
pass

def __iter__(self):
return self

def __next__(self):
while True:
if not eccodes.codes_keys_iterator_next(self._iterator):
raise StopIteration
if not self._iter_keys and not self._iter_values:
return
key = eccodes.codes_keys_iterator_get_name(self._iterator)
if self._message.is_missing(key):
continue
if self._iter_keys and not self._iter_values:
return key
value = self._message.get(key) if self._iter_values else None
if not self._iter_keys:
return value
return key, value

def __iter__(self):
return self._KeyIterator(self)

def keys(self, namespace=None):
"""Iterate over all the available keys"""
return self._KeyIterator(self, namespace, iter_keys=True, iter_values=False)

def values(self, namespace=None):
"""Iterate over the values of all the available keys"""
return self._KeyIterator(self, namespace, iter_keys=False, iter_values=True)

def items(self, namespace=None):
"""Iterate over all the available key-value pairs"""
return self._KeyIterator(self, namespace, iter_keys=True, iter_values=True)

def dump(self):
"""Print out a textual representation of the message"""
eccodes.codes_dump(self._handle)

def write_to(self, fileobj):
"""Write the message to a file object"""
assert isinstance(fileobj, io.IOBase)
eccodes.codes_write(self._handle, fileobj)

def get_buffer(self):
"""Return a buffer containing the encoded message"""
return eccodes.codes_get_message(self._handle)


class GRIBMessage(Message):
def __init__(self, handle):
super().__init__(handle)
self._data = None

@property
def data(self):
"""Return the array of values"""
if self._data is None:
self._data = self._get("values")
return self._data

def get_data_points(self):
"""Get the list of ``(lat, lon, value)`` data points"""
return eccodes.codes_grib_get_data(self._handle)

@classmethod
def from_samples(cls, name):
"""Create a message from a sample"""
return cls(eccodes.codes_grib_new_from_samples(name))
Loading

0 comments on commit 08d366d

Please sign in to comment.