diff --git a/.travis-yapf.sh b/.travis-black.sh similarity index 70% rename from .travis-yapf.sh rename to .travis-black.sh index a0710156..a3d2475d 100755 --- a/.travis-yapf.sh +++ b/.travis-black.sh @@ -2,10 +2,10 @@ set -eux -diff=$(yapf -rd geoip2 tests) +diff=$(black --check .) if [[ $? != 0 ]]; then - echo "yapf failed to run." + echo "black failed to run." echo "$diff" exit $? elif [[ $diff ]]; then diff --git a/.travis.yml b/.travis.yml index fc60124b..6b09d578 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,14 +36,16 @@ before_install: - "if [[ $RUN_SNYK && $SNYK_TOKEN ]]; then sudo apt-get install -y nodejs; npm install -g snyk; fi" install: - pip install -r requirements.txt - # We require yapf 0.28.0 to work around https://github.com/google/yapf/issues/781 - # If that issue is resolved, we should remove the version constraint. - - pip install requests_mock pylint coveralls yapf==0.28.0 + - pip install requests_mock coveralls + - | + if [[ $RUN_LINTER ]]; then + pip install --upgrade pylint black + fi - "if [[ $RUN_SNYK && $SNYK_TOKEN ]]; then snyk test --org=maxmind; fi" script: - coverage run --source=geoip2 setup.py test - "if [[ $RUN_LINTER ]]; then ./.travis-pylint.sh; fi" - - "if [[ $RUN_LINTER ]]; then ./.travis-yapf.sh; fi" + - "if [[ $RUN_LINTER ]]; then ./.travis-black.sh; fi" after_success: - coveralls - "if [[ $RUN_SNYK && $SNYK_TOKEN && $TRAVIS_BRANCH == 'master' && $TRAVIS_PULL_REQUEST == 'false' ]]; then snyk monitor --org=maxmind --project-name=maxmind/GeoIP2-python; fi" diff --git a/docs/conf.py b/docs/conf.py index aa6a9de6..043aac71 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,36 +18,38 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) import geoip2 # -- General configuration ----------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage' + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'geoip2' -copyright = '2013-2019, MaxMind, Inc.' +project = "geoip2" +copyright = "2013-2019, 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 @@ -60,126 +62,124 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinxdoc' +html_theme = "sphinxdoc" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'geoip2doc' +htmlhelp_basename = "geoip2doc" # -- Options for LaTeX output -------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. #'preamble': '', } @@ -187,39 +187,37 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'geoip2.tex', 'geoip2 Documentation', 'Gregory Oschwald', - 'manual'), + ("index", "geoip2.tex", "geoip2 Documentation", "Gregory Oschwald", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [('index', 'geoip2', 'geoip2 Documentation', ['Gregory Oschwald'], - 1)] +man_pages = [("index", "geoip2", "geoip2 Documentation", ["Gregory Oschwald"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------ @@ -227,18 +225,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'geoip2', 'geoip2 Documentation', 'Gregory Oschwald', 'geoip2', - 'One line description of project.', 'Miscellaneous'), + ( + "index", + "geoip2", + "geoip2 Documentation", + "Gregory Oschwald", + "geoip2", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {"http://docs.python.org/": None} diff --git a/examples/benchmark.py b/examples/benchmark.py index 674033dc..4a60afcc 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -10,12 +10,10 @@ import struct import timeit -parser = argparse.ArgumentParser(description='Benchmark maxminddb.') -parser.add_argument( - '--count', default=250000, type=int, help='number of lookups') -parser.add_argument('--mode', default=0, type=int, help='reader mode to use') -parser.add_argument( - '--file', default='GeoIP2-City.mmdb', help='path to mmdb file') +parser = argparse.ArgumentParser(description="Benchmark maxminddb.") +parser.add_argument("--count", default=250000, type=int, help="number of lookups") +parser.add_argument("--mode", default=0, type=int, help="reader mode to use") +parser.add_argument("--file", default="GeoIP2-City.mmdb", help="path to mmdb file") args = parser.parse_args() @@ -23,7 +21,7 @@ def lookup_ip_address(): - ip = socket.inet_ntoa(struct.pack('!L', random.getrandbits(32))) + ip = socket.inet_ntoa(struct.pack("!L", random.getrandbits(32))) try: record = reader.city(str(ip)) except geoip2.errors.AddressNotFoundError: @@ -31,8 +29,9 @@ def lookup_ip_address(): elapsed = timeit.timeit( - 'lookup_ip_address()', - setup='from __main__ import lookup_ip_address', - number=args.count) + "lookup_ip_address()", + setup="from __main__ import lookup_ip_address", + number=args.count, +) -print(args.count / elapsed, 'lookups per second') +print(args.count / elapsed, "lookups per second") diff --git a/geoip2/__init__.py b/geoip2/__init__.py index 8bb4808f..2d5ab987 100644 --- a/geoip2/__init__.py +++ b/geoip2/__init__.py @@ -1,7 +1,7 @@ # pylint:disable=C0111 -__title__ = 'geoip2' -__version__ = '3.0.0' -__author__ = 'Gregory Oschwald' -__license__ = 'Apache License, Version 2.0' -__copyright__ = 'Copyright (c) 2013-2019 Maxmind, Inc.' +__title__ = "geoip2" +__version__ = "3.0.0" +__author__ = "Gregory Oschwald" +__license__ = "Apache License, Version 2.0" +__copyright__ = "Copyright (c) 2013-2019 Maxmind, Inc." diff --git a/geoip2/compat.py b/geoip2/compat.py index e5dc3d87..7467fcf6 100644 --- a/geoip2/compat.py +++ b/geoip2/compat.py @@ -18,6 +18,8 @@ def compat_ip_network(network, strict=True): if isinstance(network, bytes): network = network.decode() return ipaddress.ip_network(network, strict) + + else: def compat_ip_address(address): diff --git a/geoip2/database.py b/geoip2/database.py index dd301e2e..43767042 100644 --- a/geoip2/database.py +++ b/geoip2/database.py @@ -7,9 +7,16 @@ import inspect import maxminddb + # pylint: disable=unused-import -from maxminddb import (MODE_AUTO, MODE_MMAP, MODE_MMAP_EXT, MODE_FILE, - MODE_MEMORY, MODE_FD) +from maxminddb import ( + MODE_AUTO, + MODE_MMAP, + MODE_MMAP_EXT, + MODE_FILE, + MODE_MEMORY, + MODE_FD, +) import geoip2 import geoip2.models @@ -39,6 +46,7 @@ class Reader(object): be thrown. """ + def __init__(self, fileish, locales=None, mode=MODE_AUTO): """Create GeoIP2 Reader. @@ -81,7 +89,7 @@ def __init__(self, fileish, locales=None, mode=MODE_AUTO): """ if locales is None: - locales = ['en'] + locales = ["en"] self._db_reader = maxminddb.open_database(fileish, mode) self._db_type = self._db_reader.metadata().database_type self._locales = locales @@ -101,7 +109,7 @@ def country(self, ip_address): """ - return self._model_for(geoip2.models.Country, 'Country', ip_address) + return self._model_for(geoip2.models.Country, "Country", ip_address) def city(self, ip_address): """Get the City object for the IP address. @@ -111,7 +119,7 @@ def city(self, ip_address): :returns: :py:class:`geoip2.models.City` object """ - return self._model_for(geoip2.models.City, 'City', ip_address) + return self._model_for(geoip2.models.City, "City", ip_address) def anonymous_ip(self, ip_address): """Get the AnonymousIP object for the IP address. @@ -121,8 +129,9 @@ def anonymous_ip(self, ip_address): :returns: :py:class:`geoip2.models.AnonymousIP` object """ - return self._flat_model_for(geoip2.models.AnonymousIP, - 'GeoIP2-Anonymous-IP', ip_address) + return self._flat_model_for( + geoip2.models.AnonymousIP, "GeoIP2-Anonymous-IP", ip_address + ) def asn(self, ip_address): """Get the ASN object for the IP address. @@ -132,8 +141,7 @@ def asn(self, ip_address): :returns: :py:class:`geoip2.models.ASN` object """ - return self._flat_model_for(geoip2.models.ASN, 'GeoLite2-ASN', - ip_address) + return self._flat_model_for(geoip2.models.ASN, "GeoLite2-ASN", ip_address) def connection_type(self, ip_address): """Get the ConnectionType object for the IP address. @@ -143,8 +151,9 @@ def connection_type(self, ip_address): :returns: :py:class:`geoip2.models.ConnectionType` object """ - return self._flat_model_for(geoip2.models.ConnectionType, - 'GeoIP2-Connection-Type', ip_address) + return self._flat_model_for( + geoip2.models.ConnectionType, "GeoIP2-Connection-Type", ip_address + ) def domain(self, ip_address): """Get the Domain object for the IP address. @@ -154,8 +163,7 @@ def domain(self, ip_address): :returns: :py:class:`geoip2.models.Domain` object """ - return self._flat_model_for(geoip2.models.Domain, 'GeoIP2-Domain', - ip_address) + return self._flat_model_for(geoip2.models.Domain, "GeoIP2-Domain", ip_address) def enterprise(self, ip_address): """Get the Enterprise object for the IP address. @@ -165,8 +173,7 @@ def enterprise(self, ip_address): :returns: :py:class:`geoip2.models.Enterprise` object """ - return self._model_for(geoip2.models.Enterprise, 'Enterprise', - ip_address) + return self._model_for(geoip2.models.Enterprise, "Enterprise", ip_address) def isp(self, ip_address): """Get the ISP object for the IP address. @@ -176,31 +183,33 @@ def isp(self, ip_address): :returns: :py:class:`geoip2.models.ISP` object """ - return self._flat_model_for(geoip2.models.ISP, 'GeoIP2-ISP', - ip_address) + return self._flat_model_for(geoip2.models.ISP, "GeoIP2-ISP", ip_address) def _get(self, database_type, ip_address): if database_type not in self._db_type: caller = inspect.stack()[2][3] - raise TypeError("The %s method cannot be used with the " - "%s database" % (caller, self._db_type)) + raise TypeError( + "The %s method cannot be used with the " + "%s database" % (caller, self._db_type) + ) (record, prefix_len) = self._db_reader.get_with_prefix_len(ip_address) if record is None: raise geoip2.errors.AddressNotFoundError( - "The address %s is not in the database." % ip_address) + "The address %s is not in the database." % ip_address + ) return (record, prefix_len) def _model_for(self, model_class, types, ip_address): (record, prefix_len) = self._get(types, ip_address) - traits = record.setdefault('traits', {}) - traits['ip_address'] = ip_address - traits['prefix_len'] = prefix_len + 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): (record, prefix_len) = self._get(types, ip_address) - record['ip_address'] = ip_address - record['prefix_len'] = prefix_len + record["ip_address"] = ip_address + record["prefix_len"] = prefix_len return model_class(record) def metadata(self): diff --git a/geoip2/errors.py b/geoip2/errors.py index c1a832ed..468b5858 100644 --- a/geoip2/errors.py +++ b/geoip2/errors.py @@ -32,6 +32,7 @@ class HTTPError(GeoIP2Error): :ivar uri: The URI queried """ + def __init__(self, message, http_status=None, uri=None): super(HTTPError, self).__init__(message) self.http_status = http_status diff --git a/geoip2/mixins.py b/geoip2/mixins.py index 4afc5a06..0dbb2716 100644 --- a/geoip2/mixins.py +++ b/geoip2/mixins.py @@ -9,8 +9,7 @@ class SimpleEquality(object): __metaclass__ = ABCMeta def __eq__(self, other): - return (isinstance(other, self.__class__) - and self.__dict__ == other.__dict__) + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ def __ne__(self, other): return not self.__eq__(other) diff --git a/geoip2/models.py b/geoip2/models.py index a5dbccbc..b0757db5 100644 --- a/geoip2/models.py +++ b/geoip2/models.py @@ -66,37 +66,36 @@ class Country(SimpleEquality): :type: :py:class:`geoip2.records.Traits` """ + def __init__(self, raw_response, locales=None): if locales is None: - locales = ['en'] + locales = ["en"] self._locales = locales - self.continent = \ - geoip2.records.Continent(locales, - **raw_response.get('continent', {})) - self.country = \ - geoip2.records.Country(locales, - **raw_response.get('country', {})) - self.registered_country = \ - geoip2.records.Country(locales, - **raw_response.get('registered_country', - {})) - self.represented_country \ - = geoip2.records.RepresentedCountry(locales, - **raw_response.get( - 'represented_country', {})) - - self.maxmind = \ - geoip2.records.MaxMind(**raw_response.get('maxmind', {})) - - self.traits = geoip2.records.Traits(**raw_response.get('traits', {})) + self.continent = geoip2.records.Continent( + locales, **raw_response.get("continent", {}) + ) + self.country = geoip2.records.Country( + locales, **raw_response.get("country", {}) + ) + self.registered_country = geoip2.records.Country( + locales, **raw_response.get("registered_country", {}) + ) + self.represented_country = geoip2.records.RepresentedCountry( + locales, **raw_response.get("represented_country", {}) + ) + + self.maxmind = geoip2.records.MaxMind(**raw_response.get("maxmind", {})) + + self.traits = geoip2.records.Traits(**raw_response.get("traits", {})) self.raw = raw_response def __repr__(self): - return '{module}.{class_name}({data}, {locales})'.format( + return "{module}.{class_name}({data}, {locales})".format( module=self.__module__, class_name=self.__class__.__name__, data=self.raw, - locales=self._locales) + locales=self._locales, + ) class City(Country): @@ -161,17 +160,15 @@ class City(Country): :type: :py:class:`geoip2.records.Traits` """ + def __init__(self, raw_response, locales=None): super(City, self).__init__(raw_response, locales) - self.city = \ - geoip2.records.City(locales, **raw_response.get('city', {})) - self.location = \ - geoip2.records.Location(**raw_response.get('location', {})) - self.postal = \ - geoip2.records.Postal(**raw_response.get('postal', {})) - self.subdivisions = \ - geoip2.records.Subdivisions(locales, - *raw_response.get('subdivisions', [])) + self.city = geoip2.records.City(locales, **raw_response.get("city", {})) + self.location = geoip2.records.Location(**raw_response.get("location", {})) + self.postal = geoip2.records.Postal(**raw_response.get("postal", {})) + self.subdivisions = geoip2.records.Subdivisions( + locales, *raw_response.get("subdivisions", []) + ) class Insights(City): @@ -310,15 +307,16 @@ class SimpleModel(SimpleEquality): def __init__(self, raw): self.raw = raw self._network = None - self._prefix_len = raw.get('prefix_len') - self.ip_address = raw.get('ip_address') + self._prefix_len = raw.get("prefix_len") + self.ip_address = raw.get("ip_address") def __repr__(self): # pylint: disable=no-member - return '{module}.{class_name}({data})'.format( + return "{module}.{class_name}({data})".format( module=self.__module__, class_name=self.__class__.__name__, - data=str(self.raw)) + data=str(self.raw), + ) @property def network(self): @@ -333,8 +331,7 @@ def network(self): prefix_len = self._prefix_len if ip_address is None or prefix_len is None: return None - network = compat_ip_network("{}/{}".format(ip_address, prefix_len), - False) + network = compat_ip_network("{}/{}".format(ip_address, prefix_len), False) self._network = network return network @@ -394,13 +391,14 @@ class AnonymousIP(SimpleModel): :type: ipaddress.IPv4Network or ipaddress.IPv6Network """ + def __init__(self, raw): super(AnonymousIP, self).__init__(raw) - self.is_anonymous = raw.get('is_anonymous', False) - self.is_anonymous_vpn = raw.get('is_anonymous_vpn', False) - self.is_hosting_provider = raw.get('is_hosting_provider', False) - self.is_public_proxy = raw.get('is_public_proxy', False) - self.is_tor_exit_node = raw.get('is_tor_exit_node', False) + self.is_anonymous = raw.get("is_anonymous", False) + self.is_anonymous_vpn = raw.get("is_anonymous_vpn", False) + self.is_hosting_provider = raw.get("is_hosting_provider", False) + self.is_public_proxy = raw.get("is_public_proxy", False) + self.is_tor_exit_node = raw.get("is_tor_exit_node", False) class ASN(SimpleModel): @@ -439,9 +437,8 @@ class ASN(SimpleModel): # pylint:disable=too-many-arguments def __init__(self, raw): super(ASN, self).__init__(raw) - self.autonomous_system_number = raw.get('autonomous_system_number') - self.autonomous_system_organization = raw.get( - 'autonomous_system_organization') + self.autonomous_system_number = raw.get("autonomous_system_number") + self.autonomous_system_organization = raw.get("autonomous_system_organization") class ConnectionType(SimpleModel): @@ -476,9 +473,10 @@ class ConnectionType(SimpleModel): :type: ipaddress.IPv4Network or ipaddress.IPv6Network """ + def __init__(self, raw): super(ConnectionType, self).__init__(raw) - self.connection_type = raw.get('connection_type') + self.connection_type = raw.get("connection_type") class Domain(SimpleModel): @@ -507,9 +505,10 @@ class Domain(SimpleModel): :type: ipaddress.IPv4Network or ipaddress.IPv6Network """ + def __init__(self, raw): super(Domain, self).__init__(raw) - self.domain = raw.get('domain') + self.domain = raw.get("domain") class ISP(ASN): @@ -560,5 +559,5 @@ class ISP(ASN): # pylint:disable=too-many-arguments def __init__(self, raw): super(ISP, self).__init__(raw) - self.isp = raw.get('isp') - self.organization = raw.get('organization') + self.isp = raw.get("isp") + self.organization = raw.get("organization") diff --git a/geoip2/records.py b/geoip2/records.py index 329d9b1b..acb29cd8 100644 --- a/geoip2/records.py +++ b/geoip2/records.py @@ -21,11 +21,10 @@ class Record(SimpleEquality): __metaclass__ = ABCMeta def __repr__(self): - args = ', '.join('%s=%r' % x for x in self.__dict__.items()) - return '{module}.{class_name}({data})'.format( - module=self.__module__, - class_name=self.__class__.__name__, - data=args) + args = ", ".join("%s=%r" % x for x in self.__dict__.items()) + return "{module}.{class_name}({data})".format( + module=self.__module__, class_name=self.__class__.__name__, data=args + ) class PlaceRecord(Record): @@ -35,7 +34,7 @@ class PlaceRecord(Record): def __init__(self, locales=None, names=None): if locales is None: - locales = ['en'] + locales = ["en"] self._locales = locales if names is None: names = {} @@ -45,8 +44,7 @@ def __init__(self, locales=None, names=None): def name(self): """Dict with locale codes as keys and localized name as value.""" # pylint:disable=E1101 - return next((self.names.get(x) - for x in self._locales if x in self.names), None) + return next((self.names.get(x) for x in self._locales if x in self.names), None) class City(PlaceRecord): @@ -87,12 +85,8 @@ class City(PlaceRecord): :type: dict """ - def __init__(self, - locales=None, - confidence=None, - geoname_id=None, - names=None, - **_): + + def __init__(self, locales=None, confidence=None, geoname_id=None, names=None, **_): self.confidence = confidence self.geoname_id = geoname_id super(City, self).__init__(locales, names) @@ -135,12 +129,8 @@ class Continent(PlaceRecord): :type: dict """ - def __init__(self, - locales=None, - code=None, - geoname_id=None, - names=None, - **_): + + def __init__(self, locales=None, code=None, geoname_id=None, names=None, **_): self.code = code self.geoname_id = geoname_id super(Continent, self).__init__(locales, names) @@ -197,14 +187,17 @@ class Country(PlaceRecord): :type: dict """ - def __init__(self, - locales=None, - confidence=None, - geoname_id=None, - is_in_european_union=False, - iso_code=None, - names=None, - **_): + + def __init__( + self, + locales=None, + confidence=None, + geoname_id=None, + is_in_european_union=False, + iso_code=None, + names=None, + **_ + ): self.confidence = confidence self.geoname_id = geoname_id self.is_in_european_union = is_in_european_union @@ -273,21 +266,23 @@ class RepresentedCountry(Country): :type: unicode """ + def __init__( - self, - locales=None, - confidence=None, - geoname_id=None, - is_in_european_union=False, - iso_code=None, - names=None, - # pylint:disable=redefined-builtin - type=None, - **_): + self, + locales=None, + confidence=None, + geoname_id=None, + is_in_european_union=False, + iso_code=None, + names=None, + # pylint:disable=redefined-builtin + type=None, + **_ + ): self.type = type - super(RepresentedCountry, - self).__init__(locales, confidence, geoname_id, - is_in_european_union, iso_code, names) + super(RepresentedCountry, self).__init__( + locales, confidence, geoname_id, is_in_european_union, iso_code, names + ) class Location(Record): @@ -356,17 +351,20 @@ class Location(Record): :type: unicode """ - def __init__(self, - average_income=None, - accuracy_radius=None, - latitude=None, - longitude=None, - metro_code=None, - population_density=None, - postal_code=None, - postal_confidence=None, - time_zone=None, - **_): + + def __init__( + self, + average_income=None, + accuracy_radius=None, + latitude=None, + longitude=None, + metro_code=None, + population_density=None, + postal_code=None, + postal_confidence=None, + time_zone=None, + **_ + ): self.average_income = average_income self.accuracy_radius = accuracy_radius self.latitude = latitude @@ -391,6 +389,7 @@ class MaxMind(Record): :type: int """ + def __init__(self, queries_remaining=None, **_): self.queries_remaining = queries_remaining @@ -422,6 +421,7 @@ class Postal(Record): :type: int """ + def __init__(self, code=None, confidence=None, **_): self.code = code self.confidence = confidence @@ -474,13 +474,16 @@ class Subdivision(PlaceRecord): :type: dict """ - def __init__(self, - locales=None, - confidence=None, - geoname_id=None, - iso_code=None, - names=None, - **_): + + def __init__( + self, + locales=None, + confidence=None, + geoname_id=None, + iso_code=None, + names=None, + **_ + ): self.confidence = confidence self.geoname_id = geoname_id self.iso_code = iso_code @@ -498,6 +501,7 @@ class Subdivisions(tuple): This attribute is returned by ``city``, ``enterprise``, and ``insights``. """ + def __new__(cls, locales, *subdivisions): subdivisions = [Subdivision(locales, **x) for x in subdivisions] obj = super(cls, Subdivisions).__new__(cls, subdivisions) @@ -734,28 +738,31 @@ class Traits(Record): :type: unicode """ - def __init__(self, - autonomous_system_number=None, - autonomous_system_organization=None, - connection_type=None, - domain=None, - is_anonymous=False, - is_anonymous_proxy=False, - is_anonymous_vpn=False, - is_hosting_provider=False, - is_legitimate_proxy=False, - is_public_proxy=False, - is_satellite_provider=False, - is_tor_exit_node=False, - isp=None, - ip_address=None, - network=None, - organization=None, - prefix_len=None, - static_ip_score=None, - user_count=None, - user_type=None, - **_): + + def __init__( + self, + autonomous_system_number=None, + autonomous_system_organization=None, + connection_type=None, + domain=None, + is_anonymous=False, + is_anonymous_proxy=False, + is_anonymous_vpn=False, + is_hosting_provider=False, + is_legitimate_proxy=False, + is_public_proxy=False, + is_satellite_provider=False, + is_tor_exit_node=False, + isp=None, + ip_address=None, + network=None, + organization=None, + prefix_len=None, + static_ip_score=None, + user_count=None, + user_type=None, + **_ + ): self.autonomous_system_number = autonomous_system_number self.autonomous_system_organization = autonomous_system_organization self.connection_type = connection_type diff --git a/geoip2/webservice.py b/geoip2/webservice.py index a5bba4e7..4f43cae0 100644 --- a/geoip2/webservice.py +++ b/geoip2/webservice.py @@ -34,9 +34,15 @@ from .compat import compat_ip_address -from .errors import (AddressNotFoundError, AuthenticationError, GeoIP2Error, - HTTPError, InvalidRequestError, OutOfQueriesError, - PermissionRequiredError) +from .errors import ( + AddressNotFoundError, + AuthenticationError, + GeoIP2Error, + HTTPError, + InvalidRequestError, + OutOfQueriesError, + PermissionRequiredError, +) class Client(object): @@ -81,39 +87,41 @@ class Client(object): * zh-CN -- Simplified Chinese. """ + def __init__( - self, - account_id=None, - license_key=None, - host='geoip.maxmind.com', - locales=None, - timeout=None, - - # This is deprecated and not documented for that reason. - # It can be removed if we do a major release in the future. - user_id=None): + self, + account_id=None, + license_key=None, + host="geoip.maxmind.com", + locales=None, + timeout=None, + # This is deprecated and not documented for that reason. + # It can be removed if we do a major release in the future. + user_id=None, + ): """Construct a Client.""" # pylint: disable=too-many-arguments if locales is None: - locales = ['en'] + locales = ["en"] if account_id is None: account_id = user_id if account_id is None: - raise TypeError('The account_id is a required parameter') + raise TypeError("The account_id is a required parameter") if license_key is None: - raise TypeError('The license_key is a required parameter') + raise TypeError("The license_key is a required parameter") self._locales = locales # requests 2.12.2 requires that the username passed to auth be bytes # or a string, with the former being preferred. - self._account_id = account_id if isinstance(account_id, - bytes) else str(account_id) + self._account_id = ( + account_id if isinstance(account_id, bytes) else str(account_id) + ) self._license_key = license_key - self._base_uri = 'https://%s/geoip/v2.1' % host + self._base_uri = "https://%s/geoip/v2.1" % host self._timeout = timeout - def city(self, ip_address='me'): + def city(self, ip_address="me"): """Call GeoIP2 Precision City endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no @@ -123,9 +131,9 @@ def city(self, ip_address='me'): :returns: :py:class:`geoip2.models.City` object """ - return self._response_for('city', geoip2.models.City, ip_address) + return self._response_for("city", geoip2.models.City, ip_address) - def country(self, ip_address='me'): + def country(self, ip_address="me"): """Call the GeoIP2 Country endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address @@ -135,9 +143,9 @@ def country(self, ip_address='me'): :returns: :py:class:`geoip2.models.Country` object """ - return self._response_for('country', geoip2.models.Country, ip_address) + return self._response_for("country", geoip2.models.Country, ip_address) - def insights(self, ip_address='me'): + def insights(self, ip_address="me"): """Call the GeoIP2 Precision: Insights endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address @@ -147,91 +155,114 @@ def insights(self, ip_address='me'): :returns: :py:class:`geoip2.models.Insights` object """ - return self._response_for('insights', geoip2.models.Insights, - ip_address) + return self._response_for("insights", geoip2.models.Insights, ip_address) def _response_for(self, path, model_class, ip_address): - if ip_address != 'me': + if ip_address != "me": ip_address = str(compat_ip_address(ip_address)) - uri = '/'.join([self._base_uri, path, ip_address]) - response = requests.get(uri, - auth=(self._account_id, self._license_key), - headers={ - 'Accept': 'application/json', - 'User-Agent': self._user_agent() - }, - timeout=self._timeout) + uri = "/".join([self._base_uri, path, ip_address]) + response = requests.get( + uri, + auth=(self._account_id, self._license_key), + headers={"Accept": "application/json", "User-Agent": self._user_agent()}, + timeout=self._timeout, + ) if response.status_code != 200: raise self._exception_for_error(response, uri) body = self._handle_success(response, uri) return model_class(body, locales=self._locales) def _user_agent(self): - return 'GeoIP2 Python Client v%s (%s)' % (geoip2.__version__, - default_user_agent()) + return "GeoIP2 Python Client v%s (%s)" % ( + geoip2.__version__, + default_user_agent(), + ) def _handle_success(self, response, uri): try: return response.json() except ValueError as ex: raise GeoIP2Error( - 'Received a 200 response for %(uri)s' - ' but could not decode the response as ' - 'JSON: ' % locals() + ', '.join(ex.args), 200, uri) + "Received a 200 response for %(uri)s" + " but could not decode the response as " + "JSON: " % locals() + ", ".join(ex.args), + 200, + uri, + ) def _exception_for_error(self, response, uri): status = response.status_code if 400 <= status < 500: return self._exception_for_4xx_status(response, status, uri) - elif 500 <= status < 600: + if 500 <= status < 600: return self._exception_for_5xx_status(status, uri) return self._exception_for_non_200_status(status, uri) def _exception_for_4xx_status(self, response, status, uri): if not response.content: return HTTPError( - 'Received a %(status)i error for %(uri)s ' - 'with no body.' % locals(), status, uri) - elif response.headers['Content-Type'].find('json') == -1: + "Received a %(status)i error for %(uri)s " "with no body." % locals(), + status, + uri, + ) + if response.headers["Content-Type"].find("json") == -1: return HTTPError( - 'Received a %i for %s with the following ' - 'body: %s' % (status, uri, response.content), status, uri) + "Received a %i for %s with the following " + "body: %s" % (status, uri, response.content), + status, + uri, + ) try: body = response.json() except ValueError as ex: return HTTPError( - 'Received a %(status)i error for %(uri)s but it did' - ' not include the expected JSON body: ' % locals() + - ', '.join(ex.args), status, uri) + "Received a %(status)i error for %(uri)s but it did" + " not include the expected JSON body: " % locals() + ", ".join(ex.args), + status, + uri, + ) else: - if 'code' in body and 'error' in body: + if "code" in body and "error" in body: return self._exception_for_web_service_error( - body.get('error'), body.get('code'), status, uri) + body.get("error"), body.get("code"), status, uri + ) return HTTPError( - 'Response contains JSON but it does not specify ' - 'code or error keys', status, uri) + "Response contains JSON but it does not specify " "code or error keys", + status, + uri, + ) def _exception_for_web_service_error(self, message, code, status, uri): - if code in ('IP_ADDRESS_NOT_FOUND', 'IP_ADDRESS_RESERVED'): + if code in ("IP_ADDRESS_NOT_FOUND", "IP_ADDRESS_RESERVED"): return AddressNotFoundError(message) - elif code in ('ACCOUNT_ID_REQUIRED', 'ACCOUNT_ID_UNKNOWN', - 'AUTHORIZATION_INVALID', 'LICENSE_KEY_REQUIRED', - 'USER_ID_REQUIRED', 'USER_ID_UNKNOWN'): + if code in ( + "ACCOUNT_ID_REQUIRED", + "ACCOUNT_ID_UNKNOWN", + "AUTHORIZATION_INVALID", + "LICENSE_KEY_REQUIRED", + "USER_ID_REQUIRED", + "USER_ID_UNKNOWN", + ): return AuthenticationError(message) - elif code in ('INSUFFICIENT_FUNDS', 'OUT_OF_QUERIES'): + if code in ("INSUFFICIENT_FUNDS", "OUT_OF_QUERIES"): return OutOfQueriesError(message) - elif code == 'PERMISSION_REQUIRED': + if code == "PERMISSION_REQUIRED": return PermissionRequiredError(message) return InvalidRequestError(message, code, status, uri) def _exception_for_5xx_status(self, status, uri): return HTTPError( - 'Received a server error (%(status)i) for ' - '%(uri)s' % locals(), status, uri) + "Received a server error (%(status)i) for " "%(uri)s" % locals(), + status, + uri, + ) def _exception_for_non_200_status(self, status, uri): return HTTPError( - 'Received a very surprising HTTP status ' - '(%(status)i) for %(uri)s' % locals(), status, uri) + "Received a very surprising HTTP status " + "(%(status)i) for %(uri)s" % locals(), + status, + uri, + ) diff --git a/pylintrc b/pylintrc index e7f9df3c..bab00808 100644 --- a/pylintrc +++ b/pylintrc @@ -1,5 +1,5 @@ [MESSAGES CONTROL] -disable=R0201,R0205,R1705,W0105 +disable=C0330,R0201,R0205,W0105 [BASIC] diff --git a/setup.py b/setup.py index ac8a8a44..a4f4968d 100644 --- a/setup.py +++ b/setup.py @@ -8,41 +8,41 @@ from setuptools import setup -packages = ['geoip2'] +packages = ["geoip2"] requirements = [i.strip() for i in open("requirements.txt").readlines()] setup( - name='geoip2', + name="geoip2", version=geoip2.__version__, - description='MaxMind GeoIP2 API', - long_description=codecs.open('README.rst', 'r', 'utf-8').read(), - author='Gregory Oschwald', - author_email='goschwald@maxmind.com', - url='http://www.maxmind.com/', - packages=['geoip2'], - package_data={'': ['LICENSE']}, - package_dir={'geoip2': 'geoip2'}, + description="MaxMind GeoIP2 API", + long_description=codecs.open("README.rst", "r", "utf-8").read(), + author="Gregory Oschwald", + author_email="goschwald@maxmind.com", + url="http://www.maxmind.com/", + packages=["geoip2"], + package_data={"": ["LICENSE"]}, + package_dir={"geoip2": "geoip2"}, include_package_data=True, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", install_requires=requirements, - extras_require={':python_version=="2.7"': ['ipaddress']}, - tests_require=['requests_mock>=0.5'], + extras_require={':python_version=="2.7"': ["ipaddress"]}, + tests_require=["requests_mock>=0.5"], test_suite="tests", license=geoip2.__license__, classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python', - 'Topic :: Internet :: Proxy Servers', - 'Topic :: Internet', + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python", + "Topic :: Internet :: Proxy Servers", + "Topic :: Internet", ], ) diff --git a/tests/database_test.py b/tests/database_test.py index cde99d44..b0d325b5 100644 --- a/tests/database_test.py +++ b/tests/database_test.py @@ -4,7 +4,8 @@ from __future__ import unicode_literals import sys -sys.path.append('..') + +sys.path.append("..") import geoip2.database import maxminddb @@ -30,45 +31,45 @@ class BaseTestReader(object): def test_language_list(self): reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Country-Test.mmdb', - ['xx', 'ru', 'pt-BR', 'es', 'en']) - record = reader.country('81.2.69.160') + "tests/data/test-data/GeoIP2-Country-Test.mmdb", + ["xx", "ru", "pt-BR", "es", "en"], + ) + record = reader.country("81.2.69.160") - self.assertEqual(record.country.name, 'Великобритания') + self.assertEqual(record.country.name, "Великобритания") reader.close() def test_unknown_address(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-City-Test.mmdb') + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-City-Test.mmdb") with self.assertRaisesRegex( - geoip2.errors.AddressNotFoundError, - 'The address 10.10.10.10 is not in the ' - 'database.'): - reader.city('10.10.10.10') + geoip2.errors.AddressNotFoundError, + "The address 10.10.10.10 is not in the " "database.", + ): + reader.city("10.10.10.10") reader.close() def test_wrong_database(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-City-Test.mmdb') + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-City-Test.mmdb") with self.assertRaisesRegex( - TypeError, 'The country method cannot be used with ' - 'the GeoIP2-City database'): - reader.country('1.1.1.1') + TypeError, + "The country method cannot be used with " "the GeoIP2-City database", + ): + reader.country("1.1.1.1") reader.close() def test_invalid_address(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-City-Test.mmdb') + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-City-Test.mmdb") with self.assertRaisesRegex( - ValueError, "u?'invalid' does not appear to be an " - "IPv4 or IPv6 address"): - reader.city('invalid') + ValueError, "u?'invalid' does not appear to be an " "IPv4 or IPv6 address" + ): + reader.city("invalid") reader.close() def test_anonymous_ip(self): reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb') - ip_address = '1.2.0.1' + "tests/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb" + ) + ip_address = "1.2.0.1" record = reader.anonymous_ip(ip_address) self.assertEqual(record.is_anonymous, True) @@ -77,141 +78,150 @@ def test_anonymous_ip(self): self.assertEqual(record.is_public_proxy, False) self.assertEqual(record.is_tor_exit_node, False) self.assertEqual(record.ip_address, ip_address) - self.assertEqual(record.network, ipaddress.ip_network('1.2.0.0/16')) + self.assertEqual(record.network, ipaddress.ip_network("1.2.0.0/16")) reader.close() def test_asn(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoLite2-ASN-Test.mmdb') + reader = geoip2.database.Reader("tests/data/test-data/GeoLite2-ASN-Test.mmdb") - ip_address = '1.128.0.0' + ip_address = "1.128.0.0" record = reader.asn(ip_address) self.assertEqual(record, eval(repr(record)), "ASN repr can be eval'd") self.assertEqual(record.autonomous_system_number, 1221) - self.assertEqual(record.autonomous_system_organization, - 'Telstra Pty Ltd') + self.assertEqual(record.autonomous_system_organization, "Telstra Pty Ltd") self.assertEqual(record.ip_address, ip_address) - self.assertEqual(record.network, ipaddress.ip_network('1.128.0.0/11')) + self.assertEqual(record.network, ipaddress.ip_network("1.128.0.0/11")) - self.assertRegex(str(record), r'geoip2.models.ASN\(.*1\.128\.0\.0.*\)', - 'str representation is correct') + self.assertRegex( + str(record), + r"geoip2.models.ASN\(.*1\.128\.0\.0.*\)", + "str representation is correct", + ) reader.close() def test_city(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-City-Test.mmdb') - record = reader.city('81.2.69.160') + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-City-Test.mmdb") + record = reader.city("81.2.69.160") - self.assertEqual(record.country.name, 'United Kingdom', - 'The default locale is en') + self.assertEqual( + record.country.name, "United Kingdom", "The default locale is en" + ) self.assertEqual(record.country.is_in_european_union, True) - self.assertEqual(record.location.accuracy_radius, 100, - 'The accuracy_radius is populated') + self.assertEqual( + record.location.accuracy_radius, 100, "The accuracy_radius is populated" + ) self.assertEqual(record.registered_country.is_in_european_union, False) reader.close() def test_connection_type(self): reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Connection-Type-Test.mmdb') - ip_address = '1.0.1.0' + "tests/data/test-data/GeoIP2-Connection-Type-Test.mmdb" + ) + ip_address = "1.0.1.0" record = reader.connection_type(ip_address) - self.assertEqual(record, eval(repr(record)), - "ConnectionType repr can be eval'd") + self.assertEqual( + record, eval(repr(record)), "ConnectionType repr can be eval'd" + ) - self.assertEqual(record.connection_type, 'Cable/DSL') + self.assertEqual(record.connection_type, "Cable/DSL") self.assertEqual(record.ip_address, ip_address) - self.assertEqual(record.network, ipaddress.ip_network('1.0.1.0/24')) + self.assertEqual(record.network, ipaddress.ip_network("1.0.1.0/24")) - self.assertRegex(str(record), r'ConnectionType\(\{.*Cable/DSL.*\}\)', - 'ConnectionType str representation is reasonable') + self.assertRegex( + str(record), + r"ConnectionType\(\{.*Cable/DSL.*\}\)", + "ConnectionType str representation is reasonable", + ) reader.close() def test_country(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Country-Test.mmdb') - record = reader.country('81.2.69.160') - self.assertEqual(record.traits.ip_address, '81.2.69.160', - 'IP address is added to model') - self.assertEqual(record.traits.network, - ipaddress.ip_network('81.2.69.160/27')) + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-Country-Test.mmdb") + record = reader.country("81.2.69.160") + self.assertEqual( + record.traits.ip_address, "81.2.69.160", "IP address is added to model" + ) + self.assertEqual(record.traits.network, ipaddress.ip_network("81.2.69.160/27")) self.assertEqual(record.country.is_in_european_union, True) self.assertEqual(record.registered_country.is_in_european_union, False) reader.close() def test_domain(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Domain-Test.mmdb') + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-Domain-Test.mmdb") - ip_address = '1.2.0.0' + ip_address = "1.2.0.0" record = reader.domain(ip_address) - self.assertEqual(record, eval(repr(record)), - "Domain repr can be eval'd") + self.assertEqual(record, eval(repr(record)), "Domain repr can be eval'd") - self.assertEqual(record.domain, 'maxmind.com') + self.assertEqual(record.domain, "maxmind.com") self.assertEqual(record.ip_address, ip_address) - self.assertEqual(record.network, ipaddress.ip_network('1.2.0.0/16')) + self.assertEqual(record.network, ipaddress.ip_network("1.2.0.0/16")) - self.assertRegex(str(record), r'Domain\(\{.*maxmind.com.*\}\)', - 'Domain str representation is reasonable') + self.assertRegex( + str(record), + r"Domain\(\{.*maxmind.com.*\}\)", + "Domain str representation is reasonable", + ) reader.close() def test_enterprise(self): with geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Enterprise-Test.mmdb') as reader: - ip_address = '74.209.24.0' + "tests/data/test-data/GeoIP2-Enterprise-Test.mmdb" + ) as reader: + ip_address = "74.209.24.0" record = reader.enterprise(ip_address) self.assertEqual(record.city.confidence, 11) self.assertEqual(record.country.confidence, 99) self.assertEqual(record.country.geoname_id, 6252001) self.assertEqual(record.country.is_in_european_union, False) self.assertEqual(record.location.accuracy_radius, 27) - self.assertEqual(record.registered_country.is_in_european_union, - False) - self.assertEqual(record.traits.connection_type, 'Cable/DSL') + self.assertEqual(record.registered_country.is_in_european_union, False) + self.assertEqual(record.traits.connection_type, "Cable/DSL") self.assertTrue(record.traits.is_legitimate_proxy) self.assertEqual(record.traits.ip_address, ip_address) - self.assertEqual(record.traits.network, - ipaddress.ip_network('74.209.16.0/20')) + self.assertEqual( + record.traits.network, ipaddress.ip_network("74.209.16.0/20") + ) def test_isp(self): - reader = geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-ISP-Test.mmdb') + reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-ISP-Test.mmdb") - ip_address = '1.128.0.0' + ip_address = "1.128.0.0" record = reader.isp(ip_address) self.assertEqual(record, eval(repr(record)), "ISP repr can be eval'd") self.assertEqual(record.autonomous_system_number, 1221) - self.assertEqual(record.autonomous_system_organization, - 'Telstra Pty Ltd') - self.assertEqual(record.isp, 'Telstra Internet') - self.assertEqual(record.organization, 'Telstra Internet') + self.assertEqual(record.autonomous_system_organization, "Telstra Pty Ltd") + self.assertEqual(record.isp, "Telstra Internet") + self.assertEqual(record.organization, "Telstra Internet") self.assertEqual(record.ip_address, ip_address) - self.assertEqual(record.network, ipaddress.ip_network('1.128.0.0/11')) + self.assertEqual(record.network, ipaddress.ip_network("1.128.0.0/11")) - self.assertRegex(str(record), r'ISP\(\{.*Telstra.*\}\)', - 'ISP str representation is reasonable') + self.assertRegex( + str(record), + r"ISP\(\{.*Telstra.*\}\)", + "ISP str representation is reasonable", + ) reader.close() def test_context_manager(self): with geoip2.database.Reader( - 'tests/data/test-data/GeoIP2-Country-Test.mmdb') as reader: - record = reader.country('81.2.69.160') - self.assertEqual(record.traits.ip_address, '81.2.69.160') + "tests/data/test-data/GeoIP2-Country-Test.mmdb" + ) as reader: + record = reader.country("81.2.69.160") + self.assertEqual(record.traits.ip_address, "81.2.69.160") -@unittest.skipUnless(maxminddb.extension, - 'No C extension module found. Skipping tests') +@unittest.skipUnless(maxminddb.extension, "No C extension module found. Skipping tests") class TestExtensionReader(BaseTestReader, unittest.TestCase): mode = geoip2.database.MODE_MMAP_EXT diff --git a/tests/models_test.py b/tests/models_test.py index 37527edf..a6e63baa 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -4,7 +4,8 @@ from __future__ import unicode_literals import sys -sys.path.append('..') + +sys.path.append("..") import geoip2.models @@ -21,157 +22,168 @@ class TestModels(unittest.TestCase): def test_insights_full(self): raw = { - 'city': { - 'confidence': 76, - 'geoname_id': 9876, - 'names': { - 'en': 'Minneapolis' - }, + "city": { + "confidence": 76, + "geoname_id": 9876, + "names": {"en": "Minneapolis"}, }, - 'continent': { - 'code': 'NA', - 'geoname_id': 42, - 'names': { - 'en': 'North America' - }, - }, - 'country': { - 'confidence': 99, - 'geoname_id': 1, - 'iso_code': 'US', - 'names': { - 'en': 'United States of America' - }, + "continent": { + "code": "NA", + "geoname_id": 42, + "names": {"en": "North America"}, }, - 'location': { - 'average_income': 24626, - 'accuracy_radius': 1500, - 'latitude': 44.98, - 'longitude': 93.2636, - 'metro_code': 765, - 'population_density': 1341, - 'time_zone': 'America/Chicago', + "country": { + "confidence": 99, + "geoname_id": 1, + "iso_code": "US", + "names": {"en": "United States of America"}, }, - 'postal': { - 'code': '55401', - 'confidence': 33, + "location": { + "average_income": 24626, + "accuracy_radius": 1500, + "latitude": 44.98, + "longitude": 93.2636, + "metro_code": 765, + "population_density": 1341, + "time_zone": "America/Chicago", }, - 'subdivisions': [ + "postal": {"code": "55401", "confidence": 33,}, + "subdivisions": [ { - 'confidence': 88, - 'geoname_id': 574635, - 'iso_code': 'MN', - 'names': { - 'en': 'Minnesota' - }, - }, - { - 'geoname_id': 123, - 'iso_code': 'HP', - 'names': { - 'en': 'Hennepin' - }, + "confidence": 88, + "geoname_id": 574635, + "iso_code": "MN", + "names": {"en": "Minnesota"}, }, + {"geoname_id": 123, "iso_code": "HP", "names": {"en": "Hennepin"},}, ], - 'registered_country': { - 'geoname_id': 2, - 'iso_code': 'CA', - 'names': { - 'en': 'Canada' - }, + "registered_country": { + "geoname_id": 2, + "iso_code": "CA", + "names": {"en": "Canada"}, }, - 'represented_country': { - 'geoname_id': 3, - 'is_in_european_union': True, - 'iso_code': 'GB', - 'names': { - 'en': 'United Kingdom' - }, - 'type': 'military', + "represented_country": { + "geoname_id": 3, + "is_in_european_union": True, + "iso_code": "GB", + "names": {"en": "United Kingdom"}, + "type": "military", }, - 'traits': { - 'autonomous_system_number': 1234, - 'autonomous_system_organization': 'AS Organization', - 'domain': 'example.com', - 'ip_address': '1.2.3.4', - 'is_anonymous': True, - 'is_anonymous_proxy': True, - 'is_anonymous_vpn': True, - 'is_hosting_provider': True, - 'is_public_proxy': True, - 'is_satellite_provider': True, - 'is_tor_exit_node': True, - 'isp': 'Comcast', - 'network_speed': 'cable/DSL', - 'organization': 'Blorg', - 'static_ip_score': 1.3, - 'user_count': 2, - 'user_type': 'college', + "traits": { + "autonomous_system_number": 1234, + "autonomous_system_organization": "AS Organization", + "domain": "example.com", + "ip_address": "1.2.3.4", + "is_anonymous": True, + "is_anonymous_proxy": True, + "is_anonymous_vpn": True, + "is_hosting_provider": True, + "is_public_proxy": True, + "is_satellite_provider": True, + "is_tor_exit_node": True, + "isp": "Comcast", + "network_speed": "cable/DSL", + "organization": "Blorg", + "static_ip_score": 1.3, + "user_count": 2, + "user_type": "college", }, } model = geoip2.models.Insights(raw) - self.assertEqual(type(model), geoip2.models.Insights, - 'geoip2.models.Insights object') - self.assertEqual(type(model.city), geoip2.records.City, - 'geoip2.records.City object') - self.assertEqual(type(model.continent), geoip2.records.Continent, - 'geoip2.records.Continent object') - self.assertEqual(type(model.country), geoip2.records.Country, - 'geoip2.records.Country object') - self.assertEqual(type(model.registered_country), - geoip2.records.Country, - 'geoip2.records.Country object') - self.assertEqual(type(model.represented_country), - geoip2.records.RepresentedCountry, - 'geoip2.records.RepresentedCountry object') - self.assertEqual(type(model.location), geoip2.records.Location, - 'geoip2.records.Location object') - self.assertEqual(type(model.subdivisions[0]), - geoip2.records.Subdivision, - 'geoip2.records.Subdivision object') - self.assertEqual(type(model.traits), geoip2.records.Traits, - 'geoip2.records.Traits object') - self.assertEqual(model.raw, raw, 'raw method returns raw input') - self.assertEqual(model.subdivisions[0].iso_code, 'MN', - 'div 1 has correct iso_code') - self.assertEqual(model.subdivisions[0].confidence, 88, - 'div 1 has correct confidence') - self.assertEqual(model.subdivisions[0].geoname_id, 574635, - 'div 1 has correct geoname_id') - self.assertEqual(model.subdivisions[0].names, {'en': 'Minnesota'}, - 'div 1 names are correct') - self.assertEqual(model.subdivisions[1].name, 'Hennepin', - 'div 2 has correct name') - self.assertEqual(model.subdivisions.most_specific.iso_code, 'HP', - 'subdivisions.most_specific returns HP') - self.assertEqual(model.represented_country.name, 'United Kingdom', - 'represented_country name is correct') - self.assertEqual(model.represented_country.type, 'military', - 'represented_country type is correct') - self.assertEqual(model.location.average_income, 24626, - 'correct average_income') - self.assertEqual(model.location.latitude, 44.98, 'correct latitude') - self.assertEqual(model.location.longitude, 93.2636, - 'correct longitude') - self.assertEqual(model.location.metro_code, 765, 'correct metro_code') - self.assertEqual(model.location.population_density, 1341, - 'correct population_density') + self.assertEqual( + type(model), geoip2.models.Insights, "geoip2.models.Insights object" + ) + self.assertEqual( + type(model.city), geoip2.records.City, "geoip2.records.City object" + ) + self.assertEqual( + type(model.continent), + geoip2.records.Continent, + "geoip2.records.Continent object", + ) + self.assertEqual( + type(model.country), geoip2.records.Country, "geoip2.records.Country object" + ) + self.assertEqual( + type(model.registered_country), + geoip2.records.Country, + "geoip2.records.Country object", + ) + self.assertEqual( + type(model.represented_country), + geoip2.records.RepresentedCountry, + "geoip2.records.RepresentedCountry object", + ) + self.assertEqual( + type(model.location), + geoip2.records.Location, + "geoip2.records.Location object", + ) + self.assertEqual( + type(model.subdivisions[0]), + geoip2.records.Subdivision, + "geoip2.records.Subdivision object", + ) + self.assertEqual( + type(model.traits), geoip2.records.Traits, "geoip2.records.Traits object" + ) + self.assertEqual(model.raw, raw, "raw method returns raw input") + self.assertEqual( + model.subdivisions[0].iso_code, "MN", "div 1 has correct iso_code" + ) + self.assertEqual( + model.subdivisions[0].confidence, 88, "div 1 has correct confidence" + ) + self.assertEqual( + model.subdivisions[0].geoname_id, 574635, "div 1 has correct geoname_id" + ) + self.assertEqual( + model.subdivisions[0].names, {"en": "Minnesota"}, "div 1 names are correct" + ) + self.assertEqual( + model.subdivisions[1].name, "Hennepin", "div 2 has correct name" + ) + self.assertEqual( + model.subdivisions.most_specific.iso_code, + "HP", + "subdivisions.most_specific returns HP", + ) + self.assertEqual( + model.represented_country.name, + "United Kingdom", + "represented_country name is correct", + ) + self.assertEqual( + model.represented_country.type, + "military", + "represented_country type is correct", + ) + self.assertEqual(model.location.average_income, 24626, "correct average_income") + self.assertEqual(model.location.latitude, 44.98, "correct latitude") + self.assertEqual(model.location.longitude, 93.2636, "correct longitude") + self.assertEqual(model.location.metro_code, 765, "correct metro_code") + self.assertEqual( + model.location.population_density, 1341, "correct population_density" + ) self.assertRegex( str(model), - r'^geoip2.models.Insights\(\{.*geoname_id.*\}, \[.*en.*\]\)', - 'Insights str representation looks reasonable') + r"^geoip2.models.Insights\(\{.*geoname_id.*\}, \[.*en.*\]\)", + "Insights str representation looks reasonable", + ) - self.assertEqual(model, eval(repr(model)), - "Insights repr can be eval'd") + self.assertEqual(model, eval(repr(model)), "Insights repr can be eval'd") - self.assertRegex(str(model.location), - r'^geoip2.records.Location\(.*longitude=.*\)', - 'Location str representation is reasonable') + self.assertRegex( + str(model.location), + r"^geoip2.records.Location\(.*longitude=.*\)", + "Location str representation is reasonable", + ) - self.assertEqual(model.location, eval(repr(model.location)), - "Location repr can be eval'd") + self.assertEqual( + model.location, eval(repr(model.location)), "Location repr can be eval'd" + ) self.assertIs(model.country.is_in_european_union, False) self.assertIs(model.registered_country.is_in_european_union, False) @@ -188,234 +200,236 @@ def test_insights_full(self): self.assertEqual(model.traits.static_ip_score, 1.3) def test_insights_min(self): - model = geoip2.models.Insights({'traits': {'ip_address': '5.6.7.8'}}) - self.assertEqual(type(model), geoip2.models.Insights, - 'geoip2.models.Insights object') - self.assertEqual(type(model.city), geoip2.records.City, - 'geoip2.records.City object') - self.assertEqual(type(model.continent), geoip2.records.Continent, - 'geoip2.records.Continent object') - self.assertEqual(type(model.country), geoip2.records.Country, - 'geoip2.records.Country object') - self.assertEqual(type(model.registered_country), - geoip2.records.Country, - 'geoip2.records.Country object') - self.assertEqual(type(model.location), geoip2.records.Location, - 'geoip2.records.Location object') - self.assertEqual(type(model.traits), geoip2.records.Traits, - 'geoip2.records.Traits object') - self.assertEqual( - type(model.subdivisions.most_specific), geoip2.records.Subdivision, - 'geoip2.records.Subdivision object returned even' - 'when none are available.') - self.assertEqual(model.subdivisions.most_specific.names, {}, - 'Empty names hash returned') + model = geoip2.models.Insights({"traits": {"ip_address": "5.6.7.8"}}) + self.assertEqual( + type(model), geoip2.models.Insights, "geoip2.models.Insights object" + ) + self.assertEqual( + type(model.city), geoip2.records.City, "geoip2.records.City object" + ) + self.assertEqual( + type(model.continent), + geoip2.records.Continent, + "geoip2.records.Continent object", + ) + self.assertEqual( + type(model.country), geoip2.records.Country, "geoip2.records.Country object" + ) + self.assertEqual( + type(model.registered_country), + geoip2.records.Country, + "geoip2.records.Country object", + ) + self.assertEqual( + type(model.location), + geoip2.records.Location, + "geoip2.records.Location object", + ) + self.assertEqual( + type(model.traits), geoip2.records.Traits, "geoip2.records.Traits object" + ) + self.assertEqual( + type(model.subdivisions.most_specific), + geoip2.records.Subdivision, + "geoip2.records.Subdivision object returned even" + "when none are available.", + ) + self.assertEqual( + model.subdivisions.most_specific.names, {}, "Empty names hash returned" + ) def test_city_full(self): raw = { - 'continent': { - 'code': 'NA', - 'geoname_id': 42, - 'names': { - 'en': 'North America' - }, - }, - 'country': { - 'geoname_id': 1, - 'iso_code': 'US', - 'names': { - 'en': 'United States of America' - }, + "continent": { + "code": "NA", + "geoname_id": 42, + "names": {"en": "North America"}, }, - 'registered_country': { - 'geoname_id': 2, - 'iso_code': 'CA', - 'names': { - 'en': 'Canada' - }, + "country": { + "geoname_id": 1, + "iso_code": "US", + "names": {"en": "United States of America"}, }, - 'traits': { - 'ip_address': '1.2.3.4', - 'is_satellite_provider': True, + "registered_country": { + "geoname_id": 2, + "iso_code": "CA", + "names": {"en": "Canada"}, }, + "traits": {"ip_address": "1.2.3.4", "is_satellite_provider": True,}, } model = geoip2.models.City(raw) - self.assertEqual(type(model), geoip2.models.City, - 'geoip2.models.City object') - self.assertEqual(type(model.city), geoip2.records.City, - 'geoip2.records.City object') - self.assertEqual(type(model.continent), geoip2.records.Continent, - 'geoip2.records.Continent object') - self.assertEqual(type(model.country), geoip2.records.Country, - 'geoip2.records.Country object') - self.assertEqual(type(model.registered_country), - geoip2.records.Country, - 'geoip2.records.Country object') - self.assertEqual(type(model.location), geoip2.records.Location, - 'geoip2.records.Location object') - self.assertEqual(type(model.traits), geoip2.records.Traits, - 'geoip2.records.Traits object') - self.assertEqual(model.raw, raw, 'raw method returns raw input') - self.assertEqual(model.continent.geoname_id, 42, - 'continent geoname_id is 42') - self.assertEqual(model.continent.code, 'NA', 'continent code is NA') - self.assertEqual(model.continent.names, {'en': 'North America'}, - 'continent names is correct') - self.assertEqual(model.continent.name, 'North America', - 'continent name is correct') - self.assertEqual(model.country.geoname_id, 1, - 'country geoname_id is 1') - self.assertEqual(model.country.iso_code, 'US', - 'country iso_code is US') - self.assertEqual(model.country.names, - {'en': 'United States of America'}, - 'country names is correct') - self.assertEqual(model.country.name, 'United States of America', - 'country name is correct') - self.assertEqual(model.country.confidence, None, - 'country confidence is None') - self.assertEqual(model.registered_country.iso_code, 'CA', - 'registered_country iso_code is CA') - self.assertEqual(model.registered_country.names, {'en': 'Canada'}, - 'registered_country names is correct') - self.assertEqual(model.registered_country.name, 'Canada', - 'registered_country name is correct') - self.assertEqual(model.traits.is_anonymous_proxy, False, - 'traits is_anonymous_proxy returns False by default') - self.assertEqual(model.traits.is_satellite_provider, True, - 'traits is_setellite_provider is True') - self.assertEqual(model.raw, raw, 'raw method produces raw output') + self.assertEqual(type(model), geoip2.models.City, "geoip2.models.City object") + self.assertEqual( + type(model.city), geoip2.records.City, "geoip2.records.City object" + ) + self.assertEqual( + type(model.continent), + geoip2.records.Continent, + "geoip2.records.Continent object", + ) + self.assertEqual( + type(model.country), geoip2.records.Country, "geoip2.records.Country object" + ) + self.assertEqual( + type(model.registered_country), + geoip2.records.Country, + "geoip2.records.Country object", + ) + self.assertEqual( + type(model.location), + geoip2.records.Location, + "geoip2.records.Location object", + ) + self.assertEqual( + type(model.traits), geoip2.records.Traits, "geoip2.records.Traits object" + ) + self.assertEqual(model.raw, raw, "raw method returns raw input") + self.assertEqual(model.continent.geoname_id, 42, "continent geoname_id is 42") + self.assertEqual(model.continent.code, "NA", "continent code is NA") + self.assertEqual( + model.continent.names, {"en": "North America"}, "continent names is correct" + ) + self.assertEqual( + model.continent.name, "North America", "continent name is correct" + ) + self.assertEqual(model.country.geoname_id, 1, "country geoname_id is 1") + self.assertEqual(model.country.iso_code, "US", "country iso_code is US") + self.assertEqual( + model.country.names, + {"en": "United States of America"}, + "country names is correct", + ) + self.assertEqual( + model.country.name, "United States of America", "country name is correct" + ) + self.assertEqual(model.country.confidence, None, "country confidence is None") + self.assertEqual( + model.registered_country.iso_code, "CA", "registered_country iso_code is CA" + ) + self.assertEqual( + model.registered_country.names, + {"en": "Canada"}, + "registered_country names is correct", + ) + self.assertEqual( + model.registered_country.name, + "Canada", + "registered_country name is correct", + ) + self.assertEqual( + model.traits.is_anonymous_proxy, + False, + "traits is_anonymous_proxy returns False by default", + ) + self.assertEqual( + model.traits.is_satellite_provider, + True, + "traits is_setellite_provider is True", + ) + self.assertEqual(model.raw, raw, "raw method produces raw output") self.assertRegex( - str(model), - r'^geoip2.models.City\(\{.*geoname_id.*\}, \[.*en.*\]\)') + str(model), r"^geoip2.models.City\(\{.*geoname_id.*\}, \[.*en.*\]\)" + ) - self.assertFalse(model == True, - '__eq__ does not blow up on weird input') + self.assertFalse(model == True, "__eq__ does not blow up on weird input") def test_unknown_keys(self): - model = geoip2.models.City({ - 'city': { - 'invalid': 0 - }, - 'continent': { - 'invalid': 0, - 'names': { - 'invalid': 0 - }, - }, - 'country': { - 'invalid': 0, - 'names': { - 'invalid': 0 - }, - }, - 'location': { - 'invalid': 0 - }, - 'postal': { - 'invalid': 0 - }, - 'subdivisions': [ - { - 'invalid': 0, - 'names': { - 'invalid': 0, - }, - }, - ], - 'registered_country': { - 'invalid': 0, - 'names': { - 'invalid': 0, - }, - }, - 'represented_country': { - 'invalid': 0, - 'names': { - 'invalid': 0, - }, - }, - 'traits': { - 'ip_address': '1.2.3.4', - 'invalid': 'blah' - }, - 'unk_base': { - 'blah': 1 + model = geoip2.models.City( + { + "city": {"invalid": 0}, + "continent": {"invalid": 0, "names": {"invalid": 0},}, + "country": {"invalid": 0, "names": {"invalid": 0},}, + "location": {"invalid": 0}, + "postal": {"invalid": 0}, + "subdivisions": [{"invalid": 0, "names": {"invalid": 0,},},], + "registered_country": {"invalid": 0, "names": {"invalid": 0,},}, + "represented_country": {"invalid": 0, "names": {"invalid": 0,},}, + "traits": {"ip_address": "1.2.3.4", "invalid": "blah"}, + "unk_base": {"blah": 1}, } - }) + ) with self.assertRaises(AttributeError): model.unk_base with self.assertRaises(AttributeError): model.traits.invalid - self.assertEqual(model.traits.ip_address, '1.2.3.4', 'correct ip') + self.assertEqual(model.traits.ip_address, "1.2.3.4", "correct ip") class TestNames(unittest.TestCase): raw = { - 'continent': { - 'code': 'NA', - 'geoname_id': 42, - 'names': { - 'de': 'Nordamerika', - 'en': 'North America', - 'es': 'América del Norte', - 'ja': '北アメリカ', - 'pt-BR': 'América do Norte', - 'ru': 'Северная Америка', - 'zh-CN': '北美洲', + "continent": { + "code": "NA", + "geoname_id": 42, + "names": { + "de": "Nordamerika", + "en": "North America", + "es": "América del Norte", + "ja": "北アメリカ", + "pt-BR": "América do Norte", + "ru": "Северная Америка", + "zh-CN": "北美洲", }, }, - 'country': { - 'geoname_id': 1, - 'iso_code': 'US', - 'names': { - 'en': 'United States of America', - 'fr': 'États-Unis', - 'zh-CN': '美国', + "country": { + "geoname_id": 1, + "iso_code": "US", + "names": { + "en": "United States of America", + "fr": "États-Unis", + "zh-CN": "美国", }, }, - 'traits': { - 'ip_address': '1.2.3.4', - }, + "traits": {"ip_address": "1.2.3.4",}, } def test_names(self): - model = geoip2.models.Country(self.raw, locales=['sq', 'ar']) - self.assertEqual(model.continent.names, self.raw['continent']['names'], - 'Correct names dict for continent') - self.assertEqual(model.country.names, self.raw['country']['names'], - 'Correct names dict for country') + model = geoip2.models.Country(self.raw, locales=["sq", "ar"]) + self.assertEqual( + model.continent.names, + self.raw["continent"]["names"], + "Correct names dict for continent", + ) + self.assertEqual( + model.country.names, + self.raw["country"]["names"], + "Correct names dict for country", + ) def test_three_locales(self): - model = geoip2.models.Country(self.raw, locales=['fr', 'zh-CN', 'en']) - self.assertEqual(model.continent.name, '北美洲', - 'continent name is in Chinese (no French available)') - self.assertEqual(model.country.name, 'États-Unis', - 'country name is in French') + model = geoip2.models.Country(self.raw, locales=["fr", "zh-CN", "en"]) + self.assertEqual( + model.continent.name, + "北美洲", + "continent name is in Chinese (no French available)", + ) + self.assertEqual(model.country.name, "États-Unis", "country name is in French") def test_two_locales(self): - model = geoip2.models.Country(self.raw, locales=['ak', 'fr']) + model = geoip2.models.Country(self.raw, locales=["ak", "fr"]) self.assertEqual( - model.continent.name, None, - 'continent name is undef (no Akan or French ' - 'available)') - self.assertEqual(model.country.name, 'États-Unis', - 'country name is in French') + model.continent.name, + None, + "continent name is undef (no Akan or French " "available)", + ) + self.assertEqual(model.country.name, "États-Unis", "country name is in French") def test_unknown_locale(self): - model = geoip2.models.Country(self.raw, locales=['aa']) - self.assertEqual(model.continent.name, None, - 'continent name is undef (no Afar available)') - self.assertEqual(model.country.name, None, - 'country name is in None (no Afar available)') + model = geoip2.models.Country(self.raw, locales=["aa"]) + self.assertEqual( + model.continent.name, None, "continent name is undef (no Afar available)" + ) + self.assertEqual( + model.country.name, None, "country name is in None (no Afar available)" + ) def test_german(self): - model = geoip2.models.Country(self.raw, locales=['de']) - self.assertEqual(model.continent.name, 'Nordamerika', - 'Correct german name for continent') + model = geoip2.models.Country(self.raw, locales=["de"]) + self.assertEqual( + model.continent.name, "Nordamerika", "Correct german name for continent" + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/webservice_test.py b/tests/webservice_test.py index 49560b30..c3721e2d 100644 --- a/tests/webservice_test.py +++ b/tests/webservice_test.py @@ -2,14 +2,21 @@ # -*- coding: utf-8 -*- import sys -sys.path.append('..') + +sys.path.append("..") import copy import geoip2 import requests_mock -from geoip2.errors import (AddressNotFoundError, AuthenticationError, - GeoIP2Error, HTTPError, InvalidRequestError, - OutOfQueriesError, PermissionRequiredError) +from geoip2.errors import ( + AddressNotFoundError, + AuthenticationError, + GeoIP2Error, + HTTPError, + InvalidRequestError, + OutOfQueriesError, + PermissionRequiredError, +) from geoip2.webservice import Client from geoip2.compat import compat_ip_network @@ -25,291 +32,327 @@ class TestClient(unittest.TestCase): def setUp(self): - self.client = Client(42, 'abcdef123456') + self.client = Client(42, "abcdef123456") - base_uri = 'https://geoip.maxmind.com/geoip/v2.1/' + base_uri = "https://geoip.maxmind.com/geoip/v2.1/" country = { - 'continent': { - 'code': 'NA', - 'geoname_id': 42, - 'names': { - 'en': 'North America' - } - }, - 'country': { - 'geoname_id': 1, - 'iso_code': 'US', - 'names': { - 'en': 'United States of America' - } - }, - 'maxmind': { - 'queries_remaining': 11 + "continent": {"code": "NA", "geoname_id": 42, "names": {"en": "North America"}}, + "country": { + "geoname_id": 1, + "iso_code": "US", + "names": {"en": "United States of America"}, }, - 'registered_country': { - 'geoname_id': 2, - 'is_in_european_union': True, - 'iso_code': 'DE', - 'names': { - 'en': 'Germany' - } - }, - 'traits': { - 'ip_address': '1.2.3.4', - 'network': '1.2.3.0/24' + "maxmind": {"queries_remaining": 11}, + "registered_country": { + "geoname_id": 2, + "is_in_european_union": True, + "iso_code": "DE", + "names": {"en": "Germany"}, }, + "traits": {"ip_address": "1.2.3.4", "network": "1.2.3.0/24"}, } # this is not a comprehensive representation of the # JSON from the server insights = copy.deepcopy(country) - insights['traits']['user_count'] = 2 - insights['traits']['static_ip_score'] = 1.3 + insights["traits"]["user_count"] = 2 + insights["traits"]["static_ip_score"] = 1.3 def _content_type(self, endpoint): - return ('application/vnd.maxmind.com-' + endpoint + - '+json; charset=UTF-8; version=1.0') + return ( + "application/vnd.maxmind.com-" + + endpoint + + "+json; charset=UTF-8; version=1.0" + ) @requests_mock.mock() def test_country_ok(self, mock): - mock.get(self.base_uri + 'country/1.2.3.4', - json=self.country, - status_code=200, - headers={'Content-Type': self._content_type('country')}) - country = self.client.country('1.2.3.4') - self.assertEqual(type(country), geoip2.models.Country, - 'return value of client.country') - self.assertEqual(country.continent.geoname_id, 42, - 'continent geoname_id is 42') - self.assertEqual(country.continent.code, 'NA', 'continent code is NA') - self.assertEqual(country.continent.name, 'North America', - 'continent name is North America') - self.assertEqual(country.country.geoname_id, 1, - 'country geoname_id is 1') - self.assertIs(country.country.is_in_european_union, False, - 'country is_in_european_union is False') - self.assertEqual(country.country.iso_code, 'US', - 'country iso_code is US') - self.assertEqual(country.country.names, - {'en': 'United States of America'}, 'country names') - self.assertEqual(country.country.name, 'United States of America', - 'country name is United States of America') - self.assertEqual(country.maxmind.queries_remaining, 11, - 'queries_remaining is 11') - self.assertIs(country.registered_country.is_in_european_union, True, - 'registered_country is_in_european_union is True') - self.assertEqual(country.traits.network, - compat_ip_network('1.2.3.0/24'), 'network') - self.assertEqual(country.raw, self.country, 'raw response is correct') + mock.get( + self.base_uri + "country/1.2.3.4", + json=self.country, + status_code=200, + headers={"Content-Type": self._content_type("country")}, + ) + country = self.client.country("1.2.3.4") + self.assertEqual( + type(country), geoip2.models.Country, "return value of client.country" + ) + self.assertEqual(country.continent.geoname_id, 42, "continent geoname_id is 42") + self.assertEqual(country.continent.code, "NA", "continent code is NA") + self.assertEqual( + country.continent.name, "North America", "continent name is North America" + ) + self.assertEqual(country.country.geoname_id, 1, "country geoname_id is 1") + self.assertIs( + country.country.is_in_european_union, + False, + "country is_in_european_union is False", + ) + self.assertEqual(country.country.iso_code, "US", "country iso_code is US") + self.assertEqual( + country.country.names, {"en": "United States of America"}, "country names" + ) + self.assertEqual( + country.country.name, + "United States of America", + "country name is United States of America", + ) + self.assertEqual( + country.maxmind.queries_remaining, 11, "queries_remaining is 11" + ) + self.assertIs( + country.registered_country.is_in_european_union, + True, + "registered_country is_in_european_union is True", + ) + self.assertEqual( + country.traits.network, compat_ip_network("1.2.3.0/24"), "network" + ) + self.assertEqual(country.raw, self.country, "raw response is correct") @requests_mock.mock() def test_me(self, mock): - mock.get(self.base_uri + 'country/me', - json=self.country, - status_code=200, - headers={'Content-Type': self._content_type('country')}) + mock.get( + self.base_uri + "country/me", + json=self.country, + status_code=200, + headers={"Content-Type": self._content_type("country")}, + ) implicit_me = self.client.country() - self.assertEqual(type(implicit_me), geoip2.models.Country, - 'country() returns Country object') + self.assertEqual( + type(implicit_me), geoip2.models.Country, "country() returns Country object" + ) explicit_me = self.client.country() - self.assertEqual(type(explicit_me), geoip2.models.Country, - 'country(\'me\') returns Country object') + self.assertEqual( + type(explicit_me), + geoip2.models.Country, + "country('me') returns Country object", + ) @requests_mock.mock() def test_200_error(self, mock): - mock.get(self.base_uri + 'country/1.1.1.1', - status_code=200, - headers={'Content-Type': self._content_type('country')}) - with self.assertRaisesRegex(GeoIP2Error, - 'could not decode the response as JSON'): - self.client.country('1.1.1.1') + mock.get( + self.base_uri + "country/1.1.1.1", + status_code=200, + headers={"Content-Type": self._content_type("country")}, + ) + with self.assertRaisesRegex( + GeoIP2Error, "could not decode the response as JSON" + ): + self.client.country("1.1.1.1") def test_bad_ip_address(self): with self.assertRaisesRegex( - ValueError, "'1.2.3' does not appear to be an IPv4 " - "or IPv6 address"): - self.client.country('1.2.3') + ValueError, "'1.2.3' does not appear to be an IPv4 " "or IPv6 address" + ): + self.client.country("1.2.3") @requests_mock.mock() def test_no_body_error(self, mock): - mock.get(self.base_uri + 'country/' + '1.2.3.7', - text='', - status_code=400, - headers={'Content-Type': self._content_type('country')}) + mock.get( + self.base_uri + "country/" + "1.2.3.7", + text="", + status_code=400, + headers={"Content-Type": self._content_type("country")}, + ) with self.assertRaisesRegex( - HTTPError, 'Received a 400 error for .* with no body'): - self.client.country('1.2.3.7') + HTTPError, "Received a 400 error for .* with no body" + ): + self.client.country("1.2.3.7") @requests_mock.mock() def test_weird_body_error(self, mock): - mock.get(self.base_uri + 'country/' + '1.2.3.8', - text='{"wierd": 42}', - status_code=400, - headers={'Content-Type': self._content_type('country')}) + mock.get( + self.base_uri + "country/" + "1.2.3.8", + text='{"wierd": 42}', + status_code=400, + headers={"Content-Type": self._content_type("country")}, + ) with self.assertRaisesRegex( - HTTPError, 'Response contains JSON but it does not ' - 'specify code or error keys'): - self.client.country('1.2.3.8') + HTTPError, + "Response contains JSON but it does not " "specify code or error keys", + ): + self.client.country("1.2.3.8") @requests_mock.mock() def test_bad_body_error(self, mock): - mock.get(self.base_uri + 'country/' + '1.2.3.9', - text='bad body', - status_code=400, - headers={'Content-Type': self._content_type('country')}) + mock.get( + self.base_uri + "country/" + "1.2.3.9", + text="bad body", + status_code=400, + headers={"Content-Type": self._content_type("country")}, + ) with self.assertRaisesRegex( - HTTPError, 'it did not include the expected JSON body'): - self.client.country('1.2.3.9') + HTTPError, "it did not include the expected JSON body" + ): + self.client.country("1.2.3.9") @requests_mock.mock() def test_500_error(self, mock): - mock.get(self.base_uri + 'country/' + '1.2.3.10', status_code=500) - with self.assertRaisesRegex(HTTPError, - r'Received a server error \(500\) for'): - self.client.country('1.2.3.10') + mock.get(self.base_uri + "country/" + "1.2.3.10", status_code=500) + with self.assertRaisesRegex(HTTPError, r"Received a server error \(500\) for"): + self.client.country("1.2.3.10") @requests_mock.mock() def test_300_error(self, mock): - mock.get(self.base_uri + 'country/' + '1.2.3.11', - status_code=300, - headers={'Content-Type': self._content_type('country')}) + mock.get( + self.base_uri + "country/" + "1.2.3.11", + status_code=300, + headers={"Content-Type": self._content_type("country")}, + ) with self.assertRaisesRegex( - HTTPError, - r'Received a very surprising HTTP status \(300\) for'): + HTTPError, r"Received a very surprising HTTP status \(300\) for" + ): - self.client.country('1.2.3.11') + self.client.country("1.2.3.11") @requests_mock.mock() def test_ip_address_required(self, mock): - self._test_error(mock, 400, 'IP_ADDRESS_REQUIRED', InvalidRequestError) + self._test_error(mock, 400, "IP_ADDRESS_REQUIRED", InvalidRequestError) @requests_mock.mock() def test_ip_address_not_found(self, mock): - self._test_error(mock, 404, 'IP_ADDRESS_NOT_FOUND', - AddressNotFoundError) + self._test_error(mock, 404, "IP_ADDRESS_NOT_FOUND", AddressNotFoundError) @requests_mock.mock() def test_ip_address_reserved(self, mock): - self._test_error(mock, 400, 'IP_ADDRESS_RESERVED', - AddressNotFoundError) + self._test_error(mock, 400, "IP_ADDRESS_RESERVED", AddressNotFoundError) @requests_mock.mock() def test_permission_required(self, mock): - self._test_error(mock, 403, 'PERMISSION_REQUIRED', - PermissionRequiredError) + self._test_error(mock, 403, "PERMISSION_REQUIRED", PermissionRequiredError) @requests_mock.mock() def test_auth_invalid(self, mock): - self._test_error(mock, 400, 'AUTHORIZATION_INVALID', - AuthenticationError) + self._test_error(mock, 400, "AUTHORIZATION_INVALID", AuthenticationError) @requests_mock.mock() def test_license_key_required(self, mock): - self._test_error(mock, 401, 'LICENSE_KEY_REQUIRED', - AuthenticationError) + self._test_error(mock, 401, "LICENSE_KEY_REQUIRED", AuthenticationError) @requests_mock.mock() def test_account_id_required(self, mock): - self._test_error(mock, 401, 'ACCOUNT_ID_REQUIRED', AuthenticationError) + self._test_error(mock, 401, "ACCOUNT_ID_REQUIRED", AuthenticationError) @requests_mock.mock() def test_user_id_required(self, mock): - self._test_error(mock, 401, 'USER_ID_REQUIRED', AuthenticationError) + self._test_error(mock, 401, "USER_ID_REQUIRED", AuthenticationError) @requests_mock.mock() def test_account_id_unkown(self, mock): - self._test_error(mock, 401, 'ACCOUNT_ID_UNKNOWN', AuthenticationError) + self._test_error(mock, 401, "ACCOUNT_ID_UNKNOWN", AuthenticationError) @requests_mock.mock() def test_user_id_unkown(self, mock): - self._test_error(mock, 401, 'USER_ID_UNKNOWN', AuthenticationError) + self._test_error(mock, 401, "USER_ID_UNKNOWN", AuthenticationError) @requests_mock.mock() def test_out_of_queries_error(self, mock): - self._test_error(mock, 402, 'OUT_OF_QUERIES', OutOfQueriesError) + self._test_error(mock, 402, "OUT_OF_QUERIES", OutOfQueriesError) def _test_error(self, mock, status, error_code, error_class): - msg = 'Some error message' - body = {'error': msg, 'code': error_code} - mock.get(self.base_uri + 'country/1.2.3.18', - json=body, - status_code=status, - headers={'Content-Type': self._content_type('country')}) + msg = "Some error message" + body = {"error": msg, "code": error_code} + mock.get( + self.base_uri + "country/1.2.3.18", + json=body, + status_code=status, + headers={"Content-Type": self._content_type("country")}, + ) with self.assertRaisesRegex(error_class, msg): - self.client.country('1.2.3.18') + self.client.country("1.2.3.18") @requests_mock.mock() def test_unknown_error(self, mock): - msg = 'Unknown error type' - ip = '1.2.3.19' - body = {'error': msg, 'code': 'UNKNOWN_TYPE'} - mock.get(self.base_uri + 'country/' + ip, - json=body, - status_code=400, - headers={'Content-Type': self._content_type('country')}) + msg = "Unknown error type" + ip = "1.2.3.19" + body = {"error": msg, "code": "UNKNOWN_TYPE"} + mock.get( + self.base_uri + "country/" + ip, + json=body, + status_code=400, + headers={"Content-Type": self._content_type("country")}, + ) with self.assertRaisesRegex(InvalidRequestError, msg): self.client.country(ip) @requests_mock.mock() def test_request(self, mock): - mock.get(self.base_uri + 'country/' + '1.2.3.4', - json=self.country, - status_code=200, - headers={'Content-Type': self._content_type('country')}) - self.client.country('1.2.3.4') + mock.get( + self.base_uri + "country/" + "1.2.3.4", + json=self.country, + status_code=200, + headers={"Content-Type": self._content_type("country")}, + ) + self.client.country("1.2.3.4") request = mock.request_history[-1] - self.assertEqual(request.path, '/geoip/v2.1/country/1.2.3.4', - 'correct URI is used') - self.assertEqual(request.headers['Accept'], 'application/json', - 'correct Accept header') - self.assertRegex(request.headers['User-Agent'], - '^GeoIP2 Python Client v', 'Correct User-Agent') - self.assertEqual(request.headers['Authorization'], - 'Basic NDI6YWJjZGVmMTIzNDU2', 'correct auth') + self.assertEqual( + request.path, "/geoip/v2.1/country/1.2.3.4", "correct URI is used" + ) + self.assertEqual( + request.headers["Accept"], "application/json", "correct Accept header" + ) + self.assertRegex( + request.headers["User-Agent"], + "^GeoIP2 Python Client v", + "Correct User-Agent", + ) + self.assertEqual( + request.headers["Authorization"], + "Basic NDI6YWJjZGVmMTIzNDU2", + "correct auth", + ) @requests_mock.mock() def test_city_ok(self, mock): - mock.get(self.base_uri + 'city/' + '1.2.3.4', - json=self.country, - status_code=200, - headers={'Content-Type': self._content_type('city')}) - city = self.client.city('1.2.3.4') - self.assertEqual(type(city), geoip2.models.City, - 'return value of client.city') - self.assertEqual(city.traits.network, compat_ip_network('1.2.3.0/24'), - 'network') + mock.get( + self.base_uri + "city/" + "1.2.3.4", + json=self.country, + status_code=200, + headers={"Content-Type": self._content_type("city")}, + ) + city = self.client.city("1.2.3.4") + self.assertEqual(type(city), geoip2.models.City, "return value of client.city") + self.assertEqual( + city.traits.network, compat_ip_network("1.2.3.0/24"), "network" + ) @requests_mock.mock() def test_insights_ok(self, mock): - mock.get(self.base_uri + 'insights/1.2.3.4', - json=self.insights, - status_code=200, - headers={'Content-Type': self._content_type('country')}) - insights = self.client.insights('1.2.3.4') - self.assertEqual(type(insights), geoip2.models.Insights, - 'return value of client.insights') - self.assertEqual(insights.traits.network, - compat_ip_network('1.2.3.0/24'), 'network') - self.assertEqual(insights.traits.static_ip_score, 1.3, - 'static_ip_score is 1.3') - self.assertEqual(insights.traits.user_count, 2, 'user_count is 2') + mock.get( + self.base_uri + "insights/1.2.3.4", + json=self.insights, + status_code=200, + headers={"Content-Type": self._content_type("country")}, + ) + insights = self.client.insights("1.2.3.4") + self.assertEqual( + type(insights), geoip2.models.Insights, "return value of client.insights" + ) + self.assertEqual( + insights.traits.network, compat_ip_network("1.2.3.0/24"), "network" + ) + self.assertEqual(insights.traits.static_ip_score, 1.3, "static_ip_score is 1.3") + self.assertEqual(insights.traits.user_count, 2, "user_count is 2") def test_named_constructor_args(self): - id = '47' - key = '1234567890ab' - for client in (Client(account_id=id, license_key=key), - Client(user_id=id, license_key=key)): + id = "47" + key = "1234567890ab" + for client in ( + Client(account_id=id, license_key=key), + Client(user_id=id, license_key=key), + ): self.assertEqual(client._account_id, id) self.assertEqual(client._license_key, key) def test_missing_constructor_args(self): with self.assertRaises(TypeError): - Client(license_key='1234567890ab') + Client(license_key="1234567890ab") with self.assertRaises(TypeError): - Client('47') + Client("47") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()