or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdchangesets.mderrors.mdindex.mdnodes.mdnotes.mdrelations.mdways.md

errors.mddocs/

0

# Error Handling

1

2

Comprehensive error hierarchy covering all API scenarios including network errors, authentication failures, data conflicts, and OSM-specific exceptions. The osmapi library provides detailed error information to help developers handle different failure conditions appropriately.

3

4

## Error Hierarchy

5

6

### Base Error Classes

7

8

```python { .api }

9

class OsmApiError(Exception):

10

"""

11

General OsmApi error class serving as superclass for all other errors.

12

Base exception for all osmapi-specific errors.

13

"""

14

15

class ApiError(OsmApiError):

16

"""

17

Base API request error with detailed response information.

18

19

Attributes:

20

- status (int): HTTP error code

21

- reason (str): Error message

22

- payload (str): Response payload when error occurred

23

"""

24

25

def __init__(self, status, reason, payload):

26

self.status = status

27

self.reason = reason

28

self.payload = payload

29

```

30

31

**Usage Example:**

32

33

```python

34

import osmapi

35

36

api = osmapi.OsmApi()

37

38

try:

39

node = api.NodeGet(999999)

40

except osmapi.ApiError as e:

41

print(f"API Error {e.status}: {e.reason}")

42

if e.payload:

43

print(f"Server response: {e.payload}")

44

except osmapi.OsmApiError as e:

45

print(f"General osmapi error: {e}")

46

```

47

48

## Authentication Errors

49

50

Errors related to authentication and authorization.

51

52

### UsernamePasswordMissingError

53

54

```python { .api }

55

class UsernamePasswordMissingError(OsmApiError):

56

"""

57

Error when username or password is missing for an authenticated request.

58

59

Raised when attempting operations that require authentication

60

without providing credentials.

61

"""

62

```

63

64

**Usage Example:**

65

66

```python

67

import osmapi

68

69

# No authentication provided

70

api = osmapi.OsmApi()

71

72

try:

73

# This requires authentication

74

api.ChangesetCreate({"comment": "Test changeset"})

75

except osmapi.UsernamePasswordMissingError:

76

print("Authentication required for this operation")

77

# Provide credentials

78

api = osmapi.OsmApi(username="user", password="pass")

79

```

80

81

### UnauthorizedApiError

82

83

```python { .api }

84

class UnauthorizedApiError(ApiError):

85

"""

86

Error when the API returned an Unauthorized error.

87

88

Raised when provided OAuth token is expired, invalid credentials

89

are used, or account lacks permissions for the operation.

90

"""

91

```

92

93

**Usage Example:**

94

95

```python

96

import osmapi

97

98

api = osmapi.OsmApi(username="wrong_user", password="wrong_pass")

99

100

try:

101

api.ChangesetCreate({"comment": "Test"})

102

except osmapi.UnauthorizedApiError as e:

103

print(f"Authentication failed: {e.reason}")

104

# Check credentials or OAuth token

105

```

106

107

## Network and Communication Errors

108

109

Errors related to network connectivity and HTTP communication.

110

111

### TimeoutApiError

112

113

```python { .api }

114

class TimeoutApiError(ApiError):

115

"""

116

Error if the HTTP request ran into a timeout.

117

118

Raised when requests exceed the configured timeout period.

119

"""

120

```

121

122

### ConnectionApiError

123

124

```python { .api }

125

class ConnectionApiError(ApiError):

126

"""

127

Error if there was a network error while connecting to the remote server.

128

129

Includes DNS failures, refused connections, and other network issues.

130

"""

131

```

132

133

### MaximumRetryLimitReachedError

134

135

```python { .api }

136

class MaximumRetryLimitReachedError(OsmApiError):

137

"""

138

Error when the maximum amount of retries is reached and we have to give up.

139

140

Raised when all retry attempts are exhausted for server errors.

141

"""

142

```

143

144

**Usage Example:**

145

146

```python

147

import osmapi

148

149

api = osmapi.OsmApi(timeout=5) # Short timeout for example

150

151

try:

152

node = api.NodeGet(123)

153

except osmapi.TimeoutApiError as e:

154

print(f"Request timed out: {e.reason}")

155

# Retry with longer timeout or check network

156

except osmapi.ConnectionApiError as e:

157

print(f"Network error: {e.reason}")

158

# Check internet connection

159

except osmapi.MaximumRetryLimitReachedError as e:

160

print(f"Server unavailable after retries: {e}")

161

```

162

163

## Response and Data Errors

164

165

Errors related to API response processing and data validation.

166

167

### XmlResponseInvalidError

168

169

```python { .api }

170

class XmlResponseInvalidError(OsmApiError):

171

"""

172

Error if the XML response from the OpenStreetMap API is invalid.

173

174

Raised when the server returns malformed XML or unexpected response format.

175

"""

176

```

