diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b37b254..d7193d7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,6 +1,6 @@
name: Python package
-on:
+on:
push:
pull_request:
types: [opened, synchronize]
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
- python-version: ["3.6"]
+ python-version: ["3.7", "3.9", "3.12"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
@@ -20,8 +20,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pytest
- pip install .
+ pip install .[test,apps]
- name: Test with pytest
run: |
- pytest
+ coverage run
diff --git a/.gitignore b/.gitignore
index 1ec0199..756579b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,8 +9,9 @@ build/*
.eggs/*
SigMF.egg-info/*
-# pytest & coverage related
+# test related
.coverage
pytest.xml
coverage.xml
+.tox/
htmlcov/*
diff --git a/README.md b/README.md
index d423975..7d9f23d 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,21 @@
-
+
This python module makes it easy to interact with Signal Metadata Format
-(SigMF) objects. This module works with Python 3.6+ and is distributed freely
-under the terms GNU Lesser GPL v3 License.
+(SigMF) recordings. This module works with Python 3.7+ and is distributed
+freely under the terms GNU Lesser GPL v3 License.
The [SigMF specification document](https://github.com/sigmf/SigMF/blob/HEAD/sigmf-spec.md)
-is located in the [SigMF](https://github.com/gnuradio/SigMF) repository.
+is located in the [SigMF](https://github.com/sigmf/SigMF) repository.
# Installation
-To install the latest release, install from pip:
+To install the latest PyPi release, install from pip:
```bash
pip install sigmf
```
-To install the latest development version, build from source:
+To install the latest git release, build from source:
```bash
git clone https://github.com/sigmf/sigmf-python.git
@@ -23,12 +23,19 @@ cd sigmf-python
pip install .
```
-To run the included QA tests:
+Testing can be run with a variety of tools:
+
```bash
-# basic
-python3 -m pytest tests/
-# fancy
-coverage run --a --source sigmf -m pytest --doctest-modules
+# pytest and coverage run locally
+pytest
+coverage run
+# run coverage in a venv
+tox run
+# other useful tools
+pylint sigmf tests
+pytype
+black
+flake8
```
# Examples
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..15422cf
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,104 @@
+[project]
+name = "SigMF"
+description = "Easily interact with Signal Metadata Format (SigMF) recordings."
+keywords = ["gnuradio", "radio"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Topic :: Scientific/Engineering",
+ "Topic :: Communications :: Ham Radio",
+]
+dynamic = ["version", "readme"]
+requires-python = ">=3.7"
+dependencies = [
+ "numpy", # for vector math
+ "jsonschema", # for spec validation
+]
+ [project.urls]
+ repository = "https://github.com/sigmf/sigmf-python"
+ [project.scripts]
+ sigmf_validate = "sigmf.validate:main"
+ sigmf_gui = "sigmf.apps.gui:main [apps]"
+ sigmf_convert_wav = "sigmf.apps.convert_wav:main [apps]"
+ [project.optional-dependencies]
+ test = [
+ "pylint",
+ "pytest",
+ "pytest-cov",
+ "hypothesis", # next-gen testing framework
+ ]
+ apps = [
+ "scipy", # for wav i/o
+ # FIXME: PySimpleGUI 2024-02-12 v5.0.0 release seems to have a bug. Unpin version when possible.
+ "PySimpleGUI < 5.0.0", # for gui interface
+ ]
+
+[tool.setuptools]
+packages = ["sigmf"]
+ [tool.setuptools.dynamic]
+ version = {attr = "sigmf.__version__"}
+ readme = {file = ["README.md"], content-type = "text/markdown"}
+ [tool.setuptools.package-data]
+ sigmf = ["*.json"]
+
+[build-system]
+requires = ["setuptools>=65.0", "setuptools-scm"]
+build-backend = "setuptools.build_meta"
+
+[tool.coverage.run]
+branch = true
+source = ["sigmf", "tests"]
+# -rA captures stdout from all tests and places it after the pytest summary
+command_line = "-m pytest -rA --doctest-modules --junitxml=pytest.xml"
+
+[tool.pytest.ini_options]
+addopts = "--doctest-modules"
+
+[tool.pylint]
+ [tool.pylint.main]
+ load-plugins = [
+ "pylint.extensions.typing",
+ "pylint.extensions.docparams",
+ ]
+ exit-zero = true
+ [tool.pylint.messages_control]
+ disable = [
+ "logging-not-lazy",
+ "missing-module-docstring",
+ "import-error",
+ "unspecified-encoding",
+ ]
+ max-line-length = 120
+ [tool.pylint.REPORTS]
+ # omit from the similarity reports
+ ignore-comments = 'yes'
+ ignore-docstrings = 'yes'
+ ignore-imports = 'yes'
+ ignore-signatures = 'yes'
+ min-similarity-lines = 4
+
+[tool.pytype]
+inputs = ['sigmf', 'tests']
+
+[tool.black]
+line-length = 120
+
+[tool.tox]
+legacy_tox_ini = '''
+ [tox]
+ skip_missing_interpreters = True
+ envlist = py{37,38,39,310,311,312}
+
+ [testenv]
+ usedevelop = True
+ deps = .[test,apps]
+ commands = coverage run
+'''
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index b7e4789..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[aliases]
-test=pytest
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 1d43b36..0000000
--- a/setup.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python3
-from setuptools import setup
-import os
-import re
-
-short_description = "Python module for interacting with SigMF recordings."
-
-with open("README.md", encoding="utf-8") as handle:
- long_description = handle.read()
-
-with open(os.path.join("sigmf", "__init__.py"), encoding="utf-8") as handle:
- version = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', handle.read()).group(1)
-
-setup(
- name="SigMF",
- version=version,
- description=short_description,
- long_description=long_description,
- long_description_content_type="text/markdown",
- url="https://github.com/sigmf/sigmf-python",
- license="GNU Lesser General Public License v3 or later (LGPLv3+)",
- classifiers=[
- "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- ],
- entry_points={
- "console_scripts": [
- "sigmf_validate = sigmf.validate:main",
- "sigmf_gui = sigmf.gui:main [gui]",
- ]
- },
- packages=["sigmf"],
- package_data={
- "sigmf": ["*.json"],
- },
- install_requires=["numpy", "jsonschema"],
- extras_require={"gui": "pysimplegui==4.0.0"},
- setup_requires=["pytest-runner"],
- tests_require=["pytest>3", "hypothesis"],
- zip_safe=False,
-)
diff --git a/sigmf/__init__.py b/sigmf/__init__.py
index fa4275e..38eed7b 100644
--- a/sigmf/__init__.py
+++ b/sigmf/__init__.py
@@ -1,19 +1,22 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-__version__ = "1.1.5"
+# version of this python module
+__version__ = "1.2.1"
+# matching version of the SigMF specification
+__specification__ = "1.2.0"
from .archive import SigMFArchive
from .sigmffile import SigMFFile, SigMFCollection
from .archivereader import SigMFArchiveReader
from . import archive
+from . import archivereader
from . import error
from . import schema
from . import sigmffile
-from . import validate
from . import utils
-from . import archivereader
+from . import validate
diff --git a/sigmf/tools/__init__.py b/sigmf/apps/__init__.py
similarity index 100%
rename from sigmf/tools/__init__.py
rename to sigmf/apps/__init__.py
diff --git a/sigmf/apps/convert_wav.py b/sigmf/apps/convert_wav.py
new file mode 100755
index 0000000..9151d2f
--- /dev/null
+++ b/sigmf/apps/convert_wav.py
@@ -0,0 +1,93 @@
+# Copyright: Multiple Authors
+#
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""converter for wav containers"""
+
+import argparse
+import datetime
+import getpass
+import logging
+import os
+import pathlib
+import tempfile
+
+from scipy.io import wavfile
+
+from .. import SigMFFile, __specification__
+from .. import __version__ as toolversion
+from .. import archive
+from ..utils import get_data_type_str
+
+log = logging.getLogger()
+
+
+def convert_wav(input_wav_filename, archive_filename=None, start_datetime=None, author=None):
+ """
+ read a .wav and write a .sigmf archive
+ """
+ input_path = pathlib.Path(input_wav_filename)
+ input_stem = input_path.stem
+ samp_rate, wav_data = wavfile.read(input_wav_filename)
+
+ global_info = {
+ SigMFFile.AUTHOR_KEY: getpass.getuser() if author is None else author,
+ SigMFFile.DATATYPE_KEY: get_data_type_str(wav_data),
+ SigMFFile.DESCRIPTION_KEY: f"Converted from {input_wav_filename}",
+ SigMFFile.NUM_CHANNELS_KEY: 1 if len(wav_data.shape) < 2 else wav_data.shape[1],
+ SigMFFile.RECORDER_KEY: os.path.basename(__file__),
+ SigMFFile.SAMPLE_RATE_KEY: samp_rate,
+ SigMFFile.VERSION_KEY: __specification__,
+ }
+
+ if start_datetime is None:
+ mtime = datetime.datetime.fromtimestamp(input_path.stat().st_mtime)
+ start_datetime = mtime.isoformat() + "Z"
+
+ capture_info = {SigMFFile.START_INDEX_KEY: 0}
+ if start_datetime is not None:
+ capture_info[SigMFFile.DATETIME_KEY] = start_datetime
+
+ tmpdir = tempfile.mkdtemp()
+ sigmf_data_filename = input_stem + archive.SIGMF_DATASET_EXT
+ sigmf_data_path = os.path.join(tmpdir, sigmf_data_filename)
+ wav_data.tofile(sigmf_data_path)
+
+ meta = SigMFFile(data_file=sigmf_data_path, global_info=global_info)
+ meta.add_capture(0, metadata=capture_info)
+
+ if archive_filename is None:
+ archive_filename = input_stem + archive.SIGMF_ARCHIVE_EXT
+ meta.tofile(archive_filename, toarchive=True)
+ return os.path.abspath(archive_filename)
+
+
+def main():
+ """
+ entry-point for sigmf_convert_wav
+ """
+ parser = argparse.ArgumentParser(description="Convert .wav to .sigmf container.")
+ parser.add_argument("input", type=str, help="Wavfile path")
+ parser.add_argument("--author", type=str, default=None, help=f"set {SigMFFile.AUTHOR_KEY} metadata")
+ parser.add_argument('-v', '--verbose', action='count', default=0)
+ parser.add_argument('--version', action='version', version=f'%(prog)s v{toolversion}')
+ args = parser.parse_args()
+
+ level_lut = {
+ 0: logging.WARNING,
+ 1: logging.INFO,
+ 2: logging.DEBUG,
+ }
+ logging.basicConfig(level=level_lut[min(args.verbose, 2)])
+
+ out_fname = convert_wav(
+ input_wav_filename=args.input,
+ author=args.author,
+ )
+ log.info(f"Write {out_fname}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sigmf/gui.py b/sigmf/apps/gui.py
similarity index 98%
rename from sigmf/gui.py
rename to sigmf/apps/gui.py
index 2814f3e..dc6f74f 100644
--- a/sigmf/gui.py
+++ b/sigmf/apps/gui.py
@@ -1,17 +1,20 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
'''GUI for creating & editing SigMF Files'''
-import os
+import argparse
import logging
+import os
+
from PySimpleGUI import *
-from .sigmffile import SigMFFile, fromarchive, dtype_info
-from .archive import SIGMF_ARCHIVE_EXT
+from .. import __version__ as toolversion
+from ..archive import SIGMF_ARCHIVE_EXT
+from ..sigmffile import SigMFFile, dtype_info, fromarchive
log = logging.getLogger()
@@ -381,13 +384,10 @@ def add_capture(capture_data_input, values, capture_selector_dict, file_data, fr
def main():
- import argparse
- from sigmf import __version__ as toolversion
-
parser = argparse.ArgumentParser(description='Edit SigMF Archive.')
parser.add_argument('-i', '--input', help='Input SigMF Archive Path.', default=None)
parser.add_argument('-v', '--verbose', action='count', default=0)
- parser.add_argument('--version', action='version', version=f'%(prog)s {toolversion}')
+ parser.add_argument('--version', action='version', version=f'%(prog)s v{toolversion}')
args = parser.parse_args()
level_lut = {
@@ -638,3 +638,6 @@ def main():
break
window.Close()
+
+if __name__ == "__main__":
+ main()
diff --git a/sigmf/archive.py b/sigmf/archive.py
index e1be40f..29a0f56 100644
--- a/sigmf/archive.py
+++ b/sigmf/archive.py
@@ -1,6 +1,6 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
@@ -17,7 +17,6 @@
from .error import SigMFFileError
-
SIGMF_ARCHIVE_EXT = ".sigmf"
SIGMF_METADATA_EXT = ".sigmf-meta"
SIGMF_DATASET_EXT = ".sigmf-data"
diff --git a/sigmf/archivereader.py b/sigmf/archivereader.py
index b61d9d5..4b0522c 100644
--- a/sigmf/archivereader.py
+++ b/sigmf/archivereader.py
@@ -1,6 +1,6 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/gnuradio/SigMF
+# This file is part of sigmf-python. https://github.com/sigmf/SigMF
#
# SPDX-License-Identifier: LGPL-3.0-or-later
diff --git a/sigmf/error.py b/sigmf/error.py
index df4e2ae..92ac194 100644
--- a/sigmf/error.py
+++ b/sigmf/error.py
@@ -1,6 +1,6 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
diff --git a/sigmf/schema-collection.json b/sigmf/schema-collection.json
index b4540b5..96b28f3 100644
--- a/sigmf/schema-collection.json
+++ b/sigmf/schema-collection.json
@@ -1,5 +1,5 @@
{
- "$id": "https://github.com/gnuradio/SigMF",
+ "$id": "https://github.com/sigmf/SigMF",
"$schema": "http://json-schema.org/draft-07/schema",
"default": {},
"required": [
@@ -20,7 +20,7 @@
"$id": "#/properties/collection/properties/core%3Aversion",
"description": "The version of the SigMF specification used to create the Collection file.",
"examples": [
- "1.0.0"
+ "1.2.0"
],
"type": "string"
},
diff --git a/sigmf/schema-meta.json b/sigmf/schema-meta.json
index c3b7ad9..9e83cf3 100644
--- a/sigmf/schema-meta.json
+++ b/sigmf/schema-meta.json
@@ -1,5 +1,5 @@
{
- "$id": "https://github.com/gnuradio/SigMF",
+ "$id": "https://github.com/sigmf/SigMF",
"$schema": "http://json-schema.org/draft-07/schema",
"default": [
"global",
diff --git a/sigmf/schema.py b/sigmf/schema.py
index a1ca14f..a34c5d2 100644
--- a/sigmf/schema.py
+++ b/sigmf/schema.py
@@ -1,13 +1,13 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
'''Schema IO'''
-import os
import json
+import os
from . import utils
diff --git a/sigmf/sigmf_hash.py b/sigmf/sigmf_hash.py
index 5f07768..414dc36 100644
--- a/sigmf/sigmf_hash.py
+++ b/sigmf/sigmf_hash.py
@@ -1,6 +1,6 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
diff --git a/sigmf/sigmffile.py b/sigmf/sigmffile.py
index c6ca130..1ef5702 100644
--- a/sigmf/sigmffile.py
+++ b/sigmf/sigmffile.py
@@ -1,23 +1,26 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
'''SigMFFile Object'''
-from collections import OrderedDict
import codecs
import json
from os import path
import warnings
+from collections import OrderedDict
+from os import path
+
import numpy as np
-from . import schema, sigmf_hash, validate
-from .archive import SigMFArchive, SIGMF_DATASET_EXT, SIGMF_METADATA_EXT, SIGMF_ARCHIVE_EXT, SIGMF_COLLECTION_EXT
+from . import __specification__, schema, sigmf_hash, validate
+from .archive import SIGMF_ARCHIVE_EXT, SIGMF_COLLECTION_EXT, SIGMF_DATASET_EXT, SIGMF_METADATA_EXT, SigMFArchive
+from .error import SigMFAccessError, SigMFFileError
from .sigmffile_collection import AbstractSigMFFileCollection, SigMFFileCollection
from .utils import dict_merge
-from .error import SigMFFileError, SigMFAccessError
+
class SigMFMetafile():
VALID_KEYS = {}
@@ -197,6 +200,7 @@ def __init__(self,
if metadata is None:
self._metadata = {self.GLOBAL_KEY:{}, self.CAPTURE_KEY:[], self.ANNOTATION_KEY:[]}
self._metadata[self.GLOBAL_KEY][self.NUM_CHANNELS_KEY] = 1
+ self._metadata[self.GLOBAL_KEY][self.VERSION_KEY] = __specification__
elif isinstance(metadata, dict):
self._metadata = metadata
else:
@@ -205,9 +209,6 @@ def __init__(self,
self.set_global_info(global_info)
if data_file is not None:
self.set_data_file(data_file, skip_checksum=skip_checksum, map_readonly=map_readonly)
- self.name = name
-
- self._metadata[self.GLOBAL_KEY][self.VERSION_KEY] = '1.0.0'
def __len__(self):
return self._memmap.shape[0]
@@ -226,7 +227,7 @@ def __next__(self):
def __getitem__(self, sli):
mem = self._memmap[sli] # matches behavior of numpy.ndarray.__getitem__()
-
+
if self._return_type is None:
return mem
@@ -267,13 +268,15 @@ def get_num_channels(self):
def _is_conforming_dataset(self):
"""
- Returns `True` if the dataset is conforming to SigMF, `False` otherwise
-
The dataset is non-conforming if the datafile contains non-sample bytes
which means global trailing_bytes field is zero or not set, all captures
`header_bytes` fields are zero or not set. Because we do not necessarily
know the filename no means of verifying the meta/data filename roots
match, but this will also check that a data file exists.
+
+ Returns
+ -------
+ `True` if the dataset is conforming to SigMF, `False` otherwise
"""
if self.get_global_field(self.TRAILING_BYTES_KEY, 0):
return False
@@ -443,7 +446,7 @@ def get_annotations(self, index=None):
annotations = self._metadata.get(self.ANNOTATION_KEY, [])
if index is None:
return annotations
-
+
annotations_including_index = []
for annotation in annotations:
if index < annotation[self.START_INDEX_KEY]:
@@ -454,7 +457,7 @@ def get_annotations(self, index=None):
if index >= annotation[self.START_INDEX_KEY] + annotation[self.LENGTH_INDEX_KEY]:
# index is after annotation end -> skip
continue
-
+
annotations_including_index.append(annotation)
return annotations_including_index
@@ -786,8 +789,6 @@ def __init__(self, metafiles=None, metadata=None, skip_checksums=False):
if not self.skip_checksums:
self.verify_stream_hashes()
- self._metadata[self.COLLECTION_KEY][self.VERSION_KEY] = '1.0.0'
-
def __len__(self):
'''
the length of a collection is the number of streams
diff --git a/sigmf/tools/wav2sigmf.py b/sigmf/tools/wav2sigmf.py
deleted file mode 100755
index fae3f0d..0000000
--- a/sigmf/tools/wav2sigmf.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python3
-
-import os, tempfile
-from scipy.io import wavfile
-import sigmf
-from sigmf import SigMFFile, SigMFArchive
-from sigmf.utils import get_data_type_str
-
-def writeSigMFArchiveFromWave(input_wav_filename, archive_filename=None, start_datetime=None, author=None):
- samplerate, wav_data = wavfile.read(input_wav_filename)
-
- global_info = {
- SigMFFile.DATATYPE_KEY: get_data_type_str(wav_data),
- SigMFFile.SAMPLE_RATE_KEY: samplerate,
- SigMFFile.DESCRIPTION_KEY: 'Converted from ' + input_wav_filename + '.',
- SigMFFile.NUM_CHANNELS_KEY: 1 if len(wav_data.shape) < 2 else wav_data.shape[1],
- SigMFFile.RECORDER_KEY: os.path.basename(__file__),
- }
- if author is None:
- try:
- import getpass
- except:
- pass
- else:
- author = getpass.getuser()
- if author is not None:
- global_info[SigMFFile.AUTHOR_KEY]: author
-
- if start_datetime is None:
- import datetime, pathlib
- fname = pathlib.Path(input_wav_filename)
- mtime = datetime.datetime.fromtimestamp(fname.stat().st_mtime)
- start_datetime = mtime.isoformat() + 'Z'
-
- capture_info = {SigMFFile.START_INDEX_KEY: 0}
- if start_datetime is not None:
- capture_info[SigMFFile.DATETIME_KEY] = start_datetime
-
- tmpdir = tempfile.mkdtemp()
- sigmf_data_filename = input_wav_filename + sigmf.archive.SIGMF_DATASET_EXT
- sigmf_data_path = os.path.join(tmpdir, sigmf_data_filename)
- wav_data.tofile(sigmf_data_path)
-
- meta = sigmf.SigMFFile(name=os.path.basename(input_wav_filename),
- data_file=sigmf_data_path,
- global_info=global_info)
- meta.add_capture(0, metadata=capture_info)
-
- if archive_filename is None:
- archive_filename = os.path.basename(input_wav_filename) + sigmf.archive.SIGMF_ARCHIVE_EXT
- meta.tofile(archive_filename, toarchive=True)
- return os.path.abspath(archive_filename)
-
-if __name__ == '__main__':
- import sys
- input_wav_filename = sys.argv[1] # produces an understandable error if nothing was provided on command line
- out_fname = writeSigMFArchiveFromWave(input_wav_filename)
- print("Wrote", out_fname)
diff --git a/sigmf/utils.py b/sigmf/utils.py
index 5f54e1f..2e61b42 100644
--- a/sigmf/utils.py
+++ b/sigmf/utils.py
@@ -1,14 +1,16 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Utilities"""
+import re
+import sys
from copy import deepcopy
from datetime import datetime
-import sys
+
import numpy as np
from . import error
@@ -30,6 +32,13 @@ def parse_iso8601_datetime(datestr: str) -> datetime:
>>> parse_iso8601_datetime("1955-11-05T06:15:00Z")
datetime.datetime(1955, 11, 5, 6, 15)
"""
+ # provided string exceeds max precision -> truncate to µs
+ match = re.match(r"^(?P.*)(?P\.[0-9]{7,})Z$", datestr)
+ if match:
+ md = match.groupdict()
+ length = min(7, len(md["frac"]))
+ datestr = ''.join([md["dt"], md["frac"][:length], "Z"])
+
try:
timestamp = datetime.strptime(datestr, '%Y-%m-%dT%H:%M:%S.%fZ')
except ValueError:
diff --git a/sigmf/validate.py b/sigmf/validate.py
index a251bdd..95f8900 100644
--- a/sigmf/validate.py
+++ b/sigmf/validate.py
@@ -1,14 +1,19 @@
# Copyright: Multiple Authors
#
-# This file is part of SigMF. https://github.com/sigmf/sigmf-python
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
'''SigMF Validator'''
+import argparse
+import json
+import logging
+
import jsonschema
-from . import schema
+from . import __version__ as toolversion
+from . import error, schema, sigmffile
def extend_with_default(validator_class):
@@ -80,15 +85,6 @@ def validate(metadata, ref_schema=schema.get_schema()):
def main():
- import argparse
- import logging
- import json
-
- from . import sigmffile
- from . import error
-
- from sigmf import __version__ as toolversion
-
parser = argparse.ArgumentParser(description='Validate SigMF Archive or file pair against JSON schema.',
prog='sigmf_validate')
parser.add_argument('filename', help='SigMF path (extension optional).')
@@ -124,5 +120,5 @@ def main():
log.info('Validation OK!')
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/tests/conftest.py b/tests/conftest.py
index 9f5f71f..d2edcd8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,27 +1,16 @@
-# Copyright 2017 GNU Radio Foundation
+# Copyright: Multiple Authors
#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Provides pytest fixtures for other tests."""
import tempfile
import pytest
+from sigmf import __specification__
from sigmf.sigmffile import SigMFFile
from .testdata import (TEST_FLOAT32_DATA_1,
@@ -83,10 +72,11 @@ def test_alternate_sigmffile(test_data_file_2):
@pytest.fixture
def test_alternate_sigmffile_2(test_data_file_3):
"""If pytest uses this signature, will return valid SigMF file."""
- f = SigMFFile(name='test3')
- f.set_global_field("core:datatype", "rf32_le")
- f.add_annotation(start_index=0, length=len(TEST_FLOAT32_DATA_3))
- f.add_capture(start_index=0)
- f.set_data_file(test_data_file_3.name)
- assert f._metadata == TEST_METADATA_3
- return f
+ meta = SigMFFile()
+ meta.set_global_field("core:datatype", "rf32_le")
+ meta.set_global_field("core:version", __specification__)
+ meta.add_annotation(start_index=0, length=len(TEST_FLOAT32_DATA_3))
+ meta.add_capture(start_index=0)
+ meta.set_data_file(test_data_file_3.name)
+ assert meta._metadata == TEST_METADATA_3
+ return meta
diff --git a/tests/test_archive.py b/tests/test_archive.py
index 4ef0936..46fc9cc 100644
--- a/tests/test_archive.py
+++ b/tests/test_archive.py
@@ -1,3 +1,11 @@
+# Copyright: Multiple Authors
+#
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Tests for SigMFArchive"""
+
import codecs
import json
import os
@@ -6,9 +14,9 @@
import tempfile
from os import path
+import jsonschema
import numpy as np
import pytest
-import jsonschema
from sigmf import error, sigmffile
from sigmf.archive import (SIGMF_DATASET_EXT,
@@ -83,7 +91,7 @@ def test_unwritable_fileobj_throws_fileerror(test_sigmffile):
def test_unwritable_name_throws_fileerror(test_sigmffile):
# Cannot assume /root/ is unwritable (e.g. Docker environment)
# so use invalid filename
- unwritable_file = '/bad_name/'
+ unwritable_file = "/bad_name/"
with pytest.raises(error.SigMFFileError):
test_sigmffile.archive(name=unwritable_file)
diff --git a/tests/test_archivereader.py b/tests/test_archivereader.py
index 2cd867b..c55c27e 100644
--- a/tests/test_archivereader.py
+++ b/tests/test_archivereader.py
@@ -1,14 +1,17 @@
-# Copyright 2023 GNU Radio Foundation
-import os
-import shutil
+# Copyright: Multiple Authors
+#
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Tests for SigMFArchiveReader"""
+
import tempfile
+import unittest
import numpy as np
-import unittest
-from sigmf import SigMFFile, SigMFArchiveReader
-from sigmf.archive import SIGMF_METADATA_EXT, SigMFArchive
-from sigmf.sigmffile_collection import SigMFFileCollection
+from sigmf import SigMFArchiveReader, SigMFFile, __specification__
class TestArchiveReader(unittest.TestCase):
@@ -47,6 +50,7 @@ def test_access_data_without_untar(self):
global_info={
SigMFFile.DATATYPE_KEY: f"{complex_prefix}{key}_le",
SigMFFile.NUM_CHANNELS_KEY: num_channels,
+ SigMFFile.VERSION_KEY: __specification__,
},
)
temp_meta.tofile(temp_archive, toarchive=True)
diff --git a/tests/test_sigmffile.py b/tests/test_sigmffile.py
index bb82cfd..ee0ee3d 100644
--- a/tests/test_sigmffile.py
+++ b/tests/test_sigmffile.py
@@ -1,31 +1,20 @@
-# Copyright 2017 GNU Radio Foundation
+# Copyright: Multiple Authors
#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Tests for SigMFFile Object"""
+import copy
+import json
import os
import shutil
import tempfile
-import json
+import unittest
from pathlib import Path
+
import numpy as np
-import unittest
-import copy
from sigmf import sigmffile, utils
from sigmf.sigmffile import SigMFFile, fromarchive
@@ -64,8 +53,8 @@ def test_iterator_basic(self):
count += 1
self.assertEqual(count, len(self.sigmf_object))
-class TestAnnotationHandling(unittest.TestCase):
+class TestAnnotationHandling(unittest.TestCase):
def test_get_annotations_with_index(self):
"""Test that only annotations containing index are returned from get_annotations()"""
smf = SigMFFile("test", copy.deepcopy(TEST_METADATA_1))
@@ -79,7 +68,7 @@ def test_get_annotations_with_index(self):
{SigMFFile.START_INDEX_KEY: 1},
]
)
-
+
def test__count_samples_from_annotation(self):
"""Make sure sample count from annotations use correct end index"""
smf = SigMFFile("test", copy.deepcopy(TEST_METADATA_1))
@@ -87,7 +76,7 @@ def test__count_samples_from_annotation(self):
smf.add_annotation(start_index=4, length=4)
sample_count = smf._count_samples()
self.assertEqual(sample_count, 32)
-
+
def test_set_data_file_without_annotations(self):
"""
Make sure setting data_file with no annotations registered does not
@@ -100,7 +89,7 @@ def test_set_data_file_without_annotations(self):
TEST_FLOAT32_DATA_1.tofile(temp_path_data)
smf.set_data_file(temp_path_data)
samples = smf.read_samples()
- self.assertTrue(len(samples)==16)
+ self.assertTrue(len(samples) == 16)
def test_set_data_file_with_annotations(self):
"""
@@ -117,7 +106,8 @@ def test_set_data_file_with_annotations(self):
# Issues warning since file ends before the final annotatio
smf.set_data_file(temp_path_data)
samples = smf.read_samples()
- self.assertTrue(len(samples)==16)
+ self.assertTrue(len(samples) == 16)
+
def simulate_capture(sigmf_md, n, capture_len):
start_index = capture_len * n
@@ -131,9 +121,7 @@ def simulate_capture(sigmf_md, n, capture_len):
"core:longitude": -105.0 + 0.0001 * n,
}
- sigmf_md.add_annotation(start_index=start_index,
- length=capture_len,
- metadata=annotation_md)
+ sigmf_md.add_annotation(start_index=start_index, length=capture_len, metadata=annotation_md)
def test_default_constructor():
@@ -296,14 +284,14 @@ def test_key_validity():
for top_key, top_val in TEST_METADATA_1.items():
if type(top_val) is dict:
for core_key in top_val.keys():
- assert core_key in vars(SigMFFile)[f'VALID_{top_key.upper()}_KEYS']
+ assert core_key in vars(SigMFFile)[f"VALID_{top_key.upper()}_KEYS"]
elif type(top_val) is list:
# annotations are in a list
for annot in top_val:
for core_key in annot.keys():
assert core_key in SigMFFile.VALID_ANNOTATION_KEYS
else:
- raise ValueError('expected list or dict')
+ raise ValueError("expected list or dict")
def test_ordered_metadata():
@@ -315,66 +303,72 @@ def test_ordered_metadata():
def test_captures_checking():
- '''
+ """
these tests make sure the various captures access tools work properly
- '''
- np.array(TEST_U8_DATA0, dtype=np.uint8).tofile('/tmp/d0.sigmf-data')
- with open('/tmp/d0.sigmf-meta','w') as f0: json.dump(TEST_U8_META0, f0)
- np.array(TEST_U8_DATA1, dtype=np.uint8).tofile('/tmp/d1.sigmf-data')
- with open('/tmp/d1.sigmf-meta','w') as f1: json.dump(TEST_U8_META1, f1)
- np.array(TEST_U8_DATA2, dtype=np.uint8).tofile('/tmp/d2.sigmf-data')
- with open('/tmp/d2.sigmf-meta','w') as f2: json.dump(TEST_U8_META2, f2)
- np.array(TEST_U8_DATA3, dtype=np.uint8).tofile('/tmp/d3.sigmf-data')
- with open('/tmp/d3.sigmf-meta','w') as f3: json.dump(TEST_U8_META3, f3)
- np.array(TEST_U8_DATA4, dtype=np.uint8).tofile('/tmp/d4.sigmf-data')
- with open('/tmp/d4.sigmf-meta','w') as f4: json.dump(TEST_U8_META4, f4)
-
- sigmf0 = sigmffile.fromfile('/tmp/d0.sigmf-meta', skip_checksum=True)
- sigmf1 = sigmffile.fromfile('/tmp/d1.sigmf-meta', skip_checksum=True)
- sigmf2 = sigmffile.fromfile('/tmp/d2.sigmf-meta', skip_checksum=True)
- sigmf3 = sigmffile.fromfile('/tmp/d3.sigmf-meta', skip_checksum=True)
- sigmf4 = sigmffile.fromfile('/tmp/d4.sigmf-meta', skip_checksum=True)
+ """
+ np.array(TEST_U8_DATA0, dtype=np.uint8).tofile("/tmp/d0.sigmf-data")
+ with open("/tmp/d0.sigmf-meta", "w") as f0:
+ json.dump(TEST_U8_META0, f0)
+ np.array(TEST_U8_DATA1, dtype=np.uint8).tofile("/tmp/d1.sigmf-data")
+ with open("/tmp/d1.sigmf-meta", "w") as f1:
+ json.dump(TEST_U8_META1, f1)
+ np.array(TEST_U8_DATA2, dtype=np.uint8).tofile("/tmp/d2.sigmf-data")
+ with open("/tmp/d2.sigmf-meta", "w") as f2:
+ json.dump(TEST_U8_META2, f2)
+ np.array(TEST_U8_DATA3, dtype=np.uint8).tofile("/tmp/d3.sigmf-data")
+ with open("/tmp/d3.sigmf-meta", "w") as f3:
+ json.dump(TEST_U8_META3, f3)
+ np.array(TEST_U8_DATA4, dtype=np.uint8).tofile("/tmp/d4.sigmf-data")
+ with open("/tmp/d4.sigmf-meta", "w") as f4:
+ json.dump(TEST_U8_META4, f4)
+
+ sigmf0 = sigmffile.fromfile("/tmp/d0.sigmf-meta", skip_checksum=True)
+ sigmf1 = sigmffile.fromfile("/tmp/d1.sigmf-meta", skip_checksum=True)
+ sigmf2 = sigmffile.fromfile("/tmp/d2.sigmf-meta", skip_checksum=True)
+ sigmf3 = sigmffile.fromfile("/tmp/d3.sigmf-meta", skip_checksum=True)
+ sigmf4 = sigmffile.fromfile("/tmp/d4.sigmf-meta", skip_checksum=True)
assert sigmf0._count_samples() == 256
assert sigmf0._is_conforming_dataset()
- assert (0,0) == sigmf0.get_capture_byte_boundarys(0)
- assert (0,256) == sigmf0.get_capture_byte_boundarys(1)
+ assert (0, 0) == sigmf0.get_capture_byte_boundarys(0)
+ assert (0, 256) == sigmf0.get_capture_byte_boundarys(1)
assert np.array_equal(TEST_U8_DATA0, sigmf0.read_samples(autoscale=False))
assert np.array_equal(np.array([]), sigmf0.read_samples_in_capture(0))
- assert np.array_equal(TEST_U8_DATA0, sigmf0.read_samples_in_capture(1,autoscale=False))
+ assert np.array_equal(TEST_U8_DATA0, sigmf0.read_samples_in_capture(1, autoscale=False))
assert sigmf1._count_samples() == 192
assert not sigmf1._is_conforming_dataset()
- assert (32,160) == sigmf1.get_capture_byte_boundarys(0)
- assert (160,224) == sigmf1.get_capture_byte_boundarys(1)
- assert np.array_equal(np.array(range(128)), sigmf1.read_samples_in_capture(0,autoscale=False))
- assert np.array_equal(np.array(range(128,192)), sigmf1.read_samples_in_capture(1,autoscale=False))
+ assert (32, 160) == sigmf1.get_capture_byte_boundarys(0)
+ assert (160, 224) == sigmf1.get_capture_byte_boundarys(1)
+ assert np.array_equal(np.array(range(128)), sigmf1.read_samples_in_capture(0, autoscale=False))
+ assert np.array_equal(np.array(range(128, 192)), sigmf1.read_samples_in_capture(1, autoscale=False))
assert sigmf2._count_samples() == 192
assert not sigmf2._is_conforming_dataset()
- assert (32,160) == sigmf2.get_capture_byte_boundarys(0)
- assert (176,240) == sigmf2.get_capture_byte_boundarys(1)
- assert np.array_equal(np.array(range(128)), sigmf2.read_samples_in_capture(0,autoscale=False))
- assert np.array_equal(np.array(range(128,192)), sigmf2.read_samples_in_capture(1,autoscale=False))
+ assert (32, 160) == sigmf2.get_capture_byte_boundarys(0)
+ assert (176, 240) == sigmf2.get_capture_byte_boundarys(1)
+ assert np.array_equal(np.array(range(128)), sigmf2.read_samples_in_capture(0, autoscale=False))
+ assert np.array_equal(np.array(range(128, 192)), sigmf2.read_samples_in_capture(1, autoscale=False))
assert sigmf3._count_samples() == 192
assert not sigmf3._is_conforming_dataset()
- assert (32,64) == sigmf3.get_capture_byte_boundarys(0)
- assert (64,160) == sigmf3.get_capture_byte_boundarys(1)
- assert (192,256) == sigmf3.get_capture_byte_boundarys(2)
- assert np.array_equal(np.array(range(32)), sigmf3.read_samples_in_capture(0,autoscale=False))
- assert np.array_equal(np.array(range(32,128)), sigmf3.read_samples_in_capture(1,autoscale=False))
- assert np.array_equal(np.array(range(128,192)), sigmf3.read_samples_in_capture(2,autoscale=False))
+ assert (32, 64) == sigmf3.get_capture_byte_boundarys(0)
+ assert (64, 160) == sigmf3.get_capture_byte_boundarys(1)
+ assert (192, 256) == sigmf3.get_capture_byte_boundarys(2)
+ assert np.array_equal(np.array(range(32)), sigmf3.read_samples_in_capture(0, autoscale=False))
+ assert np.array_equal(np.array(range(32, 128)), sigmf3.read_samples_in_capture(1, autoscale=False))
+ assert np.array_equal(np.array(range(128, 192)), sigmf3.read_samples_in_capture(2, autoscale=False))
assert sigmf4._count_samples() == 96
assert not sigmf4._is_conforming_dataset()
- assert (32,160) == sigmf4.get_capture_byte_boundarys(0)
- assert (160,224) == sigmf4.get_capture_byte_boundarys(1)
- assert np.array_equal(np.array(range(64)), sigmf4.read_samples_in_capture(0,autoscale=False)[:,0])
- assert np.array_equal(np.array(range(64,96)), sigmf4.read_samples_in_capture(1,autoscale=False)[:,1])
+ assert (32, 160) == sigmf4.get_capture_byte_boundarys(0)
+ assert (160, 224) == sigmf4.get_capture_byte_boundarys(1)
+ assert np.array_equal(np.array(range(64)), sigmf4.read_samples_in_capture(0, autoscale=False)[:, 0])
+ assert np.array_equal(np.array(range(64, 96)), sigmf4.read_samples_in_capture(1, autoscale=False)[:, 1])
+
def test_slicing():
- '''Test __getitem___ builtin for sigmffile, make sure slicing and indexing works as expected.'''
+ """Test __getitem___ builtin for sigmffile, make sure slicing and indexing works as expected."""
_, temp_data0 = tempfile.mkstemp()
np.array(TEST_U8_DATA0, dtype=np.uint8).tofile(temp_data0)
sigmf0 = SigMFFile("test1", metadata=TEST_U8_META0, data_file=temp_data0)
@@ -396,4 +390,4 @@ def test_slicing():
assert np.array_equal(channelized, sigmf2[:][:])
assert np.array_equal(sigmf2[10:20, 91:112], sigmf2.read_samples(autoscale=False)[10:20, 91:112])
assert np.array_equal(sigmf2[0], channelized[0])
- assert np.array_equal(sigmf2[1,:], channelized[1,:])
+ assert np.array_equal(sigmf2[1, :], channelized[1, :])
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..e3599dc
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,27 @@
+# Copyright: Multiple Authors
+#
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Tests for Utilities"""
+
+from datetime import datetime
+
+import pytest
+
+from sigmf import utils
+
+
+@pytest.mark.parametrize("ts, expected", [
+ ("1955-07-04T05:15:00Z", datetime(year=1955, month=7, day=4, hour=5, minute=15, second=00, microsecond=0)),
+ ("2956-08-05T06:15:12Z", datetime(year=2956, month=8, day=5, hour=6, minute=15, second=12, microsecond=0)),
+ ("3957-09-06T07:15:12.345Z", datetime(year=3957, month=9, day=6, hour=7, minute=15, second=12, microsecond=345000)),
+ ("4958-10-07T08:15:12.0345Z", datetime(year=4958, month=10, day=7, hour=8, minute=15, second=12, microsecond=34500)),
+ ("5959-11-08T09:15:12.000000Z", datetime(year=5959, month=11, day=8, hour=9, minute=15, second=12, microsecond=0)),
+ ("6960-12-09T10:15:12.123456789123Z", datetime(year=6960, month=12, day=9, hour=10, minute=15, second=12, microsecond=123456)),
+
+])
+def test_parse_simple_iso8601(ts, expected):
+ dt = utils.parse_iso8601_datetime(ts)
+ assert dt == expected
diff --git a/tests/test_validation.py b/tests/test_validation.py
index 7302a79..8af130c 100644
--- a/tests/test_validation.py
+++ b/tests/test_validation.py
@@ -1,48 +1,46 @@
-# Copyright 2016 GNU Radio Foundation
+# Copyright: Multiple Authors
#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Tests for Validator"""
import tempfile
import unittest
+from jsonschema.exceptions import ValidationError
+
import sigmf
from sigmf import SigMFFile
-from jsonschema.exceptions import ValidationError
from .testdata import TEST_FLOAT32_DATA_1, TEST_METADATA_1
def test_valid_data():
- '''assure the supplied metadata is OK'''
+ """assure the supplied metadata is OK"""
invalid_metadata = dict(TEST_METADATA_1)
SigMFFile("test", TEST_METADATA_1).validate()
+
+
class FailingCases(unittest.TestCase):
- '''Cases where the validator should throw an exception.'''
+ """Cases where the validator should throw an exception."""
+
def setUp(self):
self.metadata = dict(TEST_METADATA_1)
+ def test_no_version(self):
+ """core:version must be present"""
+ del self.metadata[SigMFFile.GLOBAL_KEY][SigMFFile.VERSION_KEY]
+ with self.assertRaises(ValidationError):
+ SigMFFile(self.metadata).validate()
+
def test_extra_top_level_key(self):
- '''no extra keys allowed on the top level'''
- self.metadata['extra'] = 0
+ """no extra keys allowed on the top level"""
+ self.metadata["extra"] = 0
with self.assertRaises(ValidationError):
SigMFFile("test", self.metadata).validate()
@@ -53,22 +51,19 @@ def test_invalid_label(self):
SigMFFile("test", self.metadata).validate()
def test_invalid_type(self):
- '''license key must be string'''
+ """license key must be string"""
self.metadata[SigMFFile.GLOBAL_KEY][SigMFFile.LICENSE_KEY] = 1
with self.assertRaises(ValidationError):
SigMFFile("test", self.metadata).validate()
def test_invalid_capture_order(self):
- '''metadata must have captures in order'''
- self.metadata[SigMFFile.CAPTURE_KEY] = [
- {SigMFFile.START_INDEX_KEY: 10},
- {SigMFFile.START_INDEX_KEY: 9}
- ]
+ """metadata must have captures in order"""
+ self.metadata[SigMFFile.CAPTURE_KEY] = [{SigMFFile.START_INDEX_KEY: 10}, {SigMFFile.START_INDEX_KEY: 9}]
with self.assertRaises(ValidationError):
SigMFFile("test", self.metadata).validate()
def test_invalid_annotation_order(self):
- '''metadata must have annotations in order'''
+ """metadata must have annotations in order"""
self.metadata[SigMFFile.ANNOTATION_KEY] = [
{
SigMFFile.START_INDEX_KEY: 2,
@@ -77,24 +72,19 @@ def test_invalid_annotation_order(self):
{
SigMFFile.START_INDEX_KEY: 1,
SigMFFile.LENGTH_INDEX_KEY: 120000,
- }
+ },
]
with self.assertRaises(ValidationError):
SigMFFile("test", self.metadata).validate()
def test_annotation_without_sample_count(self):
- '''annotation without length should be accepted'''
- self.metadata[SigMFFile.ANNOTATION_KEY] = [
- {
- SigMFFile.START_INDEX_KEY: 2
- }
- ]
+ """annotation without length should be accepted"""
+ self.metadata[SigMFFile.ANNOTATION_KEY] = [{SigMFFile.START_INDEX_KEY: 2}]
SigMFFile("test", self.metadata).validate()
-
def test_invalid_hash(self):
_, temp_path = tempfile.mkstemp()
TEST_FLOAT32_DATA_1.tofile(temp_path)
- self.metadata[SigMFFile.GLOBAL_KEY][SigMFFile.HASH_KEY] = 'derp'
+ self.metadata[SigMFFile.GLOBAL_KEY][SigMFFile.HASH_KEY] = "derp"
with self.assertRaises(sigmf.error.SigMFFileError):
SigMFFile(name="test", metadata=self.metadata, data_file=temp_path)
diff --git a/tests/testdata.py b/tests/testdata.py
index 1d5684b..98c627a 100644
--- a/tests/testdata.py
+++ b/tests/testdata.py
@@ -1,28 +1,14 @@
-# flake8: noqa
-
-# Copyright 2017 GNU Radio Foundation
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
+# Copyright: Multiple Authors
#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
+# SPDX-License-Identifier: LGPL-3.0-or-later
+"""Shared test data for tests."""
import numpy as np
-from sigmf import SigMFFile
+
+from sigmf import SigMFFile, __specification__, __version__
TEST_FLOAT32_DATA_1 = np.arange(16, dtype=np.float32)
@@ -30,11 +16,11 @@
SigMFFile.ANNOTATION_KEY: [{SigMFFile.LENGTH_INDEX_KEY: 16, SigMFFile.START_INDEX_KEY: 0}],
SigMFFile.CAPTURE_KEY: [{SigMFFile.START_INDEX_KEY: 0}],
SigMFFile.GLOBAL_KEY: {
- SigMFFile.DATATYPE_KEY: 'rf32_le',
- SigMFFile.HASH_KEY: 'f4984219b318894fa7144519185d1ae81ea721c6113243a52b51e444512a39d74cf41a4cec3c5d000bd7277cc71232c04d7a946717497e18619bdbe94bfeadd6',
+ SigMFFile.DATATYPE_KEY: "rf32_le",
+ SigMFFile.HASH_KEY: "f4984219b318894fa7144519185d1ae81ea721c6113243a52b51e444512a39d74cf41a4cec3c5d000bd7277cc71232c04d7a946717497e18619bdbe94bfeadd6",
SigMFFile.NUM_CHANNELS_KEY: 1,
- SigMFFile.VERSION_KEY: '1.0.0'
- }
+ SigMFFile.VERSION_KEY: __specification__,
+ },
}
TEST_FLOAT32_DATA_2 = np.arange(16, 32, dtype=np.float32)
@@ -67,40 +53,54 @@
TEST_U8_DATA0 = list(range(256))
TEST_U8_META0 = {
SigMFFile.ANNOTATION_KEY: [],
- SigMFFile.CAPTURE_KEY: [ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 0},
- {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 0} ], # very strange..but technically legal?
- SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: 'ru8', SigMFFile.TRAILING_BYTES_KEY: 0}
+ SigMFFile.CAPTURE_KEY: [
+ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 0},
+ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 0},
+ ], # very strange..but technically legal?
+ SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: "ru8", SigMFFile.TRAILING_BYTES_KEY: 0},
}
# Data1 is a test of a two capture recording with header_bytes and trailing_bytes set
-TEST_U8_DATA1 = [0xfe]*32 + list(range(192)) + [0xff]*32
+TEST_U8_DATA1 = [0xFE] * 32 + list(range(192)) + [0xFF] * 32
TEST_U8_META1 = {
SigMFFile.ANNOTATION_KEY: [],
- SigMFFile.CAPTURE_KEY: [ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
- {SigMFFile.START_INDEX_KEY: 128} ],
- SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: 'ru8', SigMFFile.TRAILING_BYTES_KEY: 32}
+ SigMFFile.CAPTURE_KEY: [
+ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
+ {SigMFFile.START_INDEX_KEY: 128},
+ ],
+ SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: "ru8", SigMFFile.TRAILING_BYTES_KEY: 32},
}
# Data2 is a test of a two capture recording with multiple header_bytes set
-TEST_U8_DATA2 = [0xfe]*32 + list(range(128)) + [0xfe]*16 + list(range(128,192)) + [0xff]*16
+TEST_U8_DATA2 = [0xFE] * 32 + list(range(128)) + [0xFE] * 16 + list(range(128, 192)) + [0xFF] * 16
TEST_U8_META2 = {
SigMFFile.ANNOTATION_KEY: [],
- SigMFFile.CAPTURE_KEY: [ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
- {SigMFFile.START_INDEX_KEY: 128, SigMFFile.HEADER_BYTES_KEY: 16} ],
- SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: 'ru8', SigMFFile.TRAILING_BYTES_KEY: 16}
+ SigMFFile.CAPTURE_KEY: [
+ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
+ {SigMFFile.START_INDEX_KEY: 128, SigMFFile.HEADER_BYTES_KEY: 16},
+ ],
+ SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: "ru8", SigMFFile.TRAILING_BYTES_KEY: 16},
}
# Data3 is a test of a three capture recording with multiple header_bytes set
-TEST_U8_DATA3 = [0xfe]*32 + list(range(128)) + [0xfe]*32 + list(range(128,192))
+TEST_U8_DATA3 = [0xFE] * 32 + list(range(128)) + [0xFE] * 32 + list(range(128, 192))
TEST_U8_META3 = {
SigMFFile.ANNOTATION_KEY: [],
- SigMFFile.CAPTURE_KEY: [ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
- {SigMFFile.START_INDEX_KEY: 32},
- {SigMFFile.START_INDEX_KEY: 128, SigMFFile.HEADER_BYTES_KEY: 32} ],
- SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: 'ru8'}
+ SigMFFile.CAPTURE_KEY: [
+ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
+ {SigMFFile.START_INDEX_KEY: 32},
+ {SigMFFile.START_INDEX_KEY: 128, SigMFFile.HEADER_BYTES_KEY: 32},
+ ],
+ SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: "ru8"},
}
# Data4 is a two channel version of Data0
-TEST_U8_DATA4 = [0xfe]*32 + [y for y in list(range(96)) for i in [0,1]] + [0xff]*32
+TEST_U8_DATA4 = [0xFE] * 32 + [y for y in list(range(96)) for i in [0, 1]] + [0xFF] * 32
TEST_U8_META4 = {
SigMFFile.ANNOTATION_KEY: [],
- SigMFFile.CAPTURE_KEY: [ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
- {SigMFFile.START_INDEX_KEY: 64} ],
- SigMFFile.GLOBAL_KEY: {SigMFFile.DATATYPE_KEY: 'ru8', SigMFFile.TRAILING_BYTES_KEY: 32, SigMFFile.NUM_CHANNELS_KEY: 2}
+ SigMFFile.CAPTURE_KEY: [
+ {SigMFFile.START_INDEX_KEY: 0, SigMFFile.HEADER_BYTES_KEY: 32},
+ {SigMFFile.START_INDEX_KEY: 64},
+ ],
+ SigMFFile.GLOBAL_KEY: {
+ SigMFFile.DATATYPE_KEY: "ru8",
+ SigMFFile.TRAILING_BYTES_KEY: 32,
+ SigMFFile.NUM_CHANNELS_KEY: 2,
+ },
}
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 02a42f5..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,22 +0,0 @@
-[tox]
-skip_missing_interpreters = True
-envlist = py36, py37, py38, py39, py310
-
-[testenv]
-usedevelop = True
-deps =
- pytest
- flake8
-commands =
- pytest
- - flake8
-
-[testenv:coverage]
-deps =
- pytest-cov
-commands = py.test --cov-report term-missing --cov=sigmf tests
-
-[flake8]
-max-line-length = 120
-[pycodestyle]
-max-line-length = 120