Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow None for bits_per_values when saving GRIB data #327

Merged
merged 8 commits into from
Mar 5, 2024
12 changes: 12 additions & 0 deletions earthkit/data/core/fieldlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,13 @@ def save(self, filename, append=False, **kwargs):
the target file be overwritten if already exists. Default is False
**kwargs: dict, optional
Other keyword arguments passed to :obj:`write`.

See Also
--------
:obj:`write`
:meth:`GribFieldList.save() <data.readers.grib.index.GribFieldList.save>`
:meth:`NumpyFieldList.save() <data.sources.numpy_list.NumpyFieldList.save>`

"""
flag = "wb" if not append else "ab"
with open(filename, flag) as f:
Expand All @@ -1191,6 +1198,11 @@ def write(self, f, **kwargs):
The target file object.
**kwargs: dict, optional
Other keyword arguments passed to the underlying field implementation.

See Also
--------
read

"""
for s in self:
s.write(f, **kwargs)
Expand Down
92 changes: 88 additions & 4 deletions earthkit/data/core/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,23 @@ class Metadata(metaclass=ABCMeta):
INDEX_KEYS = []
CUSTOM_KEYS = [DATETIME]

extra = None

def __iter__(self):
"""Return an iterator over the metadata keys."""
return iter(self.keys())

@abstractmethod
def __len__(self):
r"""Return the number of metadata entries."""
if not self.extra:
return self._len()
else:
extra = sum([1 for x in self.extra if not self._contains(x)])
return extra + self._len()

@abstractmethod
def _len(self):
r"""Return the number of metadata entries without the extra items."""
pass

def __getitem__(self, key):
Expand All @@ -52,17 +62,30 @@ def __getitem__(self, key):
"""
return self.get(key, raise_on_missing=True)

@abstractmethod
def __contains__(self, key):
r"""Check if ``key`` is available.

Returns
-------
bool
"""
pass
if not self.extra:
return self._contains(key)
else:
if key in self.extra:
return True
return self._contains(key)

@abstractmethod
def _contains(self, key):
r"""Check if ``key`` is available in the non extra-keys.

Returns
-------
bool
"""
pass

def keys(self):
r"""Return the metadata keys.

Expand All @@ -71,16 +94,50 @@ def keys(self):
Iterable of str

"""
pass
if not self.extra:
return self._keys()
else:
extra = [x for x in self.extra if x not in self._keys()]
if len(extra) == 0:
return self._keys()
else:
r = list(self._keys()) + extra
return r

@abstractmethod
def _keys(self):
r"""Return the metadata keys without the extra keys.

Returns
-------
Iterable of str

"""
pass

def items(self):
r"""Return the metadata items.

Returns
-------
Iterable of :obj:`(key,value)` pairs

"""
if not self.extra:
return self._items()
else:
r = dict(self._items())
r.update(self.extra)
return r.items()

@abstractmethod
def _items(self):
r"""Return the metadata items without the extra keys.

Returns
-------
Iterable of :obj:`(key,value)` pairs

"""
pass

Expand Down Expand Up @@ -115,6 +172,8 @@ def get(self, key, default=None, *, astype=None, raise_on_missing=False):
a missing value.

"""
if self._is_extra_key(key):
return self._get_extra_key(key, default=default, astype=astype)
if self._is_custom_key(key):
return self._get_custom_key(
key, default=default, astype=astype, raise_on_missing=raise_on_missing
Expand All @@ -128,6 +187,19 @@ def get(self, key, default=None, *, astype=None, raise_on_missing=False):
def _get(self, key, astype=None, default=None, raise_on_missing=False):
pass

def _is_extra_key(self, key):
return self.extra is not None and key in self.extra

def _get_extra_key(self, key, default=None, astype=None, **kwargs):
v = self.extra.get(key, default)

if astype is not None and v is not None:
try:
return astype(v)
except Exception:
return None
return v

def _is_custom_key(self, key):
return key in self.CUSTOM_KEYS and key not in self

Expand Down Expand Up @@ -300,9 +372,15 @@ def override(self, *args, **kwargs):
def __len__(self):
return len(self._d)

def _len(self):
return self._len()

def __contains__(self, key):
return key in self._d

def _contains(self, key):
pass

def _get(self, key, astype=None, default=None, raise_on_missing=False):
if not raise_on_missing:
v = self._d.get(key, default)
Expand All @@ -319,9 +397,15 @@ def _get(self, key, astype=None, default=None, raise_on_missing=False):
def keys(self):
return self._d.keys()

def _keys(self):
pass

def items(self):
return self._d.items()

def _items(self):
pass

def _base_datetime(self):
return None

Expand Down
13 changes: 11 additions & 2 deletions earthkit/data/readers/grib/codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,16 +291,25 @@ def __repr__(self):
# name = "paramId"
# return self.handle.get(name)

def write(self, f):
def write(self, f, bits_per_value=None):
r"""Writes the message to a file object.

Parameters
----------
f: file object
The target file object.
bits_per_value: int or None
Set the ``bitsPerValue`` GRIB key in the generated GRIB message. When
None the ``bitsPerValue`` stored in the metadata will be used.
"""
if bits_per_value is not None:
handle = self.handle.clone()
handle.set_long("bitsPerValue", bits_per_value)
else:
handle = self.handle

# assert isinstance(f, io.IOBase)
self.handle.write_to(f)
handle.write_to(f)

def message(self):
r"""Returns a buffer containing the encoded message.
Expand Down
29 changes: 28 additions & 1 deletion earthkit/data/readers/grib/index/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from earthkit.data.core.fieldlist import FieldList
from earthkit.data.core.index import Index, MaskIndex, MultiIndex
from earthkit.data.decorators import alias_argument
from earthkit.data.decorators import alias_argument, detect_out_filename
from earthkit.data.indexing.database import (
FILEPARTS_KEY_NAMES,
MORE_KEY_NAMES,
Expand Down Expand Up @@ -179,6 +179,33 @@ def _is_full_hypercube(self):
def _normalize_kwargs_names(self, **kwargs):
return kwargs

@detect_out_filename
def save(self, filename, append=False, bits_per_value=None):
r"""Write all the fields into a file.

Parameters
----------
filename: str
The target file path.
append: bool
When it is true append data to the target file. Otherwise
the target file be overwritten if already exists.
bits_per_value: int or None
Set the ``bitsPerValue`` GRIB key for each message in the generated
output. When None the ``bitsPerValue`` stored in the message metadata
will be used.

See Also
--------
write

"""
super().save(
filename,
append=append,
bits_per_value=bits_per_value,
)


class GribMaskFieldList(GribFieldList, MaskIndex):
def __init__(self, *args, **kwargs):
Expand Down
Loading
Loading