quantaroute-geocoding 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of quantaroute-geocoding might be problematic. Click here for more details.
- quantaroute_geocoding/__init__.py +32 -0
- quantaroute_geocoding/cli.py +281 -0
- quantaroute_geocoding/client.py +312 -0
- quantaroute_geocoding/csv_processor.py +415 -0
- quantaroute_geocoding/exceptions.py +42 -0
- quantaroute_geocoding/offline.py +280 -0
- quantaroute_geocoding-1.0.0.dist-info/METADATA +349 -0
- quantaroute_geocoding-1.0.0.dist-info/RECORD +12 -0
- quantaroute_geocoding-1.0.0.dist-info/WHEEL +5 -0
- quantaroute_geocoding-1.0.0.dist-info/entry_points.txt +2 -0
- quantaroute_geocoding-1.0.0.dist-info/licenses/LICENSE +21 -0
- quantaroute_geocoding-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QuantaRoute Geocoding Python SDK
|
|
3
|
+
|
|
4
|
+
A Python library for geocoding addresses to DigiPin codes with both online API
|
|
5
|
+
and offline processing capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__author__ = "QuantaRoute"
|
|
10
|
+
__email__ = "support@quantaroute.com"
|
|
11
|
+
|
|
12
|
+
from .client import QuantaRouteClient
|
|
13
|
+
from .offline import OfflineProcessor
|
|
14
|
+
from .csv_processor import CSVProcessor
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
QuantaRouteError,
|
|
17
|
+
APIError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
AuthenticationError,
|
|
20
|
+
ValidationError
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"QuantaRouteClient",
|
|
25
|
+
"OfflineProcessor",
|
|
26
|
+
"CSVProcessor",
|
|
27
|
+
"QuantaRouteError",
|
|
28
|
+
"APIError",
|
|
29
|
+
"RateLimitError",
|
|
30
|
+
"AuthenticationError",
|
|
31
|
+
"ValidationError"
|
|
32
|
+
]
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for QuantaRoute Geocoding
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from .csv_processor import CSVProcessor
|
|
10
|
+
from .client import QuantaRouteClient
|
|
11
|
+
from .offline import OfflineProcessor
|
|
12
|
+
from .exceptions import QuantaRouteError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
@click.version_option(version="1.0.0")
|
|
17
|
+
def main():
|
|
18
|
+
"""QuantaRoute Geocoding CLI - Process addresses and coordinates with DigiPin"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@main.command()
|
|
23
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
24
|
+
@click.argument('output_file', type=click.Path())
|
|
25
|
+
@click.option('--api-key', envvar='QUANTAROUTE_API_KEY', help='QuantaRoute API key')
|
|
26
|
+
@click.option('--address-column', default='address', help='Name of address column')
|
|
27
|
+
@click.option('--city-column', help='Name of city column')
|
|
28
|
+
@click.option('--state-column', help='Name of state column')
|
|
29
|
+
@click.option('--pincode-column', help='Name of pincode column')
|
|
30
|
+
@click.option('--country-column', help='Name of country column')
|
|
31
|
+
@click.option('--batch-size', default=50, help='Batch size for API requests')
|
|
32
|
+
@click.option('--delay', default=1.0, help='Delay between batches (seconds)')
|
|
33
|
+
@click.option('--offline', is_flag=True, help='Use offline processing (limited functionality)')
|
|
34
|
+
def geocode(
|
|
35
|
+
input_file: str,
|
|
36
|
+
output_file: str,
|
|
37
|
+
api_key: Optional[str],
|
|
38
|
+
address_column: str,
|
|
39
|
+
city_column: Optional[str],
|
|
40
|
+
state_column: Optional[str],
|
|
41
|
+
pincode_column: Optional[str],
|
|
42
|
+
country_column: Optional[str],
|
|
43
|
+
batch_size: int,
|
|
44
|
+
delay: float,
|
|
45
|
+
offline: bool
|
|
46
|
+
):
|
|
47
|
+
"""Geocode addresses from CSV file to DigiPin codes"""
|
|
48
|
+
|
|
49
|
+
if not offline and not api_key:
|
|
50
|
+
click.echo("Error: API key is required for online geocoding. Set QUANTAROUTE_API_KEY environment variable or use --api-key option.")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
processor = CSVProcessor(
|
|
55
|
+
api_key=api_key,
|
|
56
|
+
use_offline=offline,
|
|
57
|
+
batch_size=batch_size,
|
|
58
|
+
delay_between_batches=delay
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def progress_callback(processed, total, success, errors):
|
|
62
|
+
click.echo(f"Progress: {processed}/{total} ({processed/total*100:.1f}%) - Success: {success}, Errors: {errors}")
|
|
63
|
+
|
|
64
|
+
click.echo(f"Processing {input_file}...")
|
|
65
|
+
|
|
66
|
+
result = processor.process_geocoding_csv(
|
|
67
|
+
input_file=input_file,
|
|
68
|
+
output_file=output_file,
|
|
69
|
+
address_column=address_column,
|
|
70
|
+
city_column=city_column,
|
|
71
|
+
state_column=state_column,
|
|
72
|
+
pincode_column=pincode_column,
|
|
73
|
+
country_column=country_column,
|
|
74
|
+
progress_callback=progress_callback
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
click.echo(f"\nProcessing complete!")
|
|
78
|
+
click.echo(f"Total rows: {result['total_rows']}")
|
|
79
|
+
click.echo(f"Successful: {result['success_count']}")
|
|
80
|
+
click.echo(f"Errors: {result['error_count']}")
|
|
81
|
+
click.echo(f"Success rate: {result['success_rate']:.1%}")
|
|
82
|
+
click.echo(f"Output saved to: {result['output_file']}")
|
|
83
|
+
|
|
84
|
+
except QuantaRouteError as e:
|
|
85
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
click.echo(f"Unexpected error: {str(e)}", err=True)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@main.command()
|
|
91
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
92
|
+
@click.argument('output_file', type=click.Path())
|
|
93
|
+
@click.option('--api-key', envvar='QUANTAROUTE_API_KEY', help='QuantaRoute API key')
|
|
94
|
+
@click.option('--latitude-column', default='latitude', help='Name of latitude column')
|
|
95
|
+
@click.option('--longitude-column', default='longitude', help='Name of longitude column')
|
|
96
|
+
@click.option('--offline', is_flag=True, help='Use offline processing')
|
|
97
|
+
def coords_to_digipin(
|
|
98
|
+
input_file: str,
|
|
99
|
+
output_file: str,
|
|
100
|
+
api_key: Optional[str],
|
|
101
|
+
latitude_column: str,
|
|
102
|
+
longitude_column: str,
|
|
103
|
+
offline: bool
|
|
104
|
+
):
|
|
105
|
+
"""Convert coordinates to DigiPin codes from CSV file"""
|
|
106
|
+
|
|
107
|
+
if not offline and not api_key:
|
|
108
|
+
click.echo("Error: API key is required for online processing. Set QUANTAROUTE_API_KEY environment variable or use --api-key option.")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
processor = CSVProcessor(
|
|
113
|
+
api_key=api_key,
|
|
114
|
+
use_offline=offline
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def progress_callback(processed, total, success, errors):
|
|
118
|
+
click.echo(f"Progress: {processed}/{total} ({processed/total*100:.1f}%) - Success: {success}, Errors: {errors}")
|
|
119
|
+
|
|
120
|
+
click.echo(f"Processing {input_file}...")
|
|
121
|
+
|
|
122
|
+
result = processor.process_coordinates_to_digipin_csv(
|
|
123
|
+
input_file=input_file,
|
|
124
|
+
output_file=output_file,
|
|
125
|
+
latitude_column=latitude_column,
|
|
126
|
+
longitude_column=longitude_column,
|
|
127
|
+
progress_callback=progress_callback
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
click.echo(f"\nProcessing complete!")
|
|
131
|
+
click.echo(f"Total rows: {result['total_rows']}")
|
|
132
|
+
click.echo(f"Successful: {result['success_count']}")
|
|
133
|
+
click.echo(f"Errors: {result['error_count']}")
|
|
134
|
+
click.echo(f"Success rate: {result['success_rate']:.1%}")
|
|
135
|
+
click.echo(f"Output saved to: {result['output_file']}")
|
|
136
|
+
|
|
137
|
+
except QuantaRouteError as e:
|
|
138
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
click.echo(f"Unexpected error: {str(e)}", err=True)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@main.command()
|
|
144
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
145
|
+
@click.argument('output_file', type=click.Path())
|
|
146
|
+
@click.option('--api-key', envvar='QUANTAROUTE_API_KEY', help='QuantaRoute API key')
|
|
147
|
+
@click.option('--digipin-column', default='digipin', help='Name of DigiPin column')
|
|
148
|
+
@click.option('--offline', is_flag=True, help='Use offline processing')
|
|
149
|
+
def digipin_to_coords(
|
|
150
|
+
input_file: str,
|
|
151
|
+
output_file: str,
|
|
152
|
+
api_key: Optional[str],
|
|
153
|
+
digipin_column: str,
|
|
154
|
+
offline: bool
|
|
155
|
+
):
|
|
156
|
+
"""Convert DigiPin codes to coordinates from CSV file"""
|
|
157
|
+
|
|
158
|
+
if not offline and not api_key:
|
|
159
|
+
click.echo("Error: API key is required for online processing. Set QUANTAROUTE_API_KEY environment variable or use --api-key option.")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
processor = CSVProcessor(
|
|
164
|
+
api_key=api_key,
|
|
165
|
+
use_offline=offline
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def progress_callback(processed, total, success, errors):
|
|
169
|
+
click.echo(f"Progress: {processed}/{total} ({processed/total*100:.1f}%) - Success: {success}, Errors: {errors}")
|
|
170
|
+
|
|
171
|
+
click.echo(f"Processing {input_file}...")
|
|
172
|
+
|
|
173
|
+
result = processor.process_digipin_to_coordinates_csv(
|
|
174
|
+
input_file=input_file,
|
|
175
|
+
output_file=output_file,
|
|
176
|
+
digipin_column=digipin_column,
|
|
177
|
+
progress_callback=progress_callback
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
click.echo(f"\nProcessing complete!")
|
|
181
|
+
click.echo(f"Total rows: {result['total_rows']}")
|
|
182
|
+
click.echo(f"Successful: {result['success_count']}")
|
|
183
|
+
click.echo(f"Errors: {result['error_count']}")
|
|
184
|
+
click.echo(f"Success rate: {result['success_rate']:.1%}")
|
|
185
|
+
click.echo(f"Output saved to: {result['output_file']}")
|
|
186
|
+
|
|
187
|
+
except QuantaRouteError as e:
|
|
188
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
click.echo(f"Unexpected error: {str(e)}", err=True)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@main.command()
|
|
194
|
+
@click.option('--api-key', envvar='QUANTAROUTE_API_KEY', required=True, help='QuantaRoute API key')
|
|
195
|
+
def usage():
|
|
196
|
+
"""Check API usage statistics"""
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
client = QuantaRouteClient(api_key)
|
|
200
|
+
usage_data = client.get_usage()
|
|
201
|
+
|
|
202
|
+
click.echo("API Usage Statistics:")
|
|
203
|
+
click.echo("-" * 30)
|
|
204
|
+
|
|
205
|
+
usage_info = usage_data.get('usage', {})
|
|
206
|
+
click.echo(f"Current Usage: {usage_info.get('currentUsage', 0)}")
|
|
207
|
+
click.echo(f"Monthly Limit: {usage_info.get('monthlyLimit', 'Unknown')}")
|
|
208
|
+
click.echo(f"Tier: {usage_info.get('tier', 'Unknown')}")
|
|
209
|
+
click.echo(f"Reset Date: {usage_info.get('resetDate', 'Unknown')}")
|
|
210
|
+
|
|
211
|
+
rate_limit = usage_data.get('rateLimit', {})
|
|
212
|
+
click.echo(f"\nRate Limit:")
|
|
213
|
+
click.echo(f"Requests per minute: {rate_limit.get('limit', 'Unknown')}")
|
|
214
|
+
click.echo(f"Remaining: {rate_limit.get('remaining', 'Unknown')}")
|
|
215
|
+
|
|
216
|
+
except QuantaRouteError as e:
|
|
217
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
click.echo(f"Unexpected error: {str(e)}", err=True)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@main.command()
|
|
223
|
+
@click.argument('latitude', type=float)
|
|
224
|
+
@click.argument('longitude', type=float)
|
|
225
|
+
@click.option('--api-key', envvar='QUANTAROUTE_API_KEY', help='QuantaRoute API key')
|
|
226
|
+
@click.option('--offline', is_flag=True, help='Use offline processing')
|
|
227
|
+
def single_coord_to_digipin(latitude: float, longitude: float, api_key: Optional[str], offline: bool):
|
|
228
|
+
"""Convert single coordinate pair to DigiPin"""
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
if offline:
|
|
232
|
+
processor = OfflineProcessor()
|
|
233
|
+
result = processor.coordinates_to_digipin(latitude, longitude)
|
|
234
|
+
else:
|
|
235
|
+
if not api_key:
|
|
236
|
+
click.echo("Error: API key is required for online processing.")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
client = QuantaRouteClient(api_key)
|
|
240
|
+
result = client.coordinates_to_digipin(latitude, longitude)
|
|
241
|
+
|
|
242
|
+
click.echo(f"Coordinates: {latitude}, {longitude}")
|
|
243
|
+
click.echo(f"DigiPin: {result['digipin']}")
|
|
244
|
+
|
|
245
|
+
except QuantaRouteError as e:
|
|
246
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
click.echo(f"Unexpected error: {str(e)}", err=True)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@main.command()
|
|
252
|
+
@click.argument('digipin_code')
|
|
253
|
+
@click.option('--api-key', envvar='QUANTAROUTE_API_KEY', help='QuantaRoute API key')
|
|
254
|
+
@click.option('--offline', is_flag=True, help='Use offline processing')
|
|
255
|
+
def single_digipin_to_coords(digipin_code: str, api_key: Optional[str], offline: bool):
|
|
256
|
+
"""Convert single DigiPin to coordinates"""
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
if offline:
|
|
260
|
+
processor = OfflineProcessor()
|
|
261
|
+
result = processor.digipin_to_coordinates(digipin_code)
|
|
262
|
+
else:
|
|
263
|
+
if not api_key:
|
|
264
|
+
click.echo("Error: API key is required for online processing.")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
client = QuantaRouteClient(api_key)
|
|
268
|
+
result = client.reverse_geocode(digipin_code)
|
|
269
|
+
|
|
270
|
+
coords = result['coordinates']
|
|
271
|
+
click.echo(f"DigiPin: {digipin_code}")
|
|
272
|
+
click.echo(f"Coordinates: {coords['latitude']}, {coords['longitude']}")
|
|
273
|
+
|
|
274
|
+
except QuantaRouteError as e:
|
|
275
|
+
click.echo(f"Error: {str(e)}", err=True)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
click.echo(f"Unexpected error: {str(e)}", err=True)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if __name__ == '__main__':
|
|
281
|
+
main()
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QuantaRoute Geocoding API Client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
import time
|
|
7
|
+
from typing import Dict, List, Optional, Union
|
|
8
|
+
from .exceptions import APIError, RateLimitError, AuthenticationError, ValidationError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QuantaRouteClient:
|
|
12
|
+
"""
|
|
13
|
+
Client for QuantaRoute Geocoding API
|
|
14
|
+
|
|
15
|
+
Provides methods to interact with the QuantaRoute Geocoding API for
|
|
16
|
+
address geocoding, reverse geocoding, and DigiPin operations.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
api_key: str,
|
|
22
|
+
base_url: str = "https://api.quantaroute.com",
|
|
23
|
+
timeout: int = 30,
|
|
24
|
+
max_retries: int = 3
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the QuantaRoute client
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
api_key: Your QuantaRoute API key
|
|
31
|
+
base_url: Base URL for the API (default: https://api.quantaroute.com)
|
|
32
|
+
timeout: Request timeout in seconds (default: 30)
|
|
33
|
+
max_retries: Maximum number of retries for failed requests (default: 3)
|
|
34
|
+
"""
|
|
35
|
+
self.api_key = api_key
|
|
36
|
+
self.base_url = base_url.rstrip('/')
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
self.max_retries = max_retries
|
|
39
|
+
self.session = requests.Session()
|
|
40
|
+
self.session.headers.update({
|
|
41
|
+
'x-api-key': api_key,
|
|
42
|
+
'User-Agent': 'quantaroute-geocoding-python/1.0.0',
|
|
43
|
+
'Content-Type': 'application/json'
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
def _make_request(
|
|
47
|
+
self,
|
|
48
|
+
method: str,
|
|
49
|
+
endpoint: str,
|
|
50
|
+
data: Optional[Dict] = None,
|
|
51
|
+
params: Optional[Dict] = None
|
|
52
|
+
) -> Dict:
|
|
53
|
+
"""Make HTTP request with retry logic"""
|
|
54
|
+
url = f"{self.base_url}{endpoint}"
|
|
55
|
+
|
|
56
|
+
for attempt in range(self.max_retries + 1):
|
|
57
|
+
try:
|
|
58
|
+
if method.upper() == 'GET':
|
|
59
|
+
response = self.session.get(url, params=params, timeout=self.timeout)
|
|
60
|
+
else:
|
|
61
|
+
response = self.session.post(url, json=data, params=params, timeout=self.timeout)
|
|
62
|
+
|
|
63
|
+
# Handle rate limiting
|
|
64
|
+
if response.status_code == 429:
|
|
65
|
+
retry_after = int(response.headers.get('Retry-After', 60))
|
|
66
|
+
if attempt < self.max_retries:
|
|
67
|
+
time.sleep(retry_after)
|
|
68
|
+
continue
|
|
69
|
+
raise RateLimitError(
|
|
70
|
+
"Rate limit exceeded",
|
|
71
|
+
retry_after=retry_after
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Handle authentication errors
|
|
75
|
+
if response.status_code == 401:
|
|
76
|
+
raise AuthenticationError()
|
|
77
|
+
|
|
78
|
+
# Handle other client/server errors
|
|
79
|
+
if not response.ok:
|
|
80
|
+
try:
|
|
81
|
+
error_data = response.json()
|
|
82
|
+
message = error_data.get('message', f'HTTP {response.status_code}')
|
|
83
|
+
error_code = error_data.get('code')
|
|
84
|
+
except:
|
|
85
|
+
message = f'HTTP {response.status_code}: {response.text}'
|
|
86
|
+
error_code = None
|
|
87
|
+
|
|
88
|
+
raise APIError(message, response.status_code, error_code)
|
|
89
|
+
|
|
90
|
+
return response.json()
|
|
91
|
+
|
|
92
|
+
except requests.exceptions.RequestException as e:
|
|
93
|
+
if attempt < self.max_retries:
|
|
94
|
+
time.sleep(2 ** attempt) # Exponential backoff
|
|
95
|
+
continue
|
|
96
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
97
|
+
|
|
98
|
+
raise APIError("Max retries exceeded")
|
|
99
|
+
|
|
100
|
+
def geocode(
|
|
101
|
+
self,
|
|
102
|
+
address: str,
|
|
103
|
+
city: Optional[str] = None,
|
|
104
|
+
state: Optional[str] = None,
|
|
105
|
+
pincode: Optional[str] = None,
|
|
106
|
+
country: Optional[str] = None
|
|
107
|
+
) -> Dict:
|
|
108
|
+
"""
|
|
109
|
+
Geocode an address to get DigiPin and coordinates
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
address: The address to geocode
|
|
113
|
+
city: City name (optional)
|
|
114
|
+
state: State name (optional)
|
|
115
|
+
pincode: Postal code (optional)
|
|
116
|
+
country: Country name (optional, defaults to India)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dict containing DigiPin, coordinates, and address information
|
|
120
|
+
"""
|
|
121
|
+
if not address or not address.strip():
|
|
122
|
+
raise ValidationError("Address is required")
|
|
123
|
+
|
|
124
|
+
data = {
|
|
125
|
+
'address': address.strip(),
|
|
126
|
+
'city': city,
|
|
127
|
+
'state': state,
|
|
128
|
+
'pincode': pincode,
|
|
129
|
+
'country': country or 'India'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Remove None values
|
|
133
|
+
data = {k: v for k, v in data.items() if v is not None}
|
|
134
|
+
|
|
135
|
+
response = self._make_request('POST', '/v1/digipin/geocode', data)
|
|
136
|
+
return response.get('data', {})
|
|
137
|
+
|
|
138
|
+
def reverse_geocode(self, digipin: str) -> Dict:
|
|
139
|
+
"""
|
|
140
|
+
Reverse geocode a DigiPin to get coordinates and address
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
digipin: The DigiPin code to reverse geocode
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Dict containing coordinates and address information
|
|
147
|
+
"""
|
|
148
|
+
if not digipin or not digipin.strip():
|
|
149
|
+
raise ValidationError("DigiPin is required")
|
|
150
|
+
|
|
151
|
+
data = {'digipin': digipin.strip()}
|
|
152
|
+
response = self._make_request('POST', '/v1/digipin/reverse', data)
|
|
153
|
+
return response.get('data', {})
|
|
154
|
+
|
|
155
|
+
def coordinates_to_digipin(self, latitude: float, longitude: float) -> Dict:
|
|
156
|
+
"""
|
|
157
|
+
Convert coordinates to DigiPin
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
latitude: Latitude coordinate
|
|
161
|
+
longitude: Longitude coordinate
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Dict containing DigiPin and coordinates
|
|
165
|
+
"""
|
|
166
|
+
if not isinstance(latitude, (int, float)) or not isinstance(longitude, (int, float)):
|
|
167
|
+
raise ValidationError("Latitude and longitude must be numbers")
|
|
168
|
+
|
|
169
|
+
if not (-90 <= latitude <= 90):
|
|
170
|
+
raise ValidationError("Latitude must be between -90 and 90")
|
|
171
|
+
|
|
172
|
+
if not (-180 <= longitude <= 180):
|
|
173
|
+
raise ValidationError("Longitude must be between -180 and 180")
|
|
174
|
+
|
|
175
|
+
data = {
|
|
176
|
+
'latitude': float(latitude),
|
|
177
|
+
'longitude': float(longitude)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
response = self._make_request('POST', '/v1/digipin/coordinates-to-digipin', data)
|
|
181
|
+
return response.get('data', {})
|
|
182
|
+
|
|
183
|
+
def validate_digipin(self, digipin: str) -> Dict:
|
|
184
|
+
"""
|
|
185
|
+
Validate a DigiPin format and check if it's a real location
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
digipin: The DigiPin code to validate
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dict containing validation results
|
|
192
|
+
"""
|
|
193
|
+
if not digipin or not digipin.strip():
|
|
194
|
+
raise ValidationError("DigiPin is required")
|
|
195
|
+
|
|
196
|
+
response = self._make_request('GET', f'/v1/digipin/validate/{digipin.strip()}')
|
|
197
|
+
return response.get('data', {})
|
|
198
|
+
|
|
199
|
+
def batch_geocode(self, addresses: List[Dict]) -> Dict:
|
|
200
|
+
"""
|
|
201
|
+
Geocode multiple addresses in batch
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
addresses: List of address dictionaries
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dict containing batch processing results
|
|
208
|
+
"""
|
|
209
|
+
if not addresses or not isinstance(addresses, list):
|
|
210
|
+
raise ValidationError("Addresses must be a non-empty list")
|
|
211
|
+
|
|
212
|
+
if len(addresses) > 100:
|
|
213
|
+
raise ValidationError("Maximum 100 addresses allowed per batch")
|
|
214
|
+
|
|
215
|
+
# Validate each address
|
|
216
|
+
for i, addr in enumerate(addresses):
|
|
217
|
+
if not isinstance(addr, dict) or 'address' not in addr:
|
|
218
|
+
raise ValidationError(f"Address {i+1} must be a dict with 'address' key")
|
|
219
|
+
|
|
220
|
+
data = {'addresses': addresses}
|
|
221
|
+
response = self._make_request('POST', '/v1/digipin/batch', data)
|
|
222
|
+
return response.get('data', {})
|
|
223
|
+
|
|
224
|
+
def get_usage(self) -> Dict:
|
|
225
|
+
"""
|
|
226
|
+
Get API usage statistics
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dict containing usage information
|
|
230
|
+
"""
|
|
231
|
+
response = self._make_request('GET', '/v1/digipin/usage')
|
|
232
|
+
return response.get('data', {})
|
|
233
|
+
|
|
234
|
+
def autocomplete(self, query: str, limit: int = 5) -> List[Dict]:
|
|
235
|
+
"""
|
|
236
|
+
Get autocomplete suggestions for addresses
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
query: Search query (minimum 3 characters)
|
|
240
|
+
limit: Maximum number of suggestions (default: 5, max: 10)
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
List of address suggestions
|
|
244
|
+
"""
|
|
245
|
+
if not query or len(query.strip()) < 3:
|
|
246
|
+
raise ValidationError("Query must be at least 3 characters long")
|
|
247
|
+
|
|
248
|
+
if limit > 10:
|
|
249
|
+
limit = 10
|
|
250
|
+
|
|
251
|
+
params = {
|
|
252
|
+
'q': query.strip(),
|
|
253
|
+
'limit': limit
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
response = self._make_request('GET', '/v1/digipin/autocomplete', params=params)
|
|
257
|
+
return response.get('data', [])
|
|
258
|
+
|
|
259
|
+
def register_webhook(self, url: str, events: List[str], secret: Optional[str] = None) -> Dict:
|
|
260
|
+
"""
|
|
261
|
+
Register a webhook endpoint
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
url: Webhook URL
|
|
265
|
+
events: List of events to subscribe to
|
|
266
|
+
secret: Optional secret for signature verification
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Dict containing webhook registration details
|
|
270
|
+
"""
|
|
271
|
+
if not url or not url.startswith(('http://', 'https://')):
|
|
272
|
+
raise ValidationError("Valid webhook URL is required")
|
|
273
|
+
|
|
274
|
+
if not events or not isinstance(events, list):
|
|
275
|
+
raise ValidationError("Events list is required")
|
|
276
|
+
|
|
277
|
+
data = {
|
|
278
|
+
'url': url,
|
|
279
|
+
'events': events
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if secret:
|
|
283
|
+
data['secret'] = secret
|
|
284
|
+
|
|
285
|
+
response = self._make_request('POST', '/v1/digipin/webhooks', data)
|
|
286
|
+
return response.get('data', {})
|
|
287
|
+
|
|
288
|
+
def list_webhooks(self) -> List[Dict]:
|
|
289
|
+
"""
|
|
290
|
+
List registered webhooks
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of webhook configurations
|
|
294
|
+
"""
|
|
295
|
+
response = self._make_request('GET', '/v1/digipin/webhooks')
|
|
296
|
+
return response.get('data', [])
|
|
297
|
+
|
|
298
|
+
def delete_webhook(self, webhook_id: str) -> Dict:
|
|
299
|
+
"""
|
|
300
|
+
Delete a webhook
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
webhook_id: ID of the webhook to delete
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Dict containing deletion confirmation
|
|
307
|
+
"""
|
|
308
|
+
if not webhook_id:
|
|
309
|
+
raise ValidationError("Webhook ID is required")
|
|
310
|
+
|
|
311
|
+
response = self._make_request('DELETE', f'/v1/digipin/webhooks/{webhook_id}')
|
|
312
|
+
return response
|