1"""
2Models
3======
4
5These classes provide models for the data returned by the GeoIP2
6web service and databases.
7
8The only difference between the City and Insights model classes is which
9fields in each record may be populated. See
10http://dev.maxmind.com/geoip/geoip2/web-services for more details.
11
12"""
13# pylint: disable=too-many-instance-attributes,too-few-public-methods
14import ipaddress
15from abc import ABCMeta
16from typing import Any, cast, Dict, List, Optional, Union
17
18import geoip2.records
19from geoip2.mixins import SimpleEquality
20
21
22class Country(SimpleEquality):
23    """Model for the GeoIP2 Precision: Country and the GeoIP2 Country database.
24
25    This class provides the following attributes:
26
27    .. attribute:: continent
28
29      Continent object for the requested IP address.
30
31      :type: :py:class:`geoip2.records.Continent`
32
33    .. attribute:: country
34
35      Country object for the requested IP address. This record represents the
36      country where MaxMind believes the IP is located.
37
38      :type: :py:class:`geoip2.records.Country`
39
40    .. attribute:: maxmind
41
42      Information related to your MaxMind account.
43
44      :type: :py:class:`geoip2.records.MaxMind`
45
46    .. attribute:: registered_country
47
48      The registered country object for the requested IP address. This record
49      represents the country where the ISP has registered a given IP block in
50      and may differ from the user's country.
51
52      :type: :py:class:`geoip2.records.Country`
53
54    .. attribute:: represented_country
55
56      Object for the country represented by the users of the IP address
57      when that country is different than the country in ``country``. For
58      instance, the country represented by an overseas military base.
59
60      :type: :py:class:`geoip2.records.RepresentedCountry`
61
62    .. attribute:: traits
63
64      Object with the traits of the requested IP address.
65
66      :type: :py:class:`geoip2.records.Traits`
67
68    """
69
70    continent: geoip2.records.Continent
71    country: geoip2.records.Country
72    maxmind: geoip2.records.MaxMind
73    registered_country: geoip2.records.Country
74    represented_country: geoip2.records.RepresentedCountry
75    traits: geoip2.records.Traits
76
77    def __init__(
78        self, raw_response: Dict[str, Any], locales: Optional[List[str]] = None
79    ) -> None:
80        if locales is None:
81            locales = ["en"]
82        self._locales = locales
83        self.continent = geoip2.records.Continent(
84            locales, **raw_response.get("continent", {})
85        )
86        self.country = geoip2.records.Country(
87            locales, **raw_response.get("country", {})
88        )
89        self.registered_country = geoip2.records.Country(
90            locales, **raw_response.get("registered_country", {})
91        )
92        self.represented_country = geoip2.records.RepresentedCountry(
93            locales, **raw_response.get("represented_country", {})
94        )
95
96        self.maxmind = geoip2.records.MaxMind(**raw_response.get("maxmind", {}))
97
98        self.traits = geoip2.records.Traits(**raw_response.get("traits", {}))
99        self.raw = raw_response
100
101    def __repr__(self) -> str:
102        return (
103            f"{self.__module__}.{self.__class__.__name__}({self.raw}, {self._locales})"
104        )
105
106
107class City(Country):
108    """Model for the GeoIP2 Precision: City and the GeoIP2 City database.
109
110    .. attribute:: city
111
112      City object for the requested IP address.
113
114      :type: :py:class:`geoip2.records.City`
115
116    .. attribute:: continent
117
118      Continent object for the requested IP address.
119
120      :type: :py:class:`geoip2.records.Continent`
121
122    .. attribute:: country
123
124      Country object for the requested IP address. This record represents the
125      country where MaxMind believes the IP is located.
126
127      :type: :py:class:`geoip2.records.Country`
128
129    .. attribute:: location
130
131      Location object for the requested IP address.
132
133      :type: :py:class:`geoip2.records.Location`
134
135    .. attribute:: maxmind
136
137      Information related to your MaxMind account.
138
139      :type: :py:class:`geoip2.records.MaxMind`
140
141    .. attribute:: postal
142
143      Postal object for the requested IP address.
144
145      :type: :py:class:`geoip2.records.Postal`
146
147    .. attribute:: registered_country
148
149      The registered country object for the requested IP address. This record
150      represents the country where the ISP has registered a given IP block in
151      and may differ from the user's country.
152
153      :type: :py:class:`geoip2.records.Country`
154
155    .. attribute:: represented_country
156
157      Object for the country represented by the users of the IP address
158      when that country is different than the country in ``country``. For
159      instance, the country represented by an overseas military base.
160
161      :type: :py:class:`geoip2.records.RepresentedCountry`
162
163    .. attribute:: subdivisions
164
165      Object (tuple) representing the subdivisions of the country to which
166      the location of the requested IP address belongs.
167
168      :type: :py:class:`geoip2.records.Subdivisions`
169
170    .. attribute:: traits
171
172      Object with the traits of the requested IP address.
173
174      :type: :py:class:`geoip2.records.Traits`
175
176    """
177
178    city: geoip2.records.City
179    location: geoip2.records.Location
180    postal: geoip2.records.Postal
181    subdivisions: geoip2.records.Subdivisions
182
183    def __init__(
184        self, raw_response: Dict[str, Any], locales: Optional[List[str]] = None
185    ) -> None:
186        super().__init__(raw_response, locales)
187        self.city = geoip2.records.City(locales, **raw_response.get("city", {}))
188        self.location = geoip2.records.Location(**raw_response.get("location", {}))
189        self.postal = geoip2.records.Postal(**raw_response.get("postal", {}))
190        self.subdivisions = geoip2.records.Subdivisions(
191            locales, *raw_response.get("subdivisions", [])
192        )
193
194
195class Insights(City):
196    """Model for the GeoIP2 Precision: Insights web service endpoint.
197
198    .. attribute:: city
199
200      City object for the requested IP address.
201
202      :type: :py:class:`geoip2.records.City`
203
204    .. attribute:: continent
205
206      Continent object for the requested IP address.
207
208      :type: :py:class:`geoip2.records.Continent`
209
210    .. attribute:: country
211
212      Country object for the requested IP address. This record represents the
213      country where MaxMind believes the IP is located.
214
215      :type: :py:class:`geoip2.records.Country`
216
217    .. attribute:: location
218
219      Location object for the requested IP address.
220
221    .. attribute:: maxmind
222
223      Information related to your MaxMind account.
224
225      :type: :py:class:`geoip2.records.MaxMind`
226
227    .. attribute:: registered_country
228
229      The registered country object for the requested IP address. This record
230      represents the country where the ISP has registered a given IP block in
231      and may differ from the user's country.
232
233      :type: :py:class:`geoip2.records.Country`
234
235    .. attribute:: represented_country
236
237      Object for the country represented by the users of the IP address
238      when that country is different than the country in ``country``. For
239      instance, the country represented by an overseas military base.
240
241      :type: :py:class:`geoip2.records.RepresentedCountry`
242
243    .. attribute:: subdivisions
244
245      Object (tuple) representing the subdivisions of the country to which
246      the location of the requested IP address belongs.
247
248      :type: :py:class:`geoip2.records.Subdivisions`
249
250    .. attribute:: traits
251
252      Object with the traits of the requested IP address.
253
254      :type: :py:class:`geoip2.records.Traits`
255
256    """
257
258
259class Enterprise(City):
260    """Model for the GeoIP2 Enterprise database.
261
262    .. attribute:: city
263
264      City object for the requested IP address.
265
266      :type: :py:class:`geoip2.records.City`
267
268    .. attribute:: continent
269
270      Continent object for the requested IP address.
271
272      :type: :py:class:`geoip2.records.Continent`
273
274    .. attribute:: country
275
276      Country object for the requested IP address. This record represents the
277      country where MaxMind believes the IP is located.
278
279      :type: :py:class:`geoip2.records.Country`
280
281    .. attribute:: location
282
283      Location object for the requested IP address.
284
285    .. attribute:: maxmind
286
287      Information related to your MaxMind account.
288
289      :type: :py:class:`geoip2.records.MaxMind`
290
291    .. attribute:: registered_country
292
293      The registered country object for the requested IP address. This record
294      represents the country where the ISP has registered a given IP block in
295      and may differ from the user's country.
296
297      :type: :py:class:`geoip2.records.Country`
298
299    .. attribute:: represented_country
300
301      Object for the country represented by the users of the IP address
302      when that country is different than the country in ``country``. For
303      instance, the country represented by an overseas military base.
304
305      :type: :py:class:`geoip2.records.RepresentedCountry`
306
307    .. attribute:: subdivisions
308
309      Object (tuple) representing the subdivisions of the country to which
310      the location of the requested IP address belongs.
311
312      :type: :py:class:`geoip2.records.Subdivisions`
313
314    .. attribute:: traits
315
316      Object with the traits of the requested IP address.
317
318      :type: :py:class:`geoip2.records.Traits`
319
320    """
321
322
323class SimpleModel(SimpleEquality, metaclass=ABCMeta):
324    """Provides basic methods for non-location models"""
325
326    raw: Dict[str, Union[bool, str, int]]
327    ip_address: str
328
329    def __init__(self, raw: Dict[str, Union[bool, str, int]]) -> None:
330        self.raw = raw
331        self._network = None
332        self._prefix_len = raw.get("prefix_len")
333        self.ip_address = cast(str, raw.get("ip_address"))
334
335    def __repr__(self) -> str:
336        return f"{self.__module__}.{self.__class__.__name__}({self.raw})"
337
338    @property
339    def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
340        """The network for the record"""
341        # This code is duplicated for performance reasons
342        # pylint: disable=duplicate-code
343        network = self._network
344        if isinstance(network, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
345            return network
346
347        ip_address = self.ip_address
348        prefix_len = self._prefix_len
349        if ip_address is None or prefix_len is None:
350            return None
351        network = ipaddress.ip_network(f"{ip_address}/{prefix_len}", False)
352        self._network = network
353        return network
354
355
356class AnonymousIP(SimpleModel):
357    """Model class for the GeoIP2 Anonymous IP.
358
359    This class provides the following attribute:
360
361    .. attribute:: is_anonymous
362
363      This is true if the IP address belongs to any sort of anonymous network.
364
365      :type: bool
366
367    .. attribute:: is_anonymous_vpn
368
369      This is true if the IP address is registered to an anonymous VPN
370      provider.
371
372      If a VPN provider does not register subnets under names associated with
373      them, we will likely only flag their IP ranges using the
374      ``is_hosting_provider`` attribute.
375
376      :type: bool
377
378    .. attribute:: is_hosting_provider
379
380      This is true if the IP address belongs to a hosting or VPN provider
381      (see description of ``is_anonymous_vpn`` attribute).
382
383      :type: bool
384
385    .. attribute:: is_public_proxy
386
387      This is true if the IP address belongs to a public proxy.
388
389      :type: bool
390
391    .. attribute:: is_residential_proxy
392
393      This is true if the IP address is on a suspected anonymizing network
394      and belongs to a residential ISP.
395
396      :type: bool
397
398    .. attribute:: is_tor_exit_node
399
400      This is true if the IP address is a Tor exit node.
401
402      :type: bool
403
404    .. attribute:: ip_address
405
406      The IP address used in the lookup.
407
408      :type: unicode
409
410    .. attribute:: network
411
412      The network associated with the record. In particular, this is the
413      largest network where all of the fields besides ip_address have the same
414      value.
415
416      :type: ipaddress.IPv4Network or ipaddress.IPv6Network
417    """
418
419    is_anonymous: bool
420    is_anonymous_vpn: bool
421    is_hosting_provider: bool
422    is_public_proxy: bool
423    is_residential_proxy: bool
424    is_tor_exit_node: bool
425
426    def __init__(self, raw: Dict[str, bool]) -> None:
427        super().__init__(raw)  # type: ignore
428        self.is_anonymous = raw.get("is_anonymous", False)
429        self.is_anonymous_vpn = raw.get("is_anonymous_vpn", False)
430        self.is_hosting_provider = raw.get("is_hosting_provider", False)
431        self.is_public_proxy = raw.get("is_public_proxy", False)
432        self.is_residential_proxy = raw.get("is_residential_proxy", False)
433        self.is_tor_exit_node = raw.get("is_tor_exit_node", False)
434
435
436class ASN(SimpleModel):
437    """Model class for the GeoLite2 ASN.
438
439    This class provides the following attribute:
440
441    .. attribute:: autonomous_system_number
442
443      The autonomous system number associated with the IP address.
444
445      :type: int
446
447    .. attribute:: autonomous_system_organization
448
449      The organization associated with the registered autonomous system number
450      for the IP address.
451
452      :type: unicode
453
454    .. attribute:: ip_address
455
456      The IP address used in the lookup.
457
458      :type: unicode
459
460    .. attribute:: network
461
462      The network associated with the record. In particular, this is the
463      largest network where all of the fields besides ip_address have the same
464      value.
465
466      :type: ipaddress.IPv4Network or ipaddress.IPv6Network
467    """
468
469    autonomous_system_number: Optional[int]
470    autonomous_system_organization: Optional[str]
471
472    # pylint:disable=too-many-arguments
473    def __init__(self, raw: Dict[str, Union[str, int]]) -> None:
474        super().__init__(raw)
475        self.autonomous_system_number = cast(
476            Optional[int], raw.get("autonomous_system_number")
477        )
478        self.autonomous_system_organization = cast(
479            Optional[str], raw.get("autonomous_system_organization")
480        )
481
482
483class ConnectionType(SimpleModel):
484    """Model class for the GeoIP2 Connection-Type.
485
486    This class provides the following attribute:
487
488    .. attribute:: connection_type
489
490      The connection type may take the following values:
491
492      - Dialup
493      - Cable/DSL
494      - Corporate
495      - Cellular
496
497      Additional values may be added in the future.
498
499      :type: unicode
500
501    .. attribute:: ip_address
502
503      The IP address used in the lookup.
504
505      :type: unicode
506
507    .. attribute:: network
508
509      The network associated with the record. In particular, this is the
510      largest network where all of the fields besides ip_address have the same
511      value.
512
513      :type: ipaddress.IPv4Network or ipaddress.IPv6Network
514    """
515
516    connection_type: Optional[str]
517
518    def __init__(self, raw: Dict[str, Union[str, int]]) -> None:
519        super().__init__(raw)
520        self.connection_type = cast(Optional[str], raw.get("connection_type"))
521
522
523class Domain(SimpleModel):
524    """Model class for the GeoIP2 Domain.
525
526    This class provides the following attribute:
527
528    .. attribute:: domain
529
530      The domain associated with the IP address.
531
532      :type: unicode
533
534    .. attribute:: ip_address
535
536      The IP address used in the lookup.
537
538      :type: unicode
539
540    .. attribute:: network
541
542      The network associated with the record. In particular, this is the
543      largest network where all of the fields besides ip_address have the same
544      value.
545
546      :type: ipaddress.IPv4Network or ipaddress.IPv6Network
547
548    """
549
550    domain: Optional[str]
551
552    def __init__(self, raw: Dict[str, Union[str, int]]) -> None:
553        super().__init__(raw)
554        self.domain = cast(Optional[str], raw.get("domain"))
555
556
557class ISP(ASN):
558    """Model class for the GeoIP2 ISP.
559
560    This class provides the following attribute:
561
562    .. attribute:: autonomous_system_number
563
564      The autonomous system number associated with the IP address.
565
566      :type: int
567
568    .. attribute:: autonomous_system_organization
569
570      The organization associated with the registered autonomous system number
571      for the IP address.
572
573      :type: unicode
574
575    .. attribute:: isp
576
577      The name of the ISP associated with the IP address.
578
579      :type: unicode
580
581    .. attribute:: organization
582
583      The name of the organization associated with the IP address.
584
585      :type: unicode
586
587    .. attribute:: ip_address
588
589      The IP address used in the lookup.
590
591      :type: unicode
592
593    .. attribute:: network
594
595      The network associated with the record. In particular, this is the
596      largest network where all of the fields besides ip_address have the same
597      value.
598
599      :type: ipaddress.IPv4Network or ipaddress.IPv6Network
600    """
601
602    isp: Optional[str]
603    organization: Optional[str]
604
605    # pylint:disable=too-many-arguments
606    def __init__(self, raw: Dict[str, Union[str, int]]) -> None:
607        super().__init__(raw)
608        self.isp = cast(Optional[str], raw.get("isp"))
609        self.organization = cast(Optional[str], raw.get("organization"))
610