or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-support.mdcore-data-types.mddistance-calculations.mderror-handling.mdgeocoding-services.mdindex.mdrate-limiting.md

async-support.mddocs/

0

# Async Support

1

2

Geopy provides comprehensive asynchronous geocoding support using aiohttp adapter for high-performance applications requiring concurrent geocoding requests. All geocoder classes support async operations with minimal code changes.

3

4

## Capabilities

5

6

### Async Adapter

7

8

HTTP adapter for asynchronous operations using aiohttp.

9

10

```python { .api }

11

from geopy.adapters import AioHTTPAdapter

12

13

class AioHTTPAdapter:

14

"""

15

HTTP adapter using aiohttp for asynchronous requests.

16

Requires: pip install "geopy[aiohttp]"

17

"""

18

19

def __init__(self, proxy_info=None, ssl_context=None):

20

"""

21

Initialize AioHTTP adapter.

22

23

Parameters:

24

- proxy_info: Proxy configuration (optional)

25

- ssl_context: SSL context for HTTPS requests (optional)

26

"""

27

28

async def get_text(self, url, timeout, headers):

29

"""

30

Perform async GET request returning text.

31

32

Parameters:

33

- url (str): Request URL

34

- timeout (float): Request timeout in seconds

35

- headers (dict): HTTP headers

36

37

Returns:

38

str: Response text

39

"""

40

41

async def get_json(self, url, timeout, headers):

42

"""

43

Perform async GET request returning parsed JSON.

44

45

Parameters:

46

- url (str): Request URL

47

- timeout (float): Request timeout in seconds

48

- headers (dict): HTTP headers

49

50

Returns:

51

dict: Parsed JSON response

52

"""

53

```

54

55

### Async Geocoder Usage

56

57

All geocoder classes support async operations when initialized with AioHTTPAdapter.

58

59

```python { .api }

60

# Example with Nominatim - same pattern applies to all geocoders

61

from geopy.geocoders import Nominatim

62

from geopy.adapters import AioHTTPAdapter

63

64

async def async_geocoding_example():

65

async with Nominatim(

66

user_agent="async_app",

67

adapter_factory=AioHTTPAdapter

68

) as geolocator:

69

# All geocoder methods become async when using async adapter

70

location = await geolocator.geocode("New York City")

71

reverse_location = await geolocator.reverse("40.7128, -74.0060")

72

return location, reverse_location

73

```

74

75

### Async Rate Limiter

76

77

Rate limiting functionality for asynchronous geocoding operations.

78

79

```python { .api }

80

from geopy.extra.rate_limiter import AsyncRateLimiter

81

82

class AsyncRateLimiter:

83

"""

84

Rate limiting wrapper for asynchronous geocoding functions.

85

"""

86

87

def __init__(self, func, min_delay_seconds=0.0, max_retries=2,

88

error_wait_seconds=5.0, swallow_exceptions=True,

89

return_value_on_exception=None):

90

"""

91

Initialize async rate limiter.

92

93

Parameters:

94

- func: Async function to wrap

95

- min_delay_seconds (float): Minimum delay between calls

96

- max_retries (int): Number of retry attempts on errors

97

- error_wait_seconds (float): Wait time after errors

98

- swallow_exceptions (bool): Whether to suppress final exceptions

99

- return_value_on_exception: Return value when exceptions are swallowed

100

"""

101

102

async def __call__(self, *args, **kwargs):

103

"""Execute rate-limited async function call"""

104

```

105

106

## Usage Examples

107

108

### Basic Async Geocoding

109

110

```python

111

import asyncio

112

from geopy.geocoders import Nominatim

113

from geopy.adapters import AioHTTPAdapter

114

115

async def basic_async_geocoding():

116

"""Basic async geocoding example"""

117

118

async with Nominatim(

119

user_agent="basic_async_app",

120

adapter_factory=AioHTTPAdapter

121

) as geolocator:

122

123

# Forward geocoding

124

location = await geolocator.geocode("Paris, France")

125

print(f"Forward: {location.address if location else 'Not found'}")

126

127

# Reverse geocoding

128

if location:

129

reverse = await geolocator.reverse(f"{location.latitude}, {location.longitude}")

130

print(f"Reverse: {reverse.address if reverse else 'Not found'}")

131

132

# Run async function

133

asyncio.run(basic_async_geocoding())

134

```

135

