Repository: mapbox/mapbox-sdk-py

Issue:
direction validate_snapping behavior differs by Python version
Another fun Python 2/3 issue

The `_validate_snapping` method returns (bearings, radii) tuples.

In Python 3, we get the correct behavior

```python
>>> from mapbox.services import directions
>>> d = directions.Directions()
>>> d._validate_snapping([(1, 1, 1), u'unlimited'], [None, None])
([(1, 1), None], [1, 'unlimited'])
```

In Python 2, we get an Expection

```python
>>> from mapbox.services import directions
>>> d = directions.Directions()
>>> d._validate_snapping([(1, 1, 1), u'unlimited'], [None, None])
------------------------------------------------------------------------
InvalidParameterError                  Traceback (most recent call last)
<ipython-input-3-1dc0bc40d399> in <module>()
----> 1 d._validate_snapping([(1, 1, 1), u'unlimited'], [None, None])

/Users/mperry/env/sdk27/lib/python2.7/site-packages/mapbox/services/directions.pyc in _validate_snapping(self, snaps, features)
     93                     except ValueError:
     94                         raise errors.InvalidParameterError(
---> 95                             'waypoint snapping should contain 3 elements: '
     96                             '(bearing, angle, range)')
     97                     self._validate_radius(radius)

InvalidParameterError: waypoint snapping should contain 3 elements: (bearing, angle, range)
```

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 23dc89f..f6884e2 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,8 +1,9 @@
 Changes
 =======
 
-Next
-----
+0.16.1
+------
+- Python 2/3 bugfixes (#239)
 - Add support for Maps v4 (#228).
 
 0.16.0 (2018-05-08)
diff --git a/mapbox/__init__.py b/mapbox/__init__.py
index d07d0ef..aa93a1d 100644
--- a/mapbox/__init__.py
+++ b/mapbox/__init__.py
@@ -1,5 +1,5 @@
 # mapbox
-__version__ = "0.16.0"
+__version__ = "0.16.1"
 
 from .services.datasets import Datasets
 from .services.directions import Directions
diff --git a/mapbox/compat.py b/mapbox/compat.py
new file mode 100644
index 0000000..c01ed1b
--- /dev/null
+++ b/mapbox/compat.py
@@ -0,0 +1,6 @@
+import sys
+
+if sys.version_info[0] >= 3:  # pragma: no cover
+    string_type = str
+else:  # pragma: no cover
+    string_type = basestring
diff --git a/mapbox/encoding.py b/mapbox/encoding.py
index a6e6f96..40530d0 100644
--- a/mapbox/encoding.py
+++ b/mapbox/encoding.py
@@ -61,7 +61,9 @@ def encode_waypoints(features, min_limit=None, max_limit=None, precision=6):
     return a string encoded in waypoint-style used by certain mapbox APIs
     ("lon,lat" pairs separated by ";")
     """
-    coords = ['{lon},{lat}'.format(lon=round(lon, precision), lat=round(lat, precision))
+    coords = ['{lon},{lat}'.format(
+                  lon=float(round(lon, precision)),
+                  lat=float(round(lat, precision)))
               for lon, lat in read_points(features)]
 
     if min_limit is not None and len(coords) < min_limit:
@@ -88,7 +90,7 @@ def encode_coordinates_json(features):
     """Given an iterable of features
     return a JSON string to be used as the request body for the distance API:
     a JSON object, with a key coordinates,
-    which has an array of [ Longitude, Latitidue ] pairs
+    which has an array of [ Longitude, Lattitude ] pairs
     """
     coords = {
         'coordinates': list(read_points(features))}
diff --git a/mapbox/services/directions.py b/mapbox/services/directions.py
index 8df3400..30dd34b 100644
--- a/mapbox/services/directions.py
+++ b/mapbox/services/directions.py
@@ -6,6 +6,7 @@ from uritemplate import URITemplate
 
 from mapbox.encoding import encode_waypoints as encode_coordinates
 from mapbox.services.base import Service
+from mapbox.compat import string_type
 from mapbox import errors
 
 
@@ -118,7 +119,7 @@ class Directions(Service):
         if radius is None:
             return None
 
-        if isinstance(radius, str):
+        if isinstance(radius, string_type):
             if radius != 'unlimited':
                 raise errors.InvalidParameterError(
                     '{0} is not a valid radius'.format(radius))


Test patch:
diff --git a/tests/test_directions.py b/tests/test_directions.py
index d9c4fe4..4c5759b 100644
--- a/tests/test_directions.py
+++ b/tests/test_directions.py
@@ -239,10 +239,17 @@ def test_validate_radius_none():
     assert service._validate_radius(None) is None
 
 
+def test_validate_radius_unlimited():
+    service = mapbox.Directions(access_token='pk.test')
+    assert service._validate_radius('unlimited') == 'unlimited'
+
+
 def test_validate_radius_invalid():
     service = mapbox.Directions(access_token='pk.test')
     with pytest.raises(mapbox.errors.InvalidParameterError) as e:
         service._validate_radius(-1)
+    with pytest.raises(mapbox.errors.InvalidParameterError) as e:
+        service._validate_radius('nothing')
 
 
 def test_invalid_bearing_domain():
@@ -256,3 +263,11 @@ def test_bearings_without_radius():
     with pytest.raises(TypeError):
         mapbox.Directions(access_token='pk.test').directions(
             waypoint_snapping=[(270, 45), (270, 45)])
+
+
+def test_validate_snapping():
+    service = mapbox.Directions(access_token='pk.test')
+    snaps = service._validate_snapping(
+        [(1, 1, 1), u'unlimited'], [None, None])
+
+    assert snaps == ([(1, 1), None], [1, 'unlimited'])
diff --git a/tests/test_encoding.py b/tests/test_encoding.py
index 72b5152..8fb4b5e 100644
--- a/tests/test_encoding.py
+++ b/tests/test_encoding.py
@@ -128,3 +128,16 @@ def test_encode_coordinates_json():
     assert expected == json.loads(encode_coordinates_json(gj_point_features))
     assert expected == json.loads(encode_coordinates_json(gj_multipoint_features))
     assert expected == json.loads(encode_coordinates_json(gj_line_features))
+
+
+def test_encode_waypoints_rounding():
+    expected = "1.0,0.0"
+    int_coord_features = [{
+        "type": "Feature",
+        "geometry": {
+            "type": "Point",
+            "coordinates": [1, 0]
+        },
+        "properties": {}}]
+
+    assert expected == encode_waypoints(int_coord_features)