Skip to content

Commit

Permalink
Merge pull request #158 from maxmind/marsel/anycast-attr
Browse files Browse the repository at this point in the history
Add is_anycast trait
  • Loading branch information
horgh authored Dec 4, 2023
2 parents 6eba688 + 7233045 commit 43a7ac1
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 6 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ History

* IMPORTANT: Python 3.8 or greater is required. If you are using an older
version, please use an earlier release.
* The ``is_anycast`` attribute was added to ``geoip2.record.Traits``.
This returns ``True`` if the IP address belongs to an
`anycast network <https://en.wikipedia.org/wiki/Anycast>`_.
This is available for the GeoIP2 Country, City Plus, and Insights web services
and the GeoIP2 Country, City, and Enterprise databases.

4.7.0 (2023-05-09)
++++++++++++++++++
Expand Down
14 changes: 13 additions & 1 deletion geoip2/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,15 @@ class Traits(Record):
:type: bool
.. attribute:: is_anycast
This returns true if the IP address belongs to an
`anycast network <https://en.wikipedia.org/wiki/Anycast>`_.
This is available for the GeoIP2 Country, City Plus, and Insights
web services and the GeoIP2 Country, City, and Enterprise databases.
:type: bool
.. attribute:: is_hosting_provider
This is true if the IP address belongs to a hosting or VPN provider
Expand Down Expand Up @@ -815,17 +824,18 @@ class Traits(Record):
autonomous_system_organization: Optional[str]
connection_type: Optional[str]
domain: Optional[str]
ip_address: Optional[str]
is_anonymous: bool
is_anonymous_proxy: bool
is_anonymous_vpn: bool
is_anycast: 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]
mobile_country_code: Optional[str]
mobile_network_code: Optional[str]
organization: Optional[str]
Expand Down Expand Up @@ -860,6 +870,7 @@ def __init__(
user_type: Optional[str] = None,
mobile_country_code: Optional[str] = None,
mobile_network_code: Optional[str] = None,
is_anycast: bool = False,
**_,
) -> None:
self.autonomous_system_number = autonomous_system_number
Expand All @@ -869,6 +880,7 @@ def __init__(
self.is_anonymous = is_anonymous
self.is_anonymous_proxy = is_anonymous_proxy
self.is_anonymous_vpn = is_anonymous_vpn
self.is_anycast = is_anycast
self.is_hosting_provider = is_hosting_provider
self.is_legitimate_proxy = is_legitimate_proxy
self.is_public_proxy = is_public_proxy
Expand Down
2 changes: 1 addition & 1 deletion tests/data
Submodule data updated 63 files
+11 −0 .github/dependabot.yml
+52 −0 .github/workflows/codeql-analysis.yml
+38 −0 .github/workflows/go.yml
+21 −0 .github/workflows/golangci-lint.yml
+1 −0 .gitignore
+708 −0 .golangci.toml
+68 −0 cmd/write-test-data/main.go
+13 −0 go.mod
+16 −0 go.sum
+6 −0 perltidyrc
+178 −0 pkg/writer/decoder.go
+182 −0 pkg/writer/geoip2.go
+39 −0 pkg/writer/ip.go
+245 −0 pkg/writer/maxmind.go
+73 −0 pkg/writer/nestedstructures.go
+58 −0 pkg/writer/writer.go
+322 −1 source-data/GeoIP2-City-Test.json
+15 −10 source-data/GeoIP2-Connection-Type-Test.json
+110 −11 source-data/GeoIP2-Country-Test.json
+5 −0 source-data/GeoIP2-Domain-Test.json
+347 −1 source-data/GeoIP2-Enterprise-Test.json
+296 −0 source-data/GeoIP2-Precision-Enterprise-Sandbox-Test.json
+412 −2 source-data/GeoIP2-Precision-Enterprise-Test.json
+15 −0 source-data/GeoIP2-Static-IP-Score-Test.json
+18 −0 source-data/GeoIP2-User-Count-Test.json
+168 −0 source-data/GeoLite2-City-Test.json
+92 −0 source-data/GeoLite2-Country-Test.json
+0 −15 source-data/README
+ test-data/GeoIP2-Anonymous-IP-Test.mmdb
+ test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb
+ test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb
+ test-data/GeoIP2-City-Test.mmdb
+ test-data/GeoIP2-Connection-Type-Test.mmdb
+ test-data/GeoIP2-Country-Test.mmdb
+ test-data/GeoIP2-DensityIncome-Test.mmdb
+ test-data/GeoIP2-Domain-Test.mmdb
+ test-data/GeoIP2-Enterprise-Test.mmdb
+ test-data/GeoIP2-ISP-Test.mmdb
+ test-data/GeoIP2-Precision-Enterprise-Test.mmdb
+ test-data/GeoIP2-Static-IP-Score-Test.mmdb
+ test-data/GeoIP2-User-Count-Test.mmdb
+ test-data/GeoLite2-ASN-Test.mmdb
+ test-data/GeoLite2-City-Test.mmdb
+ test-data/GeoLite2-Country-Test.mmdb
+ test-data/MaxMind-DB-no-ipv4-search-tree.mmdb
+ test-data/MaxMind-DB-string-value-entries.mmdb
+ test-data/MaxMind-DB-test-broken-pointers-24.mmdb
+ test-data/MaxMind-DB-test-broken-search-tree-24.mmdb
+ test-data/MaxMind-DB-test-decoder.mmdb
+ test-data/MaxMind-DB-test-ipv4-24.mmdb
+ test-data/MaxMind-DB-test-ipv4-28.mmdb
+ test-data/MaxMind-DB-test-ipv4-32.mmdb
+ test-data/MaxMind-DB-test-ipv6-24.mmdb
+ test-data/MaxMind-DB-test-ipv6-28.mmdb
+ test-data/MaxMind-DB-test-ipv6-32.mmdb
+ test-data/MaxMind-DB-test-metadata-pointers.mmdb
+ test-data/MaxMind-DB-test-mixed-24.mmdb
+ test-data/MaxMind-DB-test-mixed-28.mmdb
+ test-data/MaxMind-DB-test-mixed-32.mmdb
+ test-data/MaxMind-DB-test-nested.mmdb
+ test-data/MaxMind-DB-test-pointer-decoder.mmdb
+28 −12 test-data/README.md
+0 −695 test-data/write-test-data.pl
18 changes: 15 additions & 3 deletions tests/database_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ def test_city(self) -> None:
record.location.accuracy_radius, 100, "The accuracy_radius is populated"
)
self.assertEqual(record.registered_country.is_in_european_union, False)
self.assertFalse(record.traits.is_anycast)

