Skip to content

Commit 02c196e

Browse files
authored
Merge pull request #90 from maxmind/greg/py3
Drop Python 2 support
2 parents ba92347 + 47227d4 commit 02c196e

19 files changed

+448
-308
lines changed

.travis.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22
language: python
33
matrix:
44
include:
5-
- python: 2.7
6-
- python: 3.5
75
- python: 3.6
86
- python: 3.7
97
- python: 3.8
108
env:
119
- RUN_SNYK=1
1210
- RUN_LINTER=1
1311
- python: nightly
14-
- python: pypy
1512
allow_failures:
1613
- python: nightly
1714

@@ -32,11 +29,12 @@ install:
3229
- pip install requests_mock coveralls
3330
- |
3431
if [[ $RUN_LINTER ]]; then
35-
pip install --upgrade pylint black
32+
pip install --upgrade pylint black mypy
3633
fi
3734
- "if [[ $RUN_SNYK && $SNYK_TOKEN ]]; then snyk test --org=maxmind; fi"
3835
script:
3936
- coverage run --source=geoip2 setup.py test
37+
- "if [[ $RUN_LINTER ]]; then mypy geoip2 tests; fi"
4038
- "if [[ $RUN_LINTER ]]; then ./.travis-pylint.sh; fi"
4139
- "if [[ $RUN_LINTER ]]; then ./.travis-black.sh; fi"
4240
after_success:

HISTORY.rst

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

6+
4.0.0
7+
++++++++++++++++++
8+
9+
* IMPORTANT: Python 2.7 and 3.5 support has been dropped. Python 3.6 or greater
10+
is required.
11+
* Type hints have been added.
12+
* The attributes ``postal_code`` and ``postal_confidence`` have been removed
13+
from ``geoip2.record.Location``. These would previously always be ``None``.
14+
* ``user_id`` is no longer supported as a named argument for the constructor
15+
on ``geoip2.webservice.Client``. Use ``account_id`` or a positional
16+
parameter instead.
17+
618
3.0.0 (2019-12-20)
719
++++++++++++++++++
820

README.rst

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,11 @@ Database Reader Exceptions
331331
--------------------------
332332

333333
If the database file does not exist or is not readable, the constructor will
334-
raise a ``FileNotFoundError`` on Python 3 or an ``IOError`` on Python 2.
335-
If the IP address passed to a method is invalid, a ``ValueError`` will be
336-
raised. If the file is invalid or there is a bug in the reader, a
337-
``maxminddb.InvalidDatabaseError`` will be raised with a description of the
338-
problem. If an IP address is not in the database, a ``AddressNotFoundError``
339-
will be raised.
334+
raise a ``FileNotFoundError``. If the IP address passed to a method is
335+
invalid, a ``ValueError`` will be raised. If the file is invalid or there is a
336+
bug in the reader, a ``maxminddb.InvalidDatabaseError`` will be raised with a
337+
description of the problem. If an IP address is not in the database, a
338+
``AddressNotFoundError`` will be raised.
340339

