Coverage for src/couchers/helpers/geoip.py: 24%

41 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-12-20 11:53 +0000

1import logging 

2from ipaddress import IPv4Network, IPv6Network 

3 

4import geoip2.database 

5from geoip2.errors import AddressNotFoundError 

6 

7from couchers.config import config 

8 

9logger = logging.getLogger(__name__) 

10 

11 

12def geoip_approximate_location(ip_address: str) -> str | None: 

13 if config["GEOLITE2_CITY_MMDB_FILE_LOCATION"] == "": 

14 return None 

15 if ip_address is None: 

16 return None 

17 try: 

18 with geoip2.database.Reader(config["GEOLITE2_CITY_MMDB_FILE_LOCATION"]) as reader: 

19 response = reader.city(ip_address) 

20 city = response.city.name 

21 country = response.country.name 

22 return f"{city}, {country}" if city else f"{country}" 

23 except AddressNotFoundError: 

24 return None 

25 except Exception as e: 

26 logger.error(f"GeoIP failed for {ip_address=}") 

27 return None 

28 

29 

30def geoip_asn(ip_address: str | None) -> tuple[int, str, IPv4Network | IPv6Network] | None: 

31 if config["GEOLITE2_ASN_MMDB_FILE_LOCATION"] == "": 

32 return None 

33 if ip_address is None: 

34 return None 

35 try: 

36 with geoip2.database.Reader(config["GEOLITE2_ASN_MMDB_FILE_LOCATION"]) as reader: 

37 response = reader.asn(ip_address) 

38 asn_number = response.autonomous_system_number 

39 asn_org = response.autonomous_system_organization 

40 network = response.network 

41 if asn_number is not None and asn_org is not None and network is not None: 

42 return asn_number, asn_org, network 

43 raise ValueError(f"Invalid response: {response}") 

44 except AddressNotFoundError: 

45 return None 

46 except Exception as e: 

47 logger.error(f"GeoIP failed for {ip_address=}") 

48 return None