Skip to content

Commit

Permalink
Merge pull request #32 from ipinfo/uman/asyncio
Browse files Browse the repository at this point in the history
Async handler
  • Loading branch information
UmanShahzad authored Nov 6, 2020
2 parents bba2f0e + 3132948 commit 91d51d4
Show file tree
Hide file tree
Showing 16 changed files with 540 additions and 54 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.vscode/
.vim/
.idea/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# IPInfo Changelog

## 4.0.0

#### Breaking Changes

- [PR #32](https://github.com/ipinfo/python/pull/32)
All EOL Python versions are no longer supported; currently, Python 3.6 or greater is now **required**.
An asynchronous handler is available from `getHandlerAsync` which returns an `AsyncHandler` which uses **aiohttp**.

## 3.0.0

#### Breaking Changes
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,37 @@ pip install ipinfo
'37.3861,-122.0840'
```

#### Async/Await

An asynchronous handler is available as well, and can be accessed and used in
almost the same exact way as the synchronous handler:

```python
>>> import ipinfo
>>> access_token = '123456789abc'
>>> handler = ipinfo.getHandlerAsync(access_token)
>>> ip_address = '216.239.36.21'
>>> async def do_req():
... details = await handler.getDetails(ip_address)
... print(details.city)
... print(details.loc)
...
>>>
>>> import asyncio
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(do_req())
Mountain View
37.4056,-122.0775
>>>
>>> ip_address = '1.1.1.1'
>>> loop.run_until_complete(do_req())
New York City
40.7143,-74.0060
```

Internally the library uses `aiohttp`, but as long as you provide an event
loop (as in this example via `asyncio`), it shouldn't matter.

### Usage

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.
Expand Down Expand Up @@ -158,6 +189,9 @@ handler = ipinfo.getHandler(cache=MyCustomCache())

### Modifying request options

**Note**: the asynchronous handler currently only accepts the `timeout` option,
input the same way as shown below.

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.

- Default request timeout: 2 seconds
Expand Down
6 changes: 6 additions & 0 deletions ipinfo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from .handler import Handler
from .handler_async import AsyncHandler


def getHandler(access_token=None, **kwargs):
"""Create and return Handler object."""
return Handler(access_token, **kwargs)


def getHandlerAsync(access_token=None, **kwargs):
"""Create an return an asynchronous Handler object."""
return AsyncHandler(access_token, **kwargs)
4 changes: 1 addition & 3 deletions ipinfo/cache/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
"""

import abc
import six


@six.add_metaclass(abc.ABCMeta)
class CacheInterface():
class CacheInterface(metaclass=abc.ABCMeta):
"""Interface for using custom cache."""

@abc.abstractmethod
Expand Down
4 changes: 3 additions & 1 deletion ipinfo/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def __getattr__(self, attr):
if attr in self.details:
return self.details[attr]
else:
raise AttributeError("{} is not a valid attribute of Details".format(attr))
raise AttributeError(
"{} is not a valid attribute of Details".format(attr)
)

@property
def all(self):
Expand Down
39 changes: 28 additions & 11 deletions ipinfo/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

class Handler:
"""
Allows client to request data for specified IP address. Instantiates and
and maintains access to cache.
Allows client to request data for specified IP address.
Instantiates and maintains access to cache.
"""

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

def __init__(self, access_token=None, **kwargs):
"""Initialize the Handler object with country name list and the cache initialized."""
"""
Initialize the Handler object with country name list and the
cache initialized.
"""
self.access_token = access_token

# load countries file
self.countries = self._read_country_names(kwargs.get("countries_file"))

# setup req opts
self.request_options = kwargs.get("request_options", {})
if "timeout" not in self.request_options:
self.request_options["timeout"] = self.REQUEST_TIMEOUT_DEFAULT

# setup cache
if "cache" in kwargs:
self.cache = kwargs["cache"]
else:
Expand All @@ -58,9 +66,12 @@ def getBatchDetails(self, ip_addresses):
# the IPs not in the cache.
lookup_addresses = []
for ip_address in ip_addresses:
# If the supplied IP address uses the objects defined in the built-in module ipaddress
# extract the appropriate string notation before formatting the URL
if isinstance(ip_address, IPv4Address) or isinstance(ip_address, IPv6Address):
# If the supplied IP address uses the objects defined in the
# built-in module ipaddress extract the appropriate string notation
# before formatting the URL.
if isinstance(ip_address, IPv4Address) or isinstance(
ip_address, IPv6Address
):
ip_address = ip_address.exploded

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

# If the supplied IP address uses the objects defined in the built-in module ipaddress
# extract the appropriate string notation before formatting the URL
if isinstance(ip_address, IPv4Address) or isinstance(ip_address, IPv6Address):
# If the supplied IP address uses the objects defined in the built-in
# module ipaddress extract the appropriate string notation before
# formatting the URL.
if isinstance(ip_address, IPv4Address) or isinstance(
ip_address, IPv6Address
):
ip_address = ip_address.exploded

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

def _read_country_names(self, countries_file=None):
"""Read list of countries from specified country file or default file."""
"""
Read list of countries from specified country file or
default file.
"""
if not countries_file:
countries_file = os.path.join(
os.path.dirname(__file__), self.COUNTRY_FILE_DEFAULT
Expand Down
Loading

0 comments on commit 91d51d4

Please sign in to comment.