136

### Concurrent Geocoding

137

138

```python

139

import asyncio

140

from geopy.geocoders import Nominatim

141

from geopy.adapters import AioHTTPAdapter

142

143

async def concurrent_geocoding():

144

"""Geocode multiple addresses concurrently"""

145

146

addresses = [

147

"New York City, USA",

148

"London, UK",

149

"Tokyo, Japan",

150

"Sydney, Australia",

151

"São Paulo, Brazil",

152

"Mumbai, India",

153

"Cairo, Egypt",

154

"Berlin, Germany"

155

]

156

157

async with Nominatim(

158

user_agent="concurrent_app",

159

adapter_factory=AioHTTPAdapter

160

) as geolocator:

161

162

# Create tasks for concurrent execution

163

tasks = [

164

geolocator.geocode(address)

165

for address in addresses

166

]

167

168

# Execute all tasks concurrently

169

results = await asyncio.gather(*tasks, return_exceptions=True)

170

171

# Process results

172

for address, result in zip(addresses, results):

173

if isinstance(result, Exception):

174

print(f"Error geocoding '{address}': {result}")

175

elif result:

176

print(f"{address} -> {result.latitude:.4f}, {result.longitude:.4f}")

177

else:

178

print(f"No results for '{address}'")

179

180

asyncio.run(concurrent_geocoding())

181

```

182

183

### Async with Rate Limiting

184

185

```python

186

import asyncio

187

from geopy.geocoders import Nominatim

188

from geopy.adapters import AioHTTPAdapter

189

from geopy.extra.rate_limiter import AsyncRateLimiter

190

191

async def rate_limited_async_geocoding():

192

"""Async geocoding with rate limiting"""

193

194

addresses = [

195

"Times Square, New York",

196

"Eiffel Tower, Paris",

197

"Big Ben, London",

198

"Colosseum, Rome",

199

"Statue of Liberty, New York",

200

"Golden Gate Bridge, San Francisco",

201

"Sydney Opera House, Australia",

202

"Machu Picchu, Peru"

203

]

204

205

async with Nominatim(

206

user_agent="rate_limited_app",

207

adapter_factory=AioHTTPAdapter

208

) as geolocator:

209

210

# Wrap geocoder with rate limiter (1 second minimum delay)

211

rate_limited_geocode = AsyncRateLimiter(

212

geolocator.geocode,

213

min_delay_seconds=1.0,

214

max_retries=3,

215

error_wait_seconds=2.0

216

)

217

218

# Process addresses with rate limiting

219

results = []

220

for address in addresses:

221

try:

222

result = await rate_limited_geocode(address)

223

results.append((address, result))

224

print(f"✓ Geocoded: {address}")

225

except Exception as e:

226

print(f"✗ Failed: {address} - {e}")

227

results.append((address, None))

228

229

return results

230

231

asyncio.run(rate_limited_async_geocoding())

232

```

233

234

### Multiple Service Async Comparison

235

236

```python

237

import asyncio

238

from geopy.geocoders import Nominatim, Photon, OpenCage

239

from geopy.adapters import AioHTTPAdapter

240

241

async def compare_services_async():

242

"""Compare multiple geocoding services asynchronously"""

243

244

address = "Central Park, New York City"

245

246

# Initialize multiple geocoders

247

geocoders = {

248

'Nominatim': Nominatim(user_agent="comparison_app", adapter_factory=AioHTTPAdapter),

249

'Photon': Photon(user_agent="comparison_app", adapter_factory=AioHTTPAdapter),

250

# Add more services as needed (with proper API keys)

251

}

252

253

async def geocode_with_service(name, geocoder, query):

254

"""Geocode with a specific service"""

255

try:

256

async with geocoder:

257

result = await geocoder.geocode(query)

258

return name, result

259

except Exception as e:

260

return name, f"Error: {e}"

261

262

# Create tasks for all services

263

tasks = [

264

geocode_with_service(name, geocoder, address)

265

for name, geocoder in geocoders.items()

266

]

267

268

# Execute concurrently

269

results = await asyncio.gather(*tasks)

270

271

# Display results

272

print(f"Geocoding results for '{address}':")

273

for service_name, result in results:

274

if isinstance(result, str): # Error

275

print(f"{service_name}: {result}")

276

elif result:

277

print(f"{service_name}: {result.address}")

278

print(f" Coordinates: {result.latitude:.6f}, {result.longitude:.6f}")

279

else:

280

print(f"{service_name}: No results")

281

print()

282

283

asyncio.run(compare_services_async())

284

```

