Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ History
support.
* Setuptools has been replaced with the uv build backend for building the
package.
* A new ``anonymizer`` object has been added to ``geoip2.models.Insights``.
This object is a ``geoip2.records.Anonymizer`` and contains the following
fields: ``anonymizer_confidence``, ``network_last_seen``, ``provider_name``,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be confidence instead of anonymizer_confidence?

Copy link
Member Author

Choose a reason for hiding this comment

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

Gah, good catch. I completely missed that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I see. I think it came from AnonymousPlus, where it is anonymizer_confidence.

``is_anonymous``, ``is_anonymous_vpn``, ``is_hosting_provider``,
``is_public_proxy``, ``is_residential_proxy``, and ``is_tor_exit_node``.
These provide information about VPN and proxy usage.
* A new ``ip_risk_snapshot`` property has been added to
``geoip2.records.Traits``. This is a float ranging from 0.01 to 99 that
represents the risk associated with the IP address. A higher score indicates
a higher risk. This field is only available from the Insights end point.
* The following properties on ``geoip2.records.Traits`` have been deprecated:
``is_anonymous``, ``is_anonymous_vpn``, ``is_hosting_provider``,
``is_public_proxy``, ``is_residential_proxy``, and ``is_tor_exit_node``.
Please use the ``anonymizer`` object in the ``Insights`` model instead.