177

178

### ResponseEmptyApiError

179

180

```python { .api }

181

class ResponseEmptyApiError(ApiError):

182

"""

183

Error when the response to the request is empty.

184

185

Raised when expecting data but receiving empty response from server.

186

"""

187

```

188

189

**Usage Example:**

190

191

```python

192

import osmapi

193

194

api = osmapi.OsmApi()

195

196

try:

197

nodes = api.NodesGet([123, 456])

198

except osmapi.XmlResponseInvalidError as e:

199

print(f"Invalid XML response: {e}")

200

# Server may be experiencing issues

201

except osmapi.ResponseEmptyApiError as e:

202

print(f"Empty response: {e.reason}")

203

# May indicate server problems or invalid request

204

```

205

206

## Changeset Errors

207

208

Errors specific to changeset operations and lifecycle.

209

210

### NoChangesetOpenError

211

212

```python { .api }

213

class NoChangesetOpenError(OsmApiError):

214

"""

215

Error when an operation requires an open changeset, but currently

216

no changeset is open.

217

218

Raised when attempting data modifications without an active changeset.

219

"""

220

```

221

222

### ChangesetAlreadyOpenError

223

224

```python { .api }

225

class ChangesetAlreadyOpenError(OsmApiError):

226

"""

227

Error when a user tries to open a changeset when there is already

228

an open changeset.

229

230

Only one changeset can be open at a time per user session.

231

"""

232

```

233

234

### ChangesetClosedApiError

235

236

```python { .api }

237

class ChangesetClosedApiError(ApiError):

238

"""

239

Error if the changeset in question has already been closed.

240

241

Raised when attempting to modify a changeset that is no longer active.

242

"""

243

```

244

245

**Usage Example:**

246

247

```python

248

import osmapi

249

250

api = osmapi.OsmApi(username="user", password="pass")

251

252

try:

253

# Forgot to open changeset

254

api.NodeCreate({"lat": 0, "lon": 0, "tag": {}})

255

except osmapi.NoChangesetOpenError:

256

print("Need to open a changeset first")

257

changeset_id = api.ChangesetCreate({"comment": "Adding nodes"})

258

259

try:

260

# Try to open second changeset

261

api.ChangesetCreate({"comment": "Another changeset"})

262

except osmapi.ChangesetAlreadyOpenError:

263

print("Close current changeset before opening new one")

264

api.ChangesetClose()

265

```

266

267

## Element Errors

268

269

Errors related to OSM elements (nodes, ways, relations) and their state.

270

271

### ElementNotFoundApiError

272

273

```python { .api }

274

class ElementNotFoundApiError(ApiError):

275

"""

276

Error if the requested element was not found.

277

278

Raised when requesting an element ID that doesn't exist.

279

"""

280

```

281

282

### ElementDeletedApiError

283

284

```python { .api }

285

class ElementDeletedApiError(ApiError):

286

"""

287

Error when the requested element is deleted.

288

289

Raised when requesting an element that has been marked as deleted.

290

"""

291

```

292

293

### OsmTypeAlreadyExistsError

294

295

```python { .api }

296

class OsmTypeAlreadyExistsError(OsmApiError):

297

"""

298

Error when a user tries to create an object that already exists.

299

300

Raised when attempting to create an element with an existing ID.

301

"""

302

```

303

304

**Usage Example:**

305

306

```python

307

import osmapi

308

309

api = osmapi.OsmApi()

310

311

try:

312

node = api.NodeGet(999999999)

313

except osmapi.ElementNotFoundApiError:

314

print("Node doesn't exist")

315

except osmapi.ElementDeletedApiError:

316

print("Node has been deleted")

317

318

# Creating elements

319

api_auth = osmapi.OsmApi(username="user", password="pass")

320

try:

321

with api_auth.Changeset({"comment": "Test"}) as changeset_id:

322

# This would fail if trying to create with existing ID

323

existing_node = {"id": 123, "lat": 0, "lon": 0, "tag": {}}

324

api_auth.NodeCreate(existing_node)

325

except osmapi.OsmTypeAlreadyExistsError:

326

print("Cannot create element with existing ID")

327

```

328

329

## Version and Conflict Errors

330

331

Errors related to version conflicts and data consistency.

332

333

### VersionMismatchApiError

334

335

```python { .api }

336

class VersionMismatchApiError(ApiError):

337

"""

338

Error if the provided version does not match the database version

339

of the element.

340

341

Raised when attempting to modify an element that has been changed

342

by another user since it was last retrieved.

343

"""

344

```

345

346

### PreconditionFailedApiError

347

348