record = reader.city("214.1.1.0")
self.assertTrue(record.traits.is_anycast)

reader.close()

Expand All @@ -150,13 +154,13 @@ def test_connection_type(self) -> None:
record, eval(repr(record)), "ConnectionType repr can be eval'd"
)

self.assertEqual(record.connection_type, "Cable/DSL")
self.assertEqual(record.connection_type, "Cellular")
self.assertEqual(record.ip_address, ip_address)
self.assertEqual(record.network, ipaddress.ip_network("1.0.1.0/24"))

self.assertRegex(
str(record),
r"ConnectionType\(\{.*Cable/DSL.*\}\)",
r"ConnectionType\(\{.*Cellular.*\}\)",
"ConnectionType str representation is reasonable",
)

Expand All @@ -171,6 +175,11 @@ def test_country(self) -> None:
self.assertEqual(record.traits.network, ipaddress.ip_network("81.2.69.160/27"))
self.assertEqual(record.country.is_in_european_union, False)
self.assertEqual(record.registered_country.is_in_european_union, False)
self.assertFalse(record.traits.is_anycast)

record = reader.country("214.1.1.0")
self.assertTrue(record.traits.is_anycast)

reader.close()

def test_domain(self) -> None:
Expand Down Expand Up @@ -211,12 +220,15 @@ def test_enterprise(self) -> None:
self.assertEqual(
record.traits.network, ipaddress.ip_network("74.209.16.0/20")
)
self.assertFalse(record.traits.is_anycast)

record = reader.enterprise("149.101.100.0")

self.assertEqual(record.traits.mobile_country_code, "310")
self.assertEqual(record.traits.mobile_network_code, "004")

record = reader.enterprise("214.1.1.0")
self.assertTrue(record.traits.is_anycast)

def test_isp(self) -> None:
with geoip2.database.Reader(
"tests/data/test-data/GeoIP2-ISP-Test.mmdb"
Expand Down
7 changes: 7 additions & 0 deletions tests/models_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def test_insights_full(self) -> None:
"is_anonymous": True,
"is_anonymous_proxy": True,
"is_anonymous_vpn": True,
"is_anycast": True,
"is_hosting_provider": True,
"is_public_proxy": True,
"is_residential_proxy": True,
Expand Down Expand Up @@ -194,6 +195,7 @@ def test_insights_full(self) -> None:
self.assertIs(model.traits.is_anonymous, True)
self.assertIs(model.traits.is_anonymous_proxy, True)
self.assertIs(model.traits.is_anonymous_vpn, True)
self.assertIs(model.traits.is_anycast, True)
self.assertIs(model.traits.is_hosting_provider, True)
self.assertIs(model.traits.is_public_proxy, True)
self.assertIs(model.traits.is_residential_proxy, True)
Expand Down Expand Up @@ -327,6 +329,11 @@ def test_city_full(self) -> None:
False,
"traits is_anonymous_proxy returns False by default",
)
self.assertEqual(
model.traits.is_anycast,
False,
"traits is_anycast returns False by default",
)
self.assertEqual(
model.traits.is_satellite_provider,
True,
Expand Down
9 changes: 8 additions & 1 deletion tests/webservice_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ class TestBaseClient(unittest.TestCase):
"iso_code": "DE",
"names": {"en": "Germany"},
},
"traits": {"ip_address": "1.2.3.4", "network": "1.2.3.0/24"},
"traits": {
"ip_address": "1.2.3.4",
"is_anycast": True,
"network": "1.2.3.0/24",
},
}

# this is not a comprehensive representation of the
Expand Down Expand Up @@ -104,6 +108,7 @@ def test_country_ok(self):
self.assertEqual(
country.traits.network, ipaddress.ip_network("1.2.3.0/24"), "network"
)
self.assertTrue(country.traits.is_anycast)
self.assertEqual(country.raw, self.country, "raw response is correct")

@httprettified
Expand Down Expand Up @@ -328,6 +333,7 @@ def test_city_ok(self):
self.assertEqual(
city.traits.network, ipaddress.ip_network("1.2.3.0/24"), "network"
)
self.assertTrue(city.traits.is_anycast)

@httprettified
def test_insights_ok(self):
Expand All @@ -345,6 +351,7 @@ def test_insights_ok(self):
self.assertEqual(
insights.traits.network, ipaddress.ip_network("1.2.3.0/24"), "network"
)
self.assertTrue(insights.traits.is_anycast)
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")

Expand Down

0 comments on commit 43a7ac1

Please sign in to comment.