285

286

### Async Batch Processing

287

288

```python

289

import asyncio

290

from geopy.geocoders import Nominatim

291

from geopy.adapters import AioHTTPAdapter

292

from geopy.extra.rate_limiter import AsyncRateLimiter

293

import csv

294

import json

295

296

class AsyncBatchGeocoder:

297

"""Batch geocoding processor with async support"""

298

299

def __init__(self, geocoder_class, batch_size=10, delay=1.0, **geocoder_kwargs):

300

self.geocoder_class = geocoder_class

301

self.geocoder_kwargs = geocoder_kwargs

302

self.batch_size = batch_size

303

self.delay = delay

304

305

async def geocode_batch(self, addresses):

306

"""Geocode a batch of addresses"""

307

308

async with self.geocoder_class(

309

adapter_factory=AioHTTPAdapter,

310

**self.geocoder_kwargs

311

) as geolocator:

312

313

# Set up rate limiting

314

rate_limited_geocode = AsyncRateLimiter(

315

geolocator.geocode,

316

min_delay_seconds=self.delay,

317

max_retries=3

318

)

319

320

# Process in batches to avoid overwhelming the service

321

results = []

322

for i in range(0, len(addresses), self.batch_size):

323

batch = addresses[i:i + self.batch_size]

324

print(f"Processing batch {i//self.batch_size + 1}/{(len(addresses)-1)//self.batch_size + 1}")

325

326

# Process batch concurrently

327

batch_tasks = [

328

self._geocode_single(rate_limited_geocode, addr, idx + i)

329

for idx, addr in enumerate(batch)

330

]

331

332

batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)

333

results.extend(batch_results)

334

335

# Brief pause between batches

336

if i + self.batch_size < len(addresses):

337

await asyncio.sleep(1)

338

339

return results

340

341

async def _geocode_single(self, geocoder_func, address, index):

342

"""Geocode a single address with error handling"""

343

try:

344

result = await geocoder_func(address)

345

return {

346

'index': index,

347

'input_address': address,

348

'status': 'success' if result else 'no_results',

349

'result_address': result.address if result else None,

350

'latitude': result.latitude if result else None,

351

'longitude': result.longitude if result else None,

352

'raw': result.raw if result else None

353

}

354

except Exception as e:

355

return {

356

'index': index,

357

'input_address': address,

358

'status': 'error',

359

'error': str(e),

360

'result_address': None,

361

'latitude': None,

362

'longitude': None,

363

'raw': None

364

}

365

366

async def batch_geocoding_example():

367

"""Example of batch geocoding from CSV file"""

368

369

# Sample data (normally would read from CSV)

370

addresses = [

371

"1600 Amphitheatre Parkway, Mountain View, CA",

372

"1 Apple Park Way, Cupertino, CA",

373

"350 Fifth Avenue, New York, NY",

374

"Times Square, New York, NY",

375

"Golden Gate Bridge, San Francisco, CA",

376

"Space Needle, Seattle, WA",

377

"Willis Tower, Chicago, IL",

378

"Hollywood Sign, Los Angeles, CA"

379

]

380

381

# Initialize batch geocoder

382

batch_geocoder = AsyncBatchGeocoder(

383

Nominatim,

384

batch_size=3,

385

delay=1.0,

386

user_agent="batch_geocoding_app"

387

)

388

389

# Process addresses

390

print(f"Starting batch geocoding of {len(addresses)} addresses...")

391

results = await batch_geocoder.geocode_batch(addresses)

392

393

# Analyze results

394

successful = sum(1 for r in results if r['status'] == 'success')

395

no_results = sum(1 for r in results if r['status'] == 'no_results')

396

errors = sum(1 for r in results if r['status'] == 'error')

397

398

print(f"\nBatch geocoding completed:")

399

print(f" Successful: {successful}")

400

print(f" No results: {no_results}")

401

print(f" Errors: {errors}")

402

403

# Save results to file

404

with open('geocoding_results.json', 'w') as f:

405

json.dump(results, f, indent=2)

406

407

print("Results saved to geocoding_results.json")

408

409

return results

410

411

# Run batch processing

412

asyncio.run(batch_geocoding_example())

413

```

