Skip to content

Commit 6d2051f

Browse files
committed
Merge branch 'develop'
2 parents 7962e87 + 4c08b49 commit 6d2051f

File tree

11 files changed

+636
-1
lines changed

11 files changed

+636
-1
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
Changelog for eccodes-python
33
============================
44

5+
1.3.2 (2021-04-16)
6+
--------------------
7+
8+
- Restore the experimental high-level interface
59

610
1.3.1 (2021-04-16)
711
--------------------

eccodes/eccodes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,20 @@
210210
WrongTypeError,
211211
)
212212

213+
from .high_level.bufr import BufrFile, BufrMessage
214+
from .high_level.gribfile import GribFile
215+
from .high_level.gribindex import GribIndex
216+
from .high_level.gribmessage import GribMessage
217+
213218
__all__ = [
214219
"__version__",
215220
"ArrayTooSmallError",
216221
"AttributeClashError",
217222
"AttributeNotFoundError",
218223
"bindings_version",
219224
"BufferTooSmallError",
225+
"BufrFile",
226+
"BufrMessage",
220227
"CodeNotFoundInTableError",
221228
"codes_any_new_from_file",
222229
"codes_bufr_copy_data",
@@ -353,7 +360,10 @@
353360
"FunctionalityNotEnabledError",
354361
"FunctionNotImplementedError",
355362
"GeocalculusError",
363+
"GribFile",
364+
"GribIndex",
356365
"GribInternalError",
366+
"GribMessage",
357367
"HashArrayNoMatchError",
358368
"InternalArrayTooSmallError",
359369
"InternalError",

eccodes/high_level/__init__.py

Whitespace-only changes.

eccodes/high_level/bufr.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
3+
Note: The high-level Python interface is currently experimental and may change in a future release.
4+
5+
Classes for handling BUFR with a high level interface.
6+
7+
``BufrFiles`` can be treated mostly as regular files and used as context
8+
managers, as can ``BufrMessages``. Each of these classes destructs itself and
9+
any child instances appropriately.
10+
11+
Author: Daniel Lee, DWD, 2016
12+
"""
13+
14+
from .. import eccodes
15+
from .codesfile import CodesFile
16+
from .codesmessage import CodesMessage
17+
18+
19+
class BufrMessage(CodesMessage):
20+
21+
__doc__ = "\n".join(CodesMessage.__doc__.splitlines()[4:]).format(
22+
prod_type="BUFR", classname="BufrMessage", parent="BufrFile", alias="bufr"
23+
)
24+
25+
product_kind = eccodes.CODES_PRODUCT_BUFR
26+
27+
# Arguments included explicitly to support introspection
28+
# TODO: Can we get this to work with an index?
29+
def __init__(self, codes_file=None, clone=None, sample=None, headers_only=False):
30+
"""
31+
Open a message and inform the GRIB file that it's been incremented.
32+
33+
The message is taken from ``codes_file``, cloned from ``clone`` or
34+
``sample``, or taken from ``index``, in that order of precedence.
35+
"""
36+
super(self.__class__, self).__init__(codes_file, clone, sample, headers_only)
37+
# self._unpacked = False
38+
39+
# def get(self, key, ktype=None):
40+
# """Return requested value, unpacking data values if necessary."""
41+
# # TODO: Only do this if accessing arrays that need unpacking
42+
# if not self._unpacked:
43+
# self.unpacked = True
44+
# return super(self.__class__, self).get(key, ktype)
45+
46+
# def missing(self, key):
47+
# """
48+
# Report if key is missing.#
49+
#
50+
# Overloaded due to confusing behaviour in ``codes_is_missing`` (SUP-1874).
51+
# """
52+
# return not bool(eccodes.codes_is_defined(self.codes_id, key))
53+
54+
def unpack(self):
55+
"""Decode data section"""
56+
eccodes.codes_set(self.codes_id, "unpack", 1)
57+
58+
def pack(self):
59+
"""Encode data section"""
60+
eccodes.codes_set(self.codes_id, "pack", 1)
61+
62+
def keys(self, namespace=None):
63+
# self.unpack()
64+
# return super(self.__class__, self).keys(namespace)
65+
iterator = eccodes.codes_bufr_keys_iterator_new(self.codes_id)
66+
keys = []
67+
while eccodes.codes_bufr_keys_iterator_next(iterator):
68+
key = eccodes.codes_bufr_keys_iterator_get_name(iterator)
69+
keys.append(key)
70+
eccodes.codes_bufr_keys_iterator_delete(iterator)
71+
return keys
72+
73+
# @property
74+
# def unpacked(self):
75+
# return self._unpacked
76+
77+
# @unpacked.setter
78+
# def unpacked(self, val):
79+
# eccodes.codes_set(self.codes_id, "unpack", val)
80+
# self._unpacked = val
81+
82+
# def __setitem__(self, key, value):
83+
# """Set item and pack BUFR."""
84+
# if not self._unpacked:
85+
# self.unpacked = True
86+
# super(self.__class__, self).__setitem__(key, value)
87+
# eccodes.codes_set(self.codes_id, "pack", True)
88+
89+
def copy_data(self, destMsg):
90+
"""Copy data values from this message to another message"""
91+
return eccodes.codes_bufr_copy_data(self.codes_id, destMsg.codes_id)
92+
93+
94+
class BufrFile(CodesFile):
95+
96+
__doc__ = "\n".join(CodesFile.__doc__.splitlines()[4:]).format(
97+
prod_type="BUFR", classname="BufrFile", alias="bufr"
98+
)
99+
100+
MessageClass = BufrMessage

eccodes/high_level/codesfile.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Note: The high-level Python interface is currently experimental and may change in a future release.
3+
4+
``CodesFile`` class that implements a file that is readable by ecCodes and
5+
closes itself and its messages when it is no longer needed.
6+
7+
Author: Daniel Lee, DWD, 2016
8+
"""
9+
10+
import io
11+
12+
from .. import eccodes
13+
14+
15+
class CodesFile(io.FileIO):
16+
17+
"""
18+
An abstract class to specify and/or implement common behaviour that files
19+
read by ecCodes should implement.
20+
21+
A {prod_type} file handle meant for use in a context manager.
22+
23+
Individual messages can be accessed using the ``next`` method. Of course,
24+
it is also possible to iterate over each message in the file::
25+
26+
>>> with {classname}(filename) as {alias}:
27+
... # Print number of messages in file
28+
... len({alias})
29+
... # Open all messages in file
30+
... for msg in {alias}:
31+
... print(msg[key_name])
32+
... len({alias}.open_messages)
33+
>>> # When the file is closed, any open messages are closed
34+
>>> len({alias}.open_messages)
35+
"""
36+
37+
#: Type of messages belonging to this file
38+
MessageClass = None
39+
40+
def __init__(self, filename, mode="rb"):
41+
"""Open file and receive codes file handle."""
42+
#: File handle for working with actual file on disc
43+
#: The class holds the file it works with because ecCodes'
44+
# typechecking does not allow using inherited classes.
45+
self.file_handle = open(filename, mode)
46+
#: Number of message in file currently being read
47+
self.message = 0
48+
#: Open messages
49+
self.open_messages = []
50+
self.name = filename
51+
52+
def __exit__(self, exception_type, exception_value, traceback):
53+
"""Close all open messages, release file handle and close file."""
54+
while self.open_messages:
55+
# Note: if the message was manually closed, this has no effect
56+
self.open_messages.pop().close()
57+
self.file_handle.close()
58+
59+
def __len__(self):
60+
"""Return total number of messages in file."""
61+
return eccodes.codes_count_in_file(self.file_handle)
62+
63+
def __enter__(self):
64+
return self
65+
66+
def close(self):
67+
"""Possibility to manually close file."""
68+
self.__exit__(None, None, None)
69+
70+
def __iter__(self):
71+
return self
72+
73+
def next(self):
74+
try:
75+
return self.MessageClass(self)
76+
except IOError:
77+
raise StopIteration()
78+
79+
def __next__(self):
80+
return self.next()

0 commit comments

Comments
 (0)