```python { .api }

349

class PreconditionFailedApiError(ApiError):

350

"""

351

Error if the precondition of the operation was not met.

352

353

Raised when:

354

- A way has nodes that do not exist or are not visible

355

- A relation has elements that do not exist or are not visible

356

- A node/way/relation is still used in a way/relation when deleting

357

"""

358

```

359

360

**Usage Example:**

361

362

```python

363

import osmapi

364

365

api = osmapi.OsmApi(username="user", password="pass")

366

367

try:

368

# Get current node

369

node = api.NodeGet(12345)

370

371

with api.Changeset({"comment": "Update node"}) as changeset_id:

372

# Someone else might have modified it meanwhile

373

node["tag"]["updated"] = "yes"

374

api.NodeUpdate(node)

375

376

except osmapi.VersionMismatchApiError as e:

377

print(f"Version conflict: {e.reason}")

378

# Re-fetch current version and merge changes

379

current_node = api.NodeGet(12345)

380

# Apply changes to current version

381

382

try:

383

with api.Changeset({"comment": "Create way"}) as changeset_id:

384

# This will fail if nodes don't exist

385

api.WayCreate({

386

"nd": [999999, 999998], # Non-existent nodes

387

"tag": {"highway": "path"}

388

})

389

except osmapi.PreconditionFailedApiError as e:

390

print(f"Referenced elements don't exist: {e.reason}")

391

```

392

393

## Notes-Specific Errors

394

395

Errors specific to Notes API operations.

396

397

### NoteAlreadyClosedApiError

398

399

```python { .api }

400

class NoteAlreadyClosedApiError(ApiError):

401

"""

402

Error if the note in question has already been closed.

403

404

Raised when attempting to close a note that is already closed.

405

"""

406

```

407

408

**Usage Example:**

409

410

```python

411

import osmapi

412

413

api = osmapi.OsmApi(username="user", password="pass")

414

415

try:

416

api.NoteClose(12345, "Issue resolved")

417

except osmapi.NoteAlreadyClosedApiError:

418

print("Note is already closed")

419

# Maybe reopen it instead

420

api.NoteReopen(12345, "Issue has returned")

421

```

422

423

## Subscription Errors

424

425

Errors related to changeset discussion subscriptions.

426

427

### AlreadySubscribedApiError

428

429

```python { .api }

430

class AlreadySubscribedApiError(ApiError):

431

"""

432

Error when a user tries to subscribe to a changeset

433

that they are already subscribed to.

434

"""

435

```

436

437

### NotSubscribedApiError

438

439

```python { .api }

440

class NotSubscribedApiError(ApiError):

441

"""

442

Error when user tries to unsubscribe from a changeset

443

that they are not subscribed to.

444

"""

445

```

446

447

**Usage Example:**

448

449

```python

450

import osmapi

451

452

api = osmapi.OsmApi(username="user", password="pass")

453

454

try:

455

api.ChangesetSubscribe(12345)

456

except osmapi.AlreadySubscribedApiError:

457

print("Already subscribed to this changeset")

458

459

try:

460

api.ChangesetUnsubscribe(12345)

461

except osmapi.NotSubscribedApiError:

462

print("Not subscribed to this changeset")

463

```

464

465

## Error Handling Strategies

466

467

### Graceful Degradation

468

469

```python

470

import osmapi

471

import time

472

473

def robust_node_get(api, node_id, max_retries=3):

474

"""Get node with retry logic and graceful error handling."""

475

476

for attempt in range(max_retries):

477

try:

478

return api.NodeGet(node_id)

479

480

except osmapi.ElementNotFoundApiError:

481

# Element doesn't exist, no point retrying

482

return None

483

484

except osmapi.ElementDeletedApiError:

485

# Element was deleted, return None or handle specially

486

return {"deleted": True, "id": node_id}

487

488

except (osmapi.TimeoutApiError, osmapi.ConnectionApiError):

489

if attempt < max_retries - 1:

490

# Wait before retry

491

time.sleep(2 ** attempt) # Exponential backoff

492

continue

493

else:

494

# Final attempt failed

495

raise

496

497

except osmapi.MaximumRetryLimitReachedError:

498

# Server is having issues

499

print(f"Server unavailable, giving up on node {node_id}")

500

return None

501

502

return None

503

```

504

505

### Version Conflict Resolution

506

507

