From c731a7774e407a8d54c295bf708989d2a039ac40 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Tue, 24 Apr 2018 21:59:35 -0400 Subject: [PATCH 1/7] Clean up package * Add Python 3 support * Add flake8 and coverage reporting * move modules into packages * stop shipping tests with built package * Try using tox-travis (fingers-crossed) --- .coveragerc | 6 +++ .travis.yml | 6 ++- setup.cfg | 5 ++- setup.py | 17 ++++--- tcxparser/__init__.py | 1 + tcxparser.py => tcxparser/tcxparser.py | 26 ++++++----- tests/__init__.py | 0 test.tcx => tests/files/test.tcx | 0 test2.tcx => tests/files/test2.tcx | 0 test_tcxparser.py => tests/test_tcxparser.py | 47 +++++++++++--------- tox.ini | 23 ++++++++++ 11 files changed, 91 insertions(+), 40 deletions(-) create mode 100644 .coveragerc create mode 100644 tcxparser/__init__.py rename tcxparser.py => tcxparser/tcxparser.py (89%) create mode 100644 tests/__init__.py rename test.tcx => tests/files/test.tcx (100%) rename test2.tcx => tests/files/test2.tcx (100%) rename test_tcxparser.py => tests/test_tcxparser.py (68%) create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..a64dbdb --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +branch = true +source = tcxparser + +[report] +show_missing = true diff --git a/.travis.yml b/.travis.yml index f1f3964..361fe5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ +sudo: false language: python python: - "2.7" -install: python setup.py install -script: python setup.py test + - "3.5" +install: pip install tox-travis +script: tox diff --git a/setup.cfg b/setup.cfg index 224a779..e91ee50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [metadata] -description-file = README.md \ No newline at end of file +description-file = README.md + +[flake8] +max-line-length = 120 diff --git a/setup.py b/setup.py index 63478a8..1230b16 100644 --- a/setup.py +++ b/setup.py @@ -1,27 +1,34 @@ -from setuptools import setup +from setuptools import setup, find_packages __version__ = '0.7.2' setup( name='python-tcxparser', version=__version__, + description='Simple parser for Garmin TCX files', + long_description=open('README.md').read(), author='Vinod Kurup', author_email='vinod@kurup.com', - py_modules=['tcxparser', 'test_tcxparser'], url='https://github.com/vkurup/python-tcxparser/', + packages=find_packages(include=['tcxparser']), + include_package_data=True, license='BSD', - description='Simple parser for Garmin TCX files', + zip_safe=False, + keywords='tcx', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries :: Python Modules', ], - long_description=open('README.md').read(), install_requires=[ "lxml", ], - test_suite="test_tcxparser", + test_suite="tests", ) diff --git a/tcxparser/__init__.py b/tcxparser/__init__.py new file mode 100644 index 0000000..65ee432 --- /dev/null +++ b/tcxparser/__init__.py @@ -0,0 +1 @@ +from .tcxparser import TCXParser # noqa diff --git a/tcxparser.py b/tcxparser/tcxparser.py similarity index 89% rename from tcxparser.py rename to tcxparser/tcxparser.py index 8fcb415..aef6151 100644 --- a/tcxparser.py +++ b/tcxparser/tcxparser.py @@ -1,4 +1,8 @@ "Simple parser for Garmin TCX files." +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals import time from lxml import objectify @@ -21,10 +25,10 @@ def altitude_points(self): def time_values(self): return [x.text for x in self.root.xpath('//ns:Time', namespaces={'ns': namespace})] - + def cadence_values(self): return [int(x.text) for x in self.root.xpath('//ns:Cadence', namespaces={'ns': namespace})] - + @property def latitude(self): if hasattr(self.activity.Lap.Track.Trackpoint, 'Position'): @@ -42,11 +46,11 @@ def activity_type(self): @property def completed_at(self): return self.activity.Lap[-1].Track.Trackpoint[-1].Time.pyval - + @property def cadence_avg(self): return self.activity.Lap[-1].Cadence - + @property def distance(self): distance_values = self.root.findall('.//ns:DistanceMeters', namespaces={'ns': namespace}) @@ -66,12 +70,12 @@ def duration(self): @property def calories(self): return sum(lap.Calories for lap in self.activity.Lap) - + @property def hr_avg(self): """Average heart rate of the workout""" hr_data = self.hr_values() - return sum(hr_data)/len(hr_data) + return int(sum(hr_data) / len(hr_data)) @property def hr_max(self): @@ -86,14 +90,14 @@ def hr_min(self): @property def pace(self): """Average pace (mm:ss/km for the workout""" - secs_per_km = self.duration/(self.distance/1000) + secs_per_km = self.duration / (self.distance / 1000) return time.strftime('%M:%S', time.gmtime(secs_per_km)) @property def altitude_avg(self): """Average altitude for the workout""" altitude_data = self.altitude_points() - return sum(altitude_data)/len(altitude_data) + return sum(altitude_data) / len(altitude_data) @property def altitude_max(self): @@ -131,6 +135,6 @@ def descent(self): @property def cadence_max(self): - """Returns max cadence of workout""" - cadence_data = self.cadence_values() - return max(cadence_data) \ No newline at end of file + """Returns max cadence of workout""" + cadence_data = self.cadence_values() + return max(cadence_data) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test.tcx b/tests/files/test.tcx similarity index 100% rename from test.tcx rename to tests/files/test.tcx diff --git a/test2.tcx b/tests/files/test2.tcx similarity index 100% rename from test2.tcx rename to tests/files/test2.tcx diff --git a/test_tcxparser.py b/tests/test_tcxparser.py similarity index 68% rename from test_tcxparser.py rename to tests/test_tcxparser.py index b5ee501..464eb8a 100644 --- a/test_tcxparser.py +++ b/tests/test_tcxparser.py @@ -1,4 +1,8 @@ -from StringIO import StringIO +import os +try: + from StringIO import StringIO +except ImportError: + from io import StringIO import unittest from tcxparser import TCXParser @@ -8,55 +12,56 @@ class TestParseTCX(unittest.TestCase): def setUp(self): tcx_file = 'test.tcx' - self.tcx = TCXParser(tcx_file) + path = os.path.join(os.path.dirname(__file__), 'files', tcx_file) + self.tcx = TCXParser(path) def test_hr_values_are_correct(self): - self.assertEquals(self.tcx.hr_values()[0], 62) - self.assertEquals(self.tcx.hr_values()[-1], 180) + self.assertEqual(self.tcx.hr_values()[0], 62) + self.assertEqual(self.tcx.hr_values()[-1], 180) def test_altitude_points_are_correct(self): - self.assertEquals(self.tcx.altitude_points()[0], 178.942626953) - self.assertEquals(self.tcx.altitude_points()[-1], 166.4453125) + self.assertEqual(self.tcx.altitude_points()[0], 178.942626953) + self.assertEqual(self.tcx.altitude_points()[-1], 166.4453125) def test_time_values_are_correct(self): - self.assertEquals(self.tcx.time_values()[0], '2012-12-26T21:29:53Z') - self.assertEquals(self.tcx.time_values()[-1], '2012-12-26T22:03:05Z') + self.assertEqual(self.tcx.time_values()[0], '2012-12-26T21:29:53Z') + self.assertEqual(self.tcx.time_values()[-1], '2012-12-26T22:03:05Z') def test_latitude_is_correct(self): - self.assertEquals(self.tcx.latitude, 35.951880198) + self.assertEqual(self.tcx.latitude, 35.951880198) def test_longitude_is_correct(self): - self.assertEquals(self.tcx.longitude, -79.0931872185) + self.assertEqual(self.tcx.longitude, -79.0931872185) def test_activity_type_is_correct(self): - self.assertEquals(self.tcx.activity_type, 'running') + self.assertEqual(self.tcx.activity_type, 'running') def test_completion_time_is_correct(self): - self.assertEquals(self.tcx.completed_at, '2012-12-26T22:03:05Z') + self.assertEqual(self.tcx.completed_at, '2012-12-26T22:03:05Z') def test_distance_is_correct(self): - self.assertEquals(self.tcx.distance, 4686.31103516) + self.assertEqual(self.tcx.distance, 4686.31103516) def test_distance_units_is_correct(self): - self.assertEquals(self.tcx.distance_units, 'meters') + self.assertEqual(self.tcx.distance_units, 'meters') def test_duration_is_correct(self): - self.assertEquals(self.tcx.duration, 1992.78) + self.assertEqual(self.tcx.duration, 1992.78) def test_calories_is_correct(self): - self.assertEquals(self.tcx.calories, 379) + self.assertEqual(self.tcx.calories, 379) def test_hr_max(self): - self.assertEquals(self.tcx.hr_max, 189) + self.assertEqual(self.tcx.hr_max, 189) def test_hr_min(self): - self.assertEquals(self.tcx.hr_min, 60) + self.assertEqual(self.tcx.hr_min, 60) def test_hr_avg(self): - self.assertEquals(self.tcx.hr_avg, 156) + self.assertEqual(self.tcx.hr_avg, 156) def test_pace(self): - self.assertEquals(self.tcx.pace, '07:05') + self.assertEqual(self.tcx.pace, '07:05') def test_altitude_avg_is_correct(self): self.assertAlmostEqual(self.tcx.altitude_avg, 172.020056184) @@ -97,7 +102,6 @@ def test_single_trackpoint_in_track_is_ok(self): tcx = TCXParser(tcx_file) self.assertEqual(tcx.distance, 5) - def test_no_error_if_no_position(self): "https://github.com/vkurup/python-tcxparser/issues/11" xml = """ @@ -120,5 +124,6 @@ def test_no_error_if_no_position(self): self.assertEqual(tcx.latitude, None) self.assertEqual(tcx.longitude, None) + if __name__ == '__main__': unittest.main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..1788f2f --- /dev/null +++ b/tox.ini @@ -0,0 +1,23 @@ +[tox] +envlist = py27, py35, flake8, coverage + +[travis] +python = + 3.5: py35 + 2.7: py27 + +[testenv:flake8] +basepython = python +deps = flake8 +commands = flake8 . + +[testenv:coverage] +basepython = python +deps = coverage +commands = coverage run setup.py test --quiet + coverage report --skip-covered --fail-under 90 + +[testenv] +setenv = + PYTHONPATH = {toxinidir} +commands = python setup.py test --quiet From e3a21b0e56d23db1d44005fbd06bdfa1fadcb3d3 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Tue, 24 Apr 2018 22:04:46 -0400 Subject: [PATCH 2/7] Try to run flake8 and coverage --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1788f2f..3706fd5 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py27, py35, flake8, coverage [travis] python = - 3.5: py35 + 3.5: py35, flake8, coverage 2.7: py27 [testenv:flake8] From 8a30509bb7483e94e11d1d08d6c439b851b68ee7 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Tue, 24 Apr 2018 22:17:28 -0400 Subject: [PATCH 3/7] Add AUTHORS.txt --- AUTHORS.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 AUTHORS.txt diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 0000000..9ab499c --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,12 @@ +Primary author: + +Vinod Kurup (@vkurup) + +Other contributions from: + +Iztok Fister Jr (@firefly-cpp) +Jason M. (@DaddyTheRunner) +Adam Neumann (@noizwaves) +Stephen Doyle (@stevedoyle) + +Thank you! From b15053b6ebcb73e14033ad0575bd2866eb69d680 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Tue, 24 Apr 2018 22:23:13 -0400 Subject: [PATCH 4/7] Remove codeship, add Travis badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 206b6a6..b0a346a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-tcxparser -[ ![Codeship Status for vkurup/python-tcxparser](https://codeship.com/projects/eb924480-4493-0134-5e03-3a611b6d72e6/status?branch=master)](https://codeship.com/projects/168475) +[![Build Status](https://travis-ci.org/vkurup/python-tcxparser.svg?branch=master)](https://travis-ci.org/vkurup/python-tcxparser) python-tcxparser is a minimal parser for Garmin's TCX file format. It is not in any way exhaustive. It extracts just enough data to allow me From dde51b21bf089ac35175ca1680c034fca368969f Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Tue, 24 Apr 2018 22:30:21 -0400 Subject: [PATCH 5/7] Incorporate changes from #16 --- tcxparser/tcxparser.py | 4 +--- tests/test_cycling.py | 18 ++++++++++++++++++ tests/test_tcxparser.py | 10 +++------- 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 tests/test_cycling.py diff --git a/tcxparser/tcxparser.py b/tcxparser/tcxparser.py index aef6151..bafefb3 100644 --- a/tcxparser/tcxparser.py +++ b/tcxparser/tcxparser.py @@ -54,9 +54,7 @@ def cadence_avg(self): @property def distance(self): distance_values = self.root.findall('.//ns:DistanceMeters', namespaces={'ns': namespace}) - if distance_values: - return distance_values[-1] - return 0 + return distance_values[-1] if distance_values else 0 @property def distance_units(self): diff --git a/tests/test_cycling.py b/tests/test_cycling.py new file mode 100644 index 0000000..dabe28a --- /dev/null +++ b/tests/test_cycling.py @@ -0,0 +1,18 @@ +import os +from unittest import TestCase + +from tcxparser import TCXParser + + +class TestParseCyclingTCX(TestCase): + + def setUp(self): + tcx_file = 'test2.tcx' + path = os.path.join(os.path.dirname(__file__), 'files', tcx_file) + self.tcx = TCXParser(path) + + def test_cadence_max_is_correct(self): + self.assertEqual(self.tcx.cadence_max, 115) + + def test_cadence_avg_is_correct(self): + self.assertEqual(self.tcx.cadence_avg, 82) diff --git a/tests/test_tcxparser.py b/tests/test_tcxparser.py index 464eb8a..d856f4d 100644 --- a/tests/test_tcxparser.py +++ b/tests/test_tcxparser.py @@ -3,12 +3,12 @@ from StringIO import StringIO except ImportError: from io import StringIO -import unittest +from unittest import TestCase from tcxparser import TCXParser -class TestParseTCX(unittest.TestCase): +class TestParseTCX(TestCase): def setUp(self): tcx_file = 'test.tcx' @@ -79,7 +79,7 @@ def test_descent_is_correct(self): self.assertAlmostEqual(self.tcx.descent, 166.307128903) -class BugTest(unittest.TestCase): +class BugTest(TestCase): def test_single_trackpoint_in_track_is_ok(self): "https://github.com/vkurup/python-tcxparser/issues/9" @@ -123,7 +123,3 @@ def test_no_error_if_no_position(self): tcx = TCXParser(tcx_file) self.assertEqual(tcx.latitude, None) self.assertEqual(tcx.longitude, None) - - -if __name__ == '__main__': - unittest.main() From 10279ec0c006187698bec3a67af2bb78ef8e12cc Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Fri, 27 Apr 2018 18:42:34 -0400 Subject: [PATCH 6/7] Prepare for release --- AUTHORS.rst | 15 +++++++++++++ AUTHORS.txt | 12 ----------- CHANGES.rst | 47 +++++++++++++++++++++++++++++++++++++++++ CHANGES.txt | 11 ---------- LICENSE.txt => LICENSE | 2 +- README.md => README.rst | 41 +++++++++++++++++++++++++++++------ setup.cfg | 2 +- setup.py | 2 +- 8 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 AUTHORS.rst delete mode 100644 AUTHORS.txt create mode 100644 CHANGES.rst delete mode 100644 CHANGES.txt rename LICENSE.txt => LICENSE (97%) rename README.md => README.rst (66%) diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..4635298 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,15 @@ +Primary author: +--------------- + +* Vinod Kurup (@vkurup) + + +Other contributions from: +------------------------- + +* Iztok Fister Jr (@firefly-cpp) +* Jason M. (@DaddyTheRunner) +* Adam Neumann (@noizwaves) +* Stephen Doyle (@stevedoyle) + +Thank you! diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 9ab499c..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,12 +0,0 @@ -Primary author: - -Vinod Kurup (@vkurup) - -Other contributions from: - -Iztok Fister Jr (@firefly-cpp) -Jason M. (@DaddyTheRunner) -Adam Neumann (@noizwaves) -Stephen Doyle (@stevedoyle) - -Thank you! diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..eeb6eec --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,47 @@ +CHANGES +======= + +v0.8.0, 2018-04-27 +------------------ + +* Add coverage, flake8 +* Support Python 3 +* Move code into its own package +* Add some cycling support (#16) +* Run tests on CI using tox-travis + + +v0.7.2, 2017-03-02 +------------------ + +* Don't fail if lat/lon not present. + + +v0.7.1, 2016-08-14 +------------------ + +* Fix for tracks with only 1 trackpoint. + + +v0.7.0, 2016-01-01 +------------------ + +* Added average altitude support. Thanks @firefly-cpp + + +v0.6.0, 2014-11-18 +------------------ + +* Added heart rate data & pace support. Thanks @stevedoyle + + +v0.3.0, 2013-01-09 +------------------ + +* Changed methods to properties + + +v0.1.0, 2013-01-07 +------------------ + +* Initial release. diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index 8b831e0..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,11 +0,0 @@ -v0.7.1, 2017-03-02 -- Don't fail if lat/lon not present. - -v0.7.1, 2016-08-14 -- Fix for tracks with only 1 trackpoint. - -v0.7.0, 2016-01-01 -- Added average altitude support. Thanks @firefly-cpp - -v0.6.0, 2014-11-18 -- Added heart rate data & pace support. Thanks @stevedoyle - -v0.3.0, 2013-01-09 -- Changed methods to properties - -v0.1.0, 2013-01-07 -- Initial release. diff --git a/LICENSE.txt b/LICENSE similarity index 97% rename from LICENSE.txt rename to LICENSE index 53d1cdd..af273dd 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-6, Vinod Kurup +Copyright (c) 2013-8, Vinod Kurup All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/README.md b/README.rst similarity index 66% rename from README.md rename to README.rst index b0a346a..d263460 100644 --- a/README.md +++ b/README.rst @@ -1,6 +1,17 @@ -# python-tcxparser +python-tcxparser +================ -[![Build Status](https://travis-ci.org/vkurup/python-tcxparser.svg?branch=master)](https://travis-ci.org/vkurup/python-tcxparser) +.. image:: https://img.shields.io/pypi/v/python-tcxparser.svg + :target: https://pypi.python.org/pypi/python-tcxparser + :alt: Latest PyPI version + +.. image:: https://travis-ci.org/vkurup/python-tcxparser.svg?branch=master + :target: https://travis-ci.org/vkurup/python-tcxparser + :alt: Latest Travis CI build status + +.. image:: https://pyup.io/repos/github/vkurup/python-tcxparser/shield.svg + :target: https://pyup.io/repos/github/vkurup/python-tcxparser/ + :alt: Updates python-tcxparser is a minimal parser for Garmin's TCX file format. It is not in any way exhaustive. It extracts just enough data to allow me @@ -21,11 +32,17 @@ Data extracted: - max and min altitude - time stamp of each data point (in ISO UTC) -## Installation +Installation +------------ + +Install it from PyPI:: - pip install python-tcxparser + pip install python-tcxparser -## Usage +Usage +----- + +Basic usage example:: >>> import tcxparser >>> tcx = tcxparser.TCXParser('/home/vinod/Downloads/20121226-212953.tcx') @@ -51,5 +68,17 @@ Data extracted: ... tcx.calories 379 -## Contact +Compatibility +------------- + +* Python 2.7 or 3.5+ + +License +------- + +BSD + +Contact +------- + Please contact me with any questions: Vinod Kurup (vinod@kurup.com) diff --git a/setup.cfg b/setup.cfg index e91ee50..12cf9b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -description-file = README.md +description-file = README.rst [flake8] max-line-length = 120 diff --git a/setup.py b/setup.py index 1230b16..4fac4f4 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -__version__ = '0.7.2' +__version__ = '0.8.0' setup( name='python-tcxparser', From ab2109911f297e12a3015ef9c70e19601ed18664 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Fri, 27 Apr 2018 18:46:04 -0400 Subject: [PATCH 7/7] Rename to RST --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4fac4f4..a0a877c 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ name='python-tcxparser', version=__version__, description='Simple parser for Garmin TCX files', - long_description=open('README.md').read(), + long_description=open('README.rst').read(), author='Vinod Kurup', author_email='vinod@kurup.com', url='https://github.com/vkurup/python-tcxparser/',