0
# Error Handling
1
2
Exception hierarchy for FTP operations including protocol errors, path validation, I/O errors, and connection issues. All exceptions inherit from AIOFTPException providing consistent error handling and detailed error information for debugging FTP operations.
3
4
## Capabilities
5
6
### Base Exception
7
8
Root exception class for all aioftp-related errors.
9
10
```python { .api }
11
class AIOFTPException(Exception):
12
"""
13
Base exception for all aioftp errors.
14
15
This is the root exception class that all other aioftp exceptions inherit from.
16
Catching this exception will catch all aioftp-specific errors.
17
"""
18
```
19
20
### Protocol Errors
21
22
Exceptions related to FTP protocol violations and unexpected server responses.
23
24
```python { .api }
25
class StatusCodeError(AIOFTPException):
26
"""
27
Raised when FTP server returns unexpected status code.
28
29
This exception is raised when the server returns a status code that doesn't
30
match what was expected for a particular FTP command. It includes detailed
31
information about both expected and received codes.
32
"""
33
34
expected_codes: tuple[Code, ...]
35
"""Status codes that were expected from the server."""
36
37
received_codes: tuple[Code, ...]
38
"""Status codes actually received from the server."""
39
40
info: Union[list[str], str]
41
"""Additional information from the server response."""
42
43
def __init__(expected_codes, received_codes, info):
44
"""
45
Initialize StatusCodeError.
46
47
Parameters:
48
- expected_codes: Codes that were expected
49
- received_codes: Codes actually received
50
- info: Server response information
51
"""
52
```
53
54
### Path Validation Errors
55
56
Exceptions for path-related validation and operations.
57
58
```python { .api }
59
class PathIsNotAbsolute(AIOFTPException):
60
"""
61
Raised when path is not absolute but should be.
62
63
Some operations require absolute paths. This exception is raised when
64
a relative path is provided where an absolute path is required.
65
"""
66
67
def __init__(path):
68
"""
69
Initialize PathIsNotAbsolute error.
70
71
Parameters:
72
- path: The invalid relative path that was provided
73
"""
74
75
class PathIOError(AIOFTPException):
76
"""
77
Universal exception for path I/O operations.
78
79
This exception wraps filesystem-related errors and provides additional
80
context about the operation that failed. It preserves the original
81
exception information for debugging.
82
"""
83
84
reason: Union[tuple, None]
85
"""Original exception information from the wrapped error."""
86
87
def __init__(reason):
88
"""
89
Initialize PathIOError.
90
91
Parameters:
92
- reason: Original exception info (usually from sys.exc_info())
93
"""
94
```
95
96
### Connection and Port Errors
97
98
Exceptions related to network connections and port availability.
99
100
```python { .api }
101
class NoAvailablePort(AIOFTPException, OSError):
102
"""
103
Raised when no data ports are available for FTP data connections.
104
105
This exception is raised when the server cannot find an available port
106
from the configured data port range for establishing data connections.
107
Inherits from both AIOFTPException and OSError for compatibility.
108
"""
109
110
def __init__(message="No available ports"):
111
"""
112
Initialize NoAvailablePort error.
113
114
Parameters:
115
- message: Error message describing the port availability issue
116
"""
117
```
118
119
## Usage Examples
120
121
### Basic Error Handling
122
123
```python
124
import aioftp
125
import asyncio
126
127
async def handle_basic_errors():
128
try:
129
async with aioftp.Client.context("ftp.example.com") as client:
130
await client.upload("local_file.txt", "remote_file.txt")
131
132
except aioftp.StatusCodeError as e:
133
print(f"FTP protocol error: {e}")
134
print(f"Expected: {e.expected_codes}, Got: {e.received_codes}")
135
print(f"Server message: {e.info}")
136
137
except aioftp.PathIOError as e:
138
print(f"File system error: {e}")
139
if e.reason:
140
print(f"Original error: {e.reason}")
141
142
except aioftp.AIOFTPException as e:
143
print(f"General FTP error: {e}")
144
145
except Exception as e:
146
print(f"Unexpected error: {e}")
147
148
asyncio.run(handle_basic_errors())
149
```
150
151
### Specific Error Types
152
153
```python
154
import aioftp
155
import asyncio
156
157
async def handle_specific_errors():
158
try:
159
async with aioftp.Client.context("ftp.example.com") as client:
160
# This might raise StatusCodeError if file doesn't exist
161
await client.download("nonexistent_file.txt", "local_file.txt")
162
163
except aioftp.StatusCodeError as e:
164
if "550" in str(e.received_codes):
165
print("File not found on server")
166
elif "426" in str(e.received_codes):
167
print("Connection closed during transfer")
168
else:
169
print(f"Other protocol error: {e}")
170
171
except aioftp.PathIsNotAbsolute as e:
172
print(f"Path must be absolute: {e}")
173
174
except aioftp.NoAvailablePort as e:
175
print(f"Server has no available data ports: {e}")
176
177
asyncio.run(handle_specific_errors())
178
```
179
180
### Server Error Handling
181
182
```python
183
import aioftp
184
import asyncio
185
from pathlib import Path
186
187
async def server_error_handling():
188
try:
189
users = [aioftp.User(
190
login="test",
191
password="test",
192
base_path=Path("/nonexistent/path") # This might cause issues
193
)]
194
195
server = aioftp.Server(users=users)
196
await server.run(host="localhost", port=21)
197
198
except aioftp.PathIOError as e:
199
print(f"Server path error: {e}")
200
print("Check that base paths exist and are accessible")
201
202
except aioftp.NoAvailablePort as e:
203
print(f"Port binding error: {e}")
204
print("Try a different port or check port availability")
205
206
except aioftp.AIOFTPException as e:
207
print(f"Server startup error: {e}")
208
209
asyncio.run(server_error_handling())
210
```
211
212
### Custom Error Handling with Retry Logic
213
214
```python
215
import aioftp
216
import asyncio
217
import logging
218
219
async def robust_ftp_operation():
220
"""Example with retry logic and comprehensive error handling."""
221
222
max_retries = 3
223
retry_delay = 1.0
224
225
for attempt in range(max_retries):
226
try:
227
async with aioftp.Client.context("ftp.example.com") as client:
228
await client.upload("important_file.txt", "backup.txt")
229
print("Upload successful!")
230
return
231
232
except aioftp.StatusCodeError as e:
233
if "550" in str(e.received_codes):
234
# Permission denied or file not found - don't retry
235
print(f"Permanent error: {e}")
236
break
237
elif "4" in str(e.received_codes)[0]:
238
# Temporary error (4xx codes) - retry
239
print(f"Temporary error (attempt {attempt + 1}): {e}")
240
if attempt < max_retries - 1:
241
await asyncio.sleep(retry_delay)
242
continue
243
244
except aioftp.PathIOError as e:
245
print(f"Local file error: {e}")
246
break # Don't retry file system errors
247
248
except (ConnectionError, OSError) as e:
249
# Network errors - retry
250
print(f"Network error (attempt {attempt + 1}): {e}")
251
if attempt < max_retries - 1:
252
await asyncio.sleep(retry_delay)
253
continue
254
255
except aioftp.AIOFTPException as e:
256
print(f"FTP error: {e}")
257
break
258
259
print("All retry attempts failed")
260
261
asyncio.run(robust_ftp_operation())
262
```
263
264
### Error Logging and Monitoring
265
266
```python
267
import aioftp
268
import asyncio
269
import logging
270
271
# Configure logging
272
logging.basicConfig(level=logging.INFO)
273
logger = logging.getLogger(__name__)
274
275
async def monitored_ftp_operation():
276
"""Example with comprehensive error logging."""
277
278
try:
279
async with aioftp.Client.context("ftp.example.com") as client:
280
# Log successful connection
281
logger.info("Connected to FTP server")
282
283
await client.upload("data.txt", "remote_data.txt")
284
logger.info("File uploaded successfully")
285
286
except aioftp.StatusCodeError as e:
287
logger.error(
288
"FTP protocol error: expected %s, got %s, info: %s",
289
e.expected_codes, e.received_codes, e.info
290
)
291
# Could send alert to monitoring system here
292
293
except aioftp.PathIOError as e:
294
logger.error("File system error: %s", e)
295
if e.reason:
296
logger.debug("Original exception: %s", e.reason)
297
298
except aioftp.NoAvailablePort as e:
299
logger.critical("No available ports for FTP data connection: %s", e)
300
# Critical infrastructure issue
301
302
except aioftp.AIOFTPException as e:
303
logger.error("General FTP error: %s", e)
304
305
except Exception as e:
306
logger.exception("Unexpected error in FTP operation")
307
308
asyncio.run(monitored_ftp_operation())
309
```
310
311
## Error Code Patterns
312
313
Common FTP status codes and their meanings:
314
315
- **1xx (Positive Preliminary)**: Command accepted, another command expected
316
- **2xx (Positive Completion)**: Command completed successfully
317
- **3xx (Positive Intermediate)**: Command accepted, more information needed
318
- **4xx (Transient Negative)**: Temporary failure, command may be retried
319
- **5xx (Permanent Negative)**: Permanent failure, command should not be retried
320
321
Common specific codes:
322
- **425**: Can't open data connection
323
- **426**: Connection closed; transfer aborted
324
- **450**: File unavailable (e.g., file busy)
325
- **451**: Local error in processing
326
- **550**: File unavailable (e.g., file not found, no access)
327
- **552**: Exceeded storage allocation
328
- **553**: File name not allowed
329
330
## Best Practices
331
332
1. **Always catch AIOFTPException** as a fallback for any aioftp-specific errors
333
2. **Check status codes** in StatusCodeError to determine if operations should be retried
334
3. **Log error details** including expected vs received codes for debugging
335
4. **Handle PathIOError separately** as it indicates local filesystem issues
336
5. **Use appropriate retry logic** for temporary errors (4xx codes)
337
6. **Don't retry permanent errors** (5xx codes) automatically