Skip to content

Commit 91d51d4

Browse files
authored
Merge pull request #32 from ipinfo/uman/asyncio
Async handler
2 parents bba2f0e + 3132948 commit 91d51d4

File tree

16 files changed

+540
-54
lines changed

16 files changed

+540
-54
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.vscode/
2+
.vim/
3+
.idea/
4+
15
# Byte-compiled / optimized / DLL files
26
__pycache__/
37
*.py[cod]

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# IPInfo Changelog
22

3+
## 4.0.0
4+
5+
#### Breaking Changes
6+
7+
- [PR #32](https://github.com/ipinfo/python/pull/32)
8+
All EOL Python versions are no longer supported; currently, Python 3.6 or greater is now **required**.
9+
An asynchronous handler is available from `getHandlerAsync` which returns an `AsyncHandler` which uses **aiohttp**.
10+
311
## 3.0.0
412

513
#### Breaking Changes

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,37 @@ pip install ipinfo
3333
'37.3861,-122.0840'
3434
```
3535

36+
#### Async/Await
37+
38+
An asynchronous handler is available as well, and can be accessed and used in
39+
almost the same exact way as the synchronous handler:
40+
41+
```python
42+
>>> import ipinfo
43+
>>> access_token = '123456789abc'
44+
>>> handler = ipinfo.getHandlerAsync(access_token)
45+
>>> ip_address = '216.239.36.21'
46+
>>> async def do_req():
47+
... details = await handler.getDetails(ip_address)
48+
... print(details.city)
49+
... print(details.loc)
50+
...
51+
>>>
52+
>>> import asyncio
53+
>>> loop = asyncio.get_event_loop()
54+
>>> loop.run_until_complete(do_req())
55+
Mountain View
56+
37.4056,-122.0775
57+
>>>
58+
>>> ip_address = '1.1.1.1'
59+
>>> loop.run_until_complete(do_req())
60+
New York City
61+
40.7143,-74.0060
62+
```
63+
64+
Internally the library uses `aiohttp`, but as long as you provide an event
65+
loop (as in this example via `asyncio`), it shouldn't matter.
66+
3667
### Usage
3768

3869
The `Handler.getDetails()` method accepts an IP address as an optional, positional argument. If no IP address is specified, the API will return data for the IP address from which it receives the request.
@@ -158,6 +189,9 @@ handler = ipinfo.getHandler(cache=MyCustomCache())
158189

159190
### Modifying request options
160191

192+
**Note**: the asynchronous handler currently only accepts the `timeout` option,
193+
input the same way as shown below.
194+
161195
Request behavior can be modified by setting the `request_options` keyword argument. `request_options` is a dictionary in which the keys are keyword arguments specified in the `requests` library. The nesting of keyword arguments is to prevent name collisions between this library and its dependencies.
162196

163197
- Default request timeout: 2 seconds

ipinfo/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from .handler import Handler
2+
from .handler_async import AsyncHandler
23

34

45
def getHandler(access_token=None, **kwargs):
56
"""Create and return Handler object."""
67
return Handler(access_token, **kwargs)
8+
9+
10+
def getHandlerAsync(access_token=None, **kwargs):
11+
"""Create an return an asynchronous Handler object."""
12+
return AsyncHandler(access_token, **kwargs)

ipinfo/cache/interface.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
"""
44

55
import abc
6-
import six
76

87

9-
@six.add_metaclass(abc.ABCMeta)
10-
class CacheInterface():
8+
class CacheInterface(metaclass=abc.ABCMeta):
119
"""Interface for using custom cache."""
1210

1311
@abc.abstractmethod

ipinfo/details.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ def __getattr__(self, attr):
1515
if attr in self.details:
1616
return self.details[attr]
1717
else:
18-
raise AttributeError("{} is not a valid attribute of Details".format(attr))
18+
raise AttributeError(
19+
"{} is not a valid attribute of Details".format(attr)
20+
)
1921

2022
@property
2123
def all(self):

ipinfo/handler.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
class Handler:
1818
"""
19-
Allows client to request data for specified IP address. Instantiates and
20-
and maintains access to cache.
19+
Allows client to request data for specified IP address.
20+
Instantiates and maintains access to cache.
2121
"""
2222

2323
API_URL = "https://ipinfo.io"
@@ -27,13 +27,21 @@ class Handler:
2727
REQUEST_TIMEOUT_DEFAULT = 2
2828

2929
def __init__(self, access_token=None, **kwargs):
30-
"""Initialize the Handler object with country name list and the cache initialized."""
30+
"""
31+
Initialize the Handler object with country name list and the
32+
cache initialized.
33+
"""
3134
self.access_token = access_token
35+
36+
# load countries file
3237
self.countries = self._read_country_names(kwargs.get("countries_file"))
38+
39+
# setup req opts
3340
self.request_options = kwargs.get("request_options", {})
3441
if "timeout" not in self.request_options:
3542
self.request_options["timeout"] = self.REQUEST_TIMEOUT_DEFAULT
3643

44+
# setup cache
3745
if "cache" in kwargs:
3846
self.cache = kwargs["cache"]
3947
else:
@@ -58,9 +66,12 @@ def getBatchDetails(self, ip_addresses):
5866
# the IPs not in the cache.
5967
lookup_addresses = []
6068
for ip_address in ip_addresses:
61-
# If the supplied IP address uses the objects defined in the built-in module ipaddress
62-
# extract the appropriate string notation before formatting the URL
63-
if isinstance(ip_address, IPv4Address) or isinstance(ip_address, IPv6Address):
69+
# If the supplied IP address uses the objects defined in the
70+
# built-in module ipaddress extract the appropriate string notation
71+
# before formatting the URL.
72+
if isinstance(ip_address, IPv4Address) or isinstance(
73+
ip_address, IPv6Address
74+
):
6475
ip_address = ip_address.exploded
6576

6677
if ip_address in self.cache:
@@ -97,9 +108,12 @@ def getBatchDetails(self, ip_addresses):
97108
def _requestDetails(self, ip_address=None):
98109
"""Get IP address data by sending request to IPinfo API."""
99110

100-
# If the supplied IP address uses the objects defined in the built-in module ipaddress
101-
# extract the appropriate string notation before formatting the URL
102-
if isinstance(ip_address, IPv4Address) or isinstance(ip_address, IPv6Address):
111+
# If the supplied IP address uses the objects defined in the built-in
112+
# module ipaddress extract the appropriate string notation before
113+
# formatting the URL.
114+
if isinstance(ip_address, IPv4Address) or isinstance(
115+
ip_address, IPv6Address
116+
):
103117
ip_address = ip_address.exploded
104118

105119
if ip_address not in self.cache:
@@ -120,7 +134,7 @@ def _requestDetails(self, ip_address=None):
120134
def _get_headers(self):
121135
"""Built headers for request to IPinfo API."""
122136
headers = {
123-
"user-agent": "IPinfoClient/Python{version}/2.0.0".format(
137+
"user-agent": "IPinfoClient/Python{version}/4.0.0".format(
124138
version=sys.version_info[0]
125139
),
126140
"accept": "application/json",
@@ -145,7 +159,10 @@ def _read_coords(self, location):
145159
return lat, lon
146160

147161
def _read_country_names(self, countries_file=None):
148-
"""Read list of countries from specified country file or default file."""
162+
"""
163+
Read list of countries from specified country file or
164+
default file.
165+
"""
149166
if not countries_file:
150167
countries_file = os.path.join(
151168
os.path.dirname(__file__), self.COUNTRY_FILE_DEFAULT

0 commit comments

Comments
 (0)