Skip to content

Commit f44bcc8

Browse files
authored
Merge pull request #43 from maxmind/greg/asn
Add GeoLite2 ASN database + run yapf
2 parents 1d29ce6 + ec3c20c commit f44bcc8

File tree

11 files changed

+243
-106
lines changed

11 files changed

+243
-106
lines changed

HISTORY.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
History
44
-------
55

6+
2.5.0
7+
++++++++++++++++++
8+
9+
* Added support for GeoLite2 ASN database.
10+
611
2.4.2 (2016-12-08)
712
++++++++++++++++++
813

README.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,22 @@ Anonymous IP Database
185185
'128.101.101.101'
186186
>>> reader.close()
187187
188+
ASN Database
189+
^^^^^^^^^^^^
190+
191+
.. code-block:: pycon
192+
193+
>>> import geoip2.database
194+
>>>
195+
>>> # This creates a Reader object. You should use the same object
196+
>>> # across multiple requests as creation of it is expensive.
197+
>>> with geoip2.database.Reader('/path/to/GeoLite2-ASN.mmdb') as reader:
198+
>>> response = reader.asn('1.128.0.0')
199+
>>> response.autonomous_system_number
200+
1221
201+
>>> response.autonomous_system_organization
202+
'Telstra Pty Ltd'
203+
188204
Connection-Type Database
189205
^^^^^^^^^^^^^^^^^^^^^^^^
190206

geoip2/compat.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
# pylint: skip-file
77

88
if sys.version_info[0] == 2:
9+
910
def compat_ip_address(address):
1011
"""Intended for internal use only."""
1112
if isinstance(address, bytes):
1213
address = address.decode()
1314
return ipaddress.ip_address(address)
1415
else:
16+
1517
def compat_ip_address(address):
1618
"""Intended for internal use only."""
1719
return ipaddress.ip_address(address)

geoip2/database.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,17 @@ def anonymous_ip(self, ip_address):
120120
return self._flat_model_for(geoip2.models.AnonymousIP,
121121
'GeoIP2-Anonymous-IP', ip_address)
122122

123+
def asn(self, ip_address):
124+
"""Get the ASN object for the IP address.
125+
126+
:param ip_address: IPv4 or IPv6 address as a string.
127+
128+
:returns: :py:class:`geoip2.models.ASN` object
129+
130+
"""
131+
return self._flat_model_for(geoip2.models.ASN, 'GeoLite2-ASN',
132+
ip_address)
133+
123134
def connection_type(self, ip_address):
124135
"""Get the ConnectionType object for the IP address.
125136
@@ -168,8 +179,8 @@ def _get(self, database_type, ip_address):
168179
if database_type not in self.metadata().database_type:
169180
caller = inspect.stack()[2][3]
170181
raise TypeError("The %s method cannot be used with the "
171-
"%s database" %
172-
(caller, self.metadata().database_type))
182+
"%s database" % (caller,
183+
self.metadata().database_type))
173184
record = self._db_reader.get(ip_address)
174185
if record is None:
175186
raise geoip2.errors.AddressNotFoundError(

geoip2/models.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,40 @@ def __init__(self, raw):
368368
self.raw = raw
369369

370370

371+
class ASN(SimpleModel):
372+
"""Model class for the GeoLite2 ASN.
373+
374+
This class provides the following attribute:
375+
376+
.. attribute:: autonomous_system_number
377+
378+
The autonomous system number associated with the IP address.
379+
380+
:type: int
381+
382+
.. attribute:: autonomous_system_organization
383+
384+
The organization associated with the registered autonomous system number
385+
for the IP address.
386+
387+
:type: unicode
388+
389+
.. attribute:: ip_address
390+
391+
The IP address used in the lookup.
392+
393+
:type: unicode
394+
"""
395+
396+
# pylint:disable=too-many-arguments
397+
def __init__(self, raw):
398+
self.autonomous_system_number = raw.get('autonomous_system_number')
399+
self.autonomous_system_organization = raw.get(
400+
'autonomous_system_organization')
401+
self.ip_address = raw.get('ip_address')
402+
self.raw = raw
403+
404+
371405
class ConnectionType(SimpleModel):
372406
"""Model class for the GeoIP2 Connection-Type.
373407
@@ -424,7 +458,7 @@ def __init__(self, raw):
424458
self.raw = raw
425459

426460

427-
class ISP(SimpleModel):
461+
class ISP(ASN):
428462
"""Model class for the GeoIP2 ISP.
429463
430464
This class provides the following attribute:
@@ -463,10 +497,6 @@ class ISP(SimpleModel):
463497

464498
# pylint:disable=too-many-arguments
465499
def __init__(self, raw):
466-
self.autonomous_system_number = raw.get('autonomous_system_number')
467-
self.autonomous_system_organization = raw.get(
468-
'autonomous_system_organization')
500+
super(ISP, self).__init__(raw)
469501
self.isp = raw.get('isp')
470502
self.organization = raw.get('organization')
471-
self.ip_address = raw.get('ip_address')
472-
self.raw = raw

geoip2/records.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ def __init__(self, locales=None, **kwargs):
5050
def name(self):
5151
"""Dict with locale codes as keys and localized name as value."""
5252
# pylint:disable=E1101
53-
return next(
54-
(self.names.get(x) for x in self._locales
55-
if x in self.names), None)
53+
return next((self.names.get(x) for x in self._locales
54+
if x in self.names), None)
5655

5756

5857
class City(PlaceRecord):
@@ -243,8 +242,8 @@ class RepresentedCountry(Country):
243242
244243
"""
245244

