or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-objects.mddecoders.mdexceptions.mdform-parsing.mdindex.mdstreaming-parsers.md

exceptions.mddocs/

0

# Exception Handling

1

2

Comprehensive exception hierarchy for robust error handling across all parsing operations. Python-multipart provides specific exception types for different error conditions, enabling precise error handling and debugging.

3

4

## Exception Hierarchy

5

6

```

7

ValueError

8

└── FormParserError (base exception)

9

├── ParseError

10

│ ├── MultipartParseError

11

│ ├── QuerystringParseError

12

│ └── DecodeError

13

└── FileError (also inherits from OSError)

14

```

15

16

## Capabilities

17

18

### Base Exceptions

19

20

#### FormParserError

21

22

Base error class for all form parser exceptions.

23

24

```python { .api }

25

class FormParserError(ValueError):

26

"""

27

Base error class for form parser.

28

Inherits from ValueError for compatibility.

29

"""

30

```

31

32

#### ParseError

33

34

Base class for parsing-related errors with offset tracking.

35

36

```python { .api }

37

class ParseError(FormParserError):

38

"""

39

Base exception for parsing errors.

40

41

Attributes:

42

- offset: Position in input data where error occurred (-1 if not specified)

43

"""

44

45

offset = -1 # Offset in input data chunk where parse error occurred

46

```

47

48

### Specific Parse Errors

49

50

#### MultipartParseError

51

52

Specific error raised when MultipartParser detects parsing errors in multipart/form-data content.

53

54

```python { .api }

55

class MultipartParseError(ParseError):

56

"""

57

Error raised by MultipartParser when detecting parse errors.

58

Used for malformed multipart boundaries, headers, or structure.

59

"""

60

```

61

62

#### QuerystringParseError

63

64

Specific error raised when QuerystringParser detects parsing errors in URL-encoded content.

65

66

```python { .api }

67

class QuerystringParseError(ParseError):

68

"""

69

Error raised by QuerystringParser when detecting parse errors.

70

Used for malformed URL-encoded data or invalid characters.

71

"""

72

```

73

74

#### DecodeError

75

76

Error raised when content decoders encounter invalid encoded data.

77

78

```python { .api }

79

class DecodeError(ParseError):

80

"""

81

Error raised by Base64Decoder or QuotedPrintableDecoder.

82

Used for invalid Base64 data or decoding failures.

83

"""

84

```

85

86

#### FileError

87

88

Exception for problems with File class operations, inheriting from both FormParserError and OSError.

89

90

```python { .api }

91

class FileError(FormParserError, OSError):

92

"""

93

Exception for File class problems.

94

Combines form parsing context with OS-level error information.

95

"""

96

```

97

98

## Usage Examples

99

100

### Basic Exception Handling

101

102

```python

103

from python_multipart import parse_form

104

from python_multipart.exceptions import (

105

FormParserError,

106

MultipartParseError,

107

DecodeError,

108

FileError

109

)

110

111

def safe_form_parsing(headers, input_stream):

112

"""Demonstrate comprehensive exception handling."""

113

114

fields = []

115

files = []

116

errors = []

117

118

def on_field(field):

119

try:

120

fields.append({

121

'name': field.field_name.decode('utf-8'),

122

'value': field.value.decode('utf-8') if field.value else None

123

})

124

except UnicodeDecodeError as e:

125

errors.append(f"Field encoding error: {e}")

126

finally:

127

field.close()

128

129

def on_file(file):

130

try:

131

files.append({

132

'field_name': file.field_name.decode('utf-8'),

133

'filename': file.file_name.decode('utf-8') if file.file_name else None,

134

'size': file.size

135

})

136

except UnicodeDecodeError as e:

137

errors.append(f"File name encoding error: {e}")

138

except Exception as e:

139

errors.append(f"File processing error: {e}")

140

finally:

141

file.close()

142

143

try:

144

parse_form(headers, input_stream, on_field, on_file)

145

146

return {

147

'success': True,

148

'fields': fields,

149

'files': files,

150

'errors': errors

151

}

152

153

except MultipartParseError as e:

154

return {

155

'success': False,

156

'error': 'Malformed multipart data',

157

'details': str(e),

158

'offset': getattr(e, 'offset', -1)

159

}

160

161

except DecodeError as e:

162

return {

163

'success': False,

164

'error': 'Content decoding failed',

165

'details': str(e),

166

'offset': getattr(e, 'offset', -1)

167

}

168

169

except FileError as e:

170

return {

171

'success': False,

172

'error': 'File handling error',

173

'details': str(e),

174

'errno': getattr(e, 'errno', None)

175

}

176

177

except FormParserError as e:

178

return {

179

'success': False,

180

'error': 'Form parsing error',

181

'details': str(e)

182

}

183

184

except Exception as e:

185

return {

186

'success': False,

187

'error': 'Unexpected error',

188

'details': str(e)

189

}

190

```

