Repository: ipinfo/python

Issue:
details.all is not json serializable because of the ip_address object

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..56a6f65
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,13 @@
+# IPInfo Changelog
+
+## 2.0.0
+
+#### Breaking Changes
+
+- Fix [Issue #8](https://github.com/ipinfo/python/issues/8).
+  Deleted the `ip_address` key in the details object which was of type [`IPv4Address`](https://docs.python.org/3/library/ipaddress.html).
+
+  This allows serializing the details object (into JSON or something else) without errors by default.
+
+  Users who expected that object type can simply pull the `ip` key instead and turn it into an [`IPv4Address`](https://docs.python.org/3/library/ipaddress.html)
+  object on their own.
diff --git a/README.md b/README.md
index b05457a..15860e9 100644
--- a/README.md
+++ b/README.md
@@ -77,21 +77,6 @@ US
 United States
 ```
 
-### IP Address
-
-`details.ip_address` will return the an `ipaddress` object from the [Python Standard Library](https://docs.python.org/3/library/ipaddress.html). `details.ip` will still return a string.
-
-```python
->>> details.ip
-104.175.221.247
->>> type(details.ip)
-<class 'str'>
->>> details.ip_address
-104.175.221.247
->>> type(details.ip_address)
-<class 'ipaddress.IPv4Address'>
-```
-
 #### Longitude and Latitude
 
 `details.latitude` and `details.longitude` will return latitude and longitude, respectively, as strings. `details.loc` will still return a composite string of both values.
@@ -125,7 +110,6 @@ United States
 'country_name': 'United States',
 'hostname': 'cpe-104-175-221-247.socal.res.rr.com',
 'ip': '104.175.221.247',
-'ip_address': IPv4Address('104.175.221.247'),
 'loc': '34.0293,-118.3570',
 'latitude': '34.0293',
 'longitude': '-118.3570',
diff --git a/ipinfo/handler.py b/ipinfo/handler.py
index a4cc3fa..07ccceb 100644
--- a/ipinfo/handler.py
+++ b/ipinfo/handler.py
@@ -2,7 +2,6 @@
 Main API client handler for fetching data from the IPinfo service.
 """
 
-import ipaddress
 import json
 import os
 import sys
@@ -30,7 +29,6 @@ class Handler:
         """Initialize the Handler object with country name list and the cache initialized."""
         self.access_token = access_token
         self.countries = self._read_country_names(kwargs.get("countries_file"))
-
         self.request_options = kwargs.get("request_options", {})
         if "timeout" not in self.request_options:
             self.request_options["timeout"] = self.REQUEST_TIMEOUT_DEFAULT
@@ -47,7 +45,6 @@ class Handler:
         """Get details for specified IP address as a Details object."""
         raw_details = self._requestDetails(ip_address)
         raw_details["country_name"] = self.countries.get(raw_details.get("country"))
-        raw_details["ip_address"] = ipaddress.ip_address(raw_details.get("ip"))
         raw_details["latitude"], raw_details["longitude"] = self._read_coords(
             raw_details.get("loc")
         )
@@ -73,7 +70,7 @@ class Handler:
     def _get_headers(self):
         """Built headers for request to IPinfo API."""
         headers = {
-            "user-agent": "IPinfoClient/Python{version}/1.0".format(
+            "user-agent": "IPinfoClient/Python{version}/2.0.0".format(
                 version=sys.version_info[0]
             ),
             "accept": "application/json",
diff --git a/requirements.txt b/requirements.txt
index 874fc8f..9078712 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
 # This file is autogenerated by pip-compile
 # To update, run:
 #
-#    pip-compile --no-index --output-file=requirements.txt requirements.in
+#    pip-compile --no-index
 #
 appdirs==1.4.3            # via black
 atomicwrites==1.3.0       # via pytest
@@ -13,13 +13,15 @@ certifi==2019.3.9         # via requests
 chardet==3.0.4            # via requests
 click==7.0                # via black, pip-tools
 idna==2.8                 # via requests
-more-itertools==6.0.0     # via pytest
+importlib-metadata==0.18  # via pluggy
+more-itertools==7.0.0     # via pytest
 pip-tools==3.7.0
-pluggy==0.9.0             # via pytest
+pluggy==0.12.0            # via pytest
 py==1.8.0                 # via pytest
 pytest==4.5.0
-requests==2.21.0
+requests==2.22.0
 six==1.12.0               # via pip-tools, pytest
 toml==0.10.0              # via black
-urllib3==1.24.1           # via requests
+urllib3==1.25.3           # via requests
 wcwidth==0.1.7            # via pytest
+zipp==0.5.1               # via importlib-metadata
diff --git a/setup.py b/setup.py
index 804bddb..217aaea 100644
--- a/setup.py
+++ b/setup.py
@@ -10,12 +10,12 @@ You can visit our developer docs at https://ipinfo.io/developers.
 
 setup(
     name="ipinfo",
-    version="1.1.2",
+    version="2.0.0",
     description="Official Python library for IPInfo",
     long_description=long_description,
     url="https://github.com/ipinfo/python",
-    author="James Timmins",
-    author_email="jameshtimmins@gmail.com",
+    author="IPinfo",
+    author_email="support@ipinfo.io",
     license="Apache License 2.0",
     packages=["ipinfo", "ipinfo.cache"],
     install_requires=["requests", "cachetools", "six"],


Test patch:
diff --git a/tests/default_cache_test.py b/tests/default_cache_test.py
index 7827839..e4d1e7f 100644
--- a/tests/default_cache_test.py
+++ b/tests/default_cache_test.py
@@ -9,13 +9,13 @@ def _get_new_cache():
 
 def test_contains():
     cache = _get_new_cache()
-    cache['foo'] = 'bar'
+    cache["foo"] = "bar"
 
-    assert 'foo' in cache
+    assert "foo" in cache
 
 
 def test_get():
     cache = _get_new_cache()
-    cache['foo'] = 'bar'
+    cache["foo"] = "bar"
 
-    assert cache['foo'] == 'bar'
+    assert cache["foo"] == "bar"
diff --git a/tests/details_test.py b/tests/details_test.py
index dd53c49..d53252e 100644
--- a/tests/details_test.py
+++ b/tests/details_test.py
@@ -4,25 +4,25 @@ from ipinfo.details import Details
 
 
 def test_init():
-    data = {'foo': 'bar'}
+    data = {"foo": "bar"}
     details = Details(data)
     assert details.details == data
 
 
 def test_getattr_success():
-    data = {'foo': 'bar'}
+    data = {"foo": "bar"}
     details = Details(data)
-    assert details.foo == data['foo']
+    assert details.foo == data["foo"]
 
 
 def test_getattr_fail():
-    data = {'foo': 'bar'}
+    data = {"foo": "bar"}
     details = Details(data)
     with pytest.raises(Exception):
         details.blah
 
 
 def test_all():
-    data = {'foo': 'bar', 'ham': 'eggs'}
+    data = {"foo": "bar", "ham": "eggs"}
     details = Details(data)
     assert details.all == data
diff --git a/tests/handler_test.py b/tests/handler_test.py
index 9368e5b..6a9769b 100644
--- a/tests/handler_test.py
+++ b/tests/handler_test.py
@@ -1,4 +1,4 @@
-import ipaddress
+import json
 
 from ipinfo.cache.default import DefaultCache
 from ipinfo.details import Details
@@ -6,39 +6,69 @@ from ipinfo.handler import Handler
 
 
 def test_init():
-    token = 'mytesttoken'
+    token = "mytesttoken"
     handler = Handler(token)
     assert handler.access_token == token
     assert isinstance(handler.cache, DefaultCache)
-    assert 'US' in handler.countries
+    assert "US" in handler.countries
 
 
 def test_headers():
-    token = 'mytesttoken'
+    token = "mytesttoken"
     handler = Handler(token)
     headers = handler._get_headers()
 
-    assert 'user-agent' in headers
-    assert 'accept' in headers
-    assert 'authorization' in headers
+    assert "user-agent" in headers
+    assert "accept" in headers
+    assert "authorization" in headers
 
 
 def test_get_details():
+    handler = Handler()
+    fake_details = {"country": "US", "ip": "127.0.0.1", "loc": "12.34,56.78"}
+
+    handler._requestDetails = lambda x: fake_details
+
+    details = handler.getDetails(fake_details["ip"])
+    assert isinstance(details, Details)
+    assert details.country == fake_details["country"]
+    assert details.country_name == "United States"
+    assert details.ip == fake_details["ip"]
+    assert details.loc == fake_details["loc"]
+    assert details.longitude == "56.78"
+    assert details.latitude == "12.34"
+
+
+def test_json_serialization():
     handler = Handler()
     fake_details = {
-        'country': 'US',
-        'ip': '127.0.0.1',
-        'loc': '12.34,56.78'
+        "asn": {
+            "asn": "AS20001",
+            "domain": "twcable.com",
+            "name": "Time Warner Cable Internet LLC",
+            "route": "104.172.0.0/14",
+            "type": "isp",
+        },
+        "city": "Los Angeles",
+        "company": {
+            "domain": "twcable.com",
+            "name": "Time Warner Cable Internet LLC",
+            "type": "isp",
+        },
+        "country": "US",
+        "country_name": "United States",
+        "hostname": "cpe-104-175-221-247.socal.res.rr.com",
+        "ip": "104.175.221.247",
+        "loc": "34.0293,-118.3570",
+        "latitude": "34.0293",
+        "longitude": "-118.3570",
+        "phone": "323",
+        "postal": "90016",
+        "region": "California",
     }
 
     handler._requestDetails = lambda x: fake_details
 
-    details = handler.getDetails(fake_details['ip'])
+    details = handler.getDetails(fake_details["ip"])
     assert isinstance(details, Details)
-    assert details.country == fake_details['country']
-    assert details.country_name == 'United States'
-    assert details.ip == fake_details['ip']
-    assert isinstance(details.ip_address, ipaddress.IPv4Address)
-    assert details.loc == fake_details['loc']
-    assert details.longitude == '56.78'
-    assert details.latitude == '12.34'
+    assert json.dumps(details.all)
diff --git a/tests/init_test.py b/tests/init_test.py
index fb20b9b..9a4f020 100644
--- a/tests/init_test.py
+++ b/tests/init_test.py
@@ -4,4 +4,4 @@ from ipinfo.handler import Handler
 
 def test_get_handler():
     handler = ipinfo.getHandler()
-    assert(isinstance(handler, Handler))
+    assert isinstance(handler, Handler)