5.1.0 (2025-05-05)
++++++++++++++++++
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ ignore = [
[tool.ruff.lint.per-file-ignores]
"docs/*" = ["ALL"]
"src/geoip2/{models,records}.py" = [ "ANN401", "D107", "PLR0913" ]
"tests/*" = ["ANN201", "D"]
# FBT003: We use assertIs with boolean literals to verify values are actual
# booleans (True/False), not just truthy/falsy values
"tests/*" = ["ANN201", "D", "FBT003"]

[tool.tox]
env_list = [
Expand Down
3 changes: 3 additions & 0 deletions src/geoip2/_internal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Internal utilities."""

import datetime
import json
from abc import ABCMeta
from typing import Any
Expand Down Expand Up @@ -42,6 +43,8 @@ def to_dict(self) -> dict[str, Any]: # noqa: C901, PLR0912
elif isinstance(value, dict):
if value:
result[key] = value
elif isinstance(value, datetime.date):
result[key] = value.isoformat()
elif value is not None and value is not False:
result[key] = value

Expand Down
41 changes: 41 additions & 0 deletions src/geoip2/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,47 @@ def __init__(
class Insights(City):
"""Model for the GeoIP2 Insights web service."""

anonymizer: geoip2.records.Anonymizer
"""Anonymizer object for the requested IP address. This object contains
information about VPN and proxy usage.
"""

def __init__(
self,
locales: Sequence[str] | None,
*,
anonymizer: dict[str, Any] | None = None,
city: dict[str, Any] | None = None,
continent: dict[str, Any] | None = None,
country: dict[str, Any] | None = None,
location: dict[str, Any] | None = None,
ip_address: IPAddress | None = None,
maxmind: dict[str, Any] | None = None,
postal: dict[str, Any] | None = None,
prefix_len: int | None = None,
registered_country: dict[str, Any] | None = None,
represented_country: dict[str, Any] | None = None,
subdivisions: list[dict[str, Any]] | None = None,
traits: dict[str, Any] | None = None,
**_: Any,
) -> None:
super().__init__(
locales,
city=city,
continent=continent,
country=country,
location=location,
ip_address=ip_address,
maxmind=maxmind,
postal=postal,
prefix_len=prefix_len,
registered_country=registered_country,
represented_country=represented_country,
subdivisions=subdivisions,
traits=traits,
)
self.anonymizer = geoip2.records.Anonymizer(**(anonymizer or {}))


class Enterprise(City):
"""Model for the GeoIP2 Enterprise database."""
Expand Down
124 changes: 124 additions & 0 deletions src/geoip2/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import datetime
import ipaddress
from abc import ABCMeta
from ipaddress import IPv4Address, IPv6Address
Expand Down Expand Up @@ -261,6 +262,98 @@ def __init__(self, *, queries_remaining: int | None = None, **_: Any) -> None:
self.queries_remaining = queries_remaining


class Anonymizer(Record):
"""Contains data for the anonymizer record associated with an IP address.

This class contains the anonymizer data associated with an IP address.

This record is returned by ``insights``.
"""

anonymizer_confidence: int | None
"""A score ranging from 1 to 99 that represents our percent confidence that
the network is currently part of an actively used VPN service. Currently
only values 30 and 99 are provided. This attribute is only available from
the Insights end point.
"""

network_last_seen: datetime.date | None
"""The last day that the network was sighted in our analysis of anonymized
networks. This attribute is only available from the Insights end point.
"""

provider_name: str | None
"""The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated
with the network. This attribute is only available from the Insights end
point.
"""

is_anonymous: bool
"""This is true if the IP address belongs to any sort of anonymous network.
This attribute is only available from the Insights end point.
"""

is_anonymous_vpn: bool
"""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 the Insights end point.
"""

is_hosting_provider: bool
"""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 the Insights end point.
"""

is_public_proxy: bool
"""This is true if the IP address belongs to a public proxy. This attribute
is only available from the Insights end point.
"""

is_residential_proxy: bool
"""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 the
Insights end point.
"""

is_tor_exit_node: bool
"""This is true if the IP address is a Tor exit node. This attribute is only
available from the Insights end point.
"""

def __init__(
self,
*,
anonymizer_confidence: int | None = None,
is_anonymous: bool = False,
is_anonymous_vpn: bool = False,
is_hosting_provider: bool = False,
is_public_proxy: bool = False,
is_residential_proxy: bool = False,
is_tor_exit_node: bool = False,
network_last_seen: str | None = None,
provider_name: str | None = None,
**_: Any,
) -> None:
self.anonymizer_confidence = anonymizer_confidence
self.is_anonymous = is_anonymous
self.is_anonymous_vpn = is_anonymous_vpn
self.is_hosting_provider = is_hosting_provider
self.is_public_proxy = is_public_proxy
self.is_residential_proxy = is_residential_proxy
self.is_tor_exit_node = is_tor_exit_node
self.network_last_seen = (
datetime.date.fromisoformat(network_last_seen)
if network_last_seen
else None
)
self.provider_name = provider_name


class Postal(Record):
"""Contains data for the postal record associated with an IP address.

Expand Down Expand Up @@ -425,10 +518,24 @@ class Traits(Record):
from the City Plus and Insights web service end points and the
Enterprise database.
"""
ip_risk_snapshot: float | None
"""The risk associated with the IP address. The value ranges from 0.01 to
99. A higher score indicates a higher risk.

Please note that the IP risk score provided in GeoIP products and services
is more static than the IP risk score provided in minFraud and is not
responsive to traffic on your network. If you need realtime IP risk scoring
based on behavioral signals on your own network, please use minFraud.

This attribute is only available from the Insights end point.
"""
_ip_address: IPAddress | None
is_anonymous: bool
"""This is true if the IP address belongs to any sort of anonymous network.
This attribute is only available from Insights.

.. deprecated:: 5.2.0
Use the ``anonymizer`` object in the ``Insights`` model instead.
"""
is_anonymous_proxy: bool
"""This is true if the IP is an anonymous proxy.
Expand All @@ -447,6 +554,9 @@ class Traits(Record):
``is_hosting_provider`` attribute.

This attribute is only available from Insights.

.. deprecated:: 5.2.0
Use the ``anonymizer`` object in the ``Insights`` model instead.
"""
is_anycast: bool
"""This returns true if the IP address belongs to an
Expand All @@ -458,6 +568,9 @@ class Traits(Record):
"""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 Insights.

.. deprecated:: 5.2.0
Use the ``anonymizer`` object in the ``Insights`` model instead.
"""
is_legitimate_proxy: bool
"""This attribute is true if MaxMind believes this IP address to be a
Expand All @@ -467,11 +580,17 @@ class Traits(Record):
is_public_proxy: bool
"""This is true if the IP address belongs to a public proxy. This attribute
is only available from Insights.

.. deprecated:: 5.2.0
Use the ``anonymizer`` object in the ``Insights`` model instead.
"""
is_residential_proxy: bool
"""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
Insights.

.. deprecated:: 5.2.0
Use the ``anonymizer`` object in the ``Insights`` model instead.
"""
is_satellite_provider: bool
"""This is true if the IP address is from a satellite provider that
Expand All @@ -486,6 +605,9 @@ class Traits(Record):
is_tor_exit_node: bool
"""This is true if the IP address is a Tor exit node. This attribute is
only available from Insights.

.. deprecated:: 5.2.0
Use the ``anonymizer`` object in the ``Insights`` model instead.
"""
isp: str | None
"""The name of the ISP associated with the IP address. This attribute is
Expand Down Expand Up @@ -560,6 +682,7 @@ def __init__(
autonomous_system_organization: str | None = None,
connection_type: str | None = None,
domain: str | None = None,
ip_risk_snapshot: float | None = None,
is_anonymous: bool = False,
is_anonymous_proxy: bool = False,
is_anonymous_vpn: bool = False,
Expand All @@ -586,6 +709,7 @@ def __init__(
self.autonomous_system_organization = autonomous_system_organization
self.connection_type = connection_type
self.domain = domain
self.ip_risk_snapshot = ip_risk_snapshot
self.is_anonymous = is_anonymous
self.is_anonymous_proxy = is_anonymous_proxy
self.is_anonymous_vpn = is_anonymous_vpn
Expand Down
Loading
Loading