Skip to content
This repository was archived by the owner on Jun 3, 2023. It is now read-only.

Commit e5ae544

Browse files
committed
Implemented transit_types.Boolean plus true,false.
2 parents 68e2b47 + d25e7df commit e5ae544

File tree

7 files changed

+82
-15
lines changed

7 files changed

+82
-15
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ writer.write(TaggedValue("ints", [1,2,3]))
9595

9696
### Python's bool and int
9797

98-
All caveats of the Python language apply to decoding Transit data.
99-
10098
In Python, bools are subclasses of int (that is, `True` is actually `1`).
10199

102100
```python
@@ -108,7 +106,7 @@ In Python, bools are subclasses of int (that is, `True` is actually `1`).
108106
True
109107
```
110108

111-
This only becomes problematic when decoding a map that contains bool and
109+
This becomes problematic when decoding a map that contains bool and
112110
int keys. The bool keys may be overridden (ie: you'll only see the int key),
113111
and the value will be one of any possible bool/int keyed value.
114112

@@ -117,6 +115,14 @@ and the value will be one of any possible bool/int keyed value.
117115
{1: 'World'}
118116
```
119117

118+
To counter this problem, the latest version of Transit Python introduces a
119+
Boolean type with singleton (by convention of use) instances of "true" and
120+
"false." A Boolean can be converted to a native Python bool with bool(x) where
121+
x is the "true" or "false" instance. Logical evaluation works correctly with
122+
Booleans (that is, they override the __nonzero__ method and correctly evaluate
123+
as true and false in simple logical evaluation), but uses of a Boolean as an
124+
integer will fail.
125+
120126
### Default type mapping
121127

122128
|Transit type|Write accepts|Read returns|

tests/exemplars_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# then import transit stuff
1818
from transit.reader import Reader, JsonUnmarshaler, MsgPackUnmarshaler
1919
from transit.writer import Writer
20-
from transit.transit_types import Keyword, Symbol, URI, frozendict, TaggedValue, Link
20+
from transit.transit_types import Keyword, Symbol, URI, frozendict, TaggedValue, Link, true, false
2121
from StringIO import StringIO
2222
from transit.helpers import mapcat
2323
from helpers import ints_centered_on, hash_of_size, array_of_symbools
@@ -96,7 +96,7 @@ def assertEqual(self, val, data):
9696
globals()["test_" + name + "_json"] = ExemplarTest
9797

9898
ARRAY_SIMPLE = (1, 2, 3)
99-
ARRAY_MIXED = (0, 1, 2.0, True, False, 'five', Keyword("six"), Symbol("seven"), '~eight', None)
99+
ARRAY_MIXED = (0, 1, 2.0, true, false, 'five', Keyword("six"), Symbol("seven"), '~eight', None)
100100
ARRAY_NESTED = (ARRAY_SIMPLE, ARRAY_MIXED)
101101
SMALL_STRINGS = ("", "a", "ab", "abc", "abcd", "abcde", "abcdef")
102102
POWERS_OF_TWO = tuple(map(lambda x: pow(2, x), range(66)))
@@ -131,14 +131,14 @@ def assertEqual(self, val, data):
131131

132132
MAP_MIXED = frozendict({Keyword("a"): 1,
133133
Keyword("b"): u"a string",
134-
Keyword("c"): True})
134+
Keyword("c"): true})
135135

136136
MAP_NESTED = frozendict({Keyword("simple"): MAP_SIMPLE,
137137
Keyword("mixed"): MAP_MIXED})
138138

139139
exemplar("nil", None)
140-
exemplar("true", True)
141-
exemplar("false", False)
140+
exemplar("true", true)
141+
exemplar("false", false)
142142
exemplar("zero", 0)
143143
exemplar("one", 1)
144144
exemplar("one_string", "hello")

tests/regression.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from transit.reader import Reader
1919
from transit.writer import Writer
20-
from transit.transit_types import Symbol, frozendict
20+
from transit.transit_types import Symbol, frozendict, true, false
2121
from StringIO import StringIO
2222

2323
class RegressionBaseTest(unittest.TestCase):
@@ -37,12 +37,44 @@ def test_roundtrip(self):
3737

3838
globals()["test_" + name + "_json"] = RegressionTest
3939

40-
regression("cache_consistency", ({"Problem?":True},
40+
regression("cache_consistency", ({"Problem?":true},
4141
Symbol("Here"),
4242
Symbol("Here")))
4343
regression("one_pair_frozendict", frozendict({"a":1}))
4444
regression("json_int_max", (2**53+100, 2**63+100))
4545
regression("newline_in_string", "a\nb")
4646

47+
class BooleanTest(unittest.TestCase):
48+
"""Even though we're roundtripping transit_types.true and
49+
transit_types.false now, make sure we can still write Python bools.
50+
51+
Additionally, make sure we can still do basic logical evaluation on transit
52+
Boolean values.
53+
"""
54+
def test_write_bool(self):
55+
for protocol in ("json", "json-verbose", "msgpack"):
56+
io = StringIO()
57+
w = Writer(io, protocol)
58+
w.write((True, False))
59+
r = Reader(protocol)
60+
io.seek(0)
61+
out_data = r.read(io)
62+
assert out_data[0] == true
63+
assert out_data[1] == false
64+
65+
def test_basic_eval(self):
66+
assert true
67+
assert not false
68+
69+
def test_or(self):
70+
assert true or false
71+
assert not (false or false)
72+
assert true or true
73+
74+
def test_and(self):
75+
assert not (true and false)
76+
assert true and true
77+
assert not (false and false)
78+
4779
if __name__ == '__main__':
4880
unittest.main()

transit/decoder.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from helpers import pairs
1919
import read_handlers as rh
2020
from rolling_cache import RollingCache, is_cacheable, is_cache_key
21+
from transit_types import true, false
2122

2223
class Tag(object):
2324
def __init__(self, tag):
@@ -88,8 +89,9 @@ def _decode(self, node, cache, as_map_key):
8889
return self.decode_list(node, cache, as_map_key)
8990
elif tp is str:
9091
return self.decode_string(unicode(node, "utf-8"), cache, as_map_key)
91-
else:
92-
return node
92+
elif tp is bool:
93+
return true if node else false
94+
return node
9395

9496
def decode_list(self, node, cache, as_map_key):
9597
"""Special case decodes map-as-array.

transit/read_handlers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def from_rep(v):
4747
class BooleanHandler(object):
4848
@staticmethod
4949
def from_rep(x):
50-
return x == "t"
50+
return transit_types.true if x == "t" else transit_types.false
5151

5252
class IntHandler(object):
5353
@staticmethod

transit/transit_types.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,29 @@ def as_map(self):
188188
@property
189189
def as_array(self):
190190
return [self.href, self.rel, self.name, self.render, self.prompt]
191+
192+
class Boolean(object):
193+
"""To allow a separate t/f that won't hash as 1/0. Don't call directly,
194+
instead use true and false as singleton objects. Can use with type check.
195+
196+
Note that the Booleans are for preserving hash/set bools that duplicate 1/0
197+
and not designed for use in Python outside of logical evaluation (don't treat
198+
as an int, they're not). You can get a Python bool using bool(x)
199+
where x is a true or false Boolean.
200+
"""
201+
def __init__(self, name):
202+
self.v = True if name == "true" else False
203+
self.name = name
204+
205+
def __nonzero__(self):
206+
return self.v
207+
208+
def __repr__(self):
209+
return self.name
210+
211+
def __str__(self):
212+
return self.name
213+
214+
# lowercase rep matches java/clojure
215+
false = Boolean("false")
216+
true = Boolean("true")

transit/write_handlers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from constants import *
1616
from class_hash import ClassDict
17-
from transit_types import Keyword, Symbol, URI, frozendict, TaggedValue, Link
17+
from transit_types import Keyword, Symbol, URI, frozendict, TaggedValue, Link, Boolean
1818
import uuid
1919
import datetime, time
2020
from dateutil import tz
@@ -103,7 +103,7 @@ def tag(_):
103103
return '?'
104104
@staticmethod
105105
def rep(b):
106-
return b
106+
return bool(b)
107107
@staticmethod
108108
def string_rep(b):
109109
return 't' if b else 'f'
@@ -258,6 +258,7 @@ def __init__(self):
258258
super(WriteHandler, self).__init__()
259259
self[type(None)] = NoneHandler
260260
self[bool] = BooleanHandler
261+
self[Boolean] = BooleanHandler
261262
self[str] = StringHandler
262263
self[unicode] = StringHandler
263264
self[list] = ArrayHandler

0 commit comments

Comments
 (0)