Profit Engine — AI-Powered Content Network
In today’s data-driven world, understanding where your users are located can unlock powerful insights for personalization, fraud detection, and analytics. An IP geolocation service allows you to map an IP address to a physical location, including country, city, latitude, and longitude. However, integrating multiple geolocation providers directly into your application can become messy and hard to maintain. That’s where an API wrapper comes in. By creating a clean, reusable wrapper, you abstract away the complexities of different APIs, handle errors gracefully, and make your code more modular. In this guide, we’ll walk through the entire process—from planning to deployment—of building and publishing an API wrapper for an IP geolocation service.
An API wrapper acts as a middleware layer between your application and the external service. Instead of making raw HTTP requests and parsing responses directly, you call simple functions in your code. Here are the key benefits:
Before writing any code, you need to select a reliable IP geolocation service. Popular options include:
For this tutorial, we’ll use ipapi as our example, but the wrapper pattern works for any provider.
A good wrapper should have a clear, intuitive interface. Our goal is to allow developers to call something like:
geolocation = IPGeolocationService(api_key='your_key') location = geolocation.lookup('8.8.8.8') print(location.city) # 'Mountain View' We’ll design the wrapper with the following methods:
<lookup(ip_address) – Returns a standardized location object.lookup_batch(ip_addresses) – Handles multiple IPs in one call (if the provider supports it).get_usage() – Returns current API usage statistics.Let’s build the wrapper using Python’s requests library. First, install the dependency:
pip install requests Now create a file called ip_geo_wrapper.py:
import requests from typing import Optional, Dict, List class IPGeolocationService: """A wrapper for the ipapi IP geolocation service.""" BASE_URL = "https://api.ipapi.com" def __init__(self, api_key: str): self.api_key = api_key self.session = requests.Session() self.session.params = {'access_key': self.api_key} def lookup(self, ip_address: str) -> Dict: """Look up geolocation data for a single IP address.""" response = self.session.get(f"{self.BASE_URL}/{ip_address}") response.raise_for_status() data = response.json() # Handle API-level errors if 'error' in data: raise ValueError(f"API error: {data['error']['info']}") return self._standardize(data) def lookup_batch(self, ip_addresses: List[str]) -> List[Dict]: """Look up multiple IPs (if provider supports batch).""" # ipapi does not support batch natively, so we loop results = [] for ip in ip_addresses: results.append(self.lookup(ip)) return results def get_usage(self) -> Dict: """Get current API usage statistics.""" response = self.session.get(f"{self.BASE_URL}/usage") response.raise_for_status() return response.json() def _standardize(self, raw_data: Dict) -> Dict: """Convert raw API response to a consistent format.""" return { 'ip': raw_data.get('ip'), 'city': raw_data.get('city'), 'region': raw_data.get('region_name'), 'country': raw_data.get('country_name'), 'latitude': raw_data.get('latitude'), 'longitude': raw_data.get('longitude'), 'timezone': raw_data.get('timezone'), 'isp': raw_data.get('isp'), } requests.Session to keep the connection alive and reduce overhead._standardize method ensures that no matter which provider you use, the output format is consistent.To make your wrapper production-ready, consider adding these features:
functools.lru_cache or a Redis backend.time.sleep to avoid hitting API limits.httpx or aiohttp for high-throughput applications.Testing is crucial for any API wrapper. Write unit tests that mock the HTTP requests:
import pytest from unittest.mock import patch from ip_geo_wrapper import IPGeolocationService @patch('ip_geo_wrapper.requests.Session.get') def test_lookup_success(mock_get): mock_response = mock_get.return_value mock_response.json.return_value = { 'ip': '8.8.8.8', 'city': 'Mountain View', 'region_name': 'California', 'country_name': 'United States', 'latitude': 37.4056, 'longitude': -122.0775, 'timezone': 'America/Los_Angeles', 'isp': 'Google LLC' } mock_response.raise_for_status.return_value = None service = IPGeolocationService(api_key='test') result = service.lookup('8.8.8.8') assert result['city'] == 'Mountain View' assert result['country'] == 'United States' ip-geo-wrapper/ ├── ip_geo_wrapper/ │ ├── __init__.py │ └── wrapper.py ├── tests/ │ └── test_wrapper.py ├── setup.py ├── README.md └── LICENSE from setuptools import setup, find_packages setup( name='ip-geo-wrapper', version='0.1.0', packages=find_packages(), install_requires=['requests'],