Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
language: python
matrix:
include:
- python: 2.7
- python: 3.5
- python: 3.6
- python: 3.7
- python: 3.8
env:
- RUN_SNYK=1
- RUN_LINTER=1
- python: nightly
- python: pypy
allow_failures:
- python: nightly

Expand All @@ -32,11 +29,12 @@ install:
- pip install requests_mock coveralls
- |
if [[ $RUN_LINTER ]]; then
pip install --upgrade pylint black
pip install --upgrade pylint black mypy
fi
- "if [[ $RUN_SNYK && $SNYK_TOKEN ]]; then snyk test --org=maxmind; fi"
script:
- coverage run --source=geoip2 setup.py test
- "if [[ $RUN_LINTER ]]; then mypy geoip2 tests; fi"
- "if [[ $RUN_LINTER ]]; then ./.travis-pylint.sh; fi"
- "if [[ $RUN_LINTER ]]; then ./.travis-black.sh; fi"
after_success:
Expand Down
12 changes: 12 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
History
-------

4.0.0
++++++++++++++++++

* IMPORTANT: Python 2.7 and 3.5 support has been dropped. Python 3.6 or greater
is required.
* Type hints have been added.
* The attributes ``postal_code`` and ``postal_confidence`` have been removed
from ``geoip2.record.Location``. These would previously always be ``None``.
* ``user_id`` is no longer supported as a named argument for the constructor
on ``geoip2.webservice.Client``. Use ``account_id`` or a positional
parameter instead.

3.0.0 (2019-12-20)
++++++++++++++++++

Expand Down
14 changes: 6 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,11 @@ Database Reader Exceptions
--------------------------

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

Values to use for Database or Dictionary Keys
---------------------------------------------
Expand Down Expand Up @@ -402,8 +401,7 @@ correction, please `contact MaxMind support
Requirements
------------

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

The Requests HTTP library is also required. See
<http://python-requests.org> for details.
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

# General information about the project.
project = "geoip2"
copyright = "2013-2019, MaxMind, Inc."
copyright = "2013-2020, MaxMind, Inc."

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ Indices and tables
* :ref:`modindex`
* :ref:`search`

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

4 changes: 2 additions & 2 deletions geoip2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# pylint:disable=C0111

__title__ = "geoip2"
__version__ = "3.0.0"
__version__ = "4.0.0"
__author__ = "Gregory Oschwald"
__license__ = "Apache License, Version 2.0"
__copyright__ = "Copyright (c) 2013-2019 Maxmind, Inc."
__copyright__ = "Copyright (c) 2013-2020 Maxmind, Inc."
31 changes: 0 additions & 31 deletions geoip2/compat.py

This file was deleted.

104 changes: 74 additions & 30 deletions geoip2/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

"""
import inspect
from typing import Any, cast, List, Optional, Type, Union

import maxminddb

# pylint: disable=unused-import
from maxminddb import (
from maxminddb import ( # type: ignore
MODE_AUTO,
MODE_MMAP,
MODE_MMAP_EXT,
Expand All @@ -21,9 +22,20 @@
import geoip2
import geoip2.models
import geoip2.errors
from geoip2.types import IPAddress
from geoip2.models import (
ASN,
AnonymousIP,
City,
ConnectionType,
Country,
Domain,
Enterprise,
ISP,
)


class Reader(object):
class Reader:
"""GeoIP2 database Reader object.

Instances of this class provide a reader for the GeoIP2 database format.
Expand All @@ -47,7 +59,9 @@ class Reader(object):

"""

def __init__(self, fileish, locales=None, mode=MODE_AUTO):
def __init__(
self, fileish: str, locales: Optional[List[str]] = None, mode: int = MODE_AUTO
) -> None:
"""Create GeoIP2 Reader.

:param fileish: The string path to the GeoIP2 database, or an existing
Expand Down Expand Up @@ -94,13 +108,13 @@ def __init__(self, fileish, locales=None, mode=MODE_AUTO):
self._db_type = self._db_reader.metadata().database_type
self._locales = locales

def __enter__(self):
def __enter__(self) -> "Reader":
return self

def __exit__(self, exc_type, exc_value, traceback):
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None:
self.close()

def country(self, ip_address):
def country(self, ip_address: IPAddress) -> Country:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to cast() the model_for call here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. mypy doesn't catch that one for some reason. These aren't actual runtime check so if they are wrong, it will just result in linting errors.

"""Get the Country object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.
Expand All @@ -109,83 +123,101 @@ def country(self, ip_address):

"""

return self._model_for(geoip2.models.Country, "Country", ip_address)
return cast(
Country, self._model_for(geoip2.models.Country, "Country", ip_address)
)

def city(self, ip_address):
def city(self, ip_address: IPAddress) -> City:
"""Get the City object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.City` object