341340
Values to use for Database or Dictionary Keys
342341
---------------------------------------------
@@ -402,8 +401,7 @@ correction, please `contact MaxMind support
402401
Requirements
403402
------------
404403

405-
This code requires Python 2.7+ or 3.5+. Older versions are not supported.
406-
This library has been tested with CPython and PyPy.
404+
Python 3.6 or greater is required. Older versions are not supported.
407405

408406
The Requests HTTP library is also required. See
409407
<http://python-requests.org> for details.

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949

5050
# General information about the project.
5151
project = "geoip2"
52-
copyright = "2013-2019, MaxMind, Inc."
52+
copyright = "2013-2020, MaxMind, Inc."
5353

5454
# The version info for the project you're documenting, acts as replacement for
5555
# |version| and |release|, also used in various other places throughout the

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ Indices and tables
3838
* :ref:`modindex`
3939
* :ref:`search`
4040

41-
:copyright: (c) 2013-2019 by MaxMind, Inc.
41+
:copyright: (c) 2013-2020 by MaxMind, Inc.
4242
:license: Apache License, Version 2.0
4343

geoip2/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# pylint:disable=C0111
22

33
__title__ = "geoip2"
4-
__version__ = "3.0.0"
4+
__version__ = "4.0.0"
55
__author__ = "Gregory Oschwald"
66
__license__ = "Apache License, Version 2.0"
7-
__copyright__ = "Copyright (c) 2013-2019 Maxmind, Inc."
7+
__copyright__ = "Copyright (c) 2013-2020 Maxmind, Inc."

geoip2/compat.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

geoip2/database.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
66
"""
77
import inspect
8+
from typing import Any, cast, List, Optional, Type, Union
89

910
import maxminddb
1011

1112
# pylint: disable=unused-import
12-
from maxminddb import (
13+
from maxminddb import ( # type: ignore
1314
MODE_AUTO,
1415
MODE_MMAP,
1516
MODE_MMAP_EXT,
@@ -21,9 +22,20 @@
2122
import geoip2
2223
import geoip2.models
2324
import geoip2.errors
25+
from geoip2.types import IPAddress
26+
from geoip2.models import (
27+
ASN,
28+
AnonymousIP,
29+
City,
30+
ConnectionType,
31+
Country,
32+
Domain,
33+
Enterprise,
34+
ISP,
35+
)
2436

2537

26-
class Reader(object):
38+
class Reader:
2739
"""GeoIP2 database Reader object.
2840
2941
Instances of this class provide a reader for the GeoIP2 database format.
@@ -47,7 +59,9 @@ class Reader(object):
4759
4860
"""
4961

50-
def __init__(self, fileish, locales=None, mode=MODE_AUTO):
62+
def __init__(
63+
self, fileish: str, locales: Optional[List[str]] = None, mode: int = MODE_AUTO
64+
) -> None:
5165
"""Create GeoIP2 Reader.
5266
5367
:param fileish: The string path to the GeoIP2 database, or an existing
@@ -94,13 +108,13 @@ def __init__(self, fileish, locales=None, mode=MODE_AUTO):
94108
self._db_type = self._db_reader.metadata().database_type
95109
self._locales = locales
96110

97-
def __enter__(self):
111+
def __enter__(self) -> "Reader":
98112
return self
99113

100-
def __exit__(self, exc_type, exc_value, traceback):
114+
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None:
101115
self.close()
102116

103-
def country(self, ip_address):
117+
def country(self, ip_address: IPAddress) -> Country:
104118
"""Get the Country object for the IP address.
105119
106120
:param ip_address: IPv4 or IPv6 address as a string.
@@ -109,83 +123,101 @@ def country(self, ip_address):
109123
110124
"""
111125

112-
return self._model_for(geoip2.models.Country, "Country", ip_address)
126+
return cast(
127+
Country, self._model_for(geoip2.models.Country, "Country", ip_address)
128+
)
113129

114-
def city(self, ip_address):
130+
def city(self, ip_address: IPAddress) -> City:
115131
"""Get the City object for the IP address.
116132
117133
:param ip_address: IPv4 or IPv6 address as a string.
118134
119135
:returns: :py:class:`geoip2.models.City` object
120136
121137
"""
122-
return self._model_for(geoip2.models.City, "City", ip_address)
138+
return cast(City, self._model_for(geoip2.models.City, "City", ip_address))
123139

124-
def anonymous_ip(self, ip_address):
140+
def anonymous_ip(self, ip_address: IPAddress) -> AnonymousIP:
125141
"""Get the AnonymousIP object for the IP address.
126142
127143
:param ip_address: IPv4 or IPv6 address as a string.
128144
129145
:returns: :py:class:`geoip2.models.AnonymousIP` object
130146
131147
"""
132-
return self._flat_model_for(
133-
geoip2.models.AnonymousIP, "GeoIP2-Anonymous-IP", ip_address
148+
return cast(
149+
AnonymousIP,
150+
self._flat_model_for(
151+
geoip2.models.AnonymousIP, "GeoIP2-Anonymous-IP", ip_address
152+
),
134153
)
135154

136-
def asn(self, ip_address):
155+
def asn(self, ip_address: IPAddress) -> ASN:
137156
"""Get the ASN object for the IP address.
138157
139158
:param ip_address: IPv4 or IPv6 address as a string.
140159
141160
:returns: :py:class:`geoip2.models.ASN` object
142161
143162
"""
144-
return self._flat_model_for(geoip2.models.ASN, "GeoLite2-ASN", ip_address)
163+
return cast(
164+
ASN, self._flat_model_for(geoip2.models.ASN, "GeoLite2-ASN", ip_address)
165+
)
145166

146-
def connection_type(self, ip_address):
167+
def connection_type(self, ip_address: IPAddress) -> ConnectionType:
147168
"""Get the ConnectionType object for the IP address.
148169
149170
:param ip_address: IPv4 or IPv6 address as a string.
150171
151172
:returns: :py:class:`geoip2.models.ConnectionType` object
152173
153174
"""
154-
return self._flat_model_for(
155-
geoip2.models.ConnectionType, "GeoIP2-Connection-Type", ip_address
175+
return cast(
176+
ConnectionType,
177+
self._flat_model_for(
178+
geoip2.models.ConnectionType, "GeoIP2-Connection-Type", ip_address
179+
),
156180
)
157181

158-
def domain(self, ip_address):
182+
def domain(self, ip_address: IPAddress) -> Domain:
159183
"""Get the Domain object for the IP address.
160184
161185
:param ip_address: IPv4 or IPv6 address as a string.
162186
163187
:returns: :py:class:`geoip2.models.Domain` object
164188
165189
"""
166-
return self._flat_model_for(geoip2.models.Domain, "GeoIP2-Domain", ip_address)
190+
return cast(
191+
Domain,
192+
self._flat_model_for(geoip2.models.Domain, "GeoIP2-Domain", ip_address),
193+
)
167194

168-
def enterprise(self, ip_address):
195+
def enterprise(self, ip_address: IPAddress) -> Enterprise:
169196
"""Get the Enterprise object for the IP address.
170197
171198
:param ip_address: IPv4 or IPv6 address as a string.
172199
173200
:returns: :py:class:`geoip2.models.Enterprise` object
174201
175202
"""
176-
return self._model_for(geoip2.models.Enterprise, "Enterprise", ip_address)
203+
return cast(
204+
Enterprise,
205+
self._model_for(geoip2.models.Enterprise, "Enterprise", ip_address),
206+
)
177207

178-
def isp(self, ip_address):
208+
def isp(self, ip_address: IPAddress) -> ISP:
179209
"""Get the ISP object for the IP address.
180210
181211
:param ip_address: IPv4 or IPv6 address as a string.
182212
183213
:returns: :py:class:`geoip2.models.ISP` object
184214
185215
"""
186-
return self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address)
216+
return cast(
217+
ISP, self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address)
218+
)
187219

188-
def _get(self, database_type, ip_address):
220+
def _get(self, database_type: str, ip_address: IPAddress) -> Any:
189221
if database_type not in self._db_type:
190222
caller = inspect.stack()[2][3]
191223
raise TypeError(
@@ -197,29 +229,41 @@ def _get(self, database_type, ip_address):
197229
raise geoip2.errors.AddressNotFoundError(
198230
"The address %s is not in the database." % ip_address
199231
)
200-
return (record, prefix_len)
201-
202-
def _model_for(self, model_class, types, ip_address):
232+
return record, prefix_len
233+
234+
def _model_for(
235+
self,
236+
model_class: Union[Type[Country], Type[Enterprise], Type[City]],
237+
types: str,
238+
ip_address: IPAddress,
239+
) -> Union[Country, Enterprise, City]:
203240
(record, prefix_len) = self._get(types, ip_address)
204241
traits = record.setdefault("traits", {})
205242
traits["ip_address"] = ip_address
206243
traits["prefix_len"] = prefix_len
207244
return model_class(record, locales=self._locales)
208245

209-
def _flat_model_for(self, model_class, types, ip_address):
246+
def _flat_model_for(
247+
self,
248+
model_class: Union[
249+
Type[Domain], Type[ISP], Type[ConnectionType], Type[ASN], Type[AnonymousIP]
250+
],
251+
types: str,
252+
ip_address: IPAddress,
253+
) -> Union[ConnectionType, ISP, AnonymousIP, Domain, ASN]:
210254
(record, prefix_len) = self._get(types, ip_address)
211255
record["ip_address"] = ip_address
212256
record["prefix_len"] = prefix_len
213257
return model_class(record)
214258

215-
def metadata(self):
259+
def metadata(self) -> maxminddb.reader.Metadata:
216260
"""The metadata for the open database.
217261
218262
:returns: :py:class:`maxminddb.reader.Metadata` object
219263
"""
220264
return self._db_reader.metadata()
221265

222-
def close(self):
266+
def close(self) -> None:
223267
"""Closes the GeoIP2 database."""
224268

225269
self._db_reader.close()

geoip2/errors.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
"""
66

7+
from typing import Optional
8+
79

810
class GeoIP2Error(RuntimeError):
911
"""There was a generic error in GeoIP2.
@@ -33,8 +35,10 @@ class HTTPError(GeoIP2Error):
3335
3436
"""
3537

36-
def __init__(self, message, http_status=None, uri=None):
37-
super(HTTPError, self).__init__(message)
38+
def __init__(
39+
self, message: str, http_status: Optional[int] = None, uri: Optional[str] = None
40+
) -> None:
41+
super().__init__(message)
3842
self.http_status = http_status
3943
self.uri = uri
4044

0 commit comments

Comments
 (0)