```python

508

import osmapi

509

510

def update_with_conflict_resolution(api, element_type, element_data):

511

"""Update element with automatic conflict resolution."""

512

513

max_attempts = 3

514

515

for attempt in range(max_attempts):

516

try:

517

if element_type == "node":

518

return api.NodeUpdate(element_data)

519

elif element_type == "way":

520

return api.WayUpdate(element_data)

521

elif element_type == "relation":

522

return api.RelationUpdate(element_data)

523

524

except osmapi.VersionMismatchApiError:

525

if attempt < max_attempts - 1:

526

# Fetch current version

527

element_id = element_data["id"]

528

529

if element_type == "node":

530

current = api.NodeGet(element_id)

531

elif element_type == "way":

532

current = api.WayGet(element_id)

533

elif element_type == "relation":

534

current = api.RelationGet(element_id)

535

536

# Merge changes (simple example - merge tags)

537

current["tag"].update(element_data["tag"])

538

element_data = current

539

continue

540

else:

541

# Too many conflicts, give up

542

raise

543

544

return None

545

```

546

547

### Comprehensive Error Logging

548

549

```python

550

import osmapi

551

import logging

552

553

# Set up logging

554

logging.basicConfig(level=logging.INFO)

555

logger = logging.getLogger(__name__)

556

557

def safe_osm_operation(operation_func, *args, **kwargs):

558

"""Wrapper for OSM operations with comprehensive error logging."""

559

560

try:

561

result = operation_func(*args, **kwargs)

562

logger.info(f"Operation successful: {operation_func.__name__}")

563

return result

564

565

except osmapi.UsernamePasswordMissingError as e:

566

logger.error(f"Authentication required: {e}")

567

raise

568

569

except osmapi.UnauthorizedApiError as e:

570

logger.error(f"Authentication failed: {e.status} - {e.reason}")

571

raise

572

573

except osmapi.ElementNotFoundApiError as e:

574

logger.warning(f"Element not found: {e.status} - {e.reason}")

575

return None

576

577

except osmapi.VersionMismatchApiError as e:

578

logger.warning(f"Version conflict: {e.status} - {e.reason}")

579

# Could implement retry logic here

580

raise

581

582

except osmapi.ChangesetClosedApiError as e:

583

logger.error(f"Changeset closed: {e.status} - {e.reason}")

584

raise

585

586

except osmapi.TimeoutApiError as e:

587

logger.error(f"Request timeout: {e.reason}")

588

# Could implement retry logic

589

raise

590

591

except osmapi.ApiError as e:

592

logger.error(f"API Error {e.status}: {e.reason}")

593

if e.payload:

594

logger.error(f"Server response: {e.payload}")

595

raise

596

597

except osmapi.OsmApiError as e:

598

logger.error(f"OSM API Error: {e}")

599

raise

600

601

except Exception as e:

602

logger.error(f"Unexpected error: {e}")

603

raise

604

605

# Usage example

606

api = osmapi.OsmApi(username="user", password="pass")

607

608

# Safe operation with logging

609

result = safe_osm_operation(api.NodeGet, 12345)

610

if result:

611

print(f"Got node: {result['id']}")

612

```

613

614

## Error Prevention Best Practices

615

616

### Data Validation

617

618

```python

619

def validate_node_data(node_data):

620

"""Validate node data before API calls."""

621

622

required_fields = ["lat", "lon"]

623

for field in required_fields:

624

if field not in node_data:

625

raise ValueError(f"Missing required field: {field}")

626

627

# Validate coordinate ranges

628

if not -90 <= node_data["lat"] <= 90:

629

raise ValueError(f"Invalid latitude: {node_data['lat']}")

630

631

if not -180 <= node_data["lon"] <= 180:

632

raise ValueError(f"Invalid longitude: {node_data['lon']}")

633

634

# Validate tags

635

if "tag" in node_data:

636

for key, value in node_data["tag"].items():

637

if not isinstance(key, str) or not isinstance(value, str):

638

raise ValueError("Tag keys and values must be strings")

639

```

640

641

### Changeset Management

642

643

```python

644

import osmapi

645

646

class SafeChangesetManager:

647

"""Context manager with enhanced error handling."""

648

649

def __init__(self, api, changeset_tags):

650

self.api = api

651

self.changeset_tags = changeset_tags

652

self.changeset_id = None

653

654

def __enter__(self):

655

try:

656

self.changeset_id = self.api.ChangesetCreate(self.changeset_tags)

657

return self.changeset_id

658

except osmapi.ChangesetAlreadyOpenError:

659

# Close existing changeset and try again

660

self.api.ChangesetClose()

661

self.changeset_id = self.api.ChangesetCreate(self.changeset_tags)

662

return self.changeset_id

663

664

def __exit__(self, exc_type, exc_val, exc_tb):

665

if self.changeset_id:

666

try:

667

self.api.ChangesetClose()

668

except osmapi.ChangesetClosedApiError:

669

# Already closed, that's fine

670

pass

671

except Exception as e:

672

logger.error(f"Error closing changeset: {e}")

673

674

# Usage

675

api = osmapi.OsmApi(username="user", password="pass")

676

with SafeChangesetManager(api, {"comment": "Safe operations"}) as changeset_id:

677

# Perform operations safely

678

pass

679

```