0
# Utilities and Error Handling
1
2
Exception classes and utility functions for error handling and form analysis. These components provide robust error handling capabilities and helper functions for advanced form processing.
3
4
## Capabilities
5
6
### Exception Classes
7
8
Base exception classes for MechanicalSoup error handling.
9
10
```python { .api }
11
class LinkNotFoundError(Exception):
12
"""
13
Base exception for when MechanicalSoup fails to find elements.
14
15
Raised in scenarios such as:
16
- Link not found during navigation
17
- Form field not found
18
- 404 errors when raise_on_404=True in Browser
19
- Form or element selection failures
20
"""
21
22
class InvalidFormMethod(LinkNotFoundError):
23
"""
24
Exception raised when form method used on wrong element type.
25
26
Inherits from LinkNotFoundError and is typically raised when:
27
- Attempting form operations on non-form elements
28
- Invalid field type operations
29
- Form method validation failures
30
"""
31
```
32
33
**Usage Example:**
34
35
```python
36
import mechanicalsoup
37
38
browser = mechanicalsoup.StatefulBrowser(raise_on_404=True)
39
40
try:
41
# This will raise LinkNotFoundError if page not found
42
browser.open("https://httpbin.org/status/404")
43
except mechanicalsoup.LinkNotFoundError as e:
44
print(f"Page not found: {e}")
45
46
try:
47
# This will raise LinkNotFoundError if form not found
48
browser.select_form("nonexistent-form")
49
except mechanicalsoup.LinkNotFoundError as e:
50
print(f"Form selection failed: {e}")
51
52
try:
53
# This might raise InvalidFormMethod for invalid operations
54
browser.select_form()
55
browser.form.set("invalid_field", "value", force=False)
56
except mechanicalsoup.InvalidFormMethod as e:
57
print(f"Invalid form operation: {e}")
58
```
59
60
### Form Analysis Utilities
61
62
Utility functions for analyzing form elements and upload capabilities.
63
64
```python { .api }
65
def is_multipart_file_upload(form, tag):
66
"""
67
Check if form element is a multipart file upload.
68
69
Analyzes form encoding and input types to determine if the form
70
supports file uploads through multipart/form-data encoding.
71
72
Parameters:
73
- form: BeautifulSoup form element to check
74
- tag: BeautifulSoup input tag to analyze
75
76
Returns:
77
bool: True if element supports multipart file upload, False otherwise
78
"""
79
```
80
81
**Usage Example:**
82
83
```python
84
import mechanicalsoup
85
from mechanicalsoup import is_multipart_file_upload
86
87
browser = mechanicalsoup.StatefulBrowser()
88
browser.open("https://httpbin.org/forms/post")
89
90
form_element = browser.page.find("form")
91
file_inputs = form_element.find_all("input", type="file")
92
93
for input_tag in file_inputs:
94
if is_multipart_file_upload(form_element, input_tag):
95
print(f"File upload field found: {input_tag.get('name')}")
96
else:
97
print(f"Regular input field: {input_tag.get('name')}")
98
```
99
100
## Error Handling Patterns
101
102
### Graceful Navigation Error Handling
103
104
Handle navigation and page loading errors gracefully.
105
106
```python
107
import mechanicalsoup
108
109
def safe_navigate(browser, url, max_retries=3):
110
"""
111
Safely navigate to URL with retry logic and error handling.
112
"""
113
for attempt in range(max_retries):
114
try:
115
response = browser.open(url)
116
if response.status_code == 200:
117
return response
118
else:
119
print(f"HTTP {response.status_code} on attempt {attempt + 1}")
120
except mechanicalsoup.LinkNotFoundError as e:
121
print(f"Navigation failed on attempt {attempt + 1}: {e}")
122
if attempt == max_retries - 1:
123
raise
124
except Exception as e:
125
print(f"Unexpected error on attempt {attempt + 1}: {e}")
126
if attempt == max_retries - 1:
127
raise
128
129
return None
130
131
# Usage
132
browser = mechanicalsoup.StatefulBrowser(raise_on_404=True)
133
try:
134
response = safe_navigate(browser, "https://httpbin.org/status/500")
135
if response:
136
print("Navigation successful")
137
except mechanicalsoup.LinkNotFoundError:
138
print("All navigation attempts failed")
139
```
140
141
### Form Operation Error Handling
142
143
Handle form selection and field setting errors.
144
145
```python
146
import mechanicalsoup
147
148
def safe_form_fill(browser, form_data, form_selector="form"):
149
"""
150
Safely fill form with comprehensive error handling.
151
"""
152
try:
153
# Select form with error handling
154
browser.select_form(form_selector)
155
if not browser.form:
156
raise mechanicalsoup.LinkNotFoundError(f"No form found with selector: {form_selector}")
157
158
# Fill form fields with individual error handling
159
for field_name, field_value in form_data.items():
160
try:
161
browser[field_name] = field_value
162
print(f"Set {field_name} = {field_value}")
163
except mechanicalsoup.InvalidFormMethod as e:
164
print(f"Failed to set field {field_name}: {e}")
165
# Try force setting as fallback
166
try:
167
browser.form.set(field_name, field_value, force=True)
168
print(f"Force set {field_name} = {field_value}")
169
except Exception as force_error:
170
print(f"Force setting also failed for {field_name}: {force_error}")
171
172
return True
173
174
except mechanicalsoup.LinkNotFoundError as e:
175
print(f"Form selection failed: {e}")
176
return False
177
except Exception as e:
178
print(f"Unexpected form filling error: {e}")
179
return False
180
181
# Usage
182
browser = mechanicalsoup.StatefulBrowser()
183
browser.open("https://httpbin.org/forms/post")
184
185
form_data = {
186
"custname": "Test User",
187
"custtel": "555-0000",
188
"nonexistent_field": "test_value"
189
}
190
191
if safe_form_fill(browser, form_data):
192
try:
193
response = browser.submit_selected()
194
print("Form submitted successfully")
195
except Exception as e:
196
print(f"Form submission failed: {e}")
197
```
198
199
### Link Following Error Handling
200
201
Handle link discovery and following with fallback strategies.
202
203
```python
204
import mechanicalsoup
205
import re
206
207
def follow_link_safe(browser, search_criteria, fallback_url=None):
208
"""
209
Safely follow links with multiple search strategies and fallbacks.
210
"""
211
try:
212
# Try to find link by various criteria
213
link = None
214
215
if isinstance(search_criteria, str):
216
# Search by text
217
link = browser.find_link(link_text=search_criteria)
218
elif isinstance(search_criteria, dict):
219
# Search by multiple criteria
220
link = browser.find_link(**search_criteria)
221
222
if link:
223
browser.follow_link(link)
224
print(f"Successfully followed link to: {browser.url}")
225
return True
226
else:
227
print("Link not found with given criteria")
228
229
# Fallback to direct URL if provided
230
if fallback_url:
231
print(f"Using fallback URL: {fallback_url}")
232
browser.open(fallback_url)
233
return True
234
235
except mechanicalsoup.LinkNotFoundError as e:
236
print(f"Link following failed: {e}")
237
if fallback_url:
238
print(f"Using fallback URL: {fallback_url}")
239
browser.open(fallback_url)
240
return True
241
except Exception as e:
242
print(f"Unexpected link following error: {e}")
243
244
return False
245
246
# Usage
247
browser = mechanicalsoup.StatefulBrowser()
248
browser.open("https://httpbin.org/")
249
250
# Try multiple search strategies
251
search_strategies = [
252
"JSON", # Search by text
253
{"url_regex": re.compile(r"/json")}, # Search by URL pattern
254
{"href": "/json"} # Search by exact href
255
]
256
257
for strategy in search_strategies:
258
if follow_link_safe(browser, strategy, fallback_url="https://httpbin.org/json"):
259
break
260
```
261
262
### File Upload Handling
263
264
Handle file upload forms with proper multipart detection.
265
266
```python
267
import mechanicalsoup
268
from mechanicalsoup import is_multipart_file_upload
269
import os
270
271
def handle_file_upload_form(browser, file_field_name, file_path, form_data=None):
272
"""
273
Handle file upload forms with proper validation and error handling.
274
"""
275
try:
276
# Ensure form is selected
277
if not browser.form:
278
browser.select_form()
279
280
form_element = browser.form.form
281
file_input = form_element.find("input", {"name": file_field_name, "type": "file"})
282
283
if not file_input:
284
raise mechanicalsoup.InvalidFormMethod(f"File input '{file_field_name}' not found")
285
286
# Verify multipart capability
287
if not is_multipart_file_upload(form_element, file_input):
288
print("Warning: Form may not support file uploads properly")
289
290
# Check file exists
291
if not os.path.exists(file_path):
292
raise FileNotFoundError(f"File not found: {file_path}")
293
294
# Fill other form fields if provided
295
if form_data:
296
for field_name, field_value in form_data.items():
297
browser[field_name] = field_value
298
299
# Handle file upload (typically done through requests)
300
with open(file_path, 'rb') as file:
301
files = {file_field_name: file}
302
response = browser.session.post(
303
browser.absolute_url(form_element.get('action', '')),
304
files=files,
305
data={input.get('name'): input.get('value', '')
306
for input in form_element.find_all('input')
307
if input.get('type') != 'file'}
308
)
309
310
print(f"File upload successful: {response.status_code}")
311
return response
312
313
except mechanicalsoup.InvalidFormMethod as e:
314
print(f"Invalid form method for file upload: {e}")
315
except FileNotFoundError as e:
316
print(f"File error: {e}")
317
except Exception as e:
318
print(f"File upload error: {e}")
319
320
return None
321
322
# Usage example (conceptual - would need actual file upload form)
323
browser = mechanicalsoup.StatefulBrowser()
324
# browser.open("https://file-upload-form.example.com")
325
#
326
# response = handle_file_upload_form(
327
# browser,
328
# file_field_name="document",
329
# file_path="/path/to/document.pdf",
330
# form_data={"description": "Important document"}
331
# )
332
```
333
334
## Exception Hierarchy
335
336
```python
337
Exception
338
└── LinkNotFoundError
339
└── InvalidFormMethod
340
```
341
342
All MechanicalSoup-specific exceptions inherit from `LinkNotFoundError`, making it easy to catch all library-related errors:
343
344
```python
345
import mechanicalsoup
346
347
browser = mechanicalsoup.StatefulBrowser(raise_on_404=True)
348
349
try:
350
browser.open("https://httpbin.org/status/404")
351
browser.select_form("nonexistent")
352
browser["field"] = "value"
353
browser.submit_selected()
354
except mechanicalsoup.LinkNotFoundError as e:
355
# Catches both LinkNotFoundError and InvalidFormMethod
356
print(f"MechanicalSoup operation failed: {e}")
357
except Exception as e:
358
# Catches other unexpected errors
359
print(f"Unexpected error: {e}")
360
```
361
362
## Version Information
363
364
Access the MechanicalSoup package version.
365
366
```python { .api }
367
__version__: str
368
"""The version string of the installed MechanicalSoup package."""
369
```
370
371
**Usage Example:**
372
373
```python
374
import mechanicalsoup
375
376
# Check the installed version
377
print(f"MechanicalSoup version: {mechanicalsoup.__version__}")
378
379
# Use version for compatibility checks
380
from packaging import version
381
if version.parse(mechanicalsoup.__version__) >= version.parse("1.4.0"):
382
print("Using modern MechanicalSoup with latest features")
383
else:
384
print("Consider upgrading MechanicalSoup")
385
```