import asyncio import re from IP2Location import IP2Location from IP2Location.database import IP2LocationRecord as _IP2LocationRecord from location import config from location.core.exceptions import ImproperlyConfigured from location.types import IPvAnyAddress DEMO_REGEX = ".*demo BIN database.*" IPV6_MISSING = ".*IPV6 ADDRESS MISSING IN IPV4 BIN*." try: database = IP2Location(config.BINARY_PATH) except Exception as exc: if config.ENVIRONMENT == "test": database = IP2Location() else: raise ImproperlyConfigured from exc class IP2LocationRecord(_IP2LocationRecord): # type: ignore[misc] resolved = False async def lookup(ip: IPvAnyAddress) -> IP2LocationRecord: """ Returns an IP2Location.IP2LocationRecord resolved for the provided IP address. """ record = database.get_all(str(ip)) if is_demo(record) or is_ip6_missing(record): return empty_record(record, ip=ip) return resolved_or_empty_record(record, ip=ip) async def lookup_batch( ips: list[IPvAnyAddress], only_resolved: bool | None = None ) -> dict[IPvAnyAddress, IP2LocationRecord]: """ Returns a mapping IP2Location.IP2LocationRecord resolved for the provided IP addresses. """ records = await asyncio.gather(*[lookup(ip) for ip in set(ips)]) return { record.ip: record for record in records if (record.resolved is True if only_resolved else True) } def is_demo(record: IP2LocationRecord) -> bool: """ Determines whether the provided result from IP2Location represents an error indicating the database is in demo mode. """ return bool(re.search(DEMO_REGEX, record.region)) def is_ip6_missing(record: IP2LocationRecord) -> bool: """ Determines whether the provided result from IP2Location represents an error indicating the ip is IPv6 format and the database is in demo mode. """ return bool(re.search(IPV6_MISSING, record.region)) def empty_record(record: IP2LocationRecord, *, ip: IPvAnyAddress) -> IP2LocationRecord: record.ip = str(ip) record.resolved = False record.country_long = "-" record.country_short = "-" record.region = "-" record.city = "-" record.latitude = 0.0 record.longitude = 0.0 return record def resolved_or_empty_record( record: IP2LocationRecord, *, ip: IPvAnyAddress ) -> IP2LocationRecord: if record.country_short and record.country_short != "-": record.resolved = True return record return empty_record(record, ip=ip)