Skip to content

Commit 3936a4e

Browse files
Merge branch 'master' into xlsx-read-support
2 parents 492d1a7 + 9bd14c4 commit 3936a4e

18 files changed

+175
-152
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ['2.7', '3.3', '3.4', '3.5', '3.6', '3.x', 'pypy2', 'pypy3']
14+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.x']
1515

1616
steps:
1717
- uses: actions/checkout@v2

LICENSE

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
MIT License
22

3-
Copyright (c) 2011-2021, David Cooper <[email protected]>
4-
Copyright (c) 2017-2021, Carey Metcalfe <[email protected]>
3+
Copyright (c) 2011-2022, David Cooper <[email protected]>
4+
Copyright (c) 2017-2022, Carey Metcalfe <[email protected]>
55

66
Permission is hereby granted, free of charge, to any person obtaining a copy
77
of this software and associated documentation files (the "Software"), to deal

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
python-fitparse
22
===============
33

4+
> :warning: **NOTE:** *I have **limited to no time** to work on this package
5+
> these days!*
6+
>
7+
> I am looking for a maintainer to help with issues and updating/releasing the package.
8+
> Please reach out via email at <[email protected]> if you have interest in helping.
9+
>
10+
> If you're having trouble using this package for whatever reason, might we suggest using
11+
> an alternative library: [fitdecode](https://github.com/polyvertex/fitdecode) by
12+
> [polyvertex](https://github.com/polyvertex).
13+
>
14+
> Cheers,
15+
>
16+
> David
17+
418
Here's a Python library to parse ANT/Garmin `.FIT` files.
519
[![Build Status](https://github.com/dtcooper/python-fitparse/workflows/test/badge.svg)](https://github.com/dtcooper/python-fitparse/actions?query=workflow%3Atest)
620

docs/api.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ The ``FitFile`` Object
7676
try:
7777
fitfile = FitFile('/path.to/fitfile.fit')
7878
fitfile.parse()
79-
except FitParseError, e:
79+
except FitParseError as e:
8080
print "Error while parsing .FIT file: %s" % e
8181
sys.exit(1)
8282

docs/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Requirements
5656

5757
The following are required to install :mod:`fitparse`,
5858

59-
* `Python <http://www.python.org/>`_ 2.7 and above
59+
* `Python <http://www.python.org/>`_ 3.6 and above
6060

6161

6262
API Documentation

fitparse/base.py

+16-22
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@
55
import struct
66
import warnings
77

8-
# Python 2 compat
9-
try:
10-
num_types = (int, float, long)
11-
except NameError:
12-
num_types = (int, float)
13-
148
from fitparse.processors import FitFileDataProcessor
159
from fitparse.profile import FIELD_TYPE_TIMESTAMP, MESSAGE_TYPES
1610
from fitparse.records import (
@@ -20,12 +14,12 @@
2014
from fitparse.utils import fileish_open, is_iterable, FitParseError, FitEOFError, FitCRCError, FitHeaderError
2115

2216

23-
class DeveloperDataMixin(object):
17+
class DeveloperDataMixin:
2418
def __init__(self, *args, check_developer_data=True, **kwargs):
2519
self.check_developer_data = check_developer_data
2620
self.dev_types = {}
2721

28-
super(DeveloperDataMixin, self).__init__(*args, **kwargs)
22+
super().__init__(*args, **kwargs)
2923

3024
def _append_dev_data_id(self, dev_data_index, application_id=None, fields=None):
3125
if fields is None:
@@ -97,7 +91,7 @@ def get_dev_type(self, dev_data_index, field_def_num):
9791
if dev_data_index not in self.dev_types:
9892
if self.check_developer_data:
9993
raise FitParseError(
100-
"No such dev_data_index=%s found when looking up field %s" % (dev_data_index, field_def_num)
94+
f"No such dev_data_index={dev_data_index} found when looking up field {field_def_num}"
10195
)
10296

10397
warnings.warn(
@@ -110,11 +104,11 @@ def get_dev_type(self, dev_data_index, field_def_num):
110104
if field_def_num not in dev_type['fields']:
111105
if self.check_developer_data:
112106
raise FitParseError(
113-
"No such field %s for dev_data_index %s" % (field_def_num, dev_data_index)
107+
f"No such field {field_def_num} for dev_data_index {dev_data_index}"
114108
)
115109

116110
warnings.warn(
117-
"Field %s for dev_data_index %s missing. Adding dummy field." % (field_def_num, dev_data_index)
111+
f"Field {field_def_num} for dev_data_index {dev_data_index} missing. Adding dummy field."
118112
)
119113
self._append_dev_field_description(
120114
dev_data_index=dev_data_index,
@@ -141,7 +135,7 @@ def __init__(self, fileish, *args, check_crc=True, data_processor=None, **kwargs
141135
# Start off by parsing the file header (sets initial attribute values)
142136
self._parse_file_header()
143137

144-
super(FitFileDecoder, self).__init__(*args, **kwargs)
138+
super().__init__(*args, **kwargs)
145139

146140
def __del__(self):
147141
self.close()
@@ -193,7 +187,7 @@ def _read_and_assert_crc(self, allow_zero=False):
193187
return
194188
if crc_computed == crc_read or (allow_zero and crc_read == 0):
195189
return
196-
raise FitCRCError('CRC Mismatch [computed: %s, read: %s]' % (
190+
raise FitCRCError('CRC Mismatch [computed: {}, read: {}]'.format(
197191
Crc.format(crc_computed), Crc.format(crc_read)))
198192

199193
##########
@@ -396,7 +390,7 @@ def _apply_scale_offset(self, field, raw_value):
396390
if isinstance(raw_value, tuple):
397391
# Contains multiple values, apply transformations to all of them
398392
return tuple(self._apply_scale_offset(field, x) for x in raw_value)
399-
elif isinstance(raw_value, num_types):
393+
elif isinstance(raw_value, (int, float)):
400394
if field.scale:
401395
raw_value = float(raw_value) / field.scale
402396
if field.offset:
@@ -530,7 +524,7 @@ def _make_set(obj):
530524
if is_iterable(obj):
531525
return set(obj)
532526
else:
533-
return set((obj,))
527+
return {obj}
534528

535529
##########
536530
# Public API
@@ -550,15 +544,15 @@ def __iter__(self):
550544
return self.get_messages()
551545

552546

553-
class CacheMixin(object):
547+
class CacheMixin:
554548
"""Add message caching to the FitFileDecoder"""
555549

556550
def __init__(self, *args, **kwargs):
557-
super(CacheMixin, self).__init__(*args, **kwargs)
551+
super().__init__(*args, **kwargs)
558552
self._messages = []
559553

560554
def _parse_message(self):
561-
self._messages.append(super(CacheMixin, self)._parse_message())
555+
self._messages.append(super()._parse_message())
562556
return self._messages[-1]
563557

564558
def get_messages(self, name=None, with_definitions=False, as_dict=False):
@@ -572,7 +566,7 @@ def get_messages(self, name=None, with_definitions=False, as_dict=False):
572566
if self._should_yield(message, with_definitions, names):
573567
yield message.as_dict() if as_dict else message
574568

575-
for message in super(CacheMixin, self).get_messages(names, with_definitions, as_dict):
569+
for message in super().get_messages(names, with_definitions, as_dict):
576570
yield message
577571

578572
@property
@@ -584,12 +578,12 @@ def parse(self):
584578
pass
585579

586580

587-
class DataProcessorMixin(object):
581+
class DataProcessorMixin:
588582
"""Add data processing to the FitFileDecoder"""
589583

590584
def __init__(self, *args, **kwargs):
591585
self._processor = kwargs.pop("data_processor", None) or FitFileDataProcessor()
592-
super(DataProcessorMixin, self).__init__(*args, **kwargs)
586+
super().__init__(*args, **kwargs)
593587

594588
def _parse_data_message(self, header):
595589
header, def_mesg, field_datas = self._parse_data_message_components(header)
@@ -612,7 +606,7 @@ class UncachedFitFile(DataProcessorMixin, FitFileDecoder):
612606

613607
def __init__(self, fileish, *args, check_crc=True, data_processor=None, **kwargs):
614608
# Ensure all optional params are passed as kwargs
615-
super(UncachedFitFile, self).__init__(
609+
super().__init__(
616610
fileish,
617611
*args,
618612
check_crc=check_crc,

fitparse/processors.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
UTC_REFERENCE = 631065600 # timestamp for UTC 00:00 Dec 31 1989
66

77

8-
class FitFileDataProcessor(object):
8+
class FitFileDataProcessor:
99
# TODO: Document API
1010
# Functions that will be called to do the processing:
1111
#def run_type_processor(field_data)
@@ -83,9 +83,18 @@ def process_type_local_date_time(self, field_data):
8383

8484
def process_type_localtime_into_day(self, field_data):
8585
if field_data.value is not None:
86-
m, s = divmod(field_data.value, 60)
87-
h, m = divmod(m, 60)
88-
field_data.value = datetime.time(h, m, s)
86+
# NOTE: Values larger or equal to 86400 should not be possible.
87+
# Additionally, if the value is exactly 86400, it will lead to an error when trying to
88+
# create the time with datetime.time(24, 0 , 0).
89+
#
90+
# E.g. Garmin does add "sleep_time": 86400 to its fit files,
91+
# which causes an error if not properly handled.
92+
if field_data.value >= 86400:
93+
field_data.value = datetime.time.max
94+
else:
95+
m, s = divmod(field_data.value, 60)
96+
h, m = divmod(m, 60)
97+
field_data.value = datetime.time(h, m, s)
8998
field_data.units = None
9099

91100

@@ -98,7 +107,7 @@ def run_field_processor(self, field_data):
98107
if field_data.name.endswith("_speed"):
99108
self.process_field_speed(field_data)
100109
else:
101-
super(StandardUnitsDataProcessor, self).run_field_processor(field_data)
110+
super().run_field_processor(field_data)
102111

103112
def process_field_distance(self, field_data):
104113
if field_data.value is not None:

fitparse/profile.py

+60-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# ***************** BEGIN AUTOMATICALLY GENERATED FIT PROFILE ******************
32
# *************************** DO NOT EDIT THIS FILE ****************************
43
# *********** EXPORTED PROFILE FROM SDK VERSION 21.67 ON 2021-11-01 ************
@@ -1432,6 +1431,14 @@
14321431
9: 'hug_a_tree',
14331432
},
14341433
),
1434+
'strava_product': FieldType(
1435+
name='strava_product',
1436+
base_type=BASE_TYPES[0x84], # uint16
1437+
values={
1438+
101: 'Strava iPhone App', # recent versions of Strava iPhone app
1439+
102: 'Strava Android App', # recent versions of Strava Android app
1440+
}
1441+
),
14351442
'garmin_product': FieldType(
14361443
name='garmin_product',
14371444
base_type=BASE_TYPES[0x84], # uint16
@@ -4364,6 +4371,19 @@
43644371
value='tacx',
43654372
raw_value=89,
43664373
),
4374+
)
4375+
),
4376+
SubField(
4377+
name='strava_product',
4378+
def_num=2,
4379+
type=FIELD_TYPES['strava_product'],
4380+
ref_fields=(
4381+
ReferenceField(
4382+
name='manufacturer',
4383+
def_num=1,
4384+
value='strava',
4385+
raw_value=265,
4386+
),
43674387
),
43684388
),
43694389
),
@@ -8123,6 +8143,19 @@
81238143
),
81248144
),
81258145
),
8146+
SubField(
8147+
name='strava_product',
8148+
def_num=4,
8149+
type=FIELD_TYPES['strava_product'],
8150+
ref_fields=(
8151+
ReferenceField(
8152+
name='manufacturer',
8153+
def_num=2,
8154+
value='strava',
8155+
raw_value=265,
8156+
),
8157+
),
8158+
),
81268159
),
81278160
),
81288161
5: Field(
@@ -10129,6 +10162,19 @@
1012910162
),
1013010163
),
1013110164
),
10165+
SubField(
10166+
name='strava_product',
10167+
def_num=1,
10168+
type=FIELD_TYPES['strava_product'],
10169+
ref_fields=(
10170+
ReferenceField(
10171+
name='manufacturer',
10172+
def_num=0,
10173+
value='strava',
10174+
raw_value=265,
10175+
),
10176+
),
10177+
),
1013210178
),
1013310179
),
1013410180
},
@@ -12109,6 +12155,19 @@
1210912155
bits=4,
1211012156
bit_offset=4,
1211112157
),
12158+
SubField(
12159+
name='strava_product',
12160+
def_num=1,
12161+
type=FIELD_TYPES['strava_product'],
12162+
ref_fields=(
12163+
ReferenceField(
12164+
name='manufacturer',
12165+
def_num=0,
12166+
value='strava',
12167+
raw_value=265,
12168+
),
12169+
),
12170+
),
1211212171
),
1211312172
),
1211412173
2: Field(

0 commit comments

Comments
 (0)