"""
return self._model_for(geoip2.models.City, "City", ip_address)
return cast(City, self._model_for(geoip2.models.City, "City", ip_address))

def anonymous_ip(self, ip_address):
def anonymous_ip(self, ip_address: IPAddress) -> AnonymousIP:
"""Get the AnonymousIP object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.AnonymousIP` object

"""
return self._flat_model_for(
geoip2.models.AnonymousIP, "GeoIP2-Anonymous-IP", ip_address
return cast(
AnonymousIP,
self._flat_model_for(
geoip2.models.AnonymousIP, "GeoIP2-Anonymous-IP", ip_address
),
)

def asn(self, ip_address):
def asn(self, ip_address: IPAddress) -> ASN:
"""Get the ASN object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.ASN` object

"""
return self._flat_model_for(geoip2.models.ASN, "GeoLite2-ASN", ip_address)
return cast(
ASN, self._flat_model_for(geoip2.models.ASN, "GeoLite2-ASN", ip_address)
)

def connection_type(self, ip_address):
def connection_type(self, ip_address: IPAddress) -> ConnectionType:
"""Get the ConnectionType object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.ConnectionType` object

"""
return self._flat_model_for(
geoip2.models.ConnectionType, "GeoIP2-Connection-Type", ip_address
return cast(
ConnectionType,
self._flat_model_for(
geoip2.models.ConnectionType, "GeoIP2-Connection-Type", ip_address
),
)

def domain(self, ip_address):
def domain(self, ip_address: IPAddress) -> Domain:
"""Get the Domain object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.Domain` object

"""
return self._flat_model_for(geoip2.models.Domain, "GeoIP2-Domain", ip_address)
return cast(
Domain,
self._flat_model_for(geoip2.models.Domain, "GeoIP2-Domain", ip_address),
)

def enterprise(self, ip_address):
def enterprise(self, ip_address: IPAddress) -> Enterprise:
"""Get the Enterprise object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.Enterprise` object

"""
return self._model_for(geoip2.models.Enterprise, "Enterprise", ip_address)
return cast(
Enterprise,
self._model_for(geoip2.models.Enterprise, "Enterprise", ip_address),
)

def isp(self, ip_address):
def isp(self, ip_address: IPAddress) -> ISP:
"""Get the ISP object for the IP address.

:param ip_address: IPv4 or IPv6 address as a string.

:returns: :py:class:`geoip2.models.ISP` object

"""
return self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address)
return cast(
ISP, self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address)
)

def _get(self, database_type, ip_address):
def _get(self, database_type: str, ip_address: IPAddress) -> Any:
if database_type not in self._db_type:
caller = inspect.stack()[2][3]
raise TypeError(
Expand All @@ -197,29 +229,41 @@ def _get(self, database_type, ip_address):
raise geoip2.errors.AddressNotFoundError(
"The address %s is not in the database." % ip_address
)
return (record, prefix_len)

def _model_for(self, model_class, types, ip_address):
return record, prefix_len

def _model_for(
self,
model_class: Union[Type[Country], Type[Enterprise], Type[City]],
types: str,
ip_address: IPAddress,
) -> Union[Country, Enterprise, City]:
(record, prefix_len) = self._get(types, ip_address)
traits = record.setdefault("traits", {})
traits["ip_address"] = ip_address
traits["prefix_len"] = prefix_len
return model_class(record, locales=self._locales)

def _flat_model_for(self, model_class, types, ip_address):
def _flat_model_for(
self,
model_class: Union[
Type[Domain], Type[ISP], Type[ConnectionType], Type[ASN], Type[AnonymousIP]
],
types: str,
ip_address: IPAddress,
) -> Union[ConnectionType, ISP, AnonymousIP, Domain, ASN]:
(record, prefix_len) = self._get(types, ip_address)
record["ip_address"] = ip_address
record["prefix_len"] = prefix_len
return model_class(record)

def metadata(self):
def metadata(self) -> maxminddb.reader.Metadata:
"""The metadata for the open database.

:returns: :py:class:`maxminddb.reader.Metadata` object
"""
return self._db_reader.metadata()

def close(self):
def close(self) -> None:
"""Closes the GeoIP2 database."""

self._db_reader.close()
8 changes: 6 additions & 2 deletions geoip2/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

"""

from typing import Optional


class GeoIP2Error(RuntimeError):
"""There was a generic error in GeoIP2.
Expand Down Expand Up @@ -33,8 +35,10 @@ class HTTPError(GeoIP2Error):

"""

def __init__(self, message, http_status=None, uri=None):
super(HTTPError, self).__init__(message)
def __init__(
self, message: str, http_status: Optional[int] = None, uri: Optional[str] = None
) -> None:
super().__init__(message)
self.http_status = http_status
self.uri = uri

Expand Down
Loading