191

192

### Parser-Specific Error Handling

193

194

```python

195

from python_multipart import MultipartParser, QuerystringParser

196

from python_multipart.exceptions import MultipartParseError, QuerystringParseError

197

198

def handle_multipart_errors(boundary, data_stream):

199

"""Handle MultipartParser specific errors."""

200

201

def on_part_data(data, start, end):

202

# Process part data

203

chunk = data[start:end]

204

print(f"Processing {len(chunk)} bytes")

205

206

callbacks = {'on_part_data': on_part_data}

207

208

try:

209

parser = MultipartParser(boundary, callbacks)

210

211

while True:

212

chunk = data_stream.read(1024)

213

if not chunk:

214

break

215

parser.write(chunk)

216

217

parser.finalize()

218

return {'success': True}

219

220

except MultipartParseError as e:

221

error_info = {

222

'success': False,

223

'error_type': 'multipart_parse_error',

224

'message': str(e),

225

'offset': e.offset

226

}

227

228

# Provide context based on offset

229

if e.offset >= 0:

230

error_info['context'] = f"Error at byte position {e.offset}"

231

232

return error_info

233

234

def handle_querystring_errors(data_stream):

235

"""Handle QuerystringParser specific errors."""

236

237

fields = {}

238

current_field = None

239

current_value = b''

240

241

def on_field_name(data, start, end):

242

nonlocal current_field

243

current_field = data[start:end].decode('utf-8')

244

245

def on_field_data(data, start, end):

246

nonlocal current_value

247

current_value += data[start:end]

248

249

def on_field_end():

250

nonlocal current_field, current_value

251

if current_field:

252

fields[current_field] = current_value.decode('utf-8')

253

current_field = None

254

current_value = b''

255

256

callbacks = {

257

'on_field_name': on_field_name,

258

'on_field_data': on_field_data,

259

'on_field_end': on_field_end

260

}

261

262

try:

263

parser = QuerystringParser(callbacks, strict_parsing=True)

264

265

while True:

266

chunk = data_stream.read(1024)

267

if not chunk:

268

break

269

parser.write(chunk)

270

271

parser.finalize()

272

return {'success': True, 'fields': fields}

273

274

except QuerystringParseError as e:

275

return {

276

'success': False,

277

'error_type': 'querystring_parse_error',

278

'message': str(e),

279

'offset': e.offset,

280

'partial_fields': fields

281

}

282

```

283

284

### Decoder Error Handling

285

286

```python

287

from python_multipart.decoders import Base64Decoder, QuotedPrintableDecoder

288

from python_multipart.exceptions import DecodeError

289

import io

290

291

def safe_base64_decode(encoded_data):

292

"""Safely decode Base64 data with error handling."""

293

294

output = io.BytesIO()

295

decoder = Base64Decoder(output)

296

297

try:

298

decoder.write(encoded_data)

299

decoder.finalize()

300

301

output.seek(0)

302

return {

303

'success': True,

304

'data': output.read()

305

}

306

307

except DecodeError as e:

308

return {

309

'success': False,

310

'error': 'Base64 decode error',

311

'message': str(e),

312

'cache_size': len(decoder.cache) if hasattr(decoder, 'cache') else 0

313

}

314

315

finally:

316

decoder.close()

317

318

def safe_quoted_printable_decode(encoded_data):

319

"""Safely decode quoted-printable data."""

320

321

output = io.BytesIO()

322

decoder = QuotedPrintableDecoder(output)

323

324

try:

325

decoder.write(encoded_data)

326

decoder.finalize()

327

328

output.seek(0)

329

return {

330

'success': True,

331

'data': output.read()

332

}

333

334

except Exception as e: # QuotedPrintableDecoder rarely raises exceptions

335

return {

336

'success': False,

337

'error': 'Quoted-printable decode error',

338

'message': str(e)

339

}

340

341

finally:

342

decoder.close()

343

344

# Test error handling

345

malformed_base64 = b"SGVsbG8gV29ybGQ!" # Invalid padding

346

result = safe_base64_decode(malformed_base64)

347

print(f"Base64 decode result: {result}")

348

```

