From 0c37e76cef3d4b0a8fe3b84492cc5d14e23b0834 Mon Sep 17 00:00:00 2001 From: Tom Fryers Date: Mon, 22 Aug 2022 21:12:56 +0100 Subject: [PATCH 1/5] Use pyupgrade --- fitparse/base.py | 30 +++++++++++++++--------------- fitparse/processors.py | 4 ++-- fitparse/profile.py | 1 - fitparse/records.py | 18 +++++++++--------- fitparse/utils.py | 2 +- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/fitparse/base.py b/fitparse/base.py index 6af6392..8880a00 100644 --- a/fitparse/base.py +++ b/fitparse/base.py @@ -20,12 +20,12 @@ from fitparse.utils import fileish_open, is_iterable, FitParseError, FitEOFError, FitCRCError, FitHeaderError -class DeveloperDataMixin(object): +class DeveloperDataMixin: def __init__(self, *args, check_developer_data=True, **kwargs): self.check_developer_data = check_developer_data self.dev_types = {} - super(DeveloperDataMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def _append_dev_data_id(self, dev_data_index, application_id=None, fields=None): if fields is None: @@ -97,7 +97,7 @@ def get_dev_type(self, dev_data_index, field_def_num): if dev_data_index not in self.dev_types: if self.check_developer_data: raise FitParseError( - "No such dev_data_index=%s found when looking up field %s" % (dev_data_index, field_def_num) + f"No such dev_data_index={dev_data_index} found when looking up field {field_def_num}" ) warnings.warn( @@ -110,11 +110,11 @@ def get_dev_type(self, dev_data_index, field_def_num): if field_def_num not in dev_type['fields']: if self.check_developer_data: raise FitParseError( - "No such field %s for dev_data_index %s" % (field_def_num, dev_data_index) + f"No such field {field_def_num} for dev_data_index {dev_data_index}" ) warnings.warn( - "Field %s for dev_data_index %s missing. Adding dummy field." % (field_def_num, dev_data_index) + f"Field {field_def_num} for dev_data_index {dev_data_index} missing. Adding dummy field." ) self._append_dev_field_description( dev_data_index=dev_data_index, @@ -141,7 +141,7 @@ def __init__(self, fileish, *args, check_crc=True, data_processor=None, **kwargs # Start off by parsing the file header (sets initial attribute values) self._parse_file_header() - super(FitFileDecoder, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __del__(self): self.close() @@ -193,7 +193,7 @@ def _read_and_assert_crc(self, allow_zero=False): return if crc_computed == crc_read or (allow_zero and crc_read == 0): return - raise FitCRCError('CRC Mismatch [computed: %s, read: %s]' % ( + raise FitCRCError('CRC Mismatch [computed: {}, read: {}]'.format( Crc.format(crc_computed), Crc.format(crc_read))) ########## @@ -530,7 +530,7 @@ def _make_set(obj): if is_iterable(obj): return set(obj) else: - return set((obj,)) + return {obj} ########## # Public API @@ -550,15 +550,15 @@ def __iter__(self): return self.get_messages() -class CacheMixin(object): +class CacheMixin: """Add message caching to the FitFileDecoder""" def __init__(self, *args, **kwargs): - super(CacheMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._messages = [] def _parse_message(self): - self._messages.append(super(CacheMixin, self)._parse_message()) + self._messages.append(super()._parse_message()) return self._messages[-1] def get_messages(self, name=None, with_definitions=False, as_dict=False): @@ -572,7 +572,7 @@ def get_messages(self, name=None, with_definitions=False, as_dict=False): if self._should_yield(message, with_definitions, names): yield message.as_dict() if as_dict else message - for message in super(CacheMixin, self).get_messages(names, with_definitions, as_dict): + for message in super().get_messages(names, with_definitions, as_dict): yield message @property @@ -584,12 +584,12 @@ def parse(self): pass -class DataProcessorMixin(object): +class DataProcessorMixin: """Add data processing to the FitFileDecoder""" def __init__(self, *args, **kwargs): self._processor = kwargs.pop("data_processor", None) or FitFileDataProcessor() - super(DataProcessorMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def _parse_data_message(self, header): header, def_mesg, field_datas = self._parse_data_message_components(header) @@ -612,7 +612,7 @@ class UncachedFitFile(DataProcessorMixin, FitFileDecoder): def __init__(self, fileish, *args, check_crc=True, data_processor=None, **kwargs): # Ensure all optional params are passed as kwargs - super(UncachedFitFile, self).__init__( + super().__init__( fileish, *args, check_crc=check_crc, diff --git a/fitparse/processors.py b/fitparse/processors.py index 268859e..6848584 100644 --- a/fitparse/processors.py +++ b/fitparse/processors.py @@ -5,7 +5,7 @@ UTC_REFERENCE = 631065600 # timestamp for UTC 00:00 Dec 31 1989 -class FitFileDataProcessor(object): +class FitFileDataProcessor: # TODO: Document API # Functions that will be called to do the processing: #def run_type_processor(field_data) @@ -107,7 +107,7 @@ def run_field_processor(self, field_data): if field_data.name.endswith("_speed"): self.process_field_speed(field_data) else: - super(StandardUnitsDataProcessor, self).run_field_processor(field_data) + super().run_field_processor(field_data) def process_field_distance(self, field_data): if field_data.value is not None: diff --git a/fitparse/profile.py b/fitparse/profile.py index aa6ddbd..ce44c78 100644 --- a/fitparse/profile.py +++ b/fitparse/profile.py @@ -1,4 +1,3 @@ - # ***************** BEGIN AUTOMATICALLY GENERATED FIT PROFILE ****************** # *************************** DO NOT EDIT THIS FILE **************************** # ************ EXPORTED PROFILE FROM SDK VERSION 20.8 ON 2019-03-05 ************ diff --git a/fitparse/records.py b/fitparse/records.py index f9149e4..857a1dc 100644 --- a/fitparse/records.py +++ b/fitparse/records.py @@ -15,7 +15,7 @@ from itertools import izip_longest as zip_longest -class RecordBase(object): +class RecordBase: # namedtuple-like base class. Subclasses should must __slots__ __slots__ = () @@ -83,7 +83,7 @@ class DevFieldDefinition(RecordBase): __slots__ = ('field', 'dev_data_index', 'base_type', 'def_num', 'size') def __init__(self, **kwargs): - super(DevFieldDefinition, self).__init__(**kwargs) + super().__init__(**kwargs) # For dev fields, the base_type and type are always the same. self.base_type = self.type @@ -129,7 +129,7 @@ def get_value(self, field_name): def get_values(self): # SIMPLIFY: get rid of this completely - return dict((f.name if f.name else f.def_num, f.value) for f in self.fields) + return {f.name if f.name else f.def_num: f.value for f in self.fields} @property def name(self): @@ -159,7 +159,7 @@ def __iter__(self): def __repr__(self): return '' % ( self.name, self.mesg_num, self.header.local_mesg_num, - ', '.join(["%s: %s" % (fd.name, fd.value) for fd in self.fields]), + ', '.join([f"{fd.name}: {fd.value}" for fd in self.fields]), ) def __str__(self): @@ -171,7 +171,7 @@ class FieldData(RecordBase): __slots__ = ('field_def', 'field', 'parent_field', 'value', 'raw_value', 'units') def __init__(self, *args, **kwargs): - super(FieldData, self).__init__(self, *args, **kwargs) + super().__init__(self, *args, **kwargs) if not self.units and self.field: # Default to units on field, otherwise None. # NOTE:Not a property since you may want to override this in a data processor @@ -233,7 +233,7 @@ def __repr__(self): ) def __str__(self): - return '%s: %s%s' % ( + return '{}: {}{}'.format( self.name, self.value, ' [%s]' % self.units if self.units else '', ) @@ -260,7 +260,7 @@ class FieldType(RecordBase): __slots__ = ('name', 'base_type', 'values') def __repr__(self): - return '' % (self.name, self.base_type) + return f'' class MessageType(RecordBase): @@ -342,7 +342,7 @@ def render(self, raw_value): return raw_value -class Crc(object): +class Crc: """FIT file CRC computation.""" CRC_TABLE = ( @@ -358,7 +358,7 @@ def __init__(self, value=0, byte_arr=None): self.update(byte_arr) def __repr__(self): - return '<%s %s>' % (self.__class__.__name__, self.value or "-") + return '<{} {}>'.format(self.__class__.__name__, self.value or "-") def __str__(self): return self.format(self.value) diff --git a/fitparse/utils.py b/fitparse/utils.py index aa70a90..568df0f 100644 --- a/fitparse/utils.py +++ b/fitparse/utils.py @@ -3,7 +3,7 @@ try: from collections.abc import Iterable except ImportError: - from collections import Iterable + from collections.abc import Iterable try: # Python 3.4+ From e48073eaaba40c6a41c45acd07523513ccdd2719 Mon Sep 17 00:00:00 2001 From: Tom Fryers Date: Mon, 22 Aug 2022 21:22:17 +0100 Subject: [PATCH 2/5] Remove Python 2 compatibility code --- docs/index.rst | 2 +- fitparse/base.py | 8 +------- fitparse/records.py | 22 ++++------------------ fitparse/utils.py | 25 +++++++------------------ scripts/fitdump | 17 ++--------------- setup.py | 4 ++-- tests/test.py | 6 +----- tests/test_records.py | 8 +------- tests/test_utils.py | 15 +++------------ 9 files changed, 22 insertions(+), 85 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 434a376..4a4fc30 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -56,7 +56,7 @@ Requirements The following are required to install :mod:`fitparse`, -* `Python `_ 2.7 and above +* `Python `_ 3.6 and above API Documentation diff --git a/fitparse/base.py b/fitparse/base.py index 8880a00..b0ebf87 100644 --- a/fitparse/base.py +++ b/fitparse/base.py @@ -5,12 +5,6 @@ import struct import warnings -# Python 2 compat -try: - num_types = (int, float, long) -except NameError: - num_types = (int, float) - from fitparse.processors import FitFileDataProcessor from fitparse.profile import FIELD_TYPE_TIMESTAMP, MESSAGE_TYPES from fitparse.records import ( @@ -396,7 +390,7 @@ def _apply_scale_offset(self, field, raw_value): if isinstance(raw_value, tuple): # Contains multiple values, apply transformations to all of them return tuple(self._apply_scale_offset(field, x) for x in raw_value) - elif isinstance(raw_value, num_types): + elif isinstance(raw_value, (int, float)): if field.scale: raw_value = float(raw_value) / field.scale if field.offset: diff --git a/fitparse/records.py b/fitparse/records.py index 857a1dc..7d557fa 100644 --- a/fitparse/records.py +++ b/fitparse/records.py @@ -1,18 +1,7 @@ import math import struct -# Python 2 compat -try: - int_types = (int, long,) - byte_iter = bytearray -except NameError: - int_types = (int,) - byte_iter = lambda x: x - -try: - from itertools import zip_longest -except ImportError: - from itertools import izip_longest as zip_longest +from itertools import zip_longest class RecordBase: @@ -336,7 +325,7 @@ def render(self, raw_value): raw_value = unpacked_num # Mask and shift like a normal number - if isinstance(raw_value, int_types): + if isinstance(raw_value, int): raw_value = (raw_value >> self.bit_offset) & ((1 << self.bits) - 1) return raw_value @@ -376,7 +365,7 @@ def format(value): @classmethod def calculate(cls, byte_arr, crc=0): """Compute CRC for input bytes.""" - for byte in byte_iter(byte_arr): + for byte in byte_arr: # Taken verbatim from FIT SDK docs tmp = cls.CRC_TABLE[crc & 0xF] crc = (crc >> 4) & 0x0FFF @@ -390,10 +379,7 @@ def calculate(cls, byte_arr, crc=0): def parse_string(string): try: - try: - s = string[:string.index(0x00)] - except TypeError: # Python 2 compat - s = string[:string.index('\x00')] + s = string[:string.index('\x00')] except ValueError: # FIT specification defines the 'string' type as follows: "Null # terminated string encoded in UTF-8 format". diff --git a/fitparse/utils.py b/fitparse/utils.py index 568df0f..65f424a 100644 --- a/fitparse/utils.py +++ b/fitparse/utils.py @@ -1,15 +1,8 @@ import io import re -try: - from collections.abc import Iterable -except ImportError: - from collections.abc import Iterable +from collections.abc import Iterable -try: - # Python 3.4+ - from pathlib import PurePath -except ImportError: - PurePath = None +from pathlib import PurePath class FitParseError(ValueError): @@ -56,18 +49,14 @@ def fileish_open(fileish, mode): # BytesIO-like object return fileish elif isinstance(fileish, str): - # Python2 - file path, file contents in the case of a TypeError - # Python3 - file path - try: - return open(fileish, mode) - except TypeError: - return io.BytesIO(fileish) + # file path + return open(fileish, mode) - # Python 3 - pathlib obj - if PurePath and isinstance(fileish, PurePath): + # pathlib obj + if isinstance(fileish, PurePath): return fileish.open(mode) - # Python 3 - file contents + # file contents return io.BytesIO(fileish) diff --git a/scripts/fitdump b/scripts/fitdump index 5e4a4b4..c907972 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -7,16 +7,8 @@ import datetime import itertools import json import os.path -import sys import types -# Python 2 compat -try: - BrokenPipeError -except NameError: - import socket - BrokenPipeError = socket.error - import fitparse @@ -44,7 +36,8 @@ def parse_args(args=None): ) parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument( - '-o', '--output', type=argparse.FileType(mode='w'), default="-", + '-o', '--output', type=argparse.FileType(mode='w', encoding="utf-8"), + default="-", help='File to output data into (defaults to stdout)', ) parser.add_argument( @@ -65,12 +58,6 @@ def parse_args(args=None): options = parser.parse_args(args) - # Work around argparse.FileType not accepting an `encoding` kwarg in - # Python < 3.4 by closing and reopening the file (unless it's stdout) - if options.output is not sys.stdout: - options.output.close() - options.output = codecs.open(options.output.name, 'w', encoding='UTF-8') - options.verbose = options.verbose >= 1 options.with_defs = (options.type == "readable" and options.verbose) options.as_dict = (options.type != "readable" and options.verbose) diff --git a/setup.py b/setup.py index 7c8dc13..ad51427 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ requires = None -if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 3): - sys.exit("Python 2.7 or Python 3.3+ are required.") +if sys.version_info < (3, 6): + sys.exit("Python 3.6+ is required.") setup( diff --git a/tests/test.py b/tests/test.py index eea5dc2..fd23282 100755 --- a/tests/test.py +++ b/tests/test.py @@ -4,7 +4,6 @@ import datetime import os from struct import pack -import sys import warnings from fitparse import FitFile @@ -12,10 +11,7 @@ from fitparse.records import BASE_TYPES, Crc from fitparse.utils import FitEOFError, FitCRCError, FitHeaderError, FitParseError -if sys.version_info >= (2, 7): - import unittest -else: - import unittest2 as unittest +import unittest def generate_messages(mesg_num, local_mesg_num, field_defs, endian='<', data=None): diff --git a/tests/test_records.py b/tests/test_records.py index 5e3b823..fd401f9 100644 --- a/tests/test_records.py +++ b/tests/test_records.py @@ -1,14 +1,8 @@ #!/usr/bin/env python -import sys - from fitparse.records import Crc -if sys.version_info >= (2, 7): - import unittest -else: - import unittest2 as unittest - +import unittest class RecordsTestCase(unittest.TestCase): def test_crc(self): diff --git a/tests/test_utils.py b/tests/test_utils.py index 01d3409..9d456aa 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,21 +2,13 @@ import io import os -import sys import tempfile -try: - # Python 3.4+ - from pathlib import Path -except ImportError: - Path = None +from pathlib import Path from fitparse.utils import fileish_open, is_iterable -if sys.version_info >= (2, 7): - import unittest -else: - import unittest2 as unittest +import unittest def testfile(filename): @@ -44,8 +36,7 @@ def test_fopen(fileish): test_fopen(f.read()) with open(testfile("nametest.FIT"), 'rb') as f: test_fopen(io.BytesIO(f.read())) - if Path: - test_fopen(Path(testfile('nametest.FIT'))) + test_fopen(Path(testfile('nametest.FIT'))) def test_fileish_open_write(self): From dad9794c398b66effde9b0140f5ace81a0a5a90f Mon Sep 17 00:00:00 2001 From: Tom Fryers Date: Mon, 22 Aug 2022 22:25:34 +0100 Subject: [PATCH 3/5] Pyupgrade tests scripts and tests --- scripts/fitdump | 19 +++++++++---------- scripts/generate_profile.py | 34 +++++++++++++++++----------------- scripts/unit_tool.py | 2 +- tests/test.py | 8 ++++---- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/scripts/fitdump b/scripts/fitdump index c907972..629f61b 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -1,5 +1,4 @@ #!/usr/bin/env python -from __future__ import print_function import argparse import codecs @@ -13,16 +12,16 @@ import fitparse def format_message(num, message, options): - s = ["{}. {}".format(num, message.name)] + s = [f"{num}. {message.name}"] if options.with_defs: - s.append(' [{}]'.format(message.type)) + s.append(f' [{message.type}]') s.append('\n') if message.type == 'data': for field_data in message: - s.append(' * {}: {}'.format(field_data.name, field_data.value)) + s.append(f' * {field_data.name}: {field_data.value}') if field_data.units: - s.append(' [{}]'.format(field_data.units)) + s.append(f' [{field_data.units}]') s.append('\n') s.append('\n') @@ -79,7 +78,7 @@ class RecordJSONEncoder(json.JSONEncoder): } } # Fall back to original to raise a TypeError - return super(RecordJSONEncoder, self).default(obj) + return super().default(obj) def generate_gpx(records, filename=None): @@ -100,7 +99,7 @@ def generate_gpx(records, filename=None): if message.name == "file_id": for field_data in message: if field_data.name == "time_created" and type(field_data.value) == datetime.datetime: - yield ' \n'.format(field_data.value.strftime(GPX_TIME_FMT)) + yield f' \n' break else: # No time found in the fields, check next record @@ -111,13 +110,13 @@ def generate_gpx(records, filename=None): break if filename: - yield ' {}\n'.format(filename) + yield f' {filename}\n' yield ' \n' yield ' \n' if filename: - yield ' {}\n'.format(filename) + yield f' {filename}\n' yield ' \n' @@ -191,7 +190,7 @@ def main(args=None): finally: try: options.output.close() - except IOError: + except OSError: pass if __name__ == '__main__': diff --git a/scripts/generate_profile.py b/scripts/generate_profile.py index c88ab60..5d5a5fb 100755 --- a/scripts/generate_profile.py +++ b/scripts/generate_profile.py @@ -28,14 +28,14 @@ def header(header, indent=0): - return '%s# %s' % (' ' * indent, (' %s ' % header).center(78 - indent, '*')) + return '{}# {}'.format(' ' * indent, (' %s ' % header).center(78 - indent, '*')) def scrub_symbol_name(symbol_name): return SYMBOL_NAME_SCRUBBER.sub('_', symbol_name) -PROFILE_HEADER_FIRST_PART = "%s\n%s" % ( +PROFILE_HEADER_FIRST_PART = "{}\n{}".format( header('BEGIN AUTOMATICALLY GENERATED FIT PROFILE'), header('DO NOT EDIT THIS FILE'), ) @@ -92,7 +92,7 @@ def scrub_symbol_name(symbol_name): def render_type(name): if name in BASE_TYPES: - return "BASE_TYPES[%s], # %s" % (BASE_TYPES[name], name) + return "BASE_TYPES[{}], # {}".format(BASE_TYPES[name], name) else: return "FIELD_TYPES['%s']," % name @@ -121,7 +121,7 @@ def get_mesg_num(self, name): def __str__(self): s = 'FIELD_TYPES = {\n' for type in sorted(self.types, key=lambda x: x.name): - s += " '%s': %s,\n" % (type.name, indent(type)) + s += " '{}': {},\n".format(type.name, indent(type)) s += '}' return s @@ -131,18 +131,18 @@ def get(self, value_name): for value in self.values: if value.name == value_name: return value - raise AssertionError("Invalid value name %s in type %s" % (value_name, self.name)) + raise AssertionError("Invalid value name {} in type {}".format(value_name, self.name)) def __str__(self): s = 'FieldType(%s\n' % render_comment(self.comment) s += " name='%s',\n" % (self.name) - s += " base_type=BASE_TYPES[%s], # %s\n" % ( + s += " base_type=BASE_TYPES[{}], # {}\n".format( BASE_TYPES[self.base_type], self.base_type, ) if self.values: s += " values={\n" for value in sorted(self.values, key=lambda x: x.value if isinstance(x.value, int) else int(x.value, 16)): - s += " %s\n" % (value,) + s += " {}\n".format(value) s += " },\n" s += ")" return s @@ -150,7 +150,7 @@ def __str__(self): class TypeValueInfo(namedtuple('TypeValueInfo', ('name', 'value', 'comment'))): def __str__(self): - return "%s: '%s',%s" % (self.value, self.name, render_comment(self.comment)) + return "{}: '{}',{}".format(self.value, self.name, render_comment(self.comment)) class MessageList(namedtuple('MessageList', ('messages'))): @@ -170,7 +170,7 @@ def __str__(self): s += '\n\n' s += "%s\n" % header(message.group_name, 4) last_group_name = message.group_name - s += " %s: %s,\n" % (message.num, indent(message)) + s += " {}: {},\n".format(message.num, indent(message)) s += '}' return s @@ -188,7 +188,7 @@ def get_field_by_name(self, mesg_name, field_name): if field.name == field_name: return mesg, field - raise ValueError('field "%s" not found in message "%s"' % (field_name, mesg_name)) + raise ValueError('field "{}" not found in message "{}"'.format(field_name, mesg_name)) class MessageInfo(namedtuple('MessageInfo', ('name', 'num', 'group_name', 'fields', 'comment'))): @@ -196,7 +196,7 @@ def get(self, field_name): for field in self.fields: if field.name == field_name: return field - raise AssertionError("Invalid field name %s in message %s" % (field_name, self.name)) + raise AssertionError("Invalid field name {} in message {}".format(field_name, self.name)) def __str__(self): s = "MessageType(%s\n" % render_comment(self.comment) @@ -391,7 +391,7 @@ def parse_types(types_rows): if value.name and value.value is not None: # Don't add ignore keyed types - if "%s:%s" % (type.name, value.name) not in IGNORE_TYPE_VALUES: + if "{}:{}".format(type.name, value.name) not in IGNORE_TYPE_VALUES: type.values.append(value) # Add missing boolean type if it's not there @@ -546,7 +546,7 @@ def get_xls_and_version_from_zip(path): def main(input_xls_or_zip, output_py_path=None): if output_py_path and os.path.exists(output_py_path): - if not open(output_py_path, 'r').read().strip().startswith(PROFILE_HEADER_FIRST_PART): + if not open(output_py_path).read().strip().startswith(PROFILE_HEADER_FIRST_PART): print("Python file doesn't begin with appropriate header. Exiting.") sys.exit(1) @@ -563,7 +563,7 @@ def main(input_xls_or_zip, output_py_path=None): for mesg_name in MESSAGE_NUM_DECLARATIONS: mesg_info = message_list.get_by_name(mesg_name) - mesg_num_declarations.append('MESG_NUM_%s = %s' % ( + mesg_num_declarations.append('MESG_NUM_{} = {}'.format( scrub_symbol_name(mesg_name).upper(), str(mesg_info.num) if mesg_info else 'None')) @@ -573,7 +573,7 @@ def main(input_xls_or_zip, output_py_path=None): mesg_name, field_name = field_fqn.split('.', maxsplit=1) mesg_info, field_info = message_list.get_field_by_name(mesg_name, field_name) - field_decl = 'FIELD_NUM_%s_%s = %s' % ( + field_decl = 'FIELD_NUM_{}_{} = {}'.format( scrub_symbol_name(mesg_name).upper(), scrub_symbol_name(field_name).upper(), str(field_info.num)) @@ -582,7 +582,7 @@ def main(input_xls_or_zip, output_py_path=None): output = '\n'.join([ "\n%s" % PROFILE_HEADER_FIRST_PART, - header('EXPORTED PROFILE FROM %s ON %s' % ( + header('EXPORTED PROFILE FROM {} ON {}'.format( ('SDK VERSION %s' % profile_version) if profile_version else 'SPREADSHEET', datetime.datetime.now().strftime('%Y-%m-%d'), )), @@ -609,7 +609,7 @@ def main(input_xls_or_zip, output_py_path=None): if output_py_path: open(output_py_path, 'w').write(output) - print('Profile version %s written to %s' % ( + print('Profile version {} written to {}'.format( profile_version if profile_version else '', output_py_path)) else: diff --git a/scripts/unit_tool.py b/scripts/unit_tool.py index ee1c876..0f66258 100755 --- a/scripts/unit_tool.py +++ b/scripts/unit_tool.py @@ -54,7 +54,7 @@ def do_fitparse_profile(): if __name__ == '__main__': if len(sys.argv) < 2: - print("Usage: {0} Profile.xls".format(os.path.basename(__file__))) + print(f"Usage: {os.path.basename(__file__)} Profile.xls") sys.exit(0) do_profile_xls() diff --git a/tests/test.py b/tests/test.py index fd23282..886f21f 100755 --- a/tests/test.py +++ b/tests/test.py @@ -35,7 +35,7 @@ def generate_messages(mesg_num, local_mesg_num, field_defs, endian='<', data=Non for mesg_data in data: s = pack('B', local_mesg_num) for value, base_type in zip(mesg_data, base_type_list): - s += pack("%s%s" % (endian, base_type.fmt), value) + s += pack("{}{}".format(endian, base_type.fmt), value) mesgs.append(s) return b''.join(mesgs) @@ -106,7 +106,7 @@ def test_basic_file_big_endian(self): def test_component_field_accumulaters(self): # TODO: abstract CSV parsing - csv_fp = open(testfile('compressed-speed-distance-records.csv'), 'r') + csv_fp = open(testfile('compressed-speed-distance-records.csv')) csv_file = csv.reader(csv_fp) next(csv_file) # Consume header @@ -252,7 +252,7 @@ def test_parsing_edge_820_fit_file(self): 'garmin-edge-820-bike-records.csv') def _csv_test_helper(self, fit_file, csv_file): - csv_fp = open(testfile(csv_file), 'r') + csv_fp = open(testfile(csv_file)) csv_messages = csv.reader(csv_fp) field_names = next(csv_messages) # Consume header @@ -298,7 +298,7 @@ def _csv_test_helper(self, fit_file, csv_file): self.assertAlmostEqual(fit_value, float(csv_value)) else: self.assertEqual(fit_value, csv_value, - msg="For %s, FIT value '%s' did not match CSV value '%s'" % (field_name, fit_value, csv_value)) + msg="For {}, FIT value '{}' did not match CSV value '{}'".format(field_name, fit_value, csv_value)) try: next(messages) From c223d38129fb8087444d1ca04a5b322a92d8522f Mon Sep 17 00:00:00 2001 From: Tom Fryers Date: Wed, 7 Sep 2022 08:57:31 +0100 Subject: [PATCH 4/5] Fix indexing --- fitparse/records.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fitparse/records.py b/fitparse/records.py index 7d557fa..e9fcd6a 100644 --- a/fitparse/records.py +++ b/fitparse/records.py @@ -379,7 +379,7 @@ def calculate(cls, byte_arr, crc=0): def parse_string(string): try: - s = string[:string.index('\x00')] + s = string[:string.index(0x00)] except ValueError: # FIT specification defines the 'string' type as follows: "Null # terminated string encoded in UTF-8 format". From e855290e3592371319b70c56841bef3fe2f27021 Mon Sep 17 00:00:00 2001 From: Tom Fryers Date: Wed, 7 Sep 2022 21:58:15 +0100 Subject: [PATCH 5/5] Update test versions --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d7ee4e..9b334bf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['2.7', '3.5', '3.6', '3.x', 'pypy2', 'pypy3'] + python-version: ['3.6', '3.7', '3.8', '3.x', 'pypy3'] steps: - uses: actions/checkout@v2