From fc8bee8acf6bf4eaea43194cacbbc0db7f88b1b5 Mon Sep 17 00:00:00 2001 From: Scott Phillips Date: Mon, 1 Nov 2021 12:03:34 -0400 Subject: [PATCH] FitVersion support to handle issue #56 --- fitparse/base.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++-- tests/test.py | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/fitparse/base.py b/fitparse/base.py index 6af6392..5f62719 100644 --- a/fitparse/base.py +++ b/fitparse/base.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import io +import logging import os import struct import warnings @@ -217,8 +218,8 @@ def _parse_file_header(self): header_size, protocol_ver_enc, profile_ver_enc, data_size = self._read_struct('2BHI4x', data=header_data) # Decode the same way the SDK does - self.protocol_version = float("%d.%d" % (protocol_ver_enc >> 4, protocol_ver_enc & ((1 << 4) - 1))) - self.profile_version = float("%d.%d" % (profile_ver_enc / 100, profile_ver_enc % 100)) + self.protocol_version = FitVersion("%d.%d" % (protocol_ver_enc >> 4, protocol_ver_enc & ((1 << 4) - 1))) + self.profile_version = FitVersion("%d.%d" % (profile_ver_enc / 100, profile_ver_enc % 100)) # Consume extra header information extra_header_size = header_size - 12 @@ -626,6 +627,65 @@ class FitFile(CacheMixin, UncachedFitFile): pass +class FitVersion(): + def __init__(self, version=None): + self._major = 0 + self._minor = 0 + if isinstance(version, float): + # Legacy match + logging.warning("Legacy matching with float versions, provide a string or FitVersion object for precision") + # Cast to a string and fall through + version = str(version) + if isinstance(version, str): + version_fields = version.split('.') + self._major = int(version_fields[0]) + self._minor = int(version_fields[1]) + else: + raise ValueError(f"Unknown quantity {version} cannot be parsed as a version string/float.") + + @property + def major(self): + return self._major + + @property + def minor(self): + return self._minor + + def __str__(self): + return f"{self.major}.{self.minor}" + + def __eq__(self, other): + if isinstance(other, FitVersion): + return self.major == other.major and self.minor == other.minor + else: + return self == FitVersion(other) + + def __gt__(self, other): + if isinstance(other, FitVersion): + return self.major > other.major or self.major == other.major and self.minor > other.minor + else: + return self > FitVersion(other) + + def __lt__(self, other): + if isinstance(other, FitVersion): + return self.major < other.major or self.major == other.major and self.minor < other.minor + else: + return self > FitVersion(other) + + def __ge__(self, other): + if isinstance(other, FitVersion): + return self.major > other.major or self.major == other.major and self.minor >= other.minor + else: + return self > FitVersion(other) + + def __le__(self, other): + if isinstance(other, FitVersion): + return self.major < other.major or self.major == other.major and self.minor <= other.minor + else: + return self > FitVersion(other) + + + # TODO: Create subclasses like Activity and do per-value monkey patching # for example local_timestamp to adjust timestamp on a per-file basis diff --git a/tests/test.py b/tests/test.py index eea5dc2..7613cbb 100755 --- a/tests/test.py +++ b/tests/test.py @@ -8,6 +8,7 @@ import warnings from fitparse import FitFile +from fitparse.base import FitVersion from fitparse.processors import UTC_REFERENCE, StandardUnitsDataProcessor from fitparse.records import BASE_TYPES, Crc from fitparse.utils import FitEOFError, FitCRCError, FitHeaderError, FitParseError @@ -77,6 +78,50 @@ def testfile(filename): return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'files', filename) +class FitVersionTestCase(unittest.TestCase): + def test_version_parse(self): + v10 = FitVersion("1.0") + v11 = FitVersion("1.1") + v12 = FitVersion(1.2) + + self.assertEqual(v10, "1.0") + self.assertEqual(v11, "1.1") + self.assertEqual(v12, "1.2") + + def test_version_parse_errors(self): + with self.assertRaises(ValueError): + FitVersion(1) + with self.assertRaises(ValueError): + FitVersion([1, 2]) + + def test_version_equality(self): + v10 = FitVersion("1.0") + v10_ = FitVersion("1.0") + v11 = FitVersion("1.1") + + self.assertEqual(v10, v10_) + self.assertEqual(v10_, v10) + self.assertNotEqual(v10, v11) + + self.assertGreaterEqual(v10, v10_) + self.assertLessEqual(v10, v10_) + + def test_version_inequality(self): + v10 = FitVersion("1.0") + v11 = FitVersion("1.1") + v20 = FitVersion("2.0") + + self.assertGreater(v11, v10) + self.assertGreater(v20, v11) + self.assertGreaterEqual(v11, v10) + self.assertGreaterEqual(v20, v11) + + self.assertLess(v10, v11) + self.assertLess(v11, v20) + self.assertLessEqual(v10, v11) + self.assertLessEqual(v11, v20) + + class FitFileTestCase(unittest.TestCase): def test_basic_file_with_one_record(self, endian='<'): f = FitFile(generate_fitfile(endian=endian))