349

350

### File Error Handling

351

352

```python

353

from python_multipart import File

354

from python_multipart.exceptions import FileError

355

import os

356

import tempfile

357

358

def safe_file_handling(file_name, field_name, content):

359

"""Demonstrate file error handling."""

360

361

# Configure file to use a restricted directory

362

config = {

363

'UPLOAD_DIR': '/restricted/path', # This might not exist

364

'MAX_MEMORY_FILE_SIZE': 1024

365

}

366

367

try:

368

file_obj = File(file_name.encode(), field_name.encode(), config)

369

370

# Write content

371

file_obj.write(content)

372

file_obj.finalize()

373

374

result = {

375

'success': True,

376

'file_name': file_name,

377

'size': file_obj.size,

378

'in_memory': file_obj.in_memory

379

}

380

381

if not file_obj.in_memory:

382

result['temp_path'] = file_obj.actual_file_name.decode()

383

384

file_obj.close()

385

return result

386

387

except FileError as e:

388

return {

389

'success': False,

390

'error_type': 'file_error',

391

'message': str(e),

392

'errno': getattr(e, 'errno', None),

393

'filename': getattr(e, 'filename', None)

394

}

395

396

except OSError as e:

397

return {

398

'success': False,

399

'error_type': 'os_error',

400

'message': str(e),

401

'errno': e.errno

402

}

403

404

except Exception as e:

405

return {

406

'success': False,

407

'error_type': 'unexpected_error',

408

'message': str(e)

409

}

410

411

# Test file error handling

412

result = safe_file_handling('test.txt', 'upload', b'File content here')

413

print(f"File handling result: {result}")

414

```

415

416

### Error Recovery Strategies

417

418

```python

419

from python_multipart import FormParser

420

from python_multipart.exceptions import FormParserError, ParseError

421

import logging

422

423

class RobustFormParser:

424

"""Form parser with error recovery and logging."""

425

426

def __init__(self):

427

self.logger = logging.getLogger(__name__)

428

self.error_count = 0

429

self.max_errors = 10

430

431

def parse_with_recovery(self, headers, input_stream):

432

"""Parse form data with error recovery."""

433

434

fields = []

435

files = []

436

errors = []

437

438

def on_field(field):

439

try:

440

fields.append({

441

'name': field.field_name.decode('utf-8', errors='replace'),

442

'value': field.value.decode('utf-8', errors='replace') if field.value else None

443

})

444

except Exception as e:

445

self.logger.warning(f"Field processing error: {e}")

446

errors.append(f"Field error: {e}")

447

finally:

448

field.close()

449

450

def on_file(file):

451

try:

452

files.append({

453

'field_name': file.field_name.decode('utf-8', errors='replace'),

454

'filename': file.file_name.decode('utf-8', errors='replace') if file.file_name else None,

455

'size': file.size

456

})

457

except Exception as e:

458

self.logger.warning(f"File processing error: {e}")

459

errors.append(f"File error: {e}")

460

finally:

461

file.close()

462

463

try:

464

# Try parsing with error recovery

465

parser = FormParser(

466

headers.get('Content-Type', 'application/octet-stream'),

467

on_field,

468

on_file

469

)

470

471

chunk_count = 0

472

while True:

473

try:

474

chunk = input_stream.read(8192)

475

if not chunk:

476

break

477

478

parser.write(chunk)

479

chunk_count += 1

480

481

except ParseError as e:

482

self.error_count += 1

483

self.logger.error(f"Parse error in chunk {chunk_count}: {e}")

484

errors.append(f"Parse error at chunk {chunk_count}: {e}")

485

486

if self.error_count >= self.max_errors:

487

raise Exception("Too many parse errors, aborting")

488

489

# Continue with next chunk

490

continue

491

492

parser.finalize()

493

parser.close()

494

495

return {

496

'success': True,

497

'fields': fields,

498

'files': files,

499

'errors': errors,

500

'error_count': self.error_count

501

}

502

503

except Exception as e:

504

self.logger.error(f"Fatal parsing error: {e}")

505

return {

506

'success': False,

507

'error': str(e),

508

'partial_fields': fields,

509

'partial_files': files,

510

'errors': errors

511

}

512

513

# Usage

514

parser = RobustFormParser()

515

# result = parser.parse_with_recovery(headers, stream)

516

```