Skip to content

Commit

Permalink
Add support for GSF v3.09 (#125)
Browse files Browse the repository at this point in the history
* Add support for GSF v3.09

* Also introduces a pattern for supporting future new versions of GSF.
* Default supported version in the top level gsfpy namespace remains as
  v3.08 for backwards compatibility.
* Provides option for changing default version in top level namespace via
  a DEFAULT_GSF_VERSION environment variable.
* Provides version-specific gsfpy3_08 and gsfpy3_09 namespaces as alternative
  to defining the default GSF version at the top level.

* Include embedded shared object libraries

* Fix typo in CONTRIBUTING.md

* Additional test for GSF version set via environment

* Add test coverage reporting

* Note that the top-level gsfpy namespace is not included in coverage reporting
  as it simply acts as a switch between gsfpy3_08 and gsfpy3_09.

* Additional tests

* Update code examples in README

* Set version to 2.0.0

* Reflect module structure of subpackages in top level namespace

* For improved backward compatibility it must be possible to do
  imports of the form 'from gsfpy.enums import ...'

* Mirror all version specific submodules using generic mechanism

* In order that imports of the form 'from gsfpy.gsfRecords import ...'
  can be used with the version-specific implementations of the package
  it is necessary to mirror those implementations from the top level
  gsfpy namespace. This is now done in a generic way using dynamic
  import, so that future versions of GSF will also be supported without
  any code changes in the top-level gsfpy package being necessary.

* Remove unneeded global

* Fix paths in README

* Fix comment in README

* Rename parameter for improved clarity

* Tidy up test data and associated documentation

* Add comment to Makefile to explain multiple pytest runs
  • Loading branch information
lmarsden authored Mar 3, 2021
1 parent b96bc04 commit 5ec4996
Show file tree
Hide file tree
Showing 123 changed files with 8,414 additions and 3,033 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
build
_build
.cache
*.so

# Installer logs
pip-log.txt
Expand Down Expand Up @@ -38,6 +37,8 @@ MANIFEST.in
pip-wheel-metadata
/poetry.toml

notebooks/

# gsf
*.nsf
*.mbn21
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# History

## 2.0.0 (2021-02-24)
- Add support for GSF v3.09

## 1.6.0 (2020-06-24)
- Add environment variable `GSFPY_LIBGSF_PATH` to enable swapping out libgsf at runtime

Expand Down
21 changes: 15 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ checktypes: .venv ## check types with mypy
poetry run mypy --ignore-missing-imports gsfpy tests

checkstyle: .venv ## check style with flake8 and black
poetry run flake8 gsfpy tests
poetry run flake8 --ignore F401,F403,F405 gsfpy tests
poetry run isort --check-only --profile black gsfpy tests
poetry run black --check --diff gsfpy tests

Expand All @@ -67,11 +67,20 @@ sast: .venv ## run static application security testing
checklicenses: .venv requirements.txt ## check dependencies meet licence rules
poetry run liccheck -s liccheck.ini

test: .venv ## run tests quickly with the default Python
poetry run pytest tests/test_libgsf_load_valid.py --verbose --capture=no
poetry run pytest tests/test_libgsf_load_invalid.py --verbose --capture=no
poetry run pytest tests/test_libgsf_load_default.py --verbose --capture=no
poetry run pytest --ignore-glob=tests/test_libgsf_load_*.py --verbose --capture=no
## run tests quickly with the default Python
## Multiple pytest runs are necessary as once the gsfpy package has been loaded for a
## specific version of GSF, or with a custom shared object library, it cannot be unloaded.
test: .venv
poetry run pytest --ignore-glob=tests/gsfpy3_08/* --ignore-glob=tests/gsfpy3_09/* --ignore-glob=tests/gsfpy/test_gsffile_with_*.py --verbose --capture=no
poetry run pytest --ignore-glob=tests/gsfpy3_08/* --ignore-glob=tests/gsfpy3_09/* --ignore-glob=tests/gsfpy/test_gsffile.py --verbose --capture=no
poetry run pytest tests/gsfpy3_08/test_libgsf_load_valid.py --verbose --capture=no
poetry run pytest tests/gsfpy3_08/test_libgsf_load_invalid.py --verbose --capture=no
poetry run pytest tests/gsfpy3_08/test_libgsf_load_default.py --verbose --capture=no
poetry run pytest --ignore-glob=tests/gsfpy3_08/test_libgsf_load_*.py --ignore-glob=tests/gsfpy3_09/* --verbose --capture=no --cov=gsfpy3_08 --cov-fail-under=95 --cov-config=tox.ini
poetry run pytest tests/gsfpy3_09/test_libgsf_load_valid.py --verbose --capture=no
poetry run pytest tests/gsfpy3_09/test_libgsf_load_invalid.py --verbose --capture=no
poetry run pytest tests/gsfpy3_09/test_libgsf_load_default.py --verbose --capture=no
poetry run pytest --ignore-glob=tests/gsfpy3_09/test_libgsf_load_*.py --ignore-glob=tests/gsfpy3_08/* --verbose --capture=no --cov=gsfpy3_09 --cov-fail-under=95 --cov-config=tox.ini

test-all: .venv ## run tests on every Python version with tox
poetry run tox
Expand Down
63 changes: 35 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
Python wrapper for the C implementation of the Generic Sensor Format library.

- Free software: MIT license
- __Notes on licensing__: The bundled `libgsf/libgsf03_08.so` is covered by the [LGPL v2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) license. A copy of this license is included in the project at `libgsf/libgsf_LICENSE.md`. This does not affect the top-level MIT licensing of the project. However, as required by the libgsf license, the libgsf shared object library used by gsfpy at runtime may be replaced with a different version by setting the `GSFPY_LIBGSF_PATH` environment variable to the absolute file path of the new library.
- __Notes on licensing__: The bundled `gsfpy3_0x/libgsf/libgsf03_0x.so` binaries are covered by the [LGPL v2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) license. Copies of this license are included in the project at `gsfpy3_0x/libgsf/libgsf_LICENSE.md`. The top-level MIT licensing of the overall `gsfpy` project is not affected by this. However, as required by the libgsf license, the libgsf shared object libraries used by the `gsfpy3_0x` packages at runtime may be replaced with a different version by setting the `GSFPY3_08_LIBGSF_PATH` and/or `GSFPY3_09_LIBGSF_PATH` environment variables to the absolute file path of the new library.

## Namespaces and supported GSF versions
The `gsfpy` package provides three namespaces: `gsfpy`, `gsfpy3_08` and `gsfpy3_09`.

The default version of GSF supported is `3.08`. Top level package functionality for `3.08` can be used either via `import gsfpy` (without setting the `DEFAULT_GSF_VERSION` environment variable - see below) or `import gsfpy3_08`. Note that `import gsfpy` will also work for versions 3.06 and 3.07 of GSF as well (older versions have not been tested).

If you are using GSF v3.09, there are two options:
* Set the `DEFAULT_GSF_VERSION` environment variable to `"3.09"`, then `import gsfpy`
* Import the 3.09 package directly with `import gsfpy3_09`


## Features

- gsfpy.bindings provides wrappers for all GSFlib functions, including I/O, utility and info functions.
- The `gsfpy(3_0x).bindings` modules provide wrappers for all GSFlib functions, including I/O, utility and info functions.
Minor exceptions are noted in the sections below.

- For added convenience the gsfpy top level package provides the following higher level abstractions:
Expand Down Expand Up @@ -38,13 +48,13 @@ pip install git+https://github.com/UKHO/gsfpy.git@master

## Examples of usage

### Open/close/read from a GSF file
### Open/close/read from a GSF file (GSF v3.08)

```python
from ctypes import string_at

from gsfpy import open_gsf
from gsfpy.enums import RecordType
from gsfpy3_08 import open_gsf
from gsfpy3_08.enums import RecordType

with open_gsf("path/to/file.gsf") as gsf_file:
# Note - file is closed automatically upon exiting 'with' block
Expand All @@ -55,16 +65,16 @@ with open_gsf("path/to/file.gsf") as gsf_file:
print(string_at(record.comment.comment))
```

### Write to a GSF file
### Write to a GSF file (GSF v3.09)

```python
from ctypes import c_int, create_string_buffer

from gsfpy import open_gsf
from gsfpy.enums import FileMode, RecordType
from gsfpy.gsfRecords import c_gsfRecords
from gsfpy3_09 import open_gsf
from gsfpy3_09.enums import FileMode, RecordType
from gsfpy3_09.gsfRecords import c_gsfRecords

comment = "My comment"
comment = b"My comment"

# Initialize the contents of the record that will be written.
# Note use of ctypes.create_string_buffer() to set POINTER(c_char) contents.
Expand All @@ -77,32 +87,29 @@ with open_gsf("path/to/file.gsf", mode=FileMode.GSF_CREATE) as gsf_file:
gsf_file.write(record, RecordType.GSF_RECORD_COMMENT)
```

### Copy GSF records
### Copy GSF records (GSF v3.08 as default)

```python
from ctypes import byref, c_int, pointer

import gsfpy
from gsfpy.enums import FileMode, RecordType
from gsfpy.gsfDataID import c_gsfDataID
from gsfpy.gsfRecords import c_gsfRecords
from gsfpy import *


# This example uses gsfpy.bindings to illustrate use of the lower level functions
# This example uses the bindings module to illustrate use of the lower level functions
file_handle = c_int(0)
data_id = c_gsfDataID()
source_records = c_gsfRecords()
target_records = c_gsfRecords()
data_id = gsfDataID.c_gsfDataID()
source_records = gsfRecords.c_gsfRecords()
target_records = gsfRecords.c_gsfRecords()

ret_val_open = gsfpy.bindings.gsfOpen(
"path/to/file.gsf", FileMode.GSF_READONLY, byref(file_handle)
ret_val_open = bindings.gsfOpen(
b"path/to/file.gsf", enums.FileMode.GSF_READONLY, byref(file_handle)
)

# Note use of ctypes.byref() as a shorthand way of passing POINTER parameters to
# the underlying foreign function call. ctypes.pointer() may also be used.
bytes_read = gsfpy.bindings.gsfRead(
file_handle,
RecordType.GSF_RECORD_COMMENT,
enums.RecordType.GSF_RECORD_COMMENT,
byref(data_id),
byref(source_records),
)
Expand All @@ -112,23 +119,23 @@ bytes_read = gsfpy.bindings.gsfRead(
# the native underlying function causes memory ownership clashes. byref()
# is only suitable for passing parameters to foreign function calls (see
# ctypes docs).
ret_val_cpy = gsfpy.bindings.gsfCopyRecords(
ret_val_cpy = bindings.gsfCopyRecords(
pointer(target_records), pointer(source_records)
)
ret_val_close = gsfpy.bindings.gsfClose(file_handle)
ret_val_close = bindings.gsfClose(file_handle)
```

### Troubleshoot

```python
import gsfpy
from gsfpy3_09.bindings import gsfIntError, gsfStringError

# The gsfIntError() and gsfStringError() functions are useful for
# diagnostics. They return an error code and corresponding error
# message, respectively.
retValIntError = gsfpy.bindings.gsfIntError()
retValStringError = gsfpy.bindings.gsfStringError()
print(retValStringError)
retValIntError = gsfIntError()
retValStringError = gsfStringError()
print(retValIntError, retValStringError)
```

## Notes on implementation
Expand Down
10 changes: 2 additions & 8 deletions gsfpy/GSF_POSITION.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
from ctypes import Structure, c_double
from gsfpy import mirror_default_gsf_version_submodule


class c_GSF_POSITION(Structure):
_fields_ = [
("lon", c_double),
("lat", c_double),
("z", c_double),
]
mirror_default_gsf_version_submodule(globals(), "GSF_POSITION")
12 changes: 2 additions & 10 deletions gsfpy/GSF_POSITION_OFFSETS.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
from ctypes import Structure, c_double
from gsfpy import mirror_default_gsf_version_submodule


# Note: the coordinate system is:
# +x forward, +y starboard, + z down, +hdg cw from north
class c_GSF_POSITION_OFFSETS(Structure):
_fields_ = [
("x", c_double),
("y", c_double),
("z", c_double),
]
mirror_default_gsf_version_submodule(globals(), "GSF_POSITION_OFFSETS")
Loading

0 comments on commit 5ec4996

Please sign in to comment.