eccodes-python-0.9.7/0000755000175000017500000000000013521303742014635 5ustar alastairalastaireccodes-python-0.9.7/CHANGELOG.rst0000644000175000017500000000126013521303732016654 0ustar alastairalastair Changelog for eccodes-python ============================ 0.9.2 (2019-07-09) ------------------ - All ecCodes tests now pass - Simplify the xx_new_from_file calls - Fix for grib_set_string_array - Use ECCODES_DIR to locate the library - Remove the new-style high-level interface. It is still available in `cfgrib `_. 0.9.1 (2019-06-06) ------------------ - ``codes_get_long_array`` and ``codes_get_double_array`` now return a ``np.ndarray``. See: `#3 `_. 0.9.0 (2019-05-07) ------------------ - Declare the project as **Beta**. 0.8.0 (2019-04-08) ------------------ - First public release. eccodes-python-0.9.7/tox.ini0000644000175000017500000000125713521303732016154 0ustar alastairalastair[tox] envlist = docs, py37, py36, py35, pypy3, deps [testenv] passenv = WHEELHOUSE PIP_FIND_LINKS PIP_WHEEL_DIR PIP_INDEX_URL setenv = PYTHONPATH = {toxinidir} deps = -r{toxinidir}/ci/requirements-tests.txt commands = pytest -v --flakes --cache-clear --basetemp={envtmpdir} {posargs} [testenv:docs] deps = -r{toxinidir}/ci/requirements-docs.txt commands = sphinx-build -W -b html docs build/sphinx/html [testenv:qc] basepython = python3.6 # needed for pytest-cov usedevelop = true commands = pytest -v --flakes --pep8 --mccabe --cov=eccodes --doctest-glob="*.rst" --cov-report=html --cache-clear --basetemp={envtmpdir} {posargs} [testenv:deps] deps = commands = python setup.py test eccodes-python-0.9.7/PKG-INFO0000644000175000017500000001537313521303732015742 0ustar alastairalastairMetadata-Version: 1.1 Name: eccodes-python Version: 0.9.2 Summary: Python interface to the ecCodes GRIB and BUFR decoder/encoder Home-page: https://github.com/ecmwf/eccodes-python Author: European Centre for Medium-Range Weather Forecasts (ECMWF) Author-email: software.support@ecmwf.int License: Apache License Version 2.0 Description: Python 3 interface to encode and decode GRIB and BUFR files via the `ECMWF ecCodes library `_. Features: - reads and writes GRIB 1 and 2 files, - reads and writes BUFR 3 and 4 files, - supports all modern versions of Python 3.7, 3.6, 3.5 and PyPy3, - works on most *Linux* distributions and *MacOS*, the *ecCodes* C-library is the only system dependency, - PyPI package can be installed without compiling, at the cost of being twice as slow as the original *ecCodes* module, - an optional compile step makes the code as fast as the original module but it needs a recent version of *ecCodes* `>= 2.13.0`. Limitations: - Microsoft Windows support is untested. Installation ============ The package is installed from PyPI with:: $ pip install eccodes-python System dependencies ------------------- The Python module depends on the ECMWF *ecCodes* library that must be installed on the system and accessible as a shared library. On a MacOS with HomeBrew use:: $ brew install eccodes Or if you manage binary packages with *Conda* use:: $ conda install -c conda-forge eccodes As an alternative you may install the official source distribution by following the instructions at https://software.ecmwf.int/wiki/display/ECC/ecCodes+installation You may run a simple selfcheck command to ensure that your system is set up correctly:: $ python -m eccodes selfcheck Found: ecCodes v2.13.0. Your system is ready. Usage ----- Refer to the *ecCodes* `documentation pages `_ for usage. Experimental features ===================== Fast bindings ------------- To test the much faster *CFFI* API level, out-of-line mode you need the *ecCodes* header files. Then you need to clone the repo in the same folder as your *ecCodes* source tree, make a ``pip`` development install and custom compile the binary bindings:: $ git clone https://github.com/ecmwf/eccodes-python $ cd eccodes-python $ pip install -e . $ python builder.py To revert back to ABI level, in-line more just remove the compiled bindings:: $ rm gribapi/_bindings.* Project resources ================= ============= ========================================================= Development https://github.com/ecmwf/eccodes-python Download https://pypi.org/project/eccodes-python Code quality .. image:: https://api.travis-ci.org/ecmwf/eccodes-python.svg?branch=master :target: https://travis-ci.org/ecmwf/eccodes-python/branches :alt: Build Status on Travis CI .. image:: https://coveralls.io/repos/ecmwf/eccodes-python/badge.svg?branch=master&service=github :target: https://coveralls.io/github/ecmwf/eccodes-python :alt: Coverage Status on Coveralls ============= ========================================================= Contributing ============ The main repository is hosted on GitHub, testing, bug reports and contributions are highly welcomed and appreciated: https://github.com/ecmwf/eccodes-python Please see the CONTRIBUTING.rst document for the best way to help. Lead developer: - `Alessandro Amici `_ - `B-Open `_ See also the list of `contributors `_ who participated in this project. License ======= Copyright 2017-2019 European Centre for Medium-Range Weather Forecasts (ECMWF). Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Changelog for eccodes-python ============================ 0.9.2 (2019-07-09) ------------------ - All ecCodes tests now pass - Simplify the xx_new_from_file calls - Fix for grib_set_string_array - Use ECCODES_DIR to locate the library - Remove the new-style high-level interface. It is still available in `cfgrib `_. 0.9.1 (2019-06-06) ------------------ - ``codes_get_long_array`` and ``codes_get_double_array`` now return a ``np.ndarray``. See: `#3 `_. 0.9.0 (2019-05-07) ------------------ - Declare the project as **Beta**. 0.8.0 (2019-04-08) ------------------ - First public release. Keywords: ecCodes GRIB BUFR Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: OS Independent eccodes-python-0.9.7/MANIFEST.in0000644000175000017500000000070213521303732016371 0ustar alastairalastairinclude .dockerignore include *.rst include *.yml include Dockerfile include LICENSE include Makefile include tox.ini include *.py recursive-include ci *.in recursive-include ci *.txt recursive-include ci *.yml recursive-include ci *.ps1 recursive-include docs *.gitkeep recursive-include docs *.py recursive-include docs *.rst recursive-include gribapi *.h recursive-include tests *.grib recursive-include tests *.ipynb recursive-include tests *.py eccodes-python-0.9.7/Dockerfile0000644000175000017500000000106713521303732016632 0ustar alastairalastair# Run tests in a more reproducible and isolated environment. # # Build the docker image once with: # docker build -t eccodes . # Run the container with: # docker run --rm -it -v `pwd`:/src eccodes-python # FROM bopen/ubuntu-pyenv:latest ARG DEBIAN_FRONTEND="noninteractive" RUN apt-get -y update && apt-get install -y --no-install-recommends \ libeccodes0 \ && rm -rf /var/lib/apt/lists/* COPY . /src/ RUN cd /src \ && make local-install-test-req \ && make local-develop \ && make local-install-dev-req \ && make distclean WORKDIR /src eccodes-python-0.9.7/tests/0000755000175000017500000000000013521303732015776 5ustar alastairalastaireccodes-python-0.9.7/tests/test_20_main.py0000644000175000017500000000043113521303732020632 0ustar alastairalastairimport pytest from eccodes import __main__ def test_main(capsys): __main__.main(argv=['selfcheck']) stdout, _ = capsys.readouterr() assert 'Your system is ready.' in stdout with pytest.raises(RuntimeError): __main__.main(argv=['non-existent-command']) eccodes-python-0.9.7/Makefile0000644000175000017500000000517213521303732016301 0ustar alastairalastair PACKAGE := eccodes-python IMAGE := $(PACKAGE)-image MODULE := eccodes PYTHONS := python3.7 python3.6 python3.5 pypy3 PYTHON := python PYTESTFLAGS_TEST := -v --flakes --doctest-glob '*.rst' --cov=$(MODULE) --cov-report=html --cache-clear PYTESTFLAGS_QC := --pep8 --mccabe $(PYTESTFLAGS_TEST) export WHEELHOUSE := ~/.wheelhouse export PIP_FIND_LINKS := $(WHEELHOUSE) export PIP_WHEEL_DIR := $(WHEELHOUSE) export PIP_INDEX_URL DOCKERBUILDFLAGS := --build-arg PIP_INDEX_URL=$(PIP_INDEX_URL) DOCKERFLAGS := -e WHEELHOUSE=$(WHEELHOUSE) \ -e PIP_FIND_LINKS=$(PIP_FIND_LINKS) \ -e PIP_WHEEL_DIR=$(PIP_WHEEL_DIR) \ -e PIP_INDEX_URL=$(PIP_INDEX_URL) PIP := $(PYTHON) -m pip MKDIR = mkdir -p ifeq ($(shell [ -d $(WHEELHOUSE) ] && echo true),true) DOCKERFLAGS += -v $(WHEELHOUSE):/root/.wheelhouse endif RUNTIME := $(shell [ -f /proc/1/cgroup ] && cat /proc/1/cgroup | grep -q docker && echo docker) ifneq ($(RUNTIME),docker) override TOXFLAGS += --workdir=.docker-tox RUN = docker run --rm -it -v$$(pwd):/src -w/src $(DOCKERFLAGS) $(IMAGE) endif default: @echo No default # local targets $(PIP_FIND_LINKS): $(MKDIR) $@ local-wheelhouse-one: $(PIP) install wheel $(PIP) wheel -r ci/requirements-tests.txt $(PIP) wheel -r ci/requirements-docs.txt local-wheelhouse: for PYTHON in $(PYTHONS); do $(MAKE) local-wheelhouse-one PYTHON=$$PYTHON; done $(PIP) wheel -r ci/requirements-dev.txt local-install-dev-req: $(PIP) install -r ci/requirements-dev.txt local-install-test-req: $(PIP_FIND_LINKS) $(PIP) install -r ci/requirements-tests.txt $(PIP) install -r ci/requirements-docs.txt local-develop: $(PIP) install -e . local-wheel: $(PIP) wheel -e . testclean: $(RM) -r */__pycache__ .coverage .cache tests/.ipynb_checkpoints *.idx tests/sample-data/*.idx out*.grib clean: testclean $(RM) -r */*.pyc htmlcov dist build .eggs distclean: clean $(RM) -r .tox .docker-tox *.egg-info cacheclean: $(RM) -r $(WHEELHOUSE)/* ~/.cache/* # container targets shell: $(RUN) notebook: DOCKERFLAGS += -p 8888:8888 notebook: $(RUN) jupyter notebook --ip=0.0.0.0 --allow-root wheelhouse: $(RUN) make local-wheelhouse update-req: $(RUN) pip-compile -o ci/requirements-tests.txt -U setup.py ci/requirements-tests.in $(RUN) pip-compile -o ci/requirements-docs.txt -U setup.py ci/requirements-docs.in test: testclean $(RUN) $(PYTHON) setup.py test --addopts "$(PYTESTFLAGS_TEST)" qc: testclean $(RUN) $(PYTHON) setup.py test --addopts "$(PYTESTFLAGS_QC)" doc: $(RUN) $(PYTHON) setup.py build_sphinx tox: testclean $(RUN) tox $(TOXFLAGS) detox: testclean $(RUN) detox $(TOXFLAGS) # image build image: docker build -t $(IMAGE) $(DOCKERBUILDFLAGS) . eccodes-python-0.9.7/eccodes/0000755000175000017500000000000013521303732016241 5ustar alastairalastaireccodes-python-0.9.7/eccodes/high_level/0000755000175000017500000000000013521303732020347 5ustar alastairalastaireccodes-python-0.9.7/eccodes/high_level/gribmessage.py0000644000175000017500000000541513521303732023216 0ustar alastairalastair""" ``GribMessage`` class that implements a GRIB message that allows access to the message's key-value pairs in a dictionary-like manner and closes the message when it is no longer needed, coordinating this with its host file. Author: Daniel Lee, DWD, 2014 """ from .codesmessage import CodesMessage from .. import eccodes class IndexNotSelectedError(Exception): """GRIB index was requested before selecting key/value pairs.""" class GribMessage(CodesMessage): __doc__ = "\n".join(CodesMessage.__doc__.splitlines()[4:]).format( prod_type="GRIB", classname="GribMessage", parent="GribFile", alias="grib") product_kind = eccodes.CODES_PRODUCT_GRIB # Arguments included explicitly to support introspection def __init__(self, codes_file=None, clone=None, sample=None, headers_only=False, gribindex=None): """ Open a message and inform the GRIB file that it's been incremented. The message is taken from ``codes_file``, cloned from ``clone`` or ``sample``, or taken from ``index``, in that order of precedence. """ grib_args_present = True if gribindex is None: grib_args_present = False super(self.__class__, self).__init__(codes_file, clone, sample, headers_only, grib_args_present) #: GribIndex referencing message self.grib_index = None if gribindex is not None: self.codes_id = eccodes.codes_new_from_index(gribindex.iid) if not self.codes_id: raise IndexNotSelectedError("All keys must have selected " "values before receiving message " "from index.") self.grib_index = gribindex gribindex.open_messages.append(self) def __exit__(self, exc_type, exc_val, exc_tb): """Release GRIB message handle and inform file of release.""" super(self.__class__, self).__exit__(exc_type, exc_val, exc_tb) if self.grib_index: self.grib_index.open_messages.remove(self) def missing(self, key): """Report if the value of a key is MISSING.""" return bool(eccodes.codes_is_missing(self.codes_id, key)) def set_missing(self, key): """Set the value of key to MISSING.""" eccodes.codes_set_missing(self.codes_id, key) @property def gid(self): """Provided for backwards compatibility.""" return self.codes_id @property def grib_file(self): """Provided for backwards compatibility.""" return self.codes_file @gid.setter def gid(self, val): self.codes_id = val @grib_file.setter def grib_file(self, val): self.codes_file = val eccodes-python-0.9.7/eccodes/high_level/__init__.py0000644000175000017500000000000013521303732022446 0ustar alastairalastaireccodes-python-0.9.7/eccodes/high_level/gribfile.py0000644000175000017500000000063613521303732022511 0ustar alastairalastair""" ``GribFile`` class that implements a GRIB file that closes itself and its messages when it is no longer needed. Author: Daniel Lee, DWD, 2014 """ from .codesfile import CodesFile from .gribmessage import GribMessage class GribFile(CodesFile): __doc__ = "\n".join(CodesFile.__doc__.splitlines()[4:]).format( prod_type="GRIB", classname="GribFile", alias="grib") MessageClass = GribMessage eccodes-python-0.9.7/eccodes/high_level/codesfile.py0000644000175000017500000000446013521303732022662 0ustar alastairalastair""" ``CodesFile`` class that implements a file that is readable by ecCodes and closes itself and its messages when it is no longer needed. Author: Daniel Lee, DWD, 2016 """ from .. import eccodes import io class CodesFile(io.FileIO): """ An abstract class to specify and/or implement common behaviour that files read by ecCodes should implement. A {prod_type} file handle meant for use in a context manager. Individual messages can be accessed using the ``next`` method. Of course, it is also possible to iterate over each message in the file:: >>> with {classname}(filename) as {alias}: ... # Print number of messages in file ... len({alias}) ... # Open all messages in file ... for msg in {alias}: ... print(msg[key_name]) ... len({alias}.open_messages) >>> # When the file is closed, any open messages are closed >>> len({alias}.open_messages) """ #: Type of messages belonging to this file MessageClass = None def __init__(self, filename, mode="rb"): """Open file and receive codes file handle.""" #: File handle for working with actual file on disc #: The class holds the file it works with because ecCodes' # typechecking does not allow using inherited classes. self.file_handle = open(filename, mode) #: Number of message in file currently being read self.message = 0 #: Open messages self.open_messages = [] self.name = filename def __exit__(self, exception_type, exception_value, traceback): """Close all open messages, release file handle and close file.""" while self.open_messages: self.open_messages.pop().close() eccodes.codes_close_file(self.file_handle) #self.file_handle.close() def __len__(self): """Return total number of messages in file.""" return eccodes.codes_count_in_file(self.file_handle) def __enter__(self): return self def close(self): """Possibility to manually close file.""" self.__exit__(None, None, None) def __iter__(self): return self def next(self): try: return self.MessageClass(self) except IOError: raise StopIteration() eccodes-python-0.9.7/eccodes/high_level/bufr.py0000644000175000017500000000637613521303732021673 0ustar alastairalastair""" Classes for handling BUFR with a high level interface. ``BufrFiles`` can be treated mostly as regular files and used as context managers, as can ``BufrMessages``. Each of these classes destructs itself and any child instances appropriately. Author: Daniel Lee, DWD, 2016 """ from .. import eccodes from .codesmessage import CodesMessage from .codesfile import CodesFile class BufrMessage(CodesMessage): __doc__ = "\n".join(CodesMessage.__doc__.splitlines()[4:]).format( prod_type="BUFR", classname="BufrMessage", parent="BufrFile", alias="bufr") product_kind = eccodes.CODES_PRODUCT_BUFR # Arguments included explicitly to support introspection # TODO: Can we get this to work with an index? def __init__(self, codes_file=None, clone=None, sample=None, headers_only=False): """ Open a message and inform the GRIB file that it's been incremented. The message is taken from ``codes_file``, cloned from ``clone`` or ``sample``, or taken from ``index``, in that order of precedence. """ super(self.__class__, self).__init__(codes_file, clone, sample, headers_only) #self._unpacked = False #def get(self, key, ktype=None): # """Return requested value, unpacking data values if necessary.""" # # TODO: Only do this if accessing arrays that need unpacking # if not self._unpacked: # self.unpacked = True # return super(self.__class__, self).get(key, ktype) #def missing(self, key): # """ # Report if key is missing.# # # Overloaded due to confusing behaviour in ``codes_is_missing`` (SUP-1874). # """ # return not bool(eccodes.codes_is_defined(self.codes_id, key)) def unpack(self): """Decode data section""" eccodes.codes_set(self.codes_id, 'unpack', 1) def pack(self): """Encode data section""" eccodes.codes_set(self.codes_id, 'pack', 1) def keys(self, namespace=None): #self.unpack() #return super(self.__class__, self).keys(namespace) iterator = eccodes.codes_bufr_keys_iterator_new(self.codes_id) keys = [] while eccodes.codes_bufr_keys_iterator_next(iterator): key = eccodes.codes_bufr_keys_iterator_get_name(iterator) keys.append(key) eccodes.codes_bufr_keys_iterator_delete(iterator) return keys #@property #def unpacked(self): # return self._unpacked #@unpacked.setter #def unpacked(self, val): # eccodes.codes_set(self.codes_id, "unpack", val) # self._unpacked = val #def __setitem__(self, key, value): # """Set item and pack BUFR.""" # if not self._unpacked: # self.unpacked = True # super(self.__class__, self).__setitem__(key, value) # eccodes.codes_set(self.codes_id, "pack", True) def copy_data(self, destMsg): """Copy data values from this message to another message""" return eccodes.codes_bufr_copy_data(self.codes_id, destMsg.codes_id) class BufrFile(CodesFile): __doc__ = "\n".join(CodesFile.__doc__.splitlines()[4:]).format( prod_type="BUFR", classname="BufrFile", alias="bufr") MessageClass = BufrMessage eccodes-python-0.9.7/eccodes/high_level/gribindex.py0000644000175000017500000000714413521303732022702 0ustar alastairalastair""" ``GribIndex`` class that implements a GRIB index that allows access to ecCodes's index functionality. Author: Daniel Lee, DWD, 2014 """ from .. import eccodes from .gribmessage import GribMessage class GribIndex(object): """ A GRIB index meant for use in a context manager. Usage:: >>> # Create index from file with keys >>> with GribIndex(filename, keys) as idx: ... # Write index to file ... idx.write(index_file) >>> # Read index from file >>> with GribIndex(file_index=index_file) as idx: ... # Add new file to index ... idx.add(other_filename) ... # Report number of unique values for given key ... idx.size(key) ... # Report unique values indexed by key ... idx.values(key) ... # Request GribMessage matching key, value ... msg = idx.select({key: value}) """ def __enter__(self): return self def __exit__(self, exception_type, exception_value, traceback): """Release GRIB message handle and inform file of release.""" while self.open_messages: self.open_messages[0].close() eccodes.codes_index_release(self.iid) def close(self): """Possibility to manually close index.""" self.__exit__(None, None, None) def __init__(self, filename=None, keys=None, file_index=None, grib_index=None): """ Create new GRIB index over ``keys`` from ``filename``. ``filename`` should be a string of the desired file's filename. ``keys`` should be a sequence of keys to index. ``file_index`` should be a string of the file that the index should be loaded from. ``grib_index`` should be another ``GribIndex``. If ``filename`` and ``keys`` are provided, the ``GribIndex`` is initialized over the given keys from the given file. If they are not provided, the ``GribIndex`` is read from ``indexfile``. If ``grib_index`` is provided, it is cloned from the given ``GribIndex``. """ #: Grib index ID self.iid = None if filename and keys: self.iid = eccodes.codes_index_new_from_file(filename, keys) elif file_index: self.iid = eccodes.codes_index_read(file_index) elif grib_index: self.iid = eccodes.codes_new_from_index(grib_index.iid) else: raise RuntimeError("No source was supplied " "(possibilities: grib_file, clone, sample).") #: Indexed keys. Only available if GRIB is initialized from file. self.keys = keys #: Open GRIB messages self.open_messages = [] def size(self, key): """Return number of distinct values for index key.""" return eccodes.codes_index_get_size(self.iid, key) def values(self, key, ktype=str): """Return distinct values of index key.""" return eccodes.codes_index_get(self.iid, key, ktype) def add(self, filename): """Add ``filename`` to the ``GribIndex``.""" eccodes.codes_index_add_file(self.iid, filename) def write(self, outfile): """Write index to filename at ``outfile``.""" eccodes.codes_index_write(self.iid, outfile) def select(self, key_value_pairs): """ Return message associated with given key value pairs. ``key_value_pairs`` should be passed as a dictionary. """ for key in key_value_pairs: eccodes.codes_index_select(self.iid, key, key_value_pairs[key]) return GribMessage(gribindex=self) eccodes-python-0.9.7/eccodes/high_level/codesmessage.py0000644000175000017500000001541713521303732023373 0ustar alastairalastair""" ``CodesMessage`` class that implements a message readable by ecCodes that allows access to the message's key-value pairs in a dictionary-like manner and closes the message when it is no longer needed, coordinating this with its host file. Author: Daniel Lee, DWD, 2016 """ from .. import eccodes class CodesMessage(object): """ An abstract class to specify and/or implement common behaviour that messages read by ecCodes should implement. A {prod_type} message. Each ``{classname}`` is stored as a key/value pair in a dictionary-like structure. It can be used in a context manager or by itself. When the ``{parent}`` it belongs to is closed, the ``{parent}`` closes any open ``{classname}``s that belong to it. If a ``{classname}`` is closed before its ``{parent}`` is closed, it informs the ``{parent}`` of its closure. Scalar and vector values are set appropriately through the same method. ``{classname}``s can be instantiated from a ``{parent}``, cloned from other ``{classname}``s or taken from samples. Iterating over the members of a ``{parent}`` extracts the ``{classname}``s it contains until the ``{parent}`` is exhausted. Usage:: >>> with {parent}(filename) as {alias}: ... # Access a key from each message ... for msg in {alias}: ... print(msg[key_name]) ... # Report number of keys in message ... len(msg) ... # Report message size in bytes ... msg.size ... # Report keys in message ... msg.keys() ... # Set scalar value ... msg[scalar_key] = 5 ... # Check key's value ... msg[scalar_key] ... msg[key_name] ... # Array values are set transparently ... msg[array_key] = [1, 2, 3] ... # Messages can be written to file ... with open(testfile, "w") as test: ... msg.write(test) ... # Messages can be cloned from other messages ... msg2 = {classname}(clone=msg) ... # If desired, messages can be closed manually or used in with ... msg.close() """ #: ecCodes enum-like PRODUCT constant product_kind = None def __init__(self, codes_file=None, clone=None, sample=None, headers_only=False, other_args_found=False): """ Open a message and inform the host file that it's been incremented. If ``codes_file`` is not supplied, the message is cloned from ``CodesMessage`` ``clone``. If neither is supplied, the ``CodesMessage`` is cloned from ``sample``. :param codes_file: A file readable for ecCodes :param clone: A valid ``CodesMessage`` :param sample: A valid sample path to create ``CodesMessage`` from """ if not other_args_found and codes_file is None and clone is None and sample is None: raise RuntimeError("CodesMessage initialization parameters not " "present.") #: Unique ID, for ecCodes interface self.codes_id = None #: File containing message self.codes_file = None if codes_file is not None: self.codes_id = eccodes.codes_new_from_file( codes_file.file_handle, self.product_kind, headers_only) if self.codes_id is None: raise IOError("CodesFile %s is exhausted" % codes_file.name) self.codes_file = codes_file self.codes_file.message += 1 self.codes_file.open_messages.append(self) elif clone is not None: self.codes_id = eccodes.codes_clone(clone.codes_id) elif sample is not None: self.codes_id = eccodes.codes_new_from_samples( sample, self.product_kind) def write(self, outfile=None): """Write message to file.""" if not outfile: # This is a hack because the API does not accept inheritance outfile = self.codes_file.file_handle eccodes.codes_write(self.codes_id, outfile) def __setitem__(self, key, value): """ Set value associated with key. Iterables and scalars are handled intelligently. """ if isinstance(key, str): if hasattr(value, "__iter__") and not isinstance(value, str): eccodes.codes_set_array(self.codes_id, key, value) else: eccodes.codes_set(self.codes_id, key, value) else: if len(key) != len(value): raise ValueError('Key array must have same size as value array') eccodes.codes_set_key_vals(self.codes_id,",".join([str(key[i])+"="+str(value[i]) for i in range(len(key))])) def keys(self, namespace=None): """Get available keys in message.""" iterator = eccodes.codes_keys_iterator_new(self.codes_id, namespace=namespace) keys = [] while eccodes.codes_keys_iterator_next(iterator): key = eccodes.codes_keys_iterator_get_name(iterator) keys.append(key) eccodes.codes_keys_iterator_delete(iterator) return keys def size(self): """Return size of message in bytes.""" return eccodes.codes_get_message_size(self.codes_id) def dump(self): """Dump message's binary content.""" return eccodes.codes_get_message(self.codes_id) def get(self, key, ktype=None): """Get value of a given key as its native or specified type.""" # if self.missing(key): # raise KeyError("Value of key %s is MISSING." % key) if eccodes.codes_get_size(self.codes_id, key) > 1: ret = eccodes.codes_get_array(self.codes_id, key, ktype) else: ret = eccodes.codes_get(self.codes_id, key, ktype) return ret def __exit__(self, exc_type, exc_val, exc_tb): """Release message handle and inform host file of release.""" eccodes.codes_release(self.codes_id) def __enter__(self): return self def close(self): """Possibility to manually close message.""" self.__exit__(None, None, None) def __contains__(self, key): """Check whether a key is present in message.""" return key in self.keys() def __len__(self): """Return key count.""" return len(self.keys()) def __getitem__(self, key): """Return value associated with key as its native type.""" return self.get(key) def __iter__(self): return iter(self.keys()) # Not yet implemented # def itervalues(self): # return self.values() def items(self): """Return list of tuples of all key/value pairs.""" return [(key, self[key]) for key in self.keys()] eccodes-python-0.9.7/eccodes/eccodes.py0000644000175000017500000001440213521303732020221 0ustar alastairalastairfrom gribapi import __version__ from gribapi import bindings_version from gribapi import GRIB_CHECK as CODES_CHECK from gribapi import CODES_PRODUCT_GRIB from gribapi import CODES_PRODUCT_BUFR from gribapi import CODES_PRODUCT_ANY from gribapi import GRIB_MISSING_DOUBLE as CODES_MISSING_DOUBLE from gribapi import GRIB_MISSING_LONG as CODES_MISSING_LONG from gribapi import gts_new_from_file as codes_gts_new_from_file from gribapi import metar_new_from_file as codes_metar_new_from_file from gribapi import codes_new_from_file from gribapi import any_new_from_file as codes_any_new_from_file from gribapi import bufr_new_from_file as codes_bufr_new_from_file from gribapi import grib_new_from_file as codes_grib_new_from_file from gribapi import codes_close_file from gribapi import grib_count_in_file as codes_count_in_file from gribapi import grib_multi_support_on as codes_grib_multi_support_on from gribapi import grib_multi_support_off as codes_grib_multi_support_off from gribapi import grib_release as codes_release from gribapi import grib_get_string as codes_get_string from gribapi import grib_set_string as codes_set_string from gribapi import grib_gribex_mode_on as codes_gribex_mode_on from gribapi import grib_gribex_mode_off as codes_gribex_mode_off from gribapi import grib_write as codes_write from gribapi import grib_multi_write as codes_grib_multi_write from gribapi import grib_multi_append as codes_grib_multi_append from gribapi import grib_get_size as codes_get_size from gribapi import grib_get_string_length as codes_get_string_length from gribapi import grib_skip_computed as codes_skip_computed from gribapi import grib_skip_coded as codes_skip_coded from gribapi import grib_skip_edition_specific as codes_skip_edition_specific from gribapi import grib_skip_duplicates as codes_skip_duplicates from gribapi import grib_skip_read_only as codes_skip_read_only from gribapi import grib_skip_function as codes_skip_function from gribapi import grib_iterator_new as codes_grib_iterator_new from gribapi import grib_iterator_delete as codes_grib_iterator_delete from gribapi import grib_iterator_next as codes_grib_iterator_next from gribapi import grib_keys_iterator_new as codes_keys_iterator_new from gribapi import grib_keys_iterator_next as codes_keys_iterator_next from gribapi import grib_keys_iterator_delete as codes_keys_iterator_delete from gribapi import grib_keys_iterator_get_name as codes_keys_iterator_get_name from gribapi import grib_keys_iterator_rewind as codes_keys_iterator_rewind from gribapi import codes_bufr_keys_iterator_new from gribapi import codes_bufr_keys_iterator_next from gribapi import codes_bufr_keys_iterator_delete from gribapi import codes_bufr_keys_iterator_get_name from gribapi import codes_bufr_keys_iterator_rewind from gribapi import grib_get_long as codes_get_long from gribapi import grib_get_double as codes_get_double from gribapi import grib_set_long as codes_set_long from gribapi import grib_set_double as codes_set_double from gribapi import grib_new_from_samples as codes_grib_new_from_samples from gribapi import codes_bufr_new_from_samples from gribapi import codes_new_from_samples from gribapi import codes_bufr_copy_data from gribapi import grib_clone as codes_clone from gribapi import grib_set_double_array as codes_set_double_array from gribapi import grib_get_double_array as codes_get_double_array from gribapi import grib_get_string_array as codes_get_string_array from gribapi import grib_set_string_array as codes_set_string_array from gribapi import grib_set_long_array as codes_set_long_array from gribapi import grib_get_long_array as codes_get_long_array from gribapi import grib_multi_new as codes_grib_multi_new from gribapi import grib_multi_release as codes_grib_multi_release from gribapi import grib_copy_namespace as codes_copy_namespace from gribapi import grib_index_new_from_file as codes_index_new_from_file from gribapi import grib_index_add_file as codes_index_add_file from gribapi import grib_index_release as codes_index_release from gribapi import grib_index_get_size as codes_index_get_size from gribapi import grib_index_get_long as codes_index_get_long from gribapi import grib_index_get_string as codes_index_get_string from gribapi import grib_index_get_double as codes_index_get_double from gribapi import grib_index_select_long as codes_index_select_long from gribapi import grib_index_select_double as codes_index_select_double from gribapi import grib_index_select_string as codes_index_select_string from gribapi import grib_new_from_index as codes_new_from_index from gribapi import grib_get_message_size as codes_get_message_size from gribapi import grib_get_message_offset as codes_get_message_offset from gribapi import grib_get_double_element as codes_get_double_element from gribapi import grib_get_double_elements as codes_get_double_elements from gribapi import grib_get_elements as codes_get_elements from gribapi import grib_set_missing as codes_set_missing from gribapi import grib_set_key_vals as codes_set_key_vals from gribapi import grib_is_missing as codes_is_missing from gribapi import grib_is_defined as codes_is_defined from gribapi import grib_find_nearest as codes_grib_find_nearest from gribapi import grib_get_native_type as codes_get_native_type from gribapi import grib_get as codes_get from gribapi import grib_get_array as codes_get_array from gribapi import grib_get_values as codes_get_values from gribapi import grib_set_values as codes_set_values from gribapi import grib_set as codes_set from gribapi import grib_set_array as codes_set_array from gribapi import grib_index_get as codes_index_get from gribapi import grib_index_select as codes_index_select from gribapi import grib_index_write as codes_index_write from gribapi import grib_index_read as codes_index_read from gribapi import grib_no_fail_on_wrong_length as codes_no_fail_on_wrong_length from gribapi import grib_gts_header as codes_gts_header from gribapi import grib_get_api_version as codes_get_api_version from gribapi import grib_get_message as codes_get_message from gribapi import grib_new_from_message as codes_new_from_message from gribapi import grib_set_definitions_path as codes_set_definitions_path from gribapi import grib_set_samples_path as codes_set_samples_path from gribapi.errors import GribInternalError as CodesInternalError from gribapi.errors import * eccodes-python-0.9.7/eccodes/__init__.py0000644000175000017500000000057313521303732020357 0ustar alastairalastairfrom __future__ import absolute_import import sys from .eccodes import * from .eccodes import __version__ from .eccodes import bindings_version if sys.version_info >= (2, 6): from .high_level.gribfile import GribFile from .high_level.gribmessage import GribMessage from .high_level.gribindex import GribIndex from .high_level.bufr import BufrFile, BufrMessage eccodes-python-0.9.7/eccodes/__main__.py0000644000175000017500000000230113521303732020327 0ustar alastairalastair# # Copyright 2017-2019 European Centre for Medium-Range Weather Forecasts (ECMWF). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Authors: # Alessandro Amici - B-Open - https://bopen.eu # import argparse from . import codes_get_api_version def selfcheck(): print("Found: ecCodes v%s." % codes_get_api_version()) print("Your system is ready.") def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument('command') args = parser.parse_args(args=argv) if args.command == 'selfcheck': selfcheck() else: raise RuntimeError("Command not recognised %r. See usage with --help." % args.command) if __name__ == '__main__': # pragma: no cover main() eccodes-python-0.9.7/setup.cfg0000644000175000017500000000117513521303732016461 0ustar alastairalastair[bdist_wheel] universal = 1 [aliases] test = pytest [tool:pytest] norecursedirs = build dist .tox .docker-tox .eggs pep8maxlinelength = 99 mccabe-complexity = 11 filterwarnings = ignore::FutureWarning pep8ignore = * E203 W503 */__init__.py E402 eccodes/eccodes.py ALL eccodes/high_level/* ALL gribapi/errors.py ALL gribapi/gribapi.py E501 flakes-ignore = */__init__.py UnusedImport */__init__.py ImportStarUsed eccodes/eccodes.py ALL eccodes/high_level/* ALL gribapi/errors.py ALL [coverage:run] branch = True [zest.releaser] python-file-with-version = gribapi/bindings.py [egg_info] tag_build = tag_date = 0 eccodes-python-0.9.7/CONTRIBUTING.rst0000644000175000017500000001050613521303732017277 0ustar alastairalastair .. highlight: console ============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. Please note, that we have hooked a CLA assiatant to this GitHub Repo. Please accept the contributors license agreement to allow us to keep a legal track of contributions and keep this package open source for the future. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://github.com/ecmwf/eccodes-python/issues If you are reporting a bug, please include: * Your operating system name and version. * Installation method and version of all dependencies. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug, including a sample file. Fix Bugs ~~~~~~~~ Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement a fix for it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. Get Started! ------------ Ready to contribute? Here's how to set up `eccodes-python` for local development. Please note this documentation assumes you already have `virtualenv` and `Git` installed and ready to go. 1. Fork the `eccodes-python` repo on GitHub. 2. Clone your fork locally:: $ cd path_for_the_repo $ git clone https://github.com/YOUR_NAME/eccodes-python.git $ cd eccodes-python 3. Assuming you have virtualenv installed (If you have Python3.5 this should already be there), you can create a new environment for your local development by typing:: $ virtualenv ../eccodes-python-env $ source ../eccodes-python-env/bin/activate This should change the shell to look something like (eccodes-python-env) $ 4. Install system dependencies as described in the README.rst file then install a known-good set of python dependencies and the your local copy with:: $ pip install -r ci/requirements-tests.txt $ pip install -e . 5. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 6. The next step would be to run the test cases. `eccodes-python` uses py.test, you can run PyTest. Before you run pytest you should ensure all dependancies are installed:: $ pip install -r ci/requirements-dev.txt $ pytest -v --flakes 7. Before raising a pull request you should also run tox. This will run the tests across different versions of Python:: $ tox 8. If your contribution is a bug fix or new feature, you should add a test to the existing test suite. 9. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature 10. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The pull request should work for Python 2.7, 3.5, 3.6 and 3.7, and for PyPy2 and Pypy3. Check the tox results and make sure that the tests pass for all supported Python versions. Testing CDS data ---------------- You can test the CF-GRIB driver on a set of products downloaded from the Climate Data Store of the `Copernicus Climate Change Service `_. If you are not register to the CDS portal register at: https://cds.climate.copernicus.eu/user/register In order to automatically download and test the GRIB files install and configure the `cdsapi` package:: $ pip install cdsapi $ pip install netcdf4 The log into the CDS portal and setup the CDS API key as described in: https://cds.climate.copernicus.eu/api-how-to Then you can run:: $ pytest -vv tests/cds_test_*.py .. eccodes-python: https://github.com/ecmwf/eccodes-python .. virtualenv: https://virtualenv.pypa.io/en/stable/installation .. git: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git eccodes-python-0.9.7/appveyor.yml0000644000175000017500000000162313521303732017226 0ustar alastairalastair# CI on Windows via appveyor # This file was based on Olivier Grisel's python-appveyor-demo branches: except: - fix-docs environment: matrix: - PYTHON: "C:\\Python36-conda64" PYTHON_VERSION: "3.6" PYTHON_ARCH: "64" CONDA_ENV: "py36-qc" - PYTHON: "C:\\Python37-conda64" PYTHON_VERSION: "3.7" PYTHON_ARCH: "64" CONDA_ENV: "py37" install: # Install miniconda Python - "powershell ./ci/install_python.ps1" # Prepend newly installed Python to the PATH of this build (this cannot be # done from inside the powershell script as it would require to restart # the parent CMD process). - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" # install xarray and dependencies - "conda env create --file ./ci/requirements-%CONDA_ENV%.yml" - "activate test_env" - "conda list" - "python setup.py install" build: false test_script: - "py.test --verbose" eccodes-python-0.9.7/docs/0000755000175000017500000000000013521303732015564 5ustar alastairalastaireccodes-python-0.9.7/docs/conf.py0000644000175000017500000001246113521303732017067 0ustar alastairalastair#!/usr/bin/env python # -*- coding: utf-8 -*- import pkg_resources import sys import os # Get the project root dir, which is the parent dir of this cwd = os.getcwd() project_root = os.path.dirname(cwd) # Insert the project root dir as the first element in the PYTHONPATH. # This lets us ensure that the source package is imported, and that its # version is used. sys.path.insert(0, project_root) # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'eccodes-python' copyright = u"2017-2019, European Centre for Medium-Range Weather Forecasts (ECMWF)." # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout # the built documents. # # The full version, including alpha/beta/rc tags. release = pkg_resources.get_distribution("eccodes-python").version # The short X.Y version. version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to # some non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built # documents. # keep_warnings = False # -- Options for HTML output ------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a # theme further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as # html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the # top of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon # of the docs. This file should be a Windows icon file (.ico) being # 16x16 or 32x32 pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) # here, relative to this directory. They are copied after the builtin # static files, so a file named "default.css" will overwrite the builtin # "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names # to template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. # Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. # Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages # will contain a tag referring to it. The value of this option # must be the base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'cfgribdoc' eccodes-python-0.9.7/docs/index.rst0000644000175000017500000000151113521303732017423 0ustar alastairalastair ======= CF-GRIB ======= :Version: |release| :Date: |today| Python 3 interface to encode and decode GRIB and BUFR files via the `ECMWF ecCodes library `_. Features: - reads and writes GRIB 1 and 2 files, - reads and writes BUFR 3 and 4 files, - supports all modern versions of Python 3.7, 3.6, 3.5 and PyPy3, - works on most *Linux* distributions and *MacOS*, the *ecCodes* C-library is the only system dependency, - PyPI package can be installed without compiling, at the cost of being twice as slow as the original *ecCodes* module, - an optional compile step makes the code as fast as the original module but it needs a recent version of *ecCodes* `>= 2.13.0`. Limitations: - Microsoft Windows support is untested. .. toctree:: :maxdepth: 2 :caption: Table of Contents eccodes-python-0.9.7/docs/_static/0000755000175000017500000000000013521303732017212 5ustar alastairalastaireccodes-python-0.9.7/docs/_static/.gitkeep0000644000175000017500000000000013521303732020631 0ustar alastairalastaireccodes-python-0.9.7/builder.py0000644000175000017500000000067513521303732016644 0ustar alastairalastairimport logging import cffi import sys ffibuilder = cffi.FFI() ffibuilder.set_source( "gribapi._bindings", '#include ', libraries=["eccodes"], ) ffibuilder.cdef( open("gribapi/grib_api.h").read() + open("gribapi/eccodes.h").read() ) if __name__ == "__main__": try: ffibuilder.compile(verbose=True) except Exception: logging.exception("can't compile ecCodes bindings") sys.exit(1) eccodes-python-0.9.7/gribapi/0000755000175000017500000000000013521303732016251 5ustar alastairalastaireccodes-python-0.9.7/gribapi/errors.py0000644000175000017500000001767313521303732020155 0ustar alastairalastair""" Exception class hierarchy """ from .bindings import ENC, ffi, lib class GribInternalError(Exception): """ @brief Wrap errors coming from the C API in a Python exception object. Base class for all exceptions """ def __init__(self, value): # Call the base class constructor with the parameters it needs Exception.__init__(self, value) if type(value) is int: self.msg = ffi.string(lib.grib_get_error_message(value)).decode(ENC) else: self.msg = value def __str__(self): return self.msg class WrongBitmapSizeError(GribInternalError): """Size of bitmap is incorrect.""" class OutOfRangeError(GribInternalError): """Value out of coding range.""" class UnsupportedEditionError(GribInternalError): """Edition not supported..""" class AttributeNotFoundError(GribInternalError): """Attribute not found..""" class TooManyAttributesError(GribInternalError): """Too many attributes. Increase MAX_ACCESSOR_ATTRIBUTES.""" class AttributeClashError(GribInternalError): """Attribute is already present, cannot add.""" class NullPointerError(GribInternalError): """Null pointer.""" class MissingBufrEntryError(GribInternalError): """Missing BUFR table entry for descriptor.""" class WrongConversionError(GribInternalError): """Wrong type conversion.""" class StringTooSmallError(GribInternalError): """String is smaller than requested.""" class InvalidKeyValueError(GribInternalError): """Invalid key value.""" class ValueDifferentError(GribInternalError): """Value is different.""" class DifferentEditionError(GribInternalError): """Edition of two messages is different.""" class InvalidBitsPerValueError(GribInternalError): """Invalid number of bits per value.""" class CorruptedIndexError(GribInternalError): """Index is corrupted.""" class MessageMalformedError(GribInternalError): """Message malformed.""" class UnderflowError(GribInternalError): """Underflow.""" class SwitchNoMatchError(GribInternalError): """Switch unable to find a matching case.""" class ConstantFieldError(GribInternalError): """Constant field.""" class MessageTooLargeError(GribInternalError): """Message is too large for the current architecture.""" class InternalArrayTooSmallError(GribInternalError): """An internal array is too small.""" class PrematureEndOfFileError(GribInternalError): """End of resource reached when reading message.""" class NullIndexError(GribInternalError): """Null index.""" class EndOfIndexError(GribInternalError): """End of index reached.""" class WrongGridError(GribInternalError): """Grid description is wrong or inconsistent.""" class NoValuesError(GribInternalError): """Unable to code a field without values.""" class EndError(GribInternalError): """End of resource.""" class WrongTypeError(GribInternalError): """Wrong type while packing.""" class NoDefinitionsError(GribInternalError): """Definitions files not found.""" class HashArrayNoMatchError(GribInternalError): """Hash array no match.""" class ConceptNoMatchError(GribInternalError): """Concept no match.""" class OutOfAreaError(GribInternalError): """The point is out of the grid area.""" class MissingKeyError(GribInternalError): """Missing a key from the fieldset.""" class InvalidOrderByError(GribInternalError): """Invalid order by.""" class InvalidNearestError(GribInternalError): """Invalid nearest id.""" class InvalidKeysIteratorError(GribInternalError): """Invalid keys iterator id.""" class InvalidIteratorError(GribInternalError): """Invalid iterator id.""" class InvalidIndexError(GribInternalError): """Invalid index id.""" class InvalidGribError(GribInternalError): """Invalid grib id.""" class InvalidFileError(GribInternalError): """Invalid file id.""" class WrongStepUnitError(GribInternalError): """Wrong units for step (step must be integer).""" class WrongStepError(GribInternalError): """Unable to set step.""" class InvalidTypeError(GribInternalError): """Invalid key type.""" class WrongLengthError(GribInternalError): """Wrong message length.""" class ValueCannotBeMissingError(GribInternalError): """Value cannot be missing.""" class InvalidSectionNumberError(GribInternalError): """Invalid section number.""" class NullHandleError(GribInternalError): """Null handle.""" class InvalidArgumentError(GribInternalError): """Invalid argument.""" class ReadOnlyError(GribInternalError): """Value is read only.""" class MemoryAllocationError(GribInternalError): """Memory allocation error.""" class GeocalculusError(GribInternalError): """Problem with calculation of geographic attributes.""" class NoMoreInSetError(GribInternalError): """Code cannot unpack because of string too small.""" class EncodingError(GribInternalError): """Encoding invalid.""" class DecodingError(GribInternalError): """Decoding invalid.""" class MessageInvalidError(GribInternalError): """Message invalid.""" class IOProblemError(GribInternalError): """Input output problem.""" class KeyValueNotFoundError(GribInternalError): """Key/value not found.""" class WrongArraySizeError(GribInternalError): """Array size mismatch.""" class CodeNotFoundInTableError(GribInternalError): """Code not found in code table.""" class FileNotFoundError(GribInternalError): """File not found.""" class ArrayTooSmallError(GribInternalError): """Passed array is too small.""" class MessageEndNotFoundError(GribInternalError): """Missing 7777 at end of message.""" class FunctionNotImplementedError(GribInternalError): """Function not yet implemented.""" class BufferTooSmallError(GribInternalError): """Passed buffer is too small.""" class InternalError(GribInternalError): """Internal error.""" class EndOfFileError(GribInternalError): """End of resource reached.""" ERROR_MAP = { -66 : WrongBitmapSizeError, -65 : OutOfRangeError, -64 : UnsupportedEditionError, -63 : AttributeNotFoundError, -62 : TooManyAttributesError, -61 : AttributeClashError, -60 : NullPointerError, -59 : MissingBufrEntryError, -58 : WrongConversionError, -57 : StringTooSmallError, -56 : InvalidKeyValueError, -55 : ValueDifferentError, -54 : DifferentEditionError, -53 : InvalidBitsPerValueError, -52 : CorruptedIndexError, -51 : MessageMalformedError, -50 : UnderflowError, -49 : SwitchNoMatchError, -48 : ConstantFieldError, -47 : MessageTooLargeError, -46 : InternalArrayTooSmallError, -45 : PrematureEndOfFileError, -44 : NullIndexError, -43 : EndOfIndexError, -42 : WrongGridError, -41 : NoValuesError, -40 : EndError, -39 : WrongTypeError, -38 : NoDefinitionsError, -37 : HashArrayNoMatchError, -36 : ConceptNoMatchError, -35 : OutOfAreaError, -34 : MissingKeyError, -33 : InvalidOrderByError, -32 : InvalidNearestError, -31 : InvalidKeysIteratorError, -30 : InvalidIteratorError, -29 : InvalidIndexError, -28 : InvalidGribError, -27 : InvalidFileError, -26 : WrongStepUnitError, -25 : WrongStepError, -24 : InvalidTypeError, -23 : WrongLengthError, -22 : ValueCannotBeMissingError, -21 : InvalidSectionNumberError, -20 : NullHandleError, -19 : InvalidArgumentError, -18 : ReadOnlyError, -17 : MemoryAllocationError, -16 : GeocalculusError, -15 : NoMoreInSetError, -14 : EncodingError, -13 : DecodingError, -12 : MessageInvalidError, -11 : IOProblemError, -10 : KeyValueNotFoundError, -9 : WrongArraySizeError, -8 : CodeNotFoundInTableError, -7 : FileNotFoundError, -6 : ArrayTooSmallError, -5 : MessageEndNotFoundError, -4 : FunctionNotImplementedError, -3 : BufferTooSmallError, -2 : InternalError, -1 : EndOfFileError } def raise_grib_error(errid): """ Raise the GribInternalError corresponding to ``errid``. """ raise ERROR_MAP[errid](errid) eccodes-python-0.9.7/gribapi/bindings.py0000644000175000017500000000323613521303732020424 0ustar alastairalastair# # Copyright 2017-2019 European Centre for Medium-Range Weather Forecasts (ECMWF). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Authors: # Alessandro Amici - B-Open - https://bopen.eu # from __future__ import absolute_import, division, print_function, unicode_literals import logging import pkgutil import os import cffi __version__ = '0.9.2' LOG = logging.getLogger(__name__) try: from ._bindings import ffi, lib except ModuleNotFoundError: ffi = cffi.FFI() ffi.cdef( pkgutil.get_data(__name__, 'grib_api.h').decode('utf-8') + pkgutil.get_data(__name__, 'eccodes.h').decode('utf-8') ) LIBNAMES = ['eccodes', 'libeccodes.so', 'libeccodes'] if os.environ.get('ECCODES_DIR'): LIBNAMES.insert(0, os.path.join(os.environ['ECCODES_DIR'], 'lib/libeccodes.so')) for libname in LIBNAMES: try: lib = ffi.dlopen(libname) LOG.info("ecCodes library found using name '%s'.", libname) break except OSError: # lazy exception lib = None LOG.info("ecCodes library not found using name '%s'.", libname) # default encoding for ecCodes strings ENC = 'ascii' eccodes-python-0.9.7/gribapi/grib_api.h0000644000175000017500000010700013521303732020174 0ustar alastairalastair/* * Copyright 2005-2018 ECMWF. * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction. */ /*! \file grib_api.h \brief grib_api C header file */ typedef enum ProductKind {PRODUCT_ANY, PRODUCT_GRIB, PRODUCT_BUFR, PRODUCT_METAR, PRODUCT_GTS, PRODUCT_TAF} ProductKind; /* Types */ /* undefined */ #define GRIB_TYPE_UNDEFINED 0 /* long integer */ #define GRIB_TYPE_LONG 1 /* double */ #define GRIB_TYPE_DOUBLE 2 /* char* */ #define GRIB_TYPE_STRING 3 /* bytes */ #define GRIB_TYPE_BYTES 4 /* section */ #define GRIB_TYPE_SECTION 5 /* label */ #define GRIB_TYPE_LABEL 6 /* missing */ #define GRIB_TYPE_MISSING 7 /*! read only keys are skipped by keys iterator. \ingroup keys_iterator \see grib_keys_iterator_new */ #define GRIB_KEYS_ITERATOR_SKIP_READ_ONLY 1 /*! edition specific keys are skipped by keys iterator. \ingroup keys_iterator \see grib_keys_iterator_new */ #define GRIB_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC 4 /*! coded keys are skipped by keys iterator. \ingroup keys_iterator \see grib_keys_iterator_new */ #define GRIB_KEYS_ITERATOR_SKIP_CODED 8 /*! computed keys are skipped by keys iterator. \ingroup keys_iterator \see grib_keys_iterator_new */ #define GRIB_KEYS_ITERATOR_SKIP_COMPUTED 16 /*! duplicates of a key are skipped by keys iterator. \ingroup keys_iterator \see grib_keys_iterator_new */ #define GRIB_KEYS_ITERATOR_SKIP_DUPLICATES 32 /*! function keys are skipped by keys iterator. \ingroup keys_iterator \see grib_keys_iterator_new */ #define GRIB_KEYS_ITERATOR_SKIP_FUNCTION 64 typedef struct grib_values grib_values; struct grib_values { const char* name; int type; long long_value; double double_value; const char* string_value; int error; int has_value; int equal; grib_values* next; } ; /*! Grib handle, structure giving access to parsed message values by keys \ingroup grib_handle */ typedef struct grib_handle grib_handle; /*! Grib multi field handle, structure used to build multi field GRIB messages. \ingroup grib_handle */ typedef struct grib_multi_handle grib_multi_handle; /*! Grib context, structure containing the memory methods, the parsers and the formats. \ingroup grib_context */ typedef struct grib_context grib_context; /*! Grib iterator, structure supporting a geographic iteration of values on a GRIB message. \ingroup grib_iterator */ typedef struct grib_iterator grib_iterator; typedef struct grib_nearest grib_nearest; /*! Grib keys iterator. Iterator over keys. \ingroup keys_iterator */ typedef struct grib_keys_iterator grib_keys_iterator; typedef struct bufr_keys_iterator bufr_keys_iterator; /*! \defgroup grib_index The grib_index The grib_index is the structure giving indexed access to messages in a file. */ /*! @{*/ /*! index structure to access messages in a file. */ typedef struct grib_index grib_index; /** * Create a new index form a file. The file is indexed with the keys in argument. * * @param c : context (NULL for default context) * @param filename : name of the file of messages to be indexed * @param keys : comma separated list of keys for the index. * The type of the key can be explicitly declared appending :l for long, * (or alternatively :i) * :d for double, :s for string to the key name. If the type is not * declared explicitly, the native type is assumed. * @param err : 0 if OK, integer value on error * @return the newly created index */ grib_index* grib_index_new_from_file(grib_context* c, char* filename,const char* keys,int *err); /** * Indexes the file given in argument in the index given in argument. * * @param index : index * @param filename : name of the file of messages to be indexed * @return 0 if OK, integer value on error */ int grib_index_add_file(grib_index *index, const char *filename); int grib_index_write(grib_index *index, const char *filename); grib_index* grib_index_read(grib_context* c,const char* filename,int *err); /** * Get the number of distinct values of the key in argument contained in the index. The key must belong to the index. * * @param index : an index created from a file. * The index must have been created with the key in argument. * @param key : key for which the number of values is computed * @param size : number of distinct values of the key in the index * @return 0 if OK, integer value on error */ int grib_index_get_size(grib_index* index,const char* key,size_t* size); /** * Get the distinct values of the key in argument contained in the index. The key must belong to the index. This function is used when the type of the key was explicitly defined as long or when the native type of the key is long. * * @param index : an index created from a file. * The index must have been created with the key in argument. * @param key : key for which the values are returned * @param values : array of values. The array must be allocated before entering this function and its size must be enough to contain all the values. * @param size : size of the values array * @return 0 if OK, integer value on error */ int grib_index_get_long(grib_index* index,const char* key, long* values,size_t *size); /** * Get the distinct values of the key in argument contained in the index. The key must belong to the index. This function is used when the type of the key was explicitly defined as string or when the native type of the key is string. * * @param index : an index created from a file. * The index must have been created with the key in argument. * @param key : key for which the values are returned * @param values : array of values. The array must be allocated before entering this function and its size must be enough to contain all the values. * @param size : size of the values array * @return 0 if OK, integer value on error */ int grib_index_get_string(grib_index* index,const char* key, char** values,size_t *size); /** * Select the message subset with key==value. The value is a long. The key must have been created with long type or have long as native type if the type was not explicitly defined in the index creation. * * @param index : an index created from a file. * The index must have been created with the key in argument. * @param key : key to be selected * @param value : value of the key to select * @return 0 if OK, integer value on error */ int grib_index_select_long(grib_index* index,const char* key,long value); /** * Select the message subset with key==value. The value is a double. The key must have been created with double type or have double as native type if the type was not explicitly defined in the index creation. * * @param index : an index created from a file. * The index must have been created with the key in argument. * @param key : key to be selected * @param value : value of the key to select * @return 0 if OK, integer value on error */ int grib_index_select_double(grib_index* index,const char* key,double value); /** * Select the message subset with key==value. The value is a string. The key must have been created with string type or have string as native type if the type was not explicitly defined in the index creation. * * @param index : an index created from a file. * The index must have been created with the key in argument. * @param key : key to be selected * @param value : value of the key to select * @return 0 if OK, integer value on error */ int grib_index_select_string(grib_index* index,const char* key,char* value); /** * Create a new handle from an index after having selected the key values. * All the keys belonging to the index must be selected before calling this function. Successive calls to this function will return all the handles compatible with the constraints defined selecting the values of the index keys. * When no more handles are available from the index a NULL pointer is returned and the err variable is set to GRIB_END_OF_INDEX. * * @param index : an index created from a file. * @param err : 0 if OK, integer value on error. GRIB_END_OF_INDEX when no more handles are contained in the index. * @return grib handle. */ grib_handle* grib_handle_new_from_index(grib_index* index,int *err); /** * Delete the index. * * @param index : index to be deleted. */ void grib_index_delete(grib_index* index); /*! @} */ /*! \defgroup grib_handle The grib_handle The grib_handle is the structure giving access to parsed grib values by keys. */ /*! @{*/ /** * Counts the messages contained in a file resource. * * @param c : the context from which the handle will be created (NULL for default context) * @param f : the file resource * @param n : the number of messages in the file * @return 0 if OK, integer value on error */ int grib_count_in_file(grib_context* c, FILE* f,int* n); /** * Create a handle from a file resource. * The file is read until a message is found. The message is then copied. * Remember always to delete the handle when it is not needed anymore to avoid * memory leaks. * * @param c : the context from which the handle will be created (NULL for default context) * @param f : the file resource * @param error : error code set if the returned handle is NULL and the end of file is not reached * @return the new handle, NULL if the resource is invalid or a problem is encountered */ grib_handle* grib_handle_new_from_file(grib_context* c, FILE* f, int* error); /** * Create a handle from a user message. The message is copied and will be freed with the handle * * @param c : the context from which the handle will be created (NULL for default context) * @param data : the actual message * @param data_len : the length of the message in number of bytes * @return the new handle, NULL if the message is invalid or a problem is encountered */ grib_handle* grib_handle_new_from_message_copy(grib_context* c, const void* data, size_t data_len); /** * Create a handle from a GRIB message contained in the samples directory. * The message is copied at the creation of the handle * * @param c : the context from which the handle will be created (NULL for default context) * @param sample_name : the name of the sample file (without the .tmpl extension) * @return the new handle, NULL if the resource is invalid or a problem is encountered */ grib_handle* grib_handle_new_from_samples (grib_context* c, const char* sample_name); /** * Clone an existing handle using the context of the original handle, * The message is copied and reparsed * * @param h : The handle to be cloned * @return the new handle, NULL if the message is invalid or a problem is encountered */ grib_handle* grib_handle_clone(grib_handle* h) ; /** * Frees a handle, also frees the message if it is not a user message * @see grib_handle_new_from_message * @param h : The handle to be deleted * @return 0 if OK, integer value on error */ int grib_handle_delete(grib_handle* h); /** * Create an empty multi field handle. * Remember always to delete the multi handle when it is not needed anymore to avoid * memory leaks. * * @param c : the context from which the handle will be created (NULL for default context) */ grib_multi_handle* grib_multi_handle_new (grib_context* c); /** * Append the sections starting with start_section of the message pointed by h at * the end of the multi field handle mh. * Remember always to delete the multi handle when it is not needed anymore to avoid * memory leaks. * * @param h : The handle from which the sections are copied. * @param start_section : section number. Starting from this section all the sections to the end of the message will be copied. * @param mh : The multi field handle on which the sections are appended. * @return 0 if OK, integer value on error */ int grib_multi_handle_append(grib_handle* h,int start_section,grib_multi_handle* mh); /** * Delete multi field handle. * * @param mh : The multi field handle to be deleted. * @return 0 if OK, integer value on error */ int grib_multi_handle_delete(grib_multi_handle* mh); /** * Write a multi field handle in a file. * Remember always to delete the multi handle when it is not needed anymore to avoid * memory leaks. * * @param mh : The multi field handle to be written. * @param f : File on which the file handle is written. * @return 0 if OK, integer value on error */ int grib_multi_handle_write(grib_multi_handle* mh,FILE* f); /*! @} */ /*! \defgroup handling_coded_messages Handling coded messages */ /*! @{ */ /** * getting the message attached to a handle * * @param h : the handle to which the buffer should be gathered * @param message : the pointer to be set to the handle's data * @param message_length : On exit, the message size in number of bytes * @return 0 if OK, integer value on error */ int grib_get_message(grib_handle* h ,const void** message, size_t *message_length); /*! \defgroup iterators Iterating on latitude/longitude/values */ /*! @{ */ /*! * \brief Create a new iterator from a handle, using current geometry and values. * * \param h : the handle from which the iterator will be created * \param flags : flags for future use. * \param error : error code * \return the new iterator, NULL if no iterator can be created */ grib_iterator* grib_iterator_new(grib_handle* h, unsigned long flags,int* error); /** * Get the next value from an iterator. * * @param i : the iterator * @param lat : on output latitude in degree * @param lon : on output longitude in degree * @param value : on output value of the point * @return positive value if successful, 0 if no more data are available */ int grib_iterator_next(grib_iterator *i, double* lat,double* lon,double* value); /** * Frees an iterator from memory * * @param i : the iterator * @return 0 if OK, integer value on error */ int grib_iterator_delete(grib_iterator *i); /*! * \brief Create a new nearest from a handle, using current geometry . * * \param h : the handle from which the iterator will be created * \param error : error code * \return the new nearest, NULL if no nearest can be created */ grib_nearest* grib_nearest_new(grib_handle* h, int* error); /** * Find the 4 nearest points of a latitude longitude point. * The flags are provided to speed up the process of searching. If you are * sure that the point you are asking for is not changing from a call * to another you can use GRIB_NEAREST_SAME_POINT. The same is valid for * the grid. Flags can be used together doing a bitwise OR. * The distances are given in kilometres. * * @param nearest : nearest structure * @param h : handle from which geography and data values are taken * @param inlat : latitude of the point to search for * @param inlon : longitude of the point to search for * @param flags : GRIB_NEAREST_SAME_POINT, GRIB_NEAREST_SAME_GRID * @param outlats : returned array of latitudes of the nearest points * @param outlons : returned array of longitudes of the nearest points * @param values : returned array of data values of the nearest points * @param distances : returned array of distances from the nearest points * @param indexes : returned array of indexes of the nearest points * @param len : size of the arrays * @return 0 if OK, integer value on error */ int grib_nearest_find(grib_nearest *nearest,grib_handle* h,double inlat,double inlon, unsigned long flags,double* outlats,double* outlons, double* values,double* distances,int* indexes,size_t *len); /** * Frees an nearest from memory * * @param nearest : the nearest * @return 0 if OK, integer value on error */ int grib_nearest_delete(grib_nearest *nearest); /** * Find the nearest point of a set of points whose latitudes and longitudes * are given in the inlats, inlons arrays respectively. * If the flag is_lsm is 1 the nearest land point is returned and the * GRIB passed as handle (h) is considered a land sea mask. * The land nearest point is the nearest point with land sea mask value>=0.5. * If no nearest land points are found the nearest value is returned. * If the flag is_lsm is 0 the nearest point is returned. * values, distances, indexes (in the "values" array) for the nearest points (ilons,ilats) * are returned. * The distances are given in kilometres. * * @param h : handle from which geography and data values are taken * @param is_lsm : lsm flag (1-> nearest land, 0-> nearest) * @param inlats : latitudes of the points to search for * @param inlons : longitudes of the points to search for * @param npoints : number of points (size of the inlats,inlons,outlats,outlons,values,distances,indexes arrays) * @param outlats : returned array of latitudes of the nearest points * @param outlons : returned array of longitudes of the nearest points * @param values : returned array of data values of the nearest points * @param distances : returned array of distances from the nearest points * @param indexes : returned array of indexes of the nearest points * @return 0 if OK, integer value on error */ int grib_nearest_find_multiple(grib_handle* h,int is_lsm, double* inlats,double* inlons,long npoints, double* outlats,double* outlons, double* values,double* distances, int* indexes); /** * Get the number of coded value from a key, if several keys of the same name are present, the total sum is returned * * @param h : the handle to get the offset from * @param key : the key to be searched * @param size : the address of a size_t where the size will be set * @return 0 if OK, integer value on error */ int grib_get_size(grib_handle* h, const char* key,size_t *size); /** * Get the length of the string representation of the key, if several keys of the same name are present, the maximum length is returned * * @param h : the handle to get the offset from * @param key : the key to be searched * @param length : the address of a size_t where the length will be set * @return 0 if OK, integer value on error */ int grib_get_length(grib_handle* h, const char* key,size_t *length); /** * Get a long value from a key, if several keys of the same name are present, the last one is returned * @see grib_set_long * * @param h : the handle to get the data from * @param key : the key to be searched * @param value : the address of a long where the data will be retrieved * @return 0 if OK, integer value on error */ int grib_get_long(grib_handle* h, const char* key, long* value); /** * Get a double value from a key, if several keys of the same name are present, the last one is returned * @see grib_set_double * * @param h : the handle to get the data from * @param key : the key to be searched * @param value : the address of a double where the data will be retrieved * @return 0 if OK, integer value on error */ int grib_get_double(grib_handle* h, const char* key, double* value); /** * Get as double the i-th element of the "key" array * * @param h : the handle to get the data from * @param key : the key to be searched * @param i : zero-based index * @param value : the address of a double where the data will be retrieved * @return 0 if OK, integer value on error */ int grib_get_double_element(grib_handle* h, const char* key, int i, double* value); /** * Get as double array the elements of the "key" array whose indexes are listed in the input array i * * @param h : the handle to get the data from * @param key : the key to be searched * @param i : zero-based array of indexes * @param size : size of the i and value arrays * @param value : the double array for the data values * @return 0 if OK, integer value on error */ int grib_get_double_elements(grib_handle* h, const char* key, int* i, long size, double* value); /** * Get a string value from a key, if several keys of the same name are present, the last one is returned * @see grib_set_string * * @param h : the handle to get the data from * @param key : the key to be searched * @param mesg : the address of a string where the data will be retrieved * @param length : the address of a size_t that contains allocated length of the string on input, and that contains the actual length of the string on output * @return 0 if OK, integer value on error */ int grib_get_string(grib_handle* h, const char* key, char* mesg, size_t *length); /** * Get string array values from a key. If several keys of the same name are present, the last one is returned * @see grib_set_string_array * * @param h : the handle to get the data from * @param key : the key to be searched * @param vals : the address of a string array where the data will be retrieved * @param length : the address of a size_t that contains allocated length of the array on input, and that contains the actual length of the array on output * @return 0 if OK, integer value on error */ int grib_get_string_array(grib_handle* h, const char* key, char** vals, size_t *length); /** * Get double array values from a key. If several keys of the same name are present, the last one is returned * @see grib_set_double_array * * @param h : the handle to get the data from * @param key : the key to be searched * @param vals : the address of a double array where the data will be retrieved * @param length : the address of a size_t that contains allocated length of the double array on input, and that contains the actual length of the double array on output * @return 0 if OK, integer value on error */ int grib_get_double_array(grib_handle* h, const char* key, double* vals, size_t *length); /** * Get long array values from a key. If several keys of the same name are present, the last one is returned * @see grib_set_long_array * * @param h : the handle to get the data from * @param key : the key to be searched * @param vals : the address of a long array where the data will be retrieved * @param length : the address of a size_t that contains allocated length of the long array on input, and that contains the actual length of the long array on output * @return 0 if OK, integer value on error */ int grib_get_long_array(grib_handle* h, const char* key, long* vals, size_t *length); /* setting data */ /** * Copy the keys belonging to a given namespace from a source handle to a destination handle * * * @param dest : destination handle * @param name : namespace * @param src : source handle * @return 0 if OK, integer value on error */ int grib_copy_namespace(grib_handle* dest, const char* name, grib_handle* src); /** * Set a long value from a key. If several keys of the same name are present, the last one is set * @see grib_get_long * * @param h : the handle to set the data to * @param key : the key to be searched * @param val : a long where the data will be read * @return 0 if OK, integer value on error */ int grib_set_long(grib_handle* h, const char* key, long val); /** * Set a double value from a key. If several keys of the same name are present, the last one is set * @see grib_get_double * * @param h : the handle to set the data to * @param key : the key to be searched * @param val : a double where the data will be read * @return 0 if OK, integer value on error */ int grib_set_double(grib_handle* h, const char* key, double val); /** * Set a string value from a key. If several keys of the same name are present, the last one is set * @see grib_get_string * * @param h : the handle to set the data to * @param key : the key to be searched * @param mesg : the address of a string where the data will be read * @param length : the address of a size_t that contains the length of the string on input, and that contains the actual packed length of the string on output * @return 0 if OK, integer value on error */ int grib_set_string(grib_handle* h, const char* key, const char* mesg, size_t *length); /** * Set a double array from a key. If several keys of the same name are present, the last one is set * @see grib_get_double_array * * @param h : the handle to set the data to * @param key : the key to be searched * @param vals : the address of a double array where the data will be read * @param length : a size_t that contains the length of the byte array on input * @return 0 if OK, integer value on error */ int grib_set_double_array(grib_handle* h, const char* key , const double* vals , size_t length); /** * Set a long array from a key. If several keys of the same name are present, the last one is set * @see grib_get_long_array * * @param h : the handle to set the data to * @param key : the key to be searched * @param vals : the address of a long array where the data will be read * @param length : a size_t that contains the length of the long array on input * @return 0 if OK, integer value on error */ int grib_set_long_array(grib_handle* h, const char* key , const long* vals, size_t length); /** * Set a string array from a key. If several keys of the same name are present, the last one is set * @see grib_get_string_array * * @param h : the handle to set the data to * @param key : the key to be searched * @param vals : the address of a string array where the data will be read * @param length : a size_t that contains the length of the array on input * @return 0 if OK, integer value on error */ int grib_set_string_array(grib_handle* h, const char *key, const char **vals, size_t length); /*! @} */ /** * Get the static default context * * @return the default context, NULL it the context is not available */ grib_context* grib_context_get_default(void); /** * Set the GTS header mode on. * The GTS headers will be preserved. * * @param c : the context */ void grib_gts_header_on(grib_context* c) ; /** * Set the GTS header mode off. * The GTS headers will be deleted. * * @param c : the context */ void grib_gts_header_off(grib_context* c); /** * Set the GRIBEX mode on. * Grib files will be compatible with GRIBEX. * * @param c : the context */ void grib_gribex_mode_on(grib_context* c); /** * Set the GRIBEX mode off. * GRIB files won't be always compatible with GRIBEX. * * @param c : the context */ void grib_gribex_mode_off(grib_context* c); /** * Sets the search path for definition files. * * @param c : the context to be modified * @param path : the search path for definition files */ void grib_context_set_definitions_path(grib_context* c, const char* path); /** * Sets the search path for sample files. * * @param c : the context to be modified * @param path : the search path for sample files */ void grib_context_set_samples_path(grib_context* c, const char* path); /** * Turn on support for multiple fields in single grib messages * * @param c : the context to be modified */ void grib_multi_support_on(grib_context* c); /** * Turn off support for multiple fields in single GRIB messages * * @param c : the context to be modified */ void grib_multi_support_off(grib_context* c); /** * Get the API version * * @return API version */ long grib_get_api_version(void); /*! \defgroup keys_iterator Iterating on keys names The keys iterator is designed to get the key names defined in a message. Key names on which the iteration is carried out can be filtered through their attributes or by the namespace they belong to. */ /*! @{ */ /*! Create a new iterator from a valid and initialised handle. * @param h : the handle whose keys you want to iterate * @param filter_flags : flags to filter out some of the keys through their attributes * @param name_space : if not null the iteration is carried out only on * keys belonging to the namespace passed. (NULL for all the keys) * @return keys iterator ready to iterate through keys according to filter_flags * and namespace */ grib_keys_iterator* grib_keys_iterator_new(grib_handle* h,unsigned long filter_flags, const char* name_space); bufr_keys_iterator* codes_bufr_keys_iterator_new(grib_handle* h, unsigned long filter_flags); /*! Step to the next iterator. * @param kiter : valid grib_keys_iterator * @return 1 if next iterator exists, 0 if no more elements to iterate on */ int grib_keys_iterator_next(grib_keys_iterator* kiter); int codes_bufr_keys_iterator_next(bufr_keys_iterator* kiter); /*! get the key name from the iterator * @param kiter : valid grib_keys_iterator * @return key name */ const char* grib_keys_iterator_get_name(grib_keys_iterator *kiter); char* codes_bufr_keys_iterator_get_name(bufr_keys_iterator* kiter); /*! Delete the iterator. * @param kiter : valid grib_keys_iterator * @return 0 if OK, integer value on error */ int grib_keys_iterator_delete(grib_keys_iterator* kiter); int codes_bufr_keys_iterator_delete(bufr_keys_iterator* kiter); /*! Rewind the iterator. * @param kiter : valid grib_keys_iterator * @return 0 if OK, integer value on error */ int grib_keys_iterator_rewind(grib_keys_iterator* kiter); int codes_bufr_keys_iterator_rewind(bufr_keys_iterator* kiter); int grib_keys_iterator_set_flags(grib_keys_iterator *kiter,unsigned long flags); /** * Convert an error code into a string * @param code : the error code * @return the error message */ const char* grib_get_error_message(int code); int grib_get_native_type(grib_handle* h, const char* name,int* type); /* aa: changed off_t to long int */ int grib_get_message_offset ( grib_handle* h,long int* offset); int grib_set_values(grib_handle* h,grib_values* grib_values , size_t arg_count); int grib_is_missing(grib_handle* h, const char* key, int* err); int grib_is_defined(grib_handle* h, const char* key); int grib_set_missing(grib_handle* h, const char* key); int grib_get_message_size ( grib_handle* h,size_t* size); int parse_keyval_string(const char *grib_tool, char *arg, int values_required, int default_type, grib_values values[], int *count); /*! \defgroup errors Error codes Error codes returned by the grib_api functions. */ /*! @{*/ /** No error */ #define GRIB_SUCCESS 0 /** End of resource reached */ #define GRIB_END_OF_FILE -1 /** Internal error */ #define GRIB_INTERNAL_ERROR -2 /** Passed buffer is too small */ #define GRIB_BUFFER_TOO_SMALL -3 /** Function not yet implemented */ #define GRIB_NOT_IMPLEMENTED -4 /** Missing 7777 at end of message */ #define GRIB_7777_NOT_FOUND -5 /** Passed array is too small */ #define GRIB_ARRAY_TOO_SMALL -6 /** File not found */ #define GRIB_FILE_NOT_FOUND -7 /** Code not found in code table */ #define GRIB_CODE_NOT_FOUND_IN_TABLE -8 /** Array size mismatch */ #define GRIB_WRONG_ARRAY_SIZE -9 /** Key/value not found */ #define GRIB_NOT_FOUND -10 /** Input output problem */ #define GRIB_IO_PROBLEM -11 /** Message invalid */ #define GRIB_INVALID_MESSAGE -12 /** Decoding invalid */ #define GRIB_DECODING_ERROR -13 /** Encoding invalid */ #define GRIB_ENCODING_ERROR -14 /** Code cannot unpack because of string too small */ #define GRIB_NO_MORE_IN_SET -15 /** Problem with calculation of geographic attributes */ #define GRIB_GEOCALCULUS_PROBLEM -16 /** Memory allocation error */ #define GRIB_OUT_OF_MEMORY -17 /** Value is read only */ #define GRIB_READ_ONLY -18 /** Invalid argument */ #define GRIB_INVALID_ARGUMENT -19 /** Null handle */ #define GRIB_NULL_HANDLE -20 /** Invalid section number */ #define GRIB_INVALID_SECTION_NUMBER -21 /** Value cannot be missing */ #define GRIB_VALUE_CANNOT_BE_MISSING -22 /** Wrong message length */ #define GRIB_WRONG_LENGTH -23 /** Invalid key type */ #define GRIB_INVALID_TYPE -24 /** Unable to set step */ #define GRIB_WRONG_STEP -25 /** Wrong units for step (step must be integer) */ #define GRIB_WRONG_STEP_UNIT -26 /** Invalid file id */ #define GRIB_INVALID_FILE -27 /** Invalid grib id */ #define GRIB_INVALID_GRIB -28 /** Invalid index id */ #define GRIB_INVALID_INDEX -29 /** Invalid iterator id */ #define GRIB_INVALID_ITERATOR -30 /** Invalid keys iterator id */ #define GRIB_INVALID_KEYS_ITERATOR -31 /** Invalid nearest id */ #define GRIB_INVALID_NEAREST -32 /** Invalid order by */ #define GRIB_INVALID_ORDERBY -33 /** Missing a key from the fieldset */ #define GRIB_MISSING_KEY -34 /** The point is out of the grid area */ #define GRIB_OUT_OF_AREA -35 /** Concept no match */ #define GRIB_CONCEPT_NO_MATCH -36 /** Hash array no match */ #define GRIB_HASH_ARRAY_NO_MATCH -37 /** Definitions files not found */ #define GRIB_NO_DEFINITIONS -38 /** Wrong type while packing */ #define GRIB_WRONG_TYPE -39 /** End of resource */ #define GRIB_END -40 /** Unable to code a field without values */ #define GRIB_NO_VALUES -41 /** Grid description is wrong or inconsistent */ #define GRIB_WRONG_GRID -42 /** End of index reached */ #define GRIB_END_OF_INDEX -43 /** Null index */ #define GRIB_NULL_INDEX -44 /** End of resource reached when reading message */ #define GRIB_PREMATURE_END_OF_FILE -45 /** An internal array is too small */ #define GRIB_INTERNAL_ARRAY_TOO_SMALL -46 /** Message is too large for the current architecture */ #define GRIB_MESSAGE_TOO_LARGE -47 /** Constant field */ #define GRIB_CONSTANT_FIELD -48 /** Switch unable to find a matching case */ #define GRIB_SWITCH_NO_MATCH -49 /** Underflow */ #define GRIB_UNDERFLOW -50 /** Message malformed */ #define GRIB_MESSAGE_MALFORMED -51 /** Index is corrupted */ #define GRIB_CORRUPTED_INDEX -52 /** Invalid number of bits per value */ #define GRIB_INVALID_BPV -53 /** Edition of two messages is different */ #define GRIB_DIFFERENT_EDITION -54 /** Value is different */ #define GRIB_VALUE_DIFFERENT -55 /** Invalid key value */ #define GRIB_INVALID_KEY_VALUE -56 /** String is smaller than requested */ #define GRIB_STRING_TOO_SMALL -57 /** Wrong type conversion */ #define GRIB_WRONG_CONVERSION -58 /** Missing BUFR table entry for descriptor */ #define GRIB_MISSING_BUFR_ENTRY -59 /** Null pointer */ #define GRIB_NULL_POINTER -60 /** Attribute is already present, cannot add */ #define GRIB_ATTRIBUTE_CLASH -61 /** Too many attributes. Increase MAX_ACCESSOR_ATTRIBUTES */ #define GRIB_TOO_MANY_ATTRIBUTES -62 /** Attribute not found. */ #define GRIB_ATTRIBUTE_NOT_FOUND -63 /** Edition not supported. */ #define GRIB_UNSUPPORTED_EDITION -64 /** Value out of coding range */ #define GRIB_OUT_OF_RANGE -65 /** Size of bitmap is incorrect */ #define GRIB_WRONG_BITMAP_SIZE -66 /*! @}*/ eccodes-python-0.9.7/gribapi/eccodes.h0000644000175000017500000000506013521303732020030 0ustar alastairalastair/* * Copyright 2005-2018 ECMWF. * * This software is licensed under the terms of the Apache Licence Version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction. */ /*! Codes handle, structure giving access to parsed values by keys \ingroup codes_handle \struct codes_handle */ typedef struct grib_handle codes_handle; /*! Codes context, structure containing the memory methods, the parsers and the formats. \ingroup codes_context \struct codes_context */ typedef struct grib_context codes_context; /** * Create a handle from a file resource. * The file is read until a message is found. The message is then copied. * Remember always to delete the handle when it is not needed anymore to avoid * memory leaks. * * @param c : the context from which the handle will be created (NULL for default context) * @param f : the file resource * @param product : the kind of product e.g. PRODUCT_GRIB, PRODUCT_BUFR * @param error : error code set if the returned handle is NULL and the end of file is not reached * @return the new handle, NULL if the resource is invalid or a problem is encountered */ grib_handle* codes_handle_new_from_file(grib_context* c, FILE* f, ProductKind product, int* error); /** * Create a handle from a BUFR message contained in a samples directory. * The message is copied at the creation of the handle * * @param c : the context from which the handle will be created (NULL for default context) * @param sample_name : the name of the sample file (without the .tmpl extension) * @return the new handle, NULL if the resource is invalid or a problem is encountered */ codes_handle* codes_bufr_handle_new_from_samples (codes_context* c, const char* sample_name); /* codes_bufr_copy_data copies all the values in the data section that are present in the same position in the data tree * and with the same number of values to the output handle. Should not exit with error if the output handle has a different * structure as the aim is to copy what is possible to be copied. * This will allow the user to add something to a message by creating a new message with additions or changes to the * unexpandedDescriptors and copying what is possible to copy from the original message. */ int codes_bufr_copy_data(grib_handle* hin, grib_handle* hout); eccodes-python-0.9.7/gribapi/gribapi.py0000644000175000017500000020477613521303732020260 0ustar alastairalastair""" @package gribapi @brief This package is the \b Python 3 interface to ecCodes. It offers almost one to one bindings to the C API functions. The Python 3 interface to ecCodes uses the NumPy package as the container of choice for the possible arrays of values that can be encoded/decoded in and from a message. Numpy is a package used for scientific computing in Python and an efficient container for generic data. @em Requirements: - Python 3.5 or higher - NumPy """ from .bindings import ENC, ffi, lib from .bindings import __version__ as bindings_version # noqa import functools import sys import os from functools import wraps import numpy as np from . import errors try: type(file) except NameError: import io file = io.IOBase long = int KEYTYPES = { 1: int, 2: float, 3: str, } CODES_PRODUCT_ANY = 0 """ Generic product kind """ CODES_PRODUCT_GRIB = 1 """ GRIB product kind """ CODES_PRODUCT_BUFR = 2 """ BUFR product kind """ CODES_PRODUCT_METAR = 3 """ METAR product kind """ CODES_PRODUCT_GTS = 4 """ GTS product kind """ CODES_PRODUCT_TAF = 5 """ TAF product kind """ # Constants for 'missing' GRIB_MISSING_DOUBLE = -1e+100 GRIB_MISSING_LONG = 2147483647 # GRIB-51 Skip function arguments type checking if the # environment variable is defined no_type_checks = os.environ.get('ECCODES_PYTHON_NO_TYPE_CHECKS') is not None # Function-arguments type-checking decorator # inspired from http://code.activestate.com/recipes/454322-type-checking-decorator/ # modified to support multiple allowed types and all types in the same decorator call # This returns a decorator. _params_ is the dict with the type specs def require(**_params_): """ The actual decorator. Receives the target function in _func_ """ def check_types(_func_, _params_=_params_): if no_type_checks: return _func_ @wraps(_func_) # The wrapper function. Replaces the target function and receives its args def modified(*args, **kw): arg_names = _func_.__code__.co_varnames # argnames, varargs, kwargs, defaults = inspect.getargspec(_func_) kw.update(zip(arg_names, args)) for name, allowed_types in _params_.items(): param = kw[name] if isinstance(allowed_types, type): allowed_types = (allowed_types,) assert any([isinstance(param, type1) for type1 in allowed_types]), \ "Parameter '%s' should be of type %s" % (name, " or ".join([t.__name__ for t in allowed_types])) return _func_(**kw) return modified return check_types # @cond class Bunch(dict): """ The collector of a bunch of named stuff :). """ def __init__(self, **kw): dict.__init__(self, kw) self.__dict__.update(kw) def __setitem__(self, key, value): dict.__setitem__(self, key, value) self.__dict__[key] = value def __setattr__(self, key, value): dict.__setitem__(self, key, value) self.__dict__[key] = value def __delitem__(self, key): dict.__delitem__(self, key) del self.__dict__[key] def __delattr__(self, key): dict.__delitem__(self, key) del self.__dict__[key] def __str__(self): state = ["%s=%r" % (attribute, value) for (attribute, value) in self.__dict__.items()] return '\n'.join(state) # @endcond def err_last(func): @functools.wraps(func) def wrapper(*args): err = ffi.new('int *') args += (err,) retval = func(*args) return err[0], retval return wrapper def get_handle(msgid): assert isinstance(msgid, int) h = ffi.cast('grib_handle*', msgid) if h == ffi.NULL: raise errors.InvalidGribError return h def put_handle(handle): if handle == ffi.NULL: raise errors.InvalidGribError return int(ffi.cast('unsigned long', handle)) def get_multi_handle(msgid): assert isinstance(msgid, int) return ffi.cast('grib_multi_handle*', msgid) def put_multi_handle(handle): return int(ffi.cast('unsigned long', handle)) def get_index(indexid): assert isinstance(indexid, int) return ffi.cast('grib_index*', indexid) def put_index(indexh): return int(ffi.cast('unsigned long', indexh)) def get_iterator(iterid): assert isinstance(iterid, int) return ffi.cast('grib_iterator*', iterid) def put_iterator(iterh): return int(ffi.cast('unsigned long', iterh)) def get_grib_keys_iterator(iterid): assert isinstance(iterid, int) return ffi.cast('grib_keys_iterator*', iterid) def put_grib_keys_iterator(iterh): return int(ffi.cast('unsigned long', iterh)) def get_bufr_keys_iterator(iterid): assert isinstance(iterid, int) return ffi.cast('bufr_keys_iterator*', iterid) def put_bufr_keys_iterator(iterh): return int(ffi.cast('unsigned long', iterh)) # @cond @require(errid=int) def GRIB_CHECK(errid): """ Utility function checking the ecCodes error code and raising an error if that was set. @param errid the C interface error id to check @exception GribInternalError """ if errid: errors.raise_grib_error(errid) # @endcond @require(fileobj=file) def gts_new_from_file(fileobj, headers_only=False): """ @brief Load in memory a GTS message from a file. The message can be accessed through its id and will be available\n until @ref codes_release is called.\n @param fileobj python file object @param headers_only whether or not to load the message with the headers only @return id of the GTS loaded in memory or None @exception GribInternalError """ # err, h = err_last(lib.gts_new_from_file)(ffi.NULL, fileobj) err, h = err_last(lib.codes_handle_new_from_file)(ffi.NULL, fileobj, CODES_PRODUCT_GTS) if err: if err == lib.GRIB_END_OF_FILE: return None else: GRIB_CHECK(err) return None if h == ffi.NULL: return None else: return put_handle(h) @require(fileobj=file) def metar_new_from_file(fileobj, headers_only=False): """ @brief Load in memory a METAR message from a file. The message can be accessed through its id and will be available\n until @ref codes_release is called.\n @param fileobj python file object @param headers_only whether or not to load the message with the headers only @return id of the METAR loaded in memory or None @exception GribInternalError """ # err, h = err_last(lib.metar_new_from_file)(ffi.NULL, fileobj) err, h = err_last(lib.codes_handle_new_from_file)(ffi.NULL, fileobj, CODES_PRODUCT_METAR) if err: if err == lib.GRIB_END_OF_FILE: return None else: GRIB_CHECK(err) return None if h == ffi.NULL: return None else: return put_handle(h) @require(fileobj=file, product_kind=int) def codes_new_from_file(fileobj, product_kind, headers_only=False): """ @brief Load in memory a message from a file for a given product. The message can be accessed through its id and will be available\n until @ref codes_release is called.\n \b Examples: \ref get_product_kind.py "get_product_kind.py" @param fileobj python file object @param product_kind one of CODES_PRODUCT_GRIB, CODES_PRODUCT_BUFR, CODES_PRODUCT_METAR or CODES_PRODUCT_GTS @param headers_only whether or not to load the message with the headers only @return id of the message loaded in memory or None @exception GribInternalError """ if product_kind == CODES_PRODUCT_GRIB: return grib_new_from_file(fileobj, headers_only) if product_kind == CODES_PRODUCT_BUFR: return bufr_new_from_file(fileobj, headers_only) if product_kind == CODES_PRODUCT_METAR: return metar_new_from_file(fileobj, headers_only) if product_kind == CODES_PRODUCT_GTS: return gts_new_from_file(fileobj, headers_only) if product_kind == CODES_PRODUCT_ANY: return any_new_from_file(fileobj, headers_only) raise Exception("Invalid product kind: " + product_kind) @require(fileobj=file) def any_new_from_file(fileobj, headers_only=False): """ @brief Load in memory a message from a file. The message can be accessed through its id and will be available\n until @ref codes_release is called.\n \b Examples: \ref grib_get_keys.py "grib_get_keys.py" @param fileobj python file object @param headers_only whether or not to load the message with the headers only @return id of the message loaded in memory or None @exception GribInternalError """ err, h = err_last(lib.codes_handle_new_from_file)(ffi.NULL, fileobj, CODES_PRODUCT_ANY) if err: if err == lib.GRIB_END_OF_FILE: return None else: GRIB_CHECK(err) return None if h == ffi.NULL: return None else: return put_handle(h) @require(fileobj=file) def bufr_new_from_file(fileobj, headers_only=False): """ @brief Load in memory a BUFR message from a file. The message can be accessed through its id and will be available\n until @ref codes_release is called.\n \b Examples: \ref bufr_get_keys.py "bufr_get_keys.py" @param fileobj python file object @param headers_only whether or not to load the message with the headers only @return id of the BUFR loaded in memory or None @exception GribInternalError """ err, h = err_last(lib.codes_handle_new_from_file)(ffi.NULL, fileobj, CODES_PRODUCT_BUFR) if err: if err == lib.GRIB_END_OF_FILE: return None else: GRIB_CHECK(err) return None if h == ffi.NULL: return None else: return put_handle(h) @require(fileobj=file) def grib_new_from_file(fileobj, headers_only=False): """ @brief Load in memory a GRIB message from a file. The message can be accessed through its gribid and will be available\n until @ref codes_release is called.\n The message can be loaded headers only by using the headers_only argument. Default is to have the headers only option set to off (False). If set to on (True), data values will be skipped. This will result in a significant performance gain if one is only interested in browsing through messages to retrieve metadata. Any attempt to retrieve data values keys when in the headers only mode will result in a key not found error. \b Examples: \ref grib_get_keys.py "grib_get_keys.py" @param fileobj python file object @param headers_only whether or not to load the message with the headers only @return id of the grib loaded in memory or None @exception GribInternalError """ # err, h = err_last(lib.grib_new_from_file)(ffi.NULL, fileobj, headers_only) err, h = err_last(lib.codes_handle_new_from_file)(ffi.NULL, fileobj, CODES_PRODUCT_GRIB) if err: if err == lib.GRIB_END_OF_FILE: return None else: GRIB_CHECK(err) return None if h == ffi.NULL: return None else: return put_handle(h) @require(fileobj=file) def codes_close_file(fileobj): # The client must call this BEFORE calling close() on the file object # so we can remove the entry in our cache # err = _internal.codes_c_close_file(fileobj.fileno(), fileobj.name) # Note: it is safe calling close() here as subsequent calls will have no effect fileobj.close() @require(fileobj=file) def grib_count_in_file(fileobj): """ @brief Count the messages in a file. \b Examples: \ref count_messages.py "count_messages.py" @param fileobj python file object @return number of messages in the file @exception GribInternalError """ num_p = ffi.new('int*') err = lib.grib_count_in_file(ffi.NULL, fileobj, num_p) GRIB_CHECK(err) return num_p[0] def grib_multi_support_on(): """ @brief Turn on the support for multiple fields in a single GRIB message. @exception GribInternalError """ lib.grib_multi_support_on(ffi.NULL) def grib_multi_support_off(): """ @brief Turn off the support for multiple fields in a single GRIB message. @exception GribInternalError """ lib.grib_multi_support_off(ffi.NULL) @require(msgid=int) def grib_release(msgid): """ @brief Free the memory for the message referred to by msgid. \b Examples: \ref grib_get_keys.py "grib_get_keys.py" @param msgid id of the message loaded in memory @exception GribInternalError """ h = get_handle(msgid) GRIB_CHECK(lib.grib_handle_delete(h)) @require(msgid=int, key=str) def grib_get_string(msgid, key): """ @brief Get the string value of a key from a message. @param msgid id of the message loaded in memory @param key key name @return string value of key @exception GribInternalError """ length = grib_get_string_length(msgid, key) h = get_handle(msgid) values = ffi.new('char[]', length) length_p = ffi.new('size_t *', length) err = lib.grib_get_string(h, key.encode(ENC), values, length_p) GRIB_CHECK(err) return ffi.string(values, length_p[0]).decode(ENC) @require(msgid=int, key=str, value=str) def grib_set_string(msgid, key, value): """ @brief Set the value for a string key in a message. @param msgid id of the message loaded in memory @param key key name @param value string value @exception GribInternalError """ h = get_handle(msgid) bvalue = value.encode(ENC) length_p = ffi.new('size_t *', len(bvalue)) GRIB_CHECK(lib.grib_set_string(h, key.encode(ENC), bvalue, length_p)) def grib_gribex_mode_on(): """ @brief Turn on the compatibility mode with GRIBEX. @exception GribInternalError """ lib.grib_c_gribex_mode_on(ffi.NULL) def grib_gribex_mode_off(): """ @brief Turn off the compatibility mode with GRIBEX. @exception GribInternalError """ lib.grib_c_gribex_mode_off(ffi.NULL) @require(msgid=int, fileobj=file) def grib_write(msgid, fileobj): """ @brief Write a message to a file. \b Examples: \ref grib_set_keys.py "grib_set_keys.py" @param msgid id of the message loaded in memory @param fileobj python file object @exception GribInternalError """ msg_bytes = grib_get_message(msgid) fileobj.write(msg_bytes) fileobj.flush() @require(multigribid=int, fileobj=file) def grib_multi_write(multigribid, fileobj): """ @brief Write a multi-field GRIB message to a file. \b Examples: \ref grib_multi_write.py "grib_multi_write.py" @param multigribid id of the multi-field grib loaded in memory @param fileobj python file object @exception GribInternalError """ mh = get_multi_handle(multigribid) GRIB_CHECK(lib.grib_multi_handle_write(mh, fileobj)) @require(ingribid=int, startsection=int, multigribid=int) def grib_multi_append(ingribid, startsection, multigribid): """ @brief Append a single-field GRIB message to a multi-field GRIB message. Only the sections with section number greather or equal "startsection" are copied from the input single message to the multi-field output grib. \b Examples: \ref grib_multi_write.py "grib_multi_write.py" @param ingribid id of the input single-field GRIB @param startsection starting from startsection (included) all the sections are copied from the input single grib to the output multi-field grib @param multigribid id of the output multi-field GRIB @exception GribInternalError """ h = get_handle(ingribid) mh = get_multi_handle(multigribid) GRIB_CHECK(lib.grib_multi_handle_append(h, startsection, mh)) @require(msgid=int, key=str) def grib_get_size(msgid, key): """ @brief Get the size of an array key. \b Examples: \ref grib_get_keys.py "grib_get_keys.py",\ref count_messages.py "count_messages.py" @param msgid id of the message loaded in memory @param key name of the key @exception GribInternalError """ h = get_handle(msgid) size_p = ffi.new('size_t*') err = lib.grib_get_size(h, key.encode(ENC), size_p) GRIB_CHECK(err) return size_p[0] @require(msgid=int, key=str) def grib_get_string_length(msgid, key): """ @brief Get the length of the string version of a key. @param msgid id of the message loaded in memory @param key name of the key @exception GribInternalError """ h = get_handle(msgid) size = ffi.new('size_t *') err = lib.grib_get_length(h, key.encode(ENC), size) GRIB_CHECK(err) return size[0] @require(iterid=int) def grib_skip_computed(iterid): """ @brief Skip the computed keys in a keys iterator. The computed keys are not coded in the message, they are computed from other keys. @see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete @param iterid keys iterator id @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_COMPUTED) @require(iterid=int) def grib_skip_coded(iterid): """ @brief Skip the coded keys in a keys iterator. The coded keys are actually coded in the message. @see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete @param iterid keys iterator id @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_CODED) @require(iterid=int) def grib_skip_edition_specific(iterid): """ @brief Skip the edition specific keys in a keys iterator. @see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete @param iterid keys iterator id @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC) @require(iterid=int) def grib_skip_duplicates(iterid): """ @brief Skip the duplicate keys in a keys iterator. @see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete @param iterid keys iterator id @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_DUPLICATES) @require(iterid=int) def grib_skip_read_only(iterid): """ @brief Skip the read_only keys in a keys iterator. Read only keys cannot be set. @see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete @param iterid keys iterator id @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_READ_ONLY) @require(iterid=int) def grib_skip_function(iterid): """ @brief Skip the function keys in a keys iterator. @see grib_keys_iterator_new,grib_keys_iterator_next,grib_keys_iterator_delete @param iterid keys iterator id @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_set_flags(gki, lib.GRIB_KEYS_ITERATOR_SKIP_FUNCTION) @require(gribid=int, mode=int) def grib_iterator_new(gribid, mode): """ @brief Create a new geoiterator for the given GRIB message, using its geometry and values. The geoiterator can be used to go through all the geopoints in a GRIB message and retrieve the values corresponding to those geopoints. \b Examples: \ref grib_iterator.py "grib_iterator.py" @param gribid id of the GRIB loaded in memory @param mode flags for future use @return geoiterator id """ h = get_handle(gribid) err, iterid = err_last(lib.grib_iterator_new)(h, mode) GRIB_CHECK(err) return put_iterator(iterid) @require(iterid=int) def grib_iterator_delete(iterid): """ @brief Delete a geoiterator and free memory. \b Examples: \ref grib_iterator.py "grib_iterator.py" @param iterid geoiterator id @exception GribInternalError """ ih = get_iterator(iterid) GRIB_CHECK(lib.grib_iterator_delete(ih)) @require(iterid=int) def grib_iterator_next(iterid): """ @brief Retrieve the next value from a geoiterator. \b Examples: \ref grib_iterator.py "grib_iterator.py" @param iterid geoiterator id @return tuple with the latitude, longitude and value @exception GribInternalError """ iterh = get_iterator(iterid) lat_p = ffi.new('double*') lon_p = ffi.new('double*') value_p = ffi.new('double*') err = lib.grib_iterator_next(iterh, lat_p, lon_p, value_p) if err == 0: return [] elif err < 0: GRIB_CHECK(err) return None else: return (lat_p[0], lon_p[0], value_p[0]) @require(msgid=int) def grib_keys_iterator_new(msgid, namespace=None): """ @brief Create a new iterator on the keys. The keys iterator can be navigated to give all the key names which can then be used to get or set the key values with \ref grib_get or \ref grib_set. The set of keys returned can be controlled with the input variable namespace or using the functions \ref grib_skip_read_only, \ref grib_skip_duplicates, \ref grib_skip_coded,\ref grib_skip_computed. If namespace is a non empty string only the keys belonging to that namespace are returned. Example namespaces are "ls" (to get the same default keys as the grib_ls) and "mars" to get the keys used by mars. \b Examples: \ref grib_iterator.py "grib_iterator.py" @param msgid id of the message loaded in memory @param namespace the namespace of the keys to search for (all the keys if None) @return keys iterator id to be used in the keys iterator functions @exception GribInternalError """ h = get_handle(msgid) bnamespace = ffi.NULL if namespace is None else namespace.encode(ENC) iterid = lib.grib_keys_iterator_new(h, 0, bnamespace) return put_grib_keys_iterator(iterid) @require(iterid=int) def grib_keys_iterator_next(iterid): """ @brief Advance to the next keys iterator value. \b Examples: \ref grib_keys_iterator.py "grib_keys_iterator.py" @param iterid keys iterator id created with @ref grib_keys_iterator_new @exception GribInternalError """ kih = get_grib_keys_iterator(iterid) res = lib.grib_keys_iterator_next(kih) if res < 0: GRIB_CHECK(res) return res @require(iterid=int) def grib_keys_iterator_delete(iterid): """ @brief Delete a keys iterator and free memory. \b Examples: \ref grib_keys_iterator.py "grib_keys_iterator.py" @param iterid keys iterator id created with @ref grib_keys_iterator_new @exception GribInternalError """ # aa: THIS LEAKS MEMORY as it doesn't free all the connected iterators kih = get_grib_keys_iterator(iterid) lib.grib_keys_iterator_delete(kih) @require(iterid=int) def grib_keys_iterator_get_name(iterid): """ @brief Get the name of a key from a keys iterator. \b Examples: \ref grib_keys_iterator.py "grib_keys_iterator.py" @param iterid keys iterator id created with @ref grib_keys_iterator_new @return key name to be retrieved @exception GribInternalError """ # aa: missing call to grib_keys_iterator_get_accessor kih = get_grib_keys_iterator(iterid) name = lib.grib_keys_iterator_get_name(kih) return ffi.string(name).decode(ENC) @require(iterid=int) def grib_keys_iterator_rewind(iterid): """ @brief Rewind a keys iterator. @param iterid keys iterator id created with @ref grib_keys_iterator_new @exception GribInternalError """ gki = get_grib_keys_iterator(iterid) GRIB_CHECK(lib.grib_keys_iterator_rewind(gki)) # BUFR keys iterator @require(msgid=int) def codes_bufr_keys_iterator_new(msgid): """ @brief Create a new iterator on the BUFR keys. The keys iterator can be navigated to give all the key names which can then be used to get or set the key values with \ref codes_get or \ref codes_set. \b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py" @param msgid id of the BUFR message loaded in memory @return keys iterator id to be used in the keys iterator functions @exception GribInternalError """ h = get_handle(msgid) bki = lib.codes_bufr_keys_iterator_new(h, 0) if bki == ffi.NULL: raise errors.InvalidKeysIteratorError return put_bufr_keys_iterator(bki) @require(iterid=int) def codes_bufr_keys_iterator_next(iterid): """ @brief Advance to the next BUFR keys iterator value. \b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py" @param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new @exception GribInternalError """ bki = get_bufr_keys_iterator(iterid) res = lib.codes_bufr_keys_iterator_next(bki) if res < 0: GRIB_CHECK(res) return res @require(iterid=int) def codes_bufr_keys_iterator_delete(iterid): """ @brief Delete a BUFR keys iterator and free memory. \b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py" @param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new @exception GribInternalError """ bki = get_bufr_keys_iterator(iterid) GRIB_CHECK(lib.codes_bufr_keys_iterator_delete(bki)) @require(iterid=int) def codes_bufr_keys_iterator_get_name(iterid): """ @brief Get the name of a key from a BUFR keys iterator. \b Examples: \ref bufr_keys_iterator.py "bufr_keys_iterator.py" @param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new @return key name to be retrieved @exception GribInternalError """ bki = get_bufr_keys_iterator(iterid) name = lib.codes_bufr_keys_iterator_get_name(bki) return ffi.string(name).decode(ENC) @require(iterid=int) def codes_bufr_keys_iterator_rewind(iterid): """ @brief Rewind a BUFR keys iterator. @param iterid keys iterator id created with @ref codes_bufr_keys_iterator_new @exception GribInternalError """ bki = get_bufr_keys_iterator(iterid) GRIB_CHECK(lib.codes_bufr_keys_iterator_rewind(bki)) @require(msgid=int, key=str) def grib_get_long(msgid, key): """ @brief Get the value of a key in a message as an integer. @param msgid id of the message loaded in memory @param key key name @return value of key as int @exception GribInternalError """ h = get_handle(msgid) value_p = ffi.new('long*') err = lib.grib_get_long(h, key.encode(ENC), value_p) GRIB_CHECK(err) return value_p[0] @require(msgid=int, key=str) def grib_get_double(msgid, key): """ @brief Get the value of a key in a message as a float. @param msgid id of the message loaded in memory @param key key name @return value of key as float @exception GribInternalError """ h = get_handle(msgid) value_p = ffi.new('double*') err = lib.grib_get_double(h, key.encode(ENC), value_p) GRIB_CHECK(err) return value_p[0] @require(msgid=int, key=str, value=(int, float, str)) def grib_set_long(msgid, key, value): """ @brief Set the integer value for a key in a message. A TypeError exception will be thrown if value cannot be represented as an integer. @param msgid id of the message loaded in memory @param key key name @param value value to set @exception GribInternalError,TypeError """ try: value = int(value) except (ValueError, TypeError): raise TypeError("Invalid type") if value > sys.maxsize: raise TypeError("Invalid type") h = get_handle(msgid) GRIB_CHECK(lib.grib_set_long(h, key.encode(ENC), value)) @require(msgid=int, key=str, value=(int, float, str)) def grib_set_double(msgid, key, value): """ @brief Set the double value for a key in a message. A TypeError exception will be thrown if value cannot be represented as a float. @param msgid id of the message loaded in memory @param key key name @param value float value to set @exception GribInternalError,TypeError """ try: value = float(value) except (ValueError, TypeError): raise TypeError("Invalid type") h = get_handle(msgid) GRIB_CHECK(lib.grib_set_double(h, key.encode(ENC), value)) @require(samplename=str, product_kind=int) def codes_new_from_samples(samplename, product_kind): """ @brief Create a new valid message from a sample for a given product. The available samples are picked up from the directory pointed to by the environment variable ECCODES_SAMPLES_PATH. To know where the samples directory is run the codes_info tool.\n \b Examples: \ref grib_samples.py "grib_samples.py" @param samplename name of the sample to be used @param product_kind CODES_PRODUCT_GRIB or CODES_PRODUCT_BUFR @return id of the message loaded in memory @exception GribInternalError """ if product_kind == CODES_PRODUCT_GRIB: return grib_new_from_samples(samplename) if product_kind == CODES_PRODUCT_BUFR: return codes_bufr_new_from_samples(samplename) raise Exception("Invalid product kind: " + product_kind) @require(samplename=str) def grib_new_from_samples(samplename): """ @brief Create a new valid GRIB message from a sample. The available samples are picked up from the directory pointed to by the environment variable ECCODES_SAMPLES_PATH. To know where the samples directory is run the codes_info tool.\n \b Examples: \ref grib_samples.py "grib_samples.py" @param samplename name of the sample to be used @return id of the message loaded in memory @exception GribInternalError """ h = lib.grib_handle_new_from_samples(ffi.NULL, samplename.encode(ENC)) if h == ffi.NULL: errors.raise_grib_error(errors.FileNotFoundError) return put_handle(h) @require(samplename=str) def codes_bufr_new_from_samples(samplename): """ @brief Create a new valid BUFR message from a sample. The available samples are picked up from the directory pointed to by the environment variable ECCODES_SAMPLES_PATH. To know where the samples directory is run the codes_info tool.\n \b Examples: \ref bufr_copy_data.py "bufr_copy_data.py" @param samplename name of the BUFR sample to be used @return id of the message loaded in memory @exception GribInternalError """ h = lib.codes_bufr_handle_new_from_samples(ffi.NULL, samplename.encode(ENC)) if h == ffi.NULL: errors.raise_grib_error(errors.FileNotFoundError) return put_handle(h) @require(msgid_src=int, msgid_dst=int) def codes_bufr_copy_data(msgid_src, msgid_dst): """ @brief Copy data values from a BUFR message msgid_src to another message msgid_dst Copies all the values in the data section that are present in the same position in the data tree and with the same number of values to the output handle. @param msgid_src id of the message from which the data are copied @param msgid_dst id of the message to which the data are copied @return id of new message @exception GribInternalError """ h_src = get_handle(msgid_src) h_dst = get_handle(msgid_dst) err = lib.codes_bufr_copy_data(h_src, h_dst) GRIB_CHECK(err) return msgid_dst @require(msgid_src=int) def grib_clone(msgid_src): r""" @brief Create a copy of a message. Create a copy of a given message (\em msgid_src) resulting in a new message in memory (\em msgid_dest) identical to the original one. \b Examples: \ref grib_clone.py "grib_clone.py" @param msgid_src id of message to be cloned @return id of clone @exception GribInternalError """ h_src = get_handle(msgid_src) h_dest = lib.grib_handle_clone(h_src) if h_dest == ffi.NULL: raise errors.InvalidGribError return put_handle(h_dest) @require(msgid=int, key=str) def grib_set_double_array(msgid, key, inarray): """ @brief Set the value of the key to a double array. The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ... The wrapper will internally try to convert the input to a NumPy array before extracting its data and length. This is possible as NumPy allows the construction of arrays from arbitrary python sequences. The elements of the input sequence need to be convertible to a double. @param msgid id of the message loaded in memory @param key key name @param inarray tuple,list,array,numpy.ndarray @exception GribInternalError """ h = get_handle(msgid) if isinstance(inarray, np.ndarray): inarray = inarray.tolist() GRIB_CHECK(lib.grib_set_double_array(h, key.encode(ENC), inarray, len(inarray))) @require(msgid=int, key=str) def grib_get_double_array(msgid, key): """ @brief Get the value of the key as a NumPy array of doubles. @param msgid id of the message loaded in memory @param key key name @return numpy.ndarray @exception GribInternalError """ h = get_handle(msgid) nval = grib_get_size(msgid, key) lenght_p = ffi.new('size_t*', nval) arr = np.empty((nval,), dtype='float64') vals_p = ffi.cast('double *', arr.ctypes.data) err = lib.grib_get_double_array(h, key.encode(ENC), vals_p, lenght_p) GRIB_CHECK(err) return arr @require(msgid=int, key=str) def grib_get_string_array(msgid, key): """ @brief Get the value of the key as a list of strings. @param msgid id of the message loaded in memory @param key key name @return list @exception GribInternalError """ length = grib_get_string_length(msgid, key) size = grib_get_size(msgid, key) h = get_handle(msgid) values_keepalive = [ffi.new('char[]', length) for _ in range(size)] values = ffi.new('char*[]', values_keepalive) size_p = ffi.new('size_t *', size) err = lib.grib_get_string_array(h, key.encode(ENC), values, size_p) GRIB_CHECK(err) return [ffi.string(values[i]).decode(ENC) for i in range(size_p[0])] @require(msgid=int, key=str) def grib_set_string_array(msgid, key, inarray): """ @brief Set the value of the key to a string array. The input array can be a python sequence like tuple, list, array, ... The wrapper will internally try to convert the input to a NumPy array before extracting its data and length. This is possible as NumPy allows the construction of arrays from arbitrary python sequences. The elements of the input sequence need to be convertible to a double. @param msgid id of the message loaded in memory @param key key name @param inarray tuple,list,array @exception GribInternalError """ h = get_handle(msgid) size = len(inarray) # See https://cffi.readthedocs.io/en/release-1.3/using.html values_keepalive = [ffi.new('char[]', s.encode(ENC)) for s in inarray] values_p = ffi.new('const char *[]', values_keepalive) GRIB_CHECK(lib.grib_set_string_array(h, key.encode(ENC), values_p, size)) @require(msgid=int, key=str) def grib_set_long_array(msgid, key, inarray): """ @brief Set the value of the key to an integer array. The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ... The wrapper will internally try to convert the input to a NumPy array before extracting its data and length. This is possible as NumPy allows the construction of arrays from arbitrary python sequences. The elements of the input sequence need to be convertible to an int. @param msgid id of the message loaded in memory @param key key name @param inarray tuple,list,python array,numpy.ndarray @exception GribInternalError """ h = get_handle(msgid) if isinstance(inarray, np.ndarray): inarray = inarray.tolist() GRIB_CHECK(lib.grib_set_long_array(h, key.encode(ENC), inarray, len(inarray))) @require(msgid=int, key=str) def grib_get_long_array(msgid, key): """ @brief Get the integer array of values for a key from a message. @param msgid id of the message loaded in memory @param key key name @return numpy.ndarray @exception GribInternalError """ h = get_handle(msgid) nval = grib_get_size(msgid, key) lenght_p = ffi.new('size_t*', nval) arr = np.empty((nval,), dtype='int64') vals_p = ffi.cast('long *', arr.ctypes.data) err = lib.grib_get_long_array(h, key.encode(ENC), vals_p, lenght_p) GRIB_CHECK(err) return arr def grib_multi_new(): """ @brief Create a new multi-field GRIB message and return its id. \b Examples: \ref grib_multi_write.py "grib_multi_write.py" @return id of the multi-field message @exception GribInternalError """ mgid = lib.grib_multi_handle_new(ffi.NULL) if mgid == ffi.NULL: raise errors.InvalidGribError return put_multi_handle(mgid) @require(gribid=int) def grib_multi_release(gribid): """ @brief Release a multi-field message from memory. \b Examples: \ref grib_multi_write.py "grib_multi_write.py" @param gribid id of the multi-field we want to release the memory for @exception GribInternalError """ mh = get_multi_handle(gribid) GRIB_CHECK(lib.grib_multi_handle_delete(mh)) @require(gribid_src=int, namespace=str, gribid_dest=int) def grib_copy_namespace(gribid_src, namespace, gribid_dest): """ @brief Copy the value of all the keys belonging to a namespace from the source message to the destination message. @param gribid_src id of source message @param gribid_dest id of destination message @param namespace namespace to be copied @exception GribInternalError """ h_src = get_handle(gribid_src) h_dest = get_handle(gribid_dest) GRIB_CHECK(lib.grib_copy_namespace(h_src, namespace.encode(ENC), h_dest)) @require(filename=str, keys=(tuple, list)) def grib_index_new_from_file(filename, keys): """ @brief Create a new index from a file. \b Examples: \ref grib_index.py "grib_index.py" @param filename path of the file to index on @param keys sequence of keys to index on. The type of the key can be explicitly declared appending :l for long (or alternatively :i), :d for double, :s for string to the key name. @return index id @exception GribInternalError """ ckeys = ",".join(keys) err, iid = err_last(lib.grib_index_new_from_file)(ffi.NULL, filename.encode(ENC), ckeys.encode(ENC)) GRIB_CHECK(err) return put_index(iid) @require(indexid=int, filename=str) def grib_index_add_file(indexid, filename): """ @brief Add a file to an index. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of the index to add the file to @param filename path of the file to be added to index @exception GribInternalError """ iid = get_index(indexid) err = lib.grib_index_add_file(iid, filename.encode(ENC)) GRIB_CHECK(err) @require(indexid=int) def grib_index_release(indexid): """ @brief Delete an index. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. @exception GribInternalError """ ih = get_index(indexid) lib.grib_index_delete(ih) @require(indexid=int, key=str) def grib_index_get_size(indexid, key): """ @brief Get the number of distinct values for the index key. The key must belong to the index. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created on the given key. @param key key for which the number of values is computed @return number of distinct values for key in index @exception GribInternalError """ ih = get_index(indexid) size_p = ffi.new('size_t*') err = lib.grib_index_get_size(ih, key.encode(ENC), size_p) GRIB_CHECK(err) return size_p[0] @require(indexid=int, key=str) def grib_index_get_long(indexid, key): """ @brief Get the distinct values of the key in argument contained in the index. The key must belong to the index. This function is used when the type of the key was explicitly defined as long or when the native type of the key is long. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key for wich the values are returned @return tuple with values of key in index @exception GribInternalError """ nval = grib_index_get_size(indexid, key) ih = get_index(indexid) values_p = ffi.new('long[]', nval) size_p = ffi.new('size_t *', nval) err = lib.grib_index_get_long(ih, key.encode(ENC), values_p, size_p) GRIB_CHECK(err) return tuple(int(values_p[i]) for i in range(size_p[0])) @require(indexid=int, key=str) def grib_index_get_string(indexid, key): """ @brief Get the distinct values of the key in argument contained in the index. The key must belong to the index. This function is used when the type of the key was explicitly defined as string or when the native type of the key is string. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key for wich the values are returned @return tuple with values of key in index @exception GribInternalError """ nval = grib_index_get_size(indexid, key) ih = get_index(indexid) max_val_size = 1024 values_keepalive = [ffi.new('char[]', max_val_size) for _ in range(nval)] values_p = ffi.new('const char *[]', values_keepalive) size_p = ffi.new('size_t *', max_val_size) err = lib.grib_index_get_string(ih, key.encode(ENC), values_p, size_p) GRIB_CHECK(err) return tuple(ffi.string(values_p[i]).decode(ENC) for i in range(size_p[0])) @require(indexid=int, key=str) def grib_index_get_double(indexid, key): """ @brief Get the distinct values of the key in argument contained in the index. The key must belong to the index. This function is used when the type of the key was explicitly defined as double or when the native type of the key is double. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key for wich the values are returned @return tuple with values of key in index @exception GribInternalError """ nval = grib_index_get_size(indexid, key) ih = get_index(indexid) values_p = ffi.new('double[]', nval) size_p = ffi.new('size_t *', nval) err = lib.grib_index_get_doule(ih, key.encode(ENC), values_p, size_p) GRIB_CHECK(err) return tuple(int(values_p[i]) for i in range(size_p[0])) @require(indexid=int, key=str, value=int) def grib_index_select_long(indexid, key, value): """ @brief Select the message subset with key==value. The value is an integer. The key must have been created with integer type or have integer as native type if the type was not explicitly defined in the index creation. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key to be selected @param value value of the key to select @exception GribInternalError """ iid = get_index(indexid) GRIB_CHECK(lib.grib_index_select_long(iid, key.encode(ENC), value)) @require(indexid=int, key=str, value=float) def grib_index_select_double(indexid, key, value): """ @brief Select the message subset with key==value. The value is a double. The key must have been created with integer type or have integer as native type if the type was not explicitly defined in the index creation. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key to be selected @param value value of the key to select @exception GribInternalError """ iid = get_index(indexid) GRIB_CHECK(lib.grib_index_select_double(iid, key.encode(ENC), value)) @require(indexid=int, key=str, value=str) def grib_index_select_string(indexid, key, value): """ @brief Select the message subset with key==value. The value is an integer. The key must have been created with string type or have string as native type if the type was not explicitly defined in the index creation. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key to be selected @param value value of the key to select @exception GribInternalError """ ih = get_index(indexid) GRIB_CHECK(lib.grib_index_select_string(ih, key.encode(ENC), value.encode(ENC))) @require(indexid=int) def grib_new_from_index(indexid): """ @brief Create a new handle from an index after having selected the key values. All the keys belonging to the index must be selected before calling this function. Successive calls to this function will return all the handles compatible with the constraints defined selecting the values of the index keys. The message can be accessed through its gribid and will be available until @ref grib_release is called. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. @return id of the message loaded in memory or None if end of index @exception GribInternalError """ ih = get_index(indexid) err, h = err_last(lib.grib_handle_new_from_index)(ih) if h == ffi.NULL or err == lib.GRIB_END_OF_INDEX: return None elif err: GRIB_CHECK(err) return None else: return put_handle(h) @require(msgid=int) def grib_get_message_size(msgid): """ @brief Get the size of a coded message. @param msgid id of the message loaded in memory @return size in bytes of the message @exception GribInternalError """ h = get_handle(msgid) size_p = ffi.new('size_t*') err = lib.grib_get_message_size(h, size_p) GRIB_CHECK(err) return size_p[0] @require(msgid=int) def grib_get_message_offset(msgid): """ @brief Get the offset of a coded message. @param msgid id of the message loaded in memory @return offset in bytes of the message @exception GribInternalError """ h = get_handle(msgid) offset_p = ffi.new('long int*') err = lib.grib_get_message_offset(h, offset_p) GRIB_CHECK(err) return offset_p[0] @require(msgid=int, key=str, index=int) def grib_get_double_element(msgid, key, index): """ @brief Get as double the i-th element of the "key" array. @param msgid id of the message loaded in memory @param key the key to be searched @param index zero based index of value to retrieve @return value @exception GribInternalError """ h = get_handle(msgid) value_p = ffi.new('double *') err = lib.grib_get_double_element(h, key.encode(ENC), index) GRIB_CHECK(err) return value_p[0] @require(msgid=int, key=str, indexes=(list, tuple)) def grib_get_double_elements(msgid, key, indexes): """ @brief Get as double array the elements of the "key" array whose indexes are listed in the input array. @param msgid id of the message loaded in memory @param key the key to be searched @param indexes list or tuple of indexes @return numpy.ndarray @exception GribInternalError """ nidx = len(indexes) h = get_handle(msgid) i_p = ffi.new('int[]', indexes) value_p = ffi.new('double[]', nidx) err = lib.grib_get_double_elements(h, key.encode(ENC), i_p, nidx, value_p) GRIB_CHECK(err) return [float(v) for v in value_p] @require(msgid=int, key=str) def grib_get_elements(msgid, key, indexes): """ @brief Retrieve the elements of the key array for the indexes specified in the input. @param msgid id of the message loaded in memory @param key the key to be searched @param indexes single index or a list of indexes @return numpy.ndarray containing the values of key for the given indexes @exception GribInternalError """ try: iter(indexes) except TypeError: indexes = (indexes,) return grib_get_double_elements(msgid, key, indexes) @require(msgid=int, key=str) def grib_set_missing(msgid, key): """ @brief Set as missing the value for a key in a GRIB message. It can be used to set a missing value in the GRIB header but not in the data values. \b Examples: \ref grib_set_missing.py "grib_set_missing.py" @param msgid id of the message loaded in memory @param key key name @exception GribInternalError """ h = get_handle(msgid) GRIB_CHECK(lib.grib_set_missing(h, key.encode(ENC))) @require(gribid=int) def grib_set_key_vals(gribid, key_vals): """ Set the values for several keys at once in a grib message. @param gribid id of the grib loaded in memory @param key_vals can be a string, list/tuple or dictionary. If a string, format must be "key1=val1,key2=val2" If a list, it must contain strings of the form "key1=val1" @exception GribInternalError """ if len(key_vals) == 0: raise errors.InvalidKeyValueError("Empty key/values argument") key_vals_str = "" if isinstance(key_vals, str): # Plain string. We need to do a DEEP copy so as not to change the original key_vals_str = ''.join(key_vals) elif isinstance(key_vals, (list, tuple)): # A list of key=val strings for kv in key_vals: if not isinstance(kv, str): raise TypeError("Invalid list/tuple element type '%s'" % kv) if '=' not in str(kv): raise errors.GribInternalError("Invalid list/tuple element format '%s'" % kv) if len(key_vals_str) > 0: key_vals_str += ',' key_vals_str += kv elif isinstance(key_vals, dict): # A dictionary mapping keys to values for key in key_vals.keys(): if len(key_vals_str) > 0: key_vals_str += ',' key_vals_str += key + '=' + str(key_vals[key]) else: raise TypeError("Invalid argument type") h = get_handle(gribid) values = ffi.new('grib_values[]', 1024) count_p = ffi.new('int*', 1000) err = lib.parse_keyval_string(ffi.NULL, key_vals_str.encode(ENC), 1, lib.GRIB_TYPE_UNDEFINED, values, count_p) GRIB_CHECK(err) err = lib.grib_set_values(h, values, count_p[0]) GRIB_CHECK(err) @require(msgid=int, key=str) def grib_is_missing(msgid, key): """ @brief Check if the value of a key is MISSING. The value of a key is considered as MISSING when all the bits assigned to it are set to 1. This is different from the actual key missing from the grib message. The value of a key MISSING has a special significance and that can be read about in the WMO documentation. @param msgid id of the message loaded in memory @param key key name @return 0->not missing, 1->missing @exception GribInternalError """ h = get_handle(msgid) err, value = err_last(lib.grib_is_missing)(h, key.encode(ENC)) GRIB_CHECK(err) return value @require(msgid=int, key=str) def grib_is_defined(msgid, key): """ @brief Check if a key is defined (exists) @param msgid id of the message loaded in memory @param key key name @return 0->not defined, 1->defined @exception GribInternalError """ h = get_handle(msgid) return lib.grib_is_defined(h, key.encode(ENC)) @require(gribid=int, inlat=(int, float), inlon=(int, float)) def grib_find_nearest(gribid, inlat, inlon, is_lsm=False, npoints=1): """ @brief Find the nearest grid point or the nearest four grid points to a given latitude/longitude. The number of nearest points returned can be controled through the npoints function argument. \b Examples: \ref grib_nearest.py "grib_nearest.py" @param gribid id of the grib loaded in memory @param inlat latitude of the point @param inlon longitude of the point @param is_lsm True if the nearest land point is required otherwise False. @param npoints 1 or 4 nearest grid points @return (npoints*(outlat,outlon,value,dist,index)) @exception GribInternalError """ h = get_handle(gribid) inlats_p = ffi.new('double*', inlat) inlons_p = ffi.new('double*', inlon) if npoints == 1: outlats_p = ffi.new('double[]', 1) outlons_p = ffi.new('double[]', 1) values_p = ffi.new('double[]', 1) distances_p = ffi.new('double[]', 1) indexes_p = ffi.new('int[]', 1) num_input_points = 1 # grib_nearest_find_multiple always returns ONE nearest neighbour err = lib.grib_nearest_find_multiple(h, is_lsm, inlats_p, inlons_p, num_input_points, outlats_p, outlons_p, values_p, distances_p, indexes_p) GRIB_CHECK(err) elif npoints == 4: outlats_p = ffi.new('double[]', npoints) outlons_p = ffi.new('double[]', npoints) values_p = ffi.new('double[]', npoints) distances_p = ffi.new('double[]', npoints) indexes_p = ffi.new('int[]', npoints) size = ffi.new('size_t *') err, nid = err_last(lib.grib_nearest_new)(h) GRIB_CHECK(err) flags = 0 err = lib.grib_nearest_find(nid, h, inlat, inlon, flags, outlats_p, outlons_p, values_p, distances_p, indexes_p, size) GRIB_CHECK(err) GRIB_CHECK(lib.grib_nearest_delete(nid)) else: raise ValueError("Invalid value for npoints. Expecting 1 or 4.") result = [] for i in range(npoints): result.append(Bunch(lat=outlats_p[i], lon=outlons_p[i], value=values_p[i], distance=distances_p[i], index=indexes_p[i])) return tuple(result) @require(msgid=int, key=str) def grib_get_native_type(msgid, key): """ @brief Retrieve the native type of a key. Possible values can be int, float or string. @param msgid id of the message loaded in memory @param key key we want to find out the type for @return type of key given as input or None if not determined @exception GribInternalError """ h = get_handle(msgid) itype_p = ffi.new('int*') err = lib.grib_get_native_type(h, key.encode(ENC), itype_p) GRIB_CHECK(err) if itype_p[0] in KEYTYPES: return KEYTYPES[itype_p[0]] else: return None @require(msgid=int, key=str) def grib_get(msgid, key, ktype=None): r""" @brief Get the value of a key in a message. The type of value returned depends on the native type of the requested key. The type of value returned can be forced by using the type argument of the function. The type argument can be int, float or str. The \em msgid references a message loaded in memory. \b Examples: \ref grib_get_keys.py "grib_get_keys.py", \ref grib_print_data.py "grib_print_data.py" @see grib_new_from_file, grib_release, grib_set @param msgid id of the message loaded in memory @param key key name @param ktype the type we want the output in (int, float or str), native type if not specified @return scalar value of key as int, float or str @exception GribInternalError """ if not key: raise ValueError("Invalid key name") if ktype is None: ktype = grib_get_native_type(msgid, key) result = None if ktype is int: result = grib_get_long(msgid, key) elif ktype is float: result = grib_get_double(msgid, key) elif ktype is str: result = grib_get_string(msgid, key) return result @require(msgid=int, key=str) def grib_get_array(msgid, key, ktype=None): """ @brief Get the contents of an array key. The type of the array returned depends on the native type of the requested key. For numeric data, the output array will be stored in a NumPy ndarray. The type of value returned can be forced by using the type argument of the function. The type argument can be int, float or string. @param msgid id of the message loaded in memory @param key the key to get the value for @param ktype the type we want the output in (can be int, float or string), native type if not specified @return numpy.ndarray @exception GribInternalError """ if ktype is None: ktype = grib_get_native_type(msgid, key) result = None if ktype is int: result = grib_get_long_array(msgid, key) elif ktype is float: result = grib_get_double_array(msgid, key) elif ktype is str: result = grib_get_string_array(msgid, key) return result @require(gribid=int) def grib_get_values(gribid): """ @brief Retrieve the contents of the 'values' key for a GRIB message. A NumPy ndarray containing the values in the GRIB message is returned. \b Examples: \ref grib_print_data.py "grib_print_data.py", \ref grib_samples.py "grib_samples.py" @param gribid id of the GRIB loaded in memory @return numpy.ndarray @exception GribInternalError """ return grib_get_double_array(gribid, "values") @require(gribid=int) def grib_set_values(gribid, values): """ @brief Set the contents of the 'values' key for a GRIB message. The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ... The wrapper will internally try to convert the input to a NumPy array before extracting its data and length. This is possible as NumPy allows the construction of arrays from arbitrary python sequences. The elements of the input sequence need to be convertible to a double. \b Examples: \ref grib_clone.py "grib_clone.py", \ref grib_samples.py "grib_samples.py" @param gribid id of the GRIB loaded in memory @param values array of values to set as tuple, list, array or numpy.ndarray """ grib_set_double_array(gribid, "values", values) @require(msgid=int, key=str) def grib_set(msgid, key, value): """ @brief Set the value for a scalar key in a message. The input value can be a python int, float or str. \b Examples: \ref grib_set_keys.py "grib_set_keys.py" @see grib_new_from_file, grib_release, grib_get @param msgid id of the message loaded in memory @param key key name @param value scalar value to set for key @exception GribInternalError """ if isinstance(value, int): grib_set_long(msgid, key, value) elif isinstance(value, float): grib_set_double(msgid, key, value) elif isinstance(value, str): grib_set_string(msgid, key, value) # elif hasattr(value, "__iter__"): # # The value passed in is iterable; i.e. a list or array etc # grib_set_array(msgid, key, value) else: raise errors.GribInternalError("Invalid type of value when setting key '%s'." % key) @require(msgid=int, key=str) def grib_set_array(msgid, key, value): """ @brief Set the value for an array key in a message. Examples of array keys: "values" - data values "pl" - list of number of points for each latitude in a reduced grid "pv" - list of vertical levels The input array can be a numpy.ndarray or a python sequence like tuple, list, array, ... The wrapper will internally try to convert the input to a NumPy array before extracting its data and length. This is possible as NumPy allows the construction of arrays from arbitrary python sequences. @param msgid id of the message loaded in memory @param key key name @param value array to set for key @exception GribInternalError """ val0 = None try: val0 = value[0] except TypeError: pass if isinstance(val0, float): grib_set_double_array(msgid, key, value) elif isinstance(val0, str): grib_set_string_array(msgid, key, value) else: # Note: Cannot do isinstance(val0,int) for numpy.int64 try: int(val0) except (ValueError, TypeError): raise errors.GribInternalError("Invalid type of value when setting key '%s'." % key) grib_set_long_array(msgid, key, value) @require(indexid=int, key=str) def grib_index_get(indexid, key, ktype=str): """ @brief Get the distinct values of an index key. The key must belong to the index. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created on the given key. @param key key for which the values are returned @param ktype the type we want the output in (int, float or str), str if not specified @return array of values @exception GribInternalError """ # Cannot get the native type of a key from an index # so right now the default is str. The user can overwrite # the type but there is no way right now to do it automatically. # if ktype is None: # ktype = grib_get_native_type(indexid,key) result = None if ktype is int: result = grib_index_get_long(indexid, key) elif ktype is float: result = grib_index_get_double(indexid, key) elif ktype is str: result = grib_index_get_string(indexid, key) return result @require(indexid=int, key=str) def grib_index_select(indexid, key, value): """ @brief Select the message subset with key==value. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of an index created from a file. The index must have been created with the key in argument. @param key key to be selected @param value value of the key to select @exception GribInternalError """ if isinstance(value, int): grib_index_select_long(indexid, key, value) elif isinstance(value, float): grib_index_select_double(indexid, key, value) elif isinstance(value, str): grib_index_select_string(indexid, key, value) else: raise errors.GribInternalError("Invalid type of value when setting key '%s'." % key) @require(indexid=int, filename=str) def grib_index_write(indexid, filename): """ @brief Write an index to a file for later reuse. An index can be loaded back from an index file with \ref grib_index_read. \b Examples: \ref grib_index.py "grib_index.py" @param indexid id of the index @param filename path of file to save the index to @exception GribInternalError """ ih = get_index(indexid) GRIB_CHECK(lib.grib_index_write(ih, filename.encode(ENC))) @require(filename=str) def grib_index_read(filename): """ @brief Loads an index previously saved with \ref grib_index_write to a file. \b Examples: \ref grib_index.py "grib_index.py" @param filename path of file to load the index from @return id of the loaded index @exception GribInternalError """ err, ih = err_last(lib.grib_index_read)(ffi.NULL, filename.encode(ENC)) GRIB_CHECK(err) return put_index(ih) @require(flag=bool) def grib_no_fail_on_wrong_length(flag): """ @brief Do not fail if the message has the wrong length. @param flag True/False """ raise NotImplementedError("API not implemented in CFFI porting.") @require(flag=bool) def grib_gts_header(flag): """ @brief Set the GTS header on/off. @param flag True/False """ context = lib.grib_context_get_default() if flag: lib.grib_gts_header_on(context) else: lib.grib_gts_header_off(context) def grib_get_api_version(): """ @brief Get the API version. Returns the version of the API as a string in the format "major.minor.revision". """ def div(v, d): return (v / d, v % d) if not lib: raise RuntimeError("Could not load the ecCodes library!") v = lib.grib_get_api_version() v, revision = div(v, 100) v, minor = div(v, 100) major = v return "%d.%d.%d" % (major, minor, revision) __version__ = grib_get_api_version() @require(msgid=int) def grib_get_message(msgid): """ @brief Get the binary message. Returns the binary string message associated with the message identified by msgid. @see grib_new_from_message @param msgid id of the message loaded in memory @return binary string message associated with msgid @exception GribInternalError """ h = get_handle(msgid) message_p = ffi.new('const void**') message_length_p = ffi.new('size_t*') err = lib.grib_get_message(h, message_p, message_length_p) GRIB_CHECK(err) # NOTE: ffi.string would stop on the first nul-character. fixed_length_buffer = ffi.buffer(ffi.cast('char*', message_p[0]), message_length_p[0]) # Convert to bytes return fixed_length_buffer[:] @require(message=(bytes, str)) def grib_new_from_message(message): """ @brief Create a handle from a message in memory. Create a new message from the input binary string and return its id. @see grib_get_message @param message binary string message @return msgid of the newly created message @exception GribInternalError """ if isinstance(message, str): message = message.encode(ENC) h = lib.grib_handle_new_from_message_copy(ffi.NULL, message, len(message)) if h == ffi.NULL: raise errors.InvalidGribError return put_handle(h) @require(defs_path=str) def grib_set_definitions_path(defs_path): """ @brief Set the definitions path @param defs_path definitions path """ context = lib.grib_context_get_default() lib.grib_context_set_definitions_path(context, defs_path.encpde(ENC)) @require(samples_path=str) def grib_set_samples_path(samples_path): """ @brief Set the samples path @param samples_path samples path """ context = lib.grib_context_get_default() lib.grib_context_set_samples_path(context, samples_path.encode(ENC)) eccodes-python-0.9.7/gribapi/__init__.py0000644000175000017500000000016413521303732020363 0ustar alastairalastairfrom .gribapi import * # noqa from .gribapi import __version__ from .gribapi import bindings_version eccodes-python-0.9.7/setup.py0000644000175000017500000000505013521303732016346 0ustar alastairalastair#!/usr/bin/env python # # Copyright 2017-2019 European Centre for Medium-Range Weather Forecasts (ECMWF). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import io import os import re import setuptools def read(path): file_path = os.path.join(os.path.dirname(__file__), *path.split('/')) return io.open(file_path, encoding='utf-8').read() # single-sourcing the package version using method 1 of: # https://packaging.python.org/guides/single-sourcing-package-version/ def parse_version_from(path): version_file = read(path) version_match = re.search(r"^__version__ = '(.*)'", version_file, re.M) if version_match is None or len(version_match.groups()) > 1: raise ValueError("couldn't parse version") return version_match.group(1) setuptools.setup( name='eccodes-python', version=parse_version_from('gribapi/bindings.py'), description='Python interface to the ecCodes GRIB and BUFR decoder/encoder', long_description=read('README.rst') + read('CHANGELOG.rst'), author='European Centre for Medium-Range Weather Forecasts (ECMWF)', author_email='software.support@ecmwf.int', license='Apache License Version 2.0', url='https://github.com/ecmwf/eccodes-python', packages=setuptools.find_packages(), include_package_data=True, install_requires=[ 'attrs', 'cffi', 'numpy', ], tests_require=[ 'pytest', 'pytest-cov', 'pytest-flakes', ], test_suite='tests', zip_safe=True, keywords='ecCodes GRIB BUFR', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Operating System :: OS Independent', ], ) eccodes-python-0.9.7/ci/0000755000175000017500000000000013521303732015227 5ustar alastairalastaireccodes-python-0.9.7/ci/requirements-tests.txt0000644000175000017500000000157613521303732021664 0ustar alastairalastair# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file ci/requirements-tests.txt setup.py ci/requirements-tests.in # apipkg==1.5 # via execnet atomicwrites==1.3.0 # via pytest attrs==19.1.0 cffi==1.12.2 coverage==4.5.3 # via pytest-cov execnet==1.5.0 # via pytest-cache future==0.17.1 mccabe==0.6.1 # via pytest-mccabe more-itertools==5.0.0 # via pytest numpy==1.16.2 pep8==1.7.1 # via pytest-pep8 pluggy==0.9.0 # via pytest py==1.8.0 # via pytest pycparser==2.19 # via cffi pyflakes==2.1.1 # via pytest-flakes pytest-cache==1.0 # via pytest-mccabe, pytest-pep8 pytest-cov==2.6.1 pytest-flakes==4.0.0 pytest-mccabe==0.1 pytest-pep8==1.0.6 pytest-runner==4.4 pytest==4.3.1 six==1.12.0 # via pytest typing==3.6.6 eccodes-python-0.9.7/ci/requirements-tests.in0000644000175000017500000000011013521303732021432 0ustar alastairalastairpytest pytest-cov pytest-flakes pytest-mccabe pytest-pep8 pytest-runner eccodes-python-0.9.7/ci/requirements-py36.yml0000644000175000017500000000025513521303732021276 0ustar alastairalastairname: test_env channels: - conda-forge dependencies: - attrs - cffi - coveralls - eccodes - future - numpy - pytest - pytest-cov - python=3.6 - typing eccodes-python-0.9.7/ci/requirements-py36-qc.yml0000644000175000017500000000034113521303732021673 0ustar alastairalastairname: test_env channels: - conda-forge dependencies: - attrs - cffi - coveralls - eccodes - future - numpy - pytest - pytest-cov - pytest-flakes - pytest-mccabe - pytest-pep8 - python=3.6 - typing eccodes-python-0.9.7/ci/requirements-py37.yml0000644000175000017500000000027713521303732021303 0ustar alastairalastairname: test_env channels: - conda-forge dependencies: - attrs - cffi - coveralls - eccodes - future - numpy - pytest - pytest-cov - pytest-flakes - python=3.7 - typing eccodes-python-0.9.7/ci/requirements-docs.in0000644000175000017500000000002513521303732021225 0ustar alastairalastairSphinx pytest-runner eccodes-python-0.9.7/ci/install_python.ps10000644000175000017500000000611613521303732020727 0ustar alastairalastair# Sample script to install Python and pip under Windows # Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ $MINICONDA_URL = "https://repo.anaconda.com/miniconda/" $BASE_URL = "https://www.python.org/ftp/python/" function DownloadMiniconda ($python_version, $platform_suffix) { $webclient = New-Object System.Net.WebClient if ($python_version -match "2.7") { $filename = "Miniconda2-latest-Windows-" + $platform_suffix + ".exe" } else { $filename = "Miniconda3-latest-Windows-" + $platform_suffix + ".exe" } $url = $MINICONDA_URL + $filename $basedir = $pwd.Path + "\" $filepath = $basedir + $filename if (Test-Path $filename) { Write-Host "Reusing" $filepath return $filepath } # Download and retry up to 3 times in case of network transient errors. Write-Host "Downloading" $filename "from" $url $retry_attempts = 2 for($i=0; $i -lt $retry_attempts; $i++){ try { $webclient.DownloadFile($url, $filepath) break } Catch [Exception]{ Start-Sleep 1 } } if (Test-Path $filepath) { Write-Host "File saved at" $filepath } else { # Retry once to get the error message if any at the last try $webclient.DownloadFile($url, $filepath) } return $filepath } function InstallMiniconda ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -match "32") { $platform_suffix = "x86" } else { $platform_suffix = "x86_64" } $filepath = DownloadMiniconda $python_version $platform_suffix Write-Host "Installing" $filepath "to" $python_home $install_log = $python_home + ".log" $args = "/S /D=$python_home" Write-Host $filepath $args Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallCondaPackages ($python_home, $spec) { $conda_path = $python_home + "\Scripts\conda.exe" $args = "install --yes " + $spec Write-Host ("conda " + $args) Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } function UpdateConda ($python_home) { $conda_path = $python_home + "\Scripts\conda.exe" Write-Host "Updating conda..." $args = "update --yes conda" Write-Host $conda_path $args Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } function main () { InstallMiniconda $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON UpdateConda $env:PYTHON InstallCondaPackages $env:PYTHON "conda-build jinja2 anaconda-client" } main eccodes-python-0.9.7/ci/requirements-docs.txt0000644000175000017500000000200313521303732021434 0ustar alastairalastair# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file ci/requirements-docs.txt setup.py ci/requirements-docs.in # alabaster==0.7.12 # via sphinx attrs==19.1.0 babel==2.6.0 # via sphinx certifi==2019.3.9 # via requests cffi==1.12.2 chardet==3.0.4 # via requests docutils==0.14 # via sphinx future==0.17.1 idna==2.8 # via requests imagesize==1.1.0 # via sphinx jinja2==2.10.1 # via sphinx markupsafe==1.1.1 # via jinja2 numpy==1.16.2 packaging==19.0 # via sphinx pycparser==2.19 # via cffi pygments==2.3.1 # via sphinx pyparsing==2.3.1 # via packaging pytest-runner==4.4 pytz==2018.9 # via babel requests==2.21.0 # via sphinx six==1.12.0 # via packaging, sphinx snowballstemmer==1.2.1 # via sphinx sphinx==1.8.5 sphinxcontrib-websupport==1.1.0 # via sphinx typing==3.6.6 urllib3>=1.24.2 # via requests eccodes-python-0.9.7/ci/requirements-dev.txt0000644000175000017500000000017313521303732021270 0ustar alastairalastaircheck-manifest detox IPython matplotlib notebook pip-tools pyroma pytest-mypy setuptools tox tox-pyenv wheel zest.releaser eccodes-python-0.9.7/ci/requirements-docs.yml0000644000175000017500000000017213521303732021423 0ustar alastairalastairname: test_env channels: - conda-forge dependencies: - attrs - cffi - eccodes - numpy - python=3.6 - sphinx eccodes-python-0.9.7/LICENSE0000644000175000017500000002613513521303732015650 0ustar alastairalastair Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. eccodes-python-0.9.7/eccodes_python.egg-info/0000755000175000017500000000000013521303732021334 5ustar alastairalastaireccodes-python-0.9.7/eccodes_python.egg-info/PKG-INFO0000644000175000017500000001537313521303732022442 0ustar alastairalastairMetadata-Version: 1.1 Name: eccodes-python Version: 0.9.2 Summary: Python interface to the ecCodes GRIB and BUFR decoder/encoder Home-page: https://github.com/ecmwf/eccodes-python Author: European Centre for Medium-Range Weather Forecasts (ECMWF) Author-email: software.support@ecmwf.int License: Apache License Version 2.0 Description: Python 3 interface to encode and decode GRIB and BUFR files via the `ECMWF ecCodes library `_. Features: - reads and writes GRIB 1 and 2 files, - reads and writes BUFR 3 and 4 files, - supports all modern versions of Python 3.7, 3.6, 3.5 and PyPy3, - works on most *Linux* distributions and *MacOS*, the *ecCodes* C-library is the only system dependency, - PyPI package can be installed without compiling, at the cost of being twice as slow as the original *ecCodes* module, - an optional compile step makes the code as fast as the original module but it needs a recent version of *ecCodes* `>= 2.13.0`. Limitations: - Microsoft Windows support is untested. Installation ============ The package is installed from PyPI with:: $ pip install eccodes-python System dependencies ------------------- The Python module depends on the ECMWF *ecCodes* library that must be installed on the system and accessible as a shared library. On a MacOS with HomeBrew use:: $ brew install eccodes Or if you manage binary packages with *Conda* use:: $ conda install -c conda-forge eccodes As an alternative you may install the official source distribution by following the instructions at https://software.ecmwf.int/wiki/display/ECC/ecCodes+installation You may run a simple selfcheck command to ensure that your system is set up correctly:: $ python -m eccodes selfcheck Found: ecCodes v2.13.0. Your system is ready. Usage ----- Refer to the *ecCodes* `documentation pages `_ for usage. Experimental features ===================== Fast bindings ------------- To test the much faster *CFFI* API level, out-of-line mode you need the *ecCodes* header files. Then you need to clone the repo in the same folder as your *ecCodes* source tree, make a ``pip`` development install and custom compile the binary bindings:: $ git clone https://github.com/ecmwf/eccodes-python $ cd eccodes-python $ pip install -e . $ python builder.py To revert back to ABI level, in-line more just remove the compiled bindings:: $ rm gribapi/_bindings.* Project resources ================= ============= ========================================================= Development https://github.com/ecmwf/eccodes-python Download https://pypi.org/project/eccodes-python Code quality .. image:: https://api.travis-ci.org/ecmwf/eccodes-python.svg?branch=master :target: https://travis-ci.org/ecmwf/eccodes-python/branches :alt: Build Status on Travis CI .. image:: https://coveralls.io/repos/ecmwf/eccodes-python/badge.svg?branch=master&service=github :target: https://coveralls.io/github/ecmwf/eccodes-python :alt: Coverage Status on Coveralls ============= ========================================================= Contributing ============ The main repository is hosted on GitHub, testing, bug reports and contributions are highly welcomed and appreciated: https://github.com/ecmwf/eccodes-python Please see the CONTRIBUTING.rst document for the best way to help. Lead developer: - `Alessandro Amici `_ - `B-Open `_ See also the list of `contributors `_ who participated in this project. License ======= Copyright 2017-2019 European Centre for Medium-Range Weather Forecasts (ECMWF). Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Changelog for eccodes-python ============================ 0.9.2 (2019-07-09) ------------------ - All ecCodes tests now pass - Simplify the xx_new_from_file calls - Fix for grib_set_string_array - Use ECCODES_DIR to locate the library - Remove the new-style high-level interface. It is still available in `cfgrib `_. 0.9.1 (2019-06-06) ------------------ - ``codes_get_long_array`` and ``codes_get_double_array`` now return a ``np.ndarray``. See: `#3 `_. 0.9.0 (2019-05-07) ------------------ - Declare the project as **Beta**. 0.8.0 (2019-04-08) ------------------ - First public release. Keywords: ecCodes GRIB BUFR Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: OS Independent eccodes-python-0.9.7/eccodes_python.egg-info/zip-safe0000644000175000017500000000000113521303732022764 0ustar alastairalastair eccodes-python-0.9.7/eccodes_python.egg-info/top_level.txt0000644000175000017500000000002013521303732024056 0ustar alastairalastaireccodes gribapi eccodes-python-0.9.7/eccodes_python.egg-info/dependency_links.txt0000644000175000017500000000000113521303732025402 0ustar alastairalastair eccodes-python-0.9.7/eccodes_python.egg-info/SOURCES.txt0000644000175000017500000000211013521303732023212 0ustar alastairalastair.dockerignore .travis.yml CHANGELOG.rst CONTRIBUTING.rst Dockerfile LICENSE MANIFEST.in Makefile README.rst appveyor.yml builder.py setup.cfg setup.py tox.ini ci/install_python.ps1 ci/requirements-dev.txt ci/requirements-docs.in ci/requirements-docs.txt ci/requirements-docs.yml ci/requirements-py36-qc.yml ci/requirements-py36.yml ci/requirements-py37.yml ci/requirements-tests.in ci/requirements-tests.txt docs/conf.py docs/index.rst docs/_static/.gitkeep eccodes/__init__.py eccodes/__main__.py eccodes/eccodes.py eccodes/high_level/__init__.py eccodes/high_level/bufr.py eccodes/high_level/codesfile.py eccodes/high_level/codesmessage.py eccodes/high_level/gribfile.py eccodes/high_level/gribindex.py eccodes/high_level/gribmessage.py eccodes_python.egg-info/PKG-INFO eccodes_python.egg-info/SOURCES.txt eccodes_python.egg-info/dependency_links.txt eccodes_python.egg-info/requires.txt eccodes_python.egg-info/top_level.txt eccodes_python.egg-info/zip-safe gribapi/__init__.py gribapi/bindings.py gribapi/eccodes.h gribapi/errors.py gribapi/grib_api.h gribapi/gribapi.py tests/test_20_main.pyeccodes-python-0.9.7/eccodes_python.egg-info/requires.txt0000644000175000017500000000002113521303732023725 0ustar alastairalastairattrs cffi numpy eccodes-python-0.9.7/README.rst0000644000175000017500000000770313521303732016332 0ustar alastairalastair Python 3 interface to encode and decode GRIB and BUFR files via the `ECMWF ecCodes library `_. Features: - reads and writes GRIB 1 and 2 files, - reads and writes BUFR 3 and 4 files, - supports all modern versions of Python 3.7, 3.6, 3.5 and PyPy3, - works on most *Linux* distributions and *MacOS*, the *ecCodes* C-library is the only system dependency, - PyPI package can be installed without compiling, at the cost of being twice as slow as the original *ecCodes* module, - an optional compile step makes the code as fast as the original module but it needs a recent version of *ecCodes* `>= 2.13.0`. Limitations: - Microsoft Windows support is untested. Installation ============ The package is installed from PyPI with:: $ pip install eccodes-python System dependencies ------------------- The Python module depends on the ECMWF *ecCodes* library that must be installed on the system and accessible as a shared library. On a MacOS with HomeBrew use:: $ brew install eccodes Or if you manage binary packages with *Conda* use:: $ conda install -c conda-forge eccodes As an alternative you may install the official source distribution by following the instructions at https://software.ecmwf.int/wiki/display/ECC/ecCodes+installation You may run a simple selfcheck command to ensure that your system is set up correctly:: $ python -m eccodes selfcheck Found: ecCodes v2.13.0. Your system is ready. Usage ----- Refer to the *ecCodes* `documentation pages `_ for usage. Experimental features ===================== Fast bindings ------------- To test the much faster *CFFI* API level, out-of-line mode you need the *ecCodes* header files. Then you need to clone the repo in the same folder as your *ecCodes* source tree, make a ``pip`` development install and custom compile the binary bindings:: $ git clone https://github.com/ecmwf/eccodes-python $ cd eccodes-python $ pip install -e . $ python builder.py To revert back to ABI level, in-line more just remove the compiled bindings:: $ rm gribapi/_bindings.* Project resources ================= ============= ========================================================= Development https://github.com/ecmwf/eccodes-python Download https://pypi.org/project/eccodes-python Code quality .. image:: https://api.travis-ci.org/ecmwf/eccodes-python.svg?branch=master :target: https://travis-ci.org/ecmwf/eccodes-python/branches :alt: Build Status on Travis CI .. image:: https://coveralls.io/repos/ecmwf/eccodes-python/badge.svg?branch=master&service=github :target: https://coveralls.io/github/ecmwf/eccodes-python :alt: Coverage Status on Coveralls ============= ========================================================= Contributing ============ The main repository is hosted on GitHub, testing, bug reports and contributions are highly welcomed and appreciated: https://github.com/ecmwf/eccodes-python Please see the CONTRIBUTING.rst document for the best way to help. Lead developer: - `Alessandro Amici `_ - `B-Open `_ See also the list of `contributors `_ who participated in this project. License ======= Copyright 2017-2019 European Centre for Medium-Range Weather Forecasts (ECMWF). Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.