Skip to content

Commit

Permalink
Merge branch 'master' into xlsx-read-support
Browse files Browse the repository at this point in the history
  • Loading branch information
fundthmcalculus authored Jun 21, 2024
2 parents 492d1a7 + 9bd14c4 commit 3936a4e
Show file tree
Hide file tree
Showing 18 changed files with 175 additions and 152 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['2.7', '3.3', '3.4', '3.5', '3.6', '3.x', 'pypy2', 'pypy3']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.x']

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
MIT License

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

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
python-fitparse
===============

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

Expand Down
2 changes: 1 addition & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ The ``FitFile`` Object
try:
fitfile = FitFile('/path.to/fitfile.fit')
fitfile.parse()
except FitParseError, e:
except FitParseError as e:
print "Error while parsing .FIT file: %s" % e
sys.exit(1)

Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Requirements

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

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


API Documentation
Expand Down
38 changes: 16 additions & 22 deletions fitparse/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -20,12 +14,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:
Expand Down Expand Up @@ -97,7 +91,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(
Expand All @@ -110,11 +104,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,
Expand All @@ -141,7 +135,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()
Expand Down Expand Up @@ -193,7 +187,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)))

##########
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -530,7 +524,7 @@ def _make_set(obj):
if is_iterable(obj):
return set(obj)
else:
return set((obj,))
return {obj}

##########
# Public API
Expand All @@ -550,15 +544,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):
Expand All @@ -572,7 +566,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
Expand All @@ -584,12 +578,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)
Expand All @@ -612,7 +606,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,
Expand Down
19 changes: 14 additions & 5 deletions fitparse/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -83,9 +83,18 @@ def process_type_local_date_time(self, field_data):

def process_type_localtime_into_day(self, field_data):
if field_data.value is not None:
m, s = divmod(field_data.value, 60)
h, m = divmod(m, 60)
field_data.value = datetime.time(h, m, s)
# NOTE: Values larger or equal to 86400 should not be possible.
# Additionally, if the value is exactly 86400, it will lead to an error when trying to
# create the time with datetime.time(24, 0 , 0).
#
# E.g. Garmin does add "sleep_time": 86400 to its fit files,
# which causes an error if not properly handled.
if field_data.value >= 86400:
field_data.value = datetime.time.max
else:
m, s = divmod(field_data.value, 60)
h, m = divmod(m, 60)
field_data.value = datetime.time(h, m, s)
field_data.units = None


Expand All @@ -98,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:
Expand Down
61 changes: 60 additions & 1 deletion fitparse/profile.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# ***************** BEGIN AUTOMATICALLY GENERATED FIT PROFILE ******************
# *************************** DO NOT EDIT THIS FILE ****************************
# *********** EXPORTED PROFILE FROM SDK VERSION 21.67 ON 2021-11-01 ************
Expand Down Expand Up @@ -1432,6 +1431,14 @@
9: 'hug_a_tree',
},
),
'strava_product': FieldType(
name='strava_product',
base_type=BASE_TYPES[0x84], # uint16
values={
101: 'Strava iPhone App', # recent versions of Strava iPhone app
102: 'Strava Android App', # recent versions of Strava Android app
}
),
'garmin_product': FieldType(
name='garmin_product',
base_type=BASE_TYPES[0x84], # uint16
Expand Down Expand Up @@ -4364,6 +4371,19 @@
value='tacx',
raw_value=89,
),
)
),
SubField(
name='strava_product',
def_num=2,
type=FIELD_TYPES['strava_product'],
ref_fields=(
ReferenceField(
name='manufacturer',
def_num=1,
value='strava',
raw_value=265,
),
),
),
),
Expand Down Expand Up @@ -8123,6 +8143,19 @@
),
),
),
SubField(
name='strava_product',
def_num=4,
type=FIELD_TYPES['strava_product'],
ref_fields=(
ReferenceField(
name='manufacturer',
def_num=2,
value='strava',
raw_value=265,
),
),
),
),
),
5: Field(
Expand Down Expand Up @@ -10129,6 +10162,19 @@
),
),
),
SubField(
name='strava_product',
def_num=1,
type=FIELD_TYPES['strava_product'],
ref_fields=(
ReferenceField(
name='manufacturer',
def_num=0,
value='strava',
raw_value=265,
),
),
),
),
),
},
Expand Down Expand Up @@ -12109,6 +12155,19 @@
bits=4,
bit_offset=4,
),
SubField(
name='strava_product',
def_num=1,
type=FIELD_TYPES['strava_product'],
ref_fields=(
ReferenceField(
name='manufacturer',
def_num=0,
value='strava',
raw_value=265,
),
),
),
),
),
2: Field(
Expand Down
Loading

0 comments on commit 3936a4e

Please sign in to comment.