From b56138adb6565b2b5952b31f835bee74199ddfbd Mon Sep 17 00:00:00 2001 From: Eric Reinecke Date: Thu, 10 Nov 2016 14:15:16 -0800 Subject: [PATCH] Addressed python3 support. resolves #19 (#32) * Addressed python3 support. resolves #19 * Added unitests for comparison of TimeRange and TimeTransform and fixed inequality checks * Added python3.5 to README and converted from io import StringIO to import io --- Makefile | 7 +++ README.md | 2 +- opentimelineio/adapters/cmx_3600.py | 6 +-- .../adapters/pretty_print_string.py | 5 +- opentimelineio/opentime.py | 35 ++++++++++++- tests/test_opentime.py | 52 +++++++++++++++++++ 6 files changed, 98 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index f7a7af48f..4b53a9975 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ test: python2.7 -m unittest discover tests +test3.5: + python3.5 -m unittest discover tests + # run all the unit tests, stopping at the first failure test_first_fail: python2.7 -m unittest discover tests --failfast @@ -10,6 +13,10 @@ test_first_fail: fast_test: env OTIO_FAST_TEST=1 python2.7 -m unittest discover tests +# skip the timecode test that takes forever +fast_test3.5: + env OTIO_FAST_TEST=1 python3.5 -m unittest discover tests + # remove pyc files clean: rm */*.pyc */*/*.pyc diff --git a/README.md b/README.md index 51415c576..fc6f26c2f 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Even though the project is python, we provide a makefile with some utility targe Developing ---------- -Currently the code base is written against python2.7. Before committing please run your changes through pep8/autopep8. +Currently the code base is written against python2.7 and python3.5. Before committing please run your changes through pep8/autopep8. Contact ------- diff --git a/opentimelineio/adapters/cmx_3600.py b/opentimelineio/adapters/cmx_3600.py index 6e5a49f08..257b23e6b 100644 --- a/opentimelineio/adapters/cmx_3600.py +++ b/opentimelineio/adapters/cmx_3600.py @@ -259,7 +259,7 @@ def __init__(self, comments): self._parse(comment) def _parse(self, comment): - for comment_id, comment_type in self.comment_id_map.iteritems(): + for comment_id, comment_type in self.comment_id_map.items(): regex = self._regex_template.format(id=comment_id) if re.match(regex, comment): self.handled[comment_type] = comment.split(':')[1].strip() @@ -284,8 +284,8 @@ def read_from_string(input_str): def write_to_string(input_otio): # TODO: We should have convenience functions in Timeline for this? # also only works for a single video track at the moment - video_tracks = filter(lambda t: t.kind == "Video", input_otio.tracks) - audio_tracks = filter(lambda t: t.kind == "Audio", input_otio.tracks) + video_tracks = list(filter(lambda t: t.kind == "Video", input_otio.tracks)) + audio_tracks = list(filter(lambda t: t.kind == "Audio", input_otio.tracks)) if len(video_tracks) != 1: raise otio.exceptions.NotSupportedError( diff --git a/opentimelineio/adapters/pretty_print_string.py b/opentimelineio/adapters/pretty_print_string.py index b5bfd403e..d2c2876c7 100644 --- a/opentimelineio/adapters/pretty_print_string.py +++ b/opentimelineio/adapters/pretty_print_string.py @@ -1,8 +1,7 @@ """ Adapter that prints to string. """ - -import StringIO +import io import opentimelineio as otio @@ -81,6 +80,6 @@ def from_base_object(base_object, indent=0): ) ) - result = StringIO.StringIO() + result = io.StringIO() result.write('\n'.join(lines)) return result.getvalue() diff --git a/opentimelineio/opentime.py b/opentimelineio/opentime.py index 874307c41..d0fb14537 100644 --- a/opentimelineio/opentime.py +++ b/opentimelineio/opentime.py @@ -69,17 +69,39 @@ def __sub__(self, other): value = (self.value_rescaled_to(scale) - other.value) return RationalTime(value=value, rate=scale) - def __cmp__(self, other): + def _comparable_floats(self, other): + """ + returns a tuple of two floats, (self, other), which are suitable + for comparison. + + If other is not of a type that can be compared, TypeError is raised + """ if not isinstance(other, RationalTime): raise TypeError( "RationalTime can only be compared to other objects of type " "RationalTime, not {}".format(type(other)) ) - return cmp( + return ( float(self.value) / self.rate, float(other.value) / other.rate ) + def __gt__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self > f_other + + def __lt__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self < f_other + + def __le__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self <= f_other + + def __ge__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self >= f_other + def __repr__(self): return ( "otio.opentime.RationalTime(value={value}," @@ -101,6 +123,9 @@ def __eq__(self, other): except AttributeError: return False + def __ne__(self, other): + return not (self == other) + def __hash__(self): return hash((self.value, self.rate)) @@ -169,6 +194,9 @@ def __eq__(self, other): except AttributeError: return False + def __ne__(self, other): + return not (self == other) + def __hash__(self): return hash((self.offset, self.scale, self.rate)) @@ -340,6 +368,9 @@ def __eq__(self, rhs): except AttributeError: return False + def __ne__(self, rhs): + return not (self == rhs) + def __repr__(self): return ( "otio.opentime.TimeRange(start_time={}, duration={})".format( diff --git a/tests/test_opentime.py b/tests/test_opentime.py index 30e51958b..72723bc21 100755 --- a/tests/test_opentime.py +++ b/tests/test_opentime.py @@ -29,14 +29,37 @@ def test_equality(self): self.assertTrue(t1 is not t2) self.assertEqual(t1, t2) + def test_inequality(self): + t1 = otio.opentime.RationalTime(30.2) + self.assertEqual(t1, t1) + t2 = otio.opentime.RationalTime(33.2) + self.assertTrue(t1 is not t2) + self.assertNotEqual(t1, t2) + t3 = otio.opentime.RationalTime(30.2) + self.assertTrue(t1 is not t3) + self.assertFalse(t1 != t3) + def test_comparison(self): t1 = otio.opentime.RationalTime(15.2) t2 = otio.opentime.RationalTime(15.6) self.assertTrue(t1 < t2) + self.assertTrue(t1 <= t2) + self.assertFalse(t1 > t2) + self.assertFalse(t1 >= t2) + + # Ensure the equality case of the comparisons works correctly + t3 = otio.opentime.RationalTime(30.4, 2) + self.assertTrue(t1 <= t3) + self.assertTrue(t1 >= t3) + self.assertTrue(t3 <= t1) + self.assertTrue(t3 >= t1) # test implicit base conversion t2 = otio.opentime.RationalTime(15.6, 48) self.assertTrue(t1 > t2) + self.assertTrue(t1 >= t2) + self.assertFalse(t1 < t2) + self.assertFalse(t1 <= t2) def test_base_conversion(self): @@ -297,6 +320,19 @@ def test_hash(self): txform2 = otio.opentime.TimeTransform(offset=tstart, scale=2, rate=10) self.assertNotEqual(hash(txform), hash(txform2)) + def test_comparison(self): + tstart = otio.opentime.RationalTime(12, 25) + txform = otio.opentime.TimeTransform(offset=tstart, scale=2) + tstart = otio.opentime.RationalTime(12, 25) + txform2 = otio.opentime.TimeTransform(offset=tstart, scale=2) + self.assertEqual(txform, txform2) + self.assertFalse(txform != txform2) + + tstart = otio.opentime.RationalTime(23, 25) + txform3 = otio.opentime.TimeTransform(offset=tstart, scale=2) + self.assertNotEqual(txform, txform3) + self.assertFalse(txform == txform3) + class TestTimeRange(unittest.TestCase): @@ -348,6 +384,22 @@ def test_repr(self): "duration=otio.opentime.RationalTime(value=6, rate=24))" ) + def test_compare(self): + start_time1 = otio.opentime.RationalTime(18, 24) + duration1 = otio.opentime.RationalTime(7, 24) + tr1 = otio.opentime.TimeRange(start_time1, duration1) + start_time2 = otio.opentime.RationalTime(18, 24) + duration2 = otio.opentime.RationalTime(14, 48) + tr2 = otio.opentime.TimeRange(start_time2, duration2) + self.assertEqual(tr1, tr2) + self.assertFalse(tr1 != tr2) + + start_time3 = otio.opentime.RationalTime(20, 24) + duration3 = otio.opentime.RationalTime(3, 24) + tr3 = otio.opentime.TimeRange(start_time3, duration3) + self.assertNotEqual(tr1, tr3) + self.assertFalse(tr1 == tr3) + def test_clamped(self): test_point_min = otio.opentime.RationalTime(-2, 24) test_point_max = otio.opentime.RationalTime(6, 24)