""" Records ======= """ # pylint:disable=too-many-arguments,too-many-instance-attributes,too-many-locals import ipaddress # pylint:disable=R0903 from abc import ABCMeta from typing import Dict, List, Optional, Type, Union from geoip2.mixins import SimpleEquality class Record(SimpleEquality, metaclass=ABCMeta): """All records are subclasses of the abstract class ``Record``.""" def __repr__(self) -> str: args = ", ".join("%s=%r" % x for x in self.__dict__.items()) return f"{self.__module__}.{self.__class__.__name__}({args})" class PlaceRecord(Record, metaclass=ABCMeta): """All records with :py:attr:`names` subclass :py:class:`PlaceRecord`.""" names: Dict[str, str] _locales: List[str] def __init__( self, locales: Optional[List[str]] = None, names: Optional[Dict[str, str]] = None, ) -> None: if locales is None: locales = ["en"] self._locales = locales if names is None: names = {} self.names = names @property def name(self) -> Optional[str]: """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) class City(PlaceRecord): """Contains data for the city record associated with an IP address. This class contains the city-level data associated with an IP address. This record is returned by ``city``, ``enterprise``, and ``insights``. Attributes: .. attribute:: confidence A value from 0-100 indicating MaxMind's confidence that the city is correct. This attribute is only available from the Insights end point and the GeoIP2 Enterprise database. :type: int .. attribute:: geoname_id The GeoName ID for the city. :type: int .. attribute:: name The name of the city based on the locales list passed to the constructor. :type: unicode .. attribute:: names A dictionary where the keys are locale codes and the values are names. :type: dict """ confidence: Optional[int] geoname_id: Optional[int] def __init__( self, locales: Optional[List[str]] = None, confidence: Optional[int] = None, geoname_id: Optional[int] = None, names: Optional[Dict[str, str]] = None, **_, ) -> None: self.confidence = confidence self.geoname_id = geoname_id super().__init__(locales, names) class Continent(PlaceRecord): """Contains data for the continent record associated with an IP address. This class contains the continent-level data associated with an IP address. Attributes: .. attribute:: code A two character continent code like "NA" (North America) or "OC" (Oceania). :type: unicode .. attribute:: geoname_id The GeoName ID for the continent. :type: int .. attribute:: name Returns the name of the continent based on the locales list passed to the constructor. :type: unicode .. attribute:: names A dictionary where the keys are locale codes and the values are names. :type: dict """ code: Optional[str] geoname_id: Optional[int] def __init__( self, locales: Optional[List[str]] = None, code: Optional[str] = None, geoname_id: Optional[int] = None, names: Optional[Dict[str, str]] = None, **_, ) -> None: self.code = code self.geoname_id = geoname_id super().__init__(locales, names) class Country(PlaceRecord): """Contains data for the country record associated with an IP address. This class contains the country-level data associated with an IP address. Attributes: .. attribute:: confidence A value from 0-100 indicating MaxMind's confidence that the country is correct. This attribute is only available from the Insights end point and the GeoIP2 Enterprise database. :type: int .. attribute:: geoname_id The GeoName ID for the country. :type: int .. attribute:: is_in_european_union This is true if the country is a member state of the European Union. :type: bool .. attribute:: iso_code The two-character `ISO 3166-1 `_ alpha code for the country. :type: unicode .. attribute:: name The name of the country based on the locales list passed to the constructor. :type: unicode .. attribute:: names A dictionary where the keys are locale codes and the values are names. :type: dict """ confidence: Optional[int] geoname_id: Optional[int] is_in_european_union: bool iso_code: Optional[str] def __init__( self, locales: Optional[List[str]] = None, confidence: Optional[int] = None, geoname_id: Optional[int] = None, is_in_european_union: bool = False, iso_code: Optional[str] = None, names: Optional[Dict[str, str]] = None, **_, ) -> None: self.confidence = confidence self.geoname_id = geoname_id self.is_in_european_union = is_in_european_union self.iso_code = iso_code super().__init__(locales, names) class RepresentedCountry(Country): """Contains data for the represented country associated with an IP address. This class contains the country-level data associated with an IP address for the IP's represented country. The represented country is the country represented by something like a military base. Attributes: .. attribute:: confidence A value from 0-100 indicating MaxMind's confidence that the country is correct. This attribute is only available from the Insights end point and the GeoIP2 Enterprise database. :type: int .. attribute:: geoname_id The GeoName ID for the country. :type: int .. attribute:: is_in_european_union This is true if the country is a member state of the European Union. :type: bool .. attribute:: iso_code The two-character `ISO 3166-1 `_ alpha code for the country. :type: unicode .. attribute:: name The name of the country based on the locales list passed to the constructor. :type: unicode .. attribute:: names A dictionary where the keys are locale codes and the values are names. :type: dict .. attribute:: type A string indicating the type of entity that is representing the country. Currently we only return ``military`` but this could expand to include other types in the future. :type: unicode """ type: Optional[str] def __init__( self, locales: Optional[List[str]] = None, confidence: Optional[int] = None, geoname_id: Optional[int] = None, is_in_european_union: bool = False, iso_code: Optional[str] = None, names: Optional[Dict[str, str]] = None, # pylint:disable=redefined-builtin type: Optional[str] = None, **_, ) -> None: self.type = type super().__init__( locales, confidence, geoname_id, is_in_european_union, iso_code, names ) class Location(Record): """Contains data for the location record associated with an IP address. This class contains the location data associated with an IP address. This record is returned by ``city``, ``enterprise``, and ``insights``. Attributes: .. attribute:: average_income The average income in US dollars associated with the requested IP address. This attribute is only available from the Insights end point. :type: int .. attribute:: accuracy_radius The approximate accuracy radius in kilometers around the latitude and longitude for the IP address. This is the radius where we have a 67% confidence that the device using the IP address resides within the circle centered at the latitude and longitude with the provided radius. :type: int .. attribute:: latitude The approximate latitude of the location associated with the IP address. This value is not precise and should not be used to identify a particular address or household. :type: float .. attribute:: longitude The approximate longitude of the location associated with the IP address. This value is not precise and should not be used to identify a particular address or household. :type: float .. attribute:: metro_code The metro code of the location if the location is in the US. MaxMind returns the same metro codes as the `Google AdWords API `_. :type: int .. attribute:: population_density The estimated population per square kilometer associated with the IP address. This attribute is only available from the Insights end point. :type: int .. attribute:: time_zone The time zone associated with location, as specified by the `IANA Time Zone Database `_, e.g., "America/New_York". :type: unicode """ average_income: Optional[int] accuracy_radius: Optional[int] latitude: Optional[float] longitude: Optional[float] metro_code: Optional[int] population_density: Optional[int] time_zone: Optional[str] def __init__( self, average_income: Optional[int] = None, accuracy_radius: Optional[int] = None, latitude: Optional[float] = None, longitude: Optional[float] = None, metro_code: Optional[int] = None, population_density: Optional[int] = None, time_zone: Optional[str] = None, **_, ) -> None: self.average_income = average_income self.accuracy_radius = accuracy_radius self.latitude = latitude self.longitude = longitude self.metro_code = metro_code self.population_density = population_density self.time_zone = time_zone class MaxMind(Record): """Contains data related to your MaxMind account. Attributes: .. attribute:: queries_remaining The number of remaining queries you have for the end point you are calling. :type: int """ queries_remaining: Optional[int] def __init__(self, queries_remaining: Optional[int] = None, **_) -> None: self.queries_remaining = queries_remaining class Postal(Record): """Contains data for the postal record associated with an IP address. This class contains the postal data associated with an IP address. This attribute is returned by ``city``, ``enterprise``, and ``insights``. Attributes: .. attribute:: code The postal code of the location. Postal codes are not available for all countries. In some countries, this will only contain part of the postal code. :type: unicode .. attribute:: confidence A value from 0-100 indicating MaxMind's confidence that the postal code is correct. This attribute is only available from the Insights end point and the GeoIP2 Enterprise database. :type: int """ code: Optional[str] confidence: Optional[int] def __init__( self, code: Optional[str] = None, confidence: Optional[int] = None, **_ ) -> None: self.code = code self.confidence = confidence class Subdivision(PlaceRecord): """Contains data for the subdivisions associated with an IP address. This class contains the subdivision data associated with an IP address. This attribute is returned by ``city``, ``enterprise``, and ``insights``. Attributes: .. attribute:: confidence This is a value from 0-100 indicating MaxMind's confidence that the subdivision is correct. This attribute is only available from the Insights end point and the GeoIP2 Enterprise database. :type: int .. attribute:: geoname_id This is a GeoName ID for the subdivision. :type: int .. attribute:: iso_code This is a string up to three characters long contain the subdivision portion of the `ISO 3166-2 code `_. :type: unicode .. attribute:: name The name of the subdivision based on the locales list passed to the constructor. :type: unicode .. attribute:: names A dictionary where the keys are locale codes and the values are names :type: dict """ confidence: Optional[int] geoname_id: Optional[int] iso_code: Optional[str] def __init__( self, locales: Optional[List[str]] = None, confidence: Optional[int] = None, geoname_id: Optional[int] = None, iso_code: Optional[str] = None, names: Optional[Dict[str, str]] = None, **_, ) -> None: self.confidence = confidence self.geoname_id = geoname_id self.iso_code = iso_code super().__init__(locales, names) class Subdivisions(tuple): """A tuple-like collection of subdivisions associated with an IP address. This class contains the subdivisions of the country associated with the IP address from largest to smallest. For instance, the response for Oxford in the United Kingdom would have England as the first element and Oxfordshire as the second element. This attribute is returned by ``city``, ``enterprise``, and ``insights``. """ def __new__( cls: Type["Subdivisions"], locales: Optional[List[str]], *subdivisions ) -> "Subdivisions": subobjs = tuple(Subdivision(locales, **x) for x in subdivisions) obj = super().__new__(cls, subobjs) # type: ignore return obj def __init__( self, locales: Optional[List[str]], *subdivisions # pylint:disable=W0613 ) -> None: self._locales = locales super().__init__() @property def most_specific(self) -> Subdivision: """The most specific (smallest) subdivision available. If there are no :py:class:`Subdivision` objects for the response, this returns an empty :py:class:`Subdivision`. :type: :py:class:`Subdivision` """ try: return self[-1] except IndexError: return Subdivision(self._locales) class Traits(Record): """Contains data for the traits record associated with an IP address. This class contains the traits data associated with an IP address. This class has the following attributes: .. attribute:: autonomous_system_number The `autonomous system number `_ associated with the IP address. This attribute is only available from the City and Insights web service end points and the GeoIP2 Enterprise database. :type: int .. attribute:: autonomous_system_organization The organization associated with the registered `autonomous system number `_ for the IP address. This attribute is only available from the City and Insights web service end points and the GeoIP2 Enterprise database. :type: unicode .. attribute:: connection_type The connection type may take the following values: - Dialup - Cable/DSL - Corporate - Cellular Additional values may be added in the future. This attribute is only available in the GeoIP2 Enterprise database. :type: unicode .. attribute:: domain The second level domain associated with the IP address. This will be something like "example.com" or "example.co.uk", not "foo.example.com". This attribute is only available from the City and Insights web service end points and the GeoIP2 Enterprise database. :type: unicode .. attribute:: ip_address The IP address that the data in the model is for. If you performed a "me" lookup against the web service, this will be the externally routable IP address for the system the code is running on. If the system is behind a NAT, this may differ from the IP address locally assigned to it. :type: unicode .. attribute:: is_anonymous This is true if the IP address belongs to any sort of anonymous network. This attribute is only available from GeoIP2 Precision Insights. :type: bool .. attribute:: is_anonymous_proxy This is true if the IP is an anonymous proxy. See http://dev.maxmind.com/faq/geoip#anonproxy for further details. :type: bool .. deprecated:: 2.2.0 Use our our `GeoIP2 Anonymous IP database `_ instead. .. attribute:: is_anonymous_vpn This is true if the IP address is registered to an anonymous VPN provider. If a VPN provider does not register subnets under names associated with them, we will likely only flag their IP ranges using the ``is_hosting_provider`` attribute. This attribute is only available from GeoIP2 Precision Insights. :type: bool .. attribute:: is_hosting_provider This is true if the IP address belongs to a hosting or VPN provider (see description of ``is_anonymous_vpn`` attribute). This attribute is only available from GeoIP2 Precision Insights. :type: bool .. attribute:: is_legitimate_proxy This attribute is true if MaxMind believes this IP address to be a legitimate proxy, such as an internal VPN used by a corporation. This attribute is only available in the GeoIP2 Enterprise database. :type: bool .. attribute:: is_public_proxy This is true if the IP address belongs to a public proxy. This attribute is only available from GeoIP2 Precision Insights. :type: bool .. attribute:: is_residential_proxy This is true if the IP address is on a suspected anonymizing network and belongs to a residential ISP. This attribute is only available from GeoIP2 Precision Insights. :type: bool .. attribute:: is_satellite_provider This is true if the IP address is from a satellite provider that provides service to multiple countries. :type: bool .. deprecated:: 2.2.0 Due to the increased coverage by mobile carriers, very few satellite providers now serve multiple countries. As a result, the output does not provide sufficiently relevant data for us to maintain it. .. attribute:: is_tor_exit_node This is true if the IP address is a Tor exit node. This attribute is only available from GeoIP2 Precision Insights. :type: bool .. attribute:: isp The name of the ISP associated with the IP address. This attribute is only available from the City and Insights web service end points and the GeoIP2 Enterprise database. :type: unicode .. attribute:: network The network associated with the record. In particular, this is the largest network where all of the fields besides ip_address have the same value. :type: ipaddress.IPv4Network or ipaddress.IPv6Network .. attribute:: organization The name of the organization associated with the IP address. This attribute is only available from the City and Insights web service end points and the GeoIP2 Enterprise database. :type: unicode .. attribute:: static_ip_score An indicator of how static or dynamic an IP address is. The value ranges from 0 to 99.99 with higher values meaning a greater static association. For example, many IP addresses with a user_type of cellular have a lifetime under one. Static Cable/DSL IPs typically have a lifetime above thirty. This indicator can be useful for deciding whether an IP address represents the same user over time. This attribute is only available from GeoIP2 Precision Insights. :type: float .. attribute:: user_count The estimated number of users sharing the IP/network during the past 24 hours. For IPv4, the count is for the individual IP. For IPv6, the count is for the /64 network. This attribute is only available from GeoIP2 Precision Insights. :type: int .. attribute:: user_type The user type associated with the IP address. This can be one of the following values: * business * cafe * cellular * college * content_delivery_network * dialup * government * hosting * library * military * residential * router * school * search_engine_spider * traveler This attribute is only available from the Insights end point and the GeoIP2 Enterprise database. :type: unicode """ autonomous_system_number: Optional[int] autonomous_system_organization: Optional[str] connection_type: Optional[str] domain: Optional[str] is_anonymous: bool is_anonymous_proxy: bool is_anonymous_vpn: bool is_hosting_provider: bool is_legitimate_proxy: bool is_public_proxy: bool is_residential_proxy: bool is_satellite_provider: bool is_tor_exit_node: bool isp: Optional[str] ip_address: Optional[str] organization: Optional[str] static_ip_score: Optional[float] user_count: Optional[int] user_type: Optional[str] _network: Optional[str] _prefix_len: Optional[int] def __init__( self, autonomous_system_number: Optional[int] = None, autonomous_system_organization: Optional[str] = None, connection_type: Optional[str] = None, domain: Optional[str] = None, is_anonymous: bool = False, is_anonymous_proxy: bool = False, is_anonymous_vpn: bool = False, is_hosting_provider: bool = False, is_legitimate_proxy: bool = False, is_public_proxy: bool = False, is_residential_proxy: bool = False, is_satellite_provider: bool = False, is_tor_exit_node: bool = False, isp: Optional[str] = None, ip_address: Optional[str] = None, network: Optional[str] = None, organization: Optional[str] = None, prefix_len: Optional[int] = None, static_ip_score: Optional[float] = None, user_count: Optional[int] = None, user_type: Optional[str] = None, **_, ) -> None: self.autonomous_system_number = autonomous_system_number self.autonomous_system_organization = autonomous_system_organization self.connection_type = connection_type self.domain = domain self.is_anonymous = is_anonymous self.is_anonymous_proxy = is_anonymous_proxy self.is_anonymous_vpn = is_anonymous_vpn self.is_hosting_provider = is_hosting_provider self.is_legitimate_proxy = is_legitimate_proxy self.is_public_proxy = is_public_proxy self.is_residential_proxy = is_residential_proxy self.is_satellite_provider = is_satellite_provider self.is_tor_exit_node = is_tor_exit_node self.isp = isp self.organization = organization self.static_ip_score = static_ip_score self.user_type = user_type self.user_count = user_count self.ip_address = ip_address self._network = network self._prefix_len = prefix_len # This code is duplicated for performance reasons # pylint: disable=duplicate-code @property def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]: """The network for the record""" network = self._network if isinstance(network, (ipaddress.IPv4Network, ipaddress.IPv6Network)): return network if network is None: ip_address = self.ip_address prefix_len = self._prefix_len if ip_address is None or prefix_len is None: return None network = f"{ip_address}/{prefix_len}" network = ipaddress.ip_network(network, False) self._network = network return network # type: ignore