forked from pantsbuild/pants
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make util.objects.datatype classes not iterable
Datatypes are typically used as structs not collections. This makes it tricky to assume iterating over them is a reasonable operation. In fact, often it is not. This patch makes datatype classes not iterable. It does so by overriding `__iter__` and the methods from namedtuple's template that expect `self` to be iterable. Additional adjustments: - ensure `super.__eq__` value is propagated even if it is NotImplemented - add is_iterable=True to FileContent, since it is used as an iterable Testing Done: Added tests that checked the behavior, then iterated on failures from the initial CI run. Current CI away in PR. Bugs closed: 3790 Reviewed at https://rbcommons.com/s/twitter/r/4163/ closes pantsbuild#3790
- Loading branch information
1 parent
dcdb55f
commit 517fc38
Showing
5 changed files
with
169 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# coding=utf-8 | ||
# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import (absolute_import, division, generators, nested_scopes, print_function, | ||
unicode_literals, with_statement) | ||
|
||
import copy | ||
import pickle | ||
import unittest | ||
|
||
from pants.util.objects import datatype | ||
|
||
|
||
class ExportedDatatype(datatype('ExportedDatatype', ['val'])): | ||
pass | ||
|
||
|
||
class AbsClass(object): | ||
pass | ||
|
||
|
||
class ReturnsNotImplemented(object): | ||
def __eq__(self, other): | ||
return NotImplemented | ||
|
||
|
||
class DatatypeTest(unittest.TestCase): | ||
|
||
def test_eq_with_not_implemented_super(self): | ||
class DatatypeSuperNotImpl(datatype('Foo', ['val']), ReturnsNotImplemented, tuple): | ||
pass | ||
|
||
self.assertNotEqual(DatatypeSuperNotImpl(1), DatatypeSuperNotImpl(1)) | ||
|
||
def test_type_included_in_eq(self): | ||
foo = datatype('Foo', ['val']) | ||
bar = datatype('Bar', ['val']) | ||
|
||
self.assertFalse(foo(1) == bar(1)) | ||
self.assertTrue(foo(1) != bar(1)) | ||
|
||
def test_subclasses_not_equal(self): | ||
foo = datatype('Foo', ['val']) | ||
class Bar(foo): | ||
pass | ||
|
||
self.assertFalse(foo(1) == Bar(1)) | ||
self.assertTrue(foo(1) != Bar(1)) | ||
|
||
def test_repr(self): | ||
bar = datatype('Bar', ['val', 'zal']) | ||
self.assertEqual('Bar(val=1, zal=1)', repr(bar(1, 1))) | ||
|
||
class Foo(datatype('F', ['val']), AbsClass): | ||
pass | ||
|
||
# Maybe this should be 'Foo(val=1)'? | ||
self.assertEqual('F(val=1)', repr(Foo(1))) | ||
|
||
def test_not_iterable(self): | ||
bar = datatype('Bar', ['val']) | ||
with self.assertRaises(TypeError): | ||
for x in bar(1): | ||
pass | ||
|
||
def test_deep_copy(self): | ||
# deep copy calls into __getnewargs__, which namedtuple defines as implicitly using __iter__. | ||
|
||
bar = datatype('Bar', ['val']) | ||
|
||
self.assertEqual(bar(1), copy.deepcopy(bar(1))) | ||
|
||
def test_atrs(self): | ||
bar = datatype('Bar', ['val']) | ||
self.assertEqual(1, bar(1).val) | ||
|
||
def test_as_dict(self): | ||
bar = datatype('Bar', ['val']) | ||
|
||
self.assertEqual({'val': 1}, bar(1)._asdict()) | ||
|
||
def test_replace_non_iterable(self): | ||
bar = datatype('Bar', ['val', 'zal']) | ||
|
||
self.assertEqual(bar(1, 3), bar(1, 2)._replace(zal=3)) | ||
|
||
def test_properties_not_assignable(self): | ||
bar = datatype('Bar', ['val']) | ||
bar_inst = bar(1) | ||
with self.assertRaises(AttributeError): | ||
bar_inst.val = 2 | ||
|
||
def test_invalid_field_name(self): | ||
with self.assertRaises(ValueError): | ||
datatype('Bar', ['0isntanallowedfirstchar']) | ||
|
||
def test_subclass_pickleable(self): | ||
before = ExportedDatatype(1) | ||
dumps = pickle.dumps(before, protocol=2) | ||
after = pickle.loads(dumps) | ||
self.assertEqual(before, after) | ||
|
||
def test_mixed_argument_types(self): | ||
bar = datatype('Bar', ['val', 'zal']) | ||
self.assertEqual(bar(1, 2), bar(val=1, zal=2)) | ||
self.assertEqual(bar(1, 2), bar(zal=2, val=1)) | ||
|
||
def test_double_passed_arg(self): | ||
bar = datatype('Bar', ['val', 'zal']) | ||
with self.assertRaises(TypeError): | ||
bar(1, val=1) | ||
|
||
def test_too_many_args(self): | ||
bar = datatype('Bar', ['val', 'zal']) | ||
with self.assertRaises(TypeError): | ||
bar(1, 1, 1) | ||
|
||
def test_unexpect_kwarg(self): | ||
bar = datatype('Bar', ['val']) | ||
with self.assertRaises(TypeError): | ||
bar(other=1) |