414

415

### Async Error Handling

416

417

```python

418

import asyncio

419

from geopy.geocoders import Nominatim

420

from geopy.adapters import AioHTTPAdapter

421

from geopy.exc import *

422

423

async def robust_async_geocoding():

424

"""Async geocoding with comprehensive error handling"""

425

426

async def safe_geocode(geolocator, query, max_retries=3):

427

"""Safely geocode with retries and error handling"""

428

429

for attempt in range(max_retries):

430

try:

431

result = await geolocator.geocode(query, timeout=10)

432

return {'status': 'success', 'result': result, 'query': query}

433

434

except GeocoderRateLimited as e:

435

if attempt == max_retries - 1:

436

return {'status': 'rate_limited', 'error': str(e), 'query': query}

437

438

wait_time = e.retry_after if e.retry_after else 2 ** attempt

439

print(f"Rate limited for '{query}', waiting {wait_time}s...")

440

await asyncio.sleep(wait_time)

441

442

except GeocoderTimedOut as e:

443

if attempt == max_retries - 1:

444

return {'status': 'timeout', 'error': str(e), 'query': query}

445

446

print(f"Timeout for '{query}', retrying...")

447

await asyncio.sleep(1)

448

449

except GeocoderServiceError as e:

450

return {'status': 'service_error', 'error': str(e), 'query': query}

451

452

except Exception as e:

453

return {'status': 'unexpected_error', 'error': str(e), 'query': query}

454

455

return {'status': 'max_retries_exceeded', 'query': query}

456

457

queries = [

458

"Valid Address, New York, NY",

459

"Invalid Address 123456789",

460

"London, UK",

461

"Tokyo, Japan"

462

]

463

464

async with Nominatim(

465

user_agent="robust_async_app",

466

adapter_factory=AioHTTPAdapter

467

) as geolocator:

468

469

# Process all queries concurrently

470

tasks = [safe_geocode(geolocator, query) for query in queries]

471

results = await asyncio.gather(*tasks)

472

473

# Analyze results

474

for result in results:

475

status = result['status']

476

query = result['query']

477

478

if status == 'success':

479

location = result['result']

480

if location:

481

print(f"✓ {query} -> {location.address}")

482

else:

483

print(f"○ {query} -> No results")

484

else:

485

error = result.get('error', 'Unknown error')

486

print(f"✗ {query} -> {status}: {error}")

487

488

asyncio.run(robust_async_geocoding())

489

```

490

491

### Performance Monitoring

492

493

```python

494

import asyncio

495

import time

496

from geopy.geocoders import Nominatim

497

from geopy.adapters import AioHTTPAdapter

498

499

async def async_performance_test():

500

"""Compare sync vs async performance"""

501

502

addresses = [

503

"New York, NY", "Los Angeles, CA", "Chicago, IL",

504

"Houston, TX", "Phoenix, AZ", "Philadelphia, PA",

505

"San Antonio, TX", "San Diego, CA", "Dallas, TX",

506

"San Jose, CA"

507

]

508

509

# Async performance test

510

start_time = time.time()

511

512

async with Nominatim(

513

user_agent="performance_test",

514

adapter_factory=AioHTTPAdapter

515

) as geolocator:

516

517

tasks = [geolocator.geocode(addr) for addr in addresses]

518

async_results = await asyncio.gather(*tasks, return_exceptions=True)

519

520

async_time = time.time() - start_time

521

522

# Sync performance test (for comparison)

523

start_time = time.time()

524

sync_geolocator = Nominatim(user_agent="performance_test")

525

sync_results = []

526

527

for addr in addresses:

528

try:

529

result = sync_geolocator.geocode(addr)

530

sync_results.append(result)

531

except Exception as e:

532

sync_results.append(e)

533

534

sync_time = time.time() - start_time

535

536

# Results

537

async_success = sum(1 for r in async_results if hasattr(r, 'address'))

538

sync_success = sum(1 for r in sync_results if hasattr(r, 'address'))

539

540

print(f"Performance Comparison:")

541

print(f"Async: {async_time:.2f}s ({async_success}/{len(addresses)} successful)")

542

print(f"Sync: {sync_time:.2f}s ({sync_success}/{len(addresses)} successful)")

543

print(f"Speedup: {sync_time/async_time:.2f}x")

544

545

asyncio.run(async_performance_test())

546

```