- Implement component fields:
ComponentField.extract(),ComponentField.pack_into(),Composite.decompose(), andComponentProxydescriptor — transparent read/write access to bit-packed sub-fields (e.g.compressed_speed_distanceinRecord) Metadataclass gains acomponentsregistry;MessageMetaautomatically installsComponentProxyfor component names that do not conflict with regular field names
- Fix
CompressedTimestampHeader.write()— was setting bit 0 (1) instead of bit 7 (1 << 7), making the written byte unrecognisable as a compressed-timestamp record byRecordHeader.read() - Fix
Composite.__init__not propagatingbase.size— caused zero-byte fields when reading/writing messages with composite fields (same root cause as theDynamicsize bug fixed in 0.5.0) - Fix
_load_plugins()compatibility with Python 3.9 —entry_points(group=...)was added in 3.10; fall back toentry_points().get(group, [])on older interpreters
- Test suite expanded: 166 tests, 94 % coverage (was 122 / 93 %)
- Add
tests/test_record_headers.py— full coverage ofCompressedTimestampHeaderread / write / repr /process_message - Add
tests/test_filelike.py— coversFileLike.create(),_apply_mixinwith aFitFilesubclass, and all_validateerror paths
- Drop Python 2 support; require Python ≥ 3.9
- Drop compatibility shims for
long,unicode,super(Cls, self),__nonzero__,raise T, V, TB
FitFile.open()acceptsstr,pathlib.Path, or any binary-readable streamFitFile.open()supportsmode="r","w", and"a"; context-manager protocol (with ... as f:)FitFile.filter_by(*types)— yield messages by classFitFile.get_messages(type_or_name)— yield messages by class or case-insensitive string nameFitFile.merge(other)— append all messages from anotherFitFile, skipping a duplicateFileIdFitFile.extend(),FitFile.remove(),FitFile.pop()- Plugin system: register custom
MessageandFitFilesubclasses viafit.messages/fit.filesentry-point groups
- Fix
Dynamic.__init__not propagatingbase.size— causedstruct.erroron write/read of any message containing aDynamicfield (e.g.FileId.product) - Fix
FileId.create()writingproductas a raw string instead of going through thegarmin_productsubfield — causedstruct.erroron write - Fix
KNOWNtype registry: filter out@propertydescriptors (Dynamic.type) viaisinstance(val, int) - Fix
Type.__hash__missing after defining__eq__ - Fix integer division in
ArrayType:/→// - Fix
".FIT"literal →b".FIT"(struct unpacks4sas bytes in Python 3) - Fix
0xDEADBEAFtypo →0xDEADBEEFinFileId - Fix bytes handling:
ord(chunk[i])→chunk[i],"".join()→b"".join()
- Replace
Meta(dict)with a proper@dataclass— typed, introspectable, no dict-access magic - Move all business logic out of
__init__.pyfiles into dedicated modules (fitfile.py,messages/message.py,types/base.py,files/filelike.py,record/constants.py);__init__.pyfiles are now thin re-export stubs - Replace
setup.pywithpyproject.toml - Add
py.typedmarker (PEP 561) - Add full type annotations,
from __future__ import annotations, and docstrings throughout
- Add
pytesttest suite (122 tests, 93 % coverage) - Add
black,ruff, andmypyconfiguration - Add
pre-commitconfiguration - Add Makefile for common development tasks
- Add
tox.inifor multi-version testing (py39, py310, py312) - Add GitHub Actions CI matrix (Python 3.9, 3.10, 3.12, 3.13)
- Last Python 2 release