246-
_valid_attributes = set(['confidence', 'geoname_id', 'iso_code', 'names',
247-
'type'])
245+
_valid_attributes = set(
246+
['confidence', 'geoname_id', 'iso_code', 'names', 'type'])
248247

249248

250249
class Location(Record):
@@ -314,9 +313,11 @@ class Location(Record):
314313
315314
"""
316315

317-
_valid_attributes = set(['average_income', 'accuracy_radius', 'latitude',
318-
'longitude', 'metro_code', 'population_density',
319-
'postal_code', 'postal_confidence', 'time_zone'])
316+
_valid_attributes = set([
317+
'average_income', 'accuracy_radius', 'latitude', 'longitude',
318+
'metro_code', 'population_density', 'postal_code', 'postal_confidence',
319+
'time_zone'
320+
])
320321

321322

322323
class MaxMind(Record):
@@ -594,14 +595,17 @@ class Traits(Record):
594595
595596
"""
596597

597-
_valid_attributes = set(
598-
['autonomous_system_number', 'autonomous_system_organization',
599-
'connection_type', 'domain', 'is_anonymous_proxy',
600-
'is_legitimate_proxy', 'is_satellite_provider', 'isp', 'ip_address',
601-
'organization', 'user_type'])
598+
_valid_attributes = set([
599+
'autonomous_system_number', 'autonomous_system_organization',
600+
'connection_type', 'domain', 'is_anonymous_proxy',
601+
'is_legitimate_proxy', 'is_satellite_provider', 'isp', 'ip_address',
602+
'organization', 'user_type'
603+
])
602604

603605
def __init__(self, **kwargs):
604-
for k in ['is_anonymous_proxy', 'is_legitimate_proxy',
605-
'is_satellite_provider']:
606+
for k in [
607+
'is_anonymous_proxy', 'is_legitimate_proxy',
608+
'is_satellite_provider'
609+
]:
606610
kwargs[k] = bool(kwargs.get(k, False))
607611
super(Traits, self).__init__(**kwargs)

geoip2/webservice.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,14 @@ def _response_for(self, path, model_class, ip_address):
141141
if ip_address != 'me':
142142
ip_address = str(compat_ip_address(ip_address))
143143
uri = '/'.join([self._base_uri, path, ip_address])
144-
response = requests.get(uri,
145-
auth=(self._user_id, self._license_key),
146-
headers={
147-
'Accept': 'application/json',
148-
'User-Agent': self._user_agent()
149-
},
150-
timeout=self._timeout)
144+
response = requests.get(
145+
uri,
146+
auth=(self._user_id, self._license_key),
147+
headers={
148+
'Accept': 'application/json',
149+
'User-Agent': self._user_agent()
150+
},
151+
timeout=self._timeout)
151152
if response.status_code == 200:
152153
body = self._handle_success(response, uri)
153154
return model_class(body, locales=self._locales)

tests/data

Submodule data updated 39 files

tests/database_test.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626

2727
class BaseTestReader(object):
28-
2928
def test_language_list(self):
3029
reader = geoip2.database.Reader(
3130
'tests/data/test-data/GeoIP2-Country-Test.mmdb',
@@ -76,6 +75,26 @@ def test_anonymous_ip(self):
7675
self.assertEqual(record.ip_address, ip_address)
7776
reader.close()
7877

78+
def test_asn(self):
79+
reader = geoip2.database.Reader(
80+
'tests/data/test-data/GeoLite2-ASN-Test.mmdb')
81+
82+
ip_address = '1.128.0.0'
83+
record = reader.asn(ip_address)
84+
self.assertEqual(record.autonomous_system_number, 1221)
85+
self.assertEqual(record.autonomous_system_organization,
86+
'Telstra Pty Ltd')
87+
self.assertEqual(record.ip_address, ip_address)
88+
89+
self.assertRegex(
90+
str(record),
91+
r'geoip2.models.ASN\(.*1\.128\.0\.0.*\)',
92+
'str representation is correct')
93+
94+
self.assertEqual(record, eval(repr(record)), "ASN repr can be eval'd")
95+
96+
reader.close()
97+
7998
def test_city(self):
8099
reader = geoip2.database.Reader(
81100
'tests/data/test-data/GeoIP2-City-Test.mmdb')
@@ -101,7 +120,8 @@ def test_connection_type(self):
101120
str(record), r'ConnectionType\(\{.*Cable/DSL.*\}\)',
102121
'ConnectionType str representation is reasonable')
103122

104-
self.assertEqual(record, eval(repr(record)),
123+
self.assertEqual(record,
124+
eval(repr(record)),
105125
"ConnectionType repr can be eval'd")
106126

107127
reader.close()
@@ -127,8 +147,8 @@ def test_domain(self):
127147
str(record), r'Domain\(\{.*maxmind.com.*\}\)',
128148
'Domain str representation is reasonable')
129149

130-
self.assertEqual(record, eval(repr(record)),
131-
"Domain repr can be eval'd")
150+
self.assertEqual(record,
151+
eval(repr(record)), "Domain repr can be eval'd")
132152

133153
reader.close()
134154

0 commit comments

Comments
 (0)