tftpy
Tftpy is a pure Python TFTP implementation that includes both client and server classes. It supports RFCs 1350, 2347, 2348, and the tsize option from RFC 2349, with hooks for progress indicators. The current version is 0.8.7, released on February 4, 2026. The project is actively maintained, with regular updates and bug fixes.
Common errors
-
TypeError: unsupported operand type(s) for %: 'bytes' and 'int'
cause This error occurs in older Python 3 environments (e.g., 3.4.6) due to changes in string formatting, specifically with bytes and integers when encoding TFTP packets.fixUpgrade your Python environment to 3.8 or newer, and ensure you are using a compatible tftpy version (0.8.0+). -
AttributeError: 'TftpContextClientUpload' object has no attribute 'sendDAT'
cause This error has been reported in older versions (e.g., 0.5.0) of tftpy, particularly during file upload operations. It indicates an incompatibility or bug in the state machine for uploads in those specific versions.fixUpgrade to the latest version of tftpy, as significant refactoring and bug fixes have been implemented since version 0.5.0. -
ERROR:tftpy:Received OACK in state ack / WARNING:tftpy:Timeout waiting for traffic, retrying...
cause These messages often indicate network unreliability, packet loss, or an issue with the remote TFTP server's response handling. It can also occur if a TFTP client sends OACK packets with blank or unexpected options, which older tftpy versions might not handle gracefully.fixCheck network connectivity and latency. Ensure your TFTP server (if external) is properly configured and responsive. If using an older tftpy client, update to the latest version, as robustness for unreliable networks and buggy clients (including handling of blank OACK options) has been improved in recent versions. -
tftpy.TftpException: The tftproot must be a directory.
cause The directory path provided to the `tftpy.TftpServer` constructor either does not exist or is not a directory.fixEnsure the directory you intend to serve files from exists and is accessible before initializing `TftpServer`. You can create it programmatically using `os.makedirs(path, exist_ok=True)`.
Warnings
- breaking Older versions of tftpy (prior to 0.8.0) did not fully support Python 3.x, leading to compatibility issues. Version 0.8.0 introduced Python 3.x support, and current versions require Python >=3.8.
- gotcha When initializing TftpServer, the `root` directory provided must exist and be a valid directory. If it doesn't exist, a `TftpException` will be raised.
- gotcha Attempting to bind a TFTP server to the standard TFTP port (69) on most operating systems will require root or administrator privileges. Without these, a permission error (e.g., `OSError: [Errno 13] Permission denied`) will occur.
- deprecated In version 0.8.1, the use of `log.warn()` was replaced with `log.warning()` to align with standard Python logging practices, fixing a security issue related to breaking out of the tftproot.
Install
-
pip install tftpy
Imports
- TftpClient
import tftpy client = tftpy.TftpClient(...)
- TftpServer
import tftpy server = tftpy.TftpServer(...)
Quickstart
import tftpy
import os
import threading
import time
# --- Server Setup ---
# Create a temporary directory for the TFTP server root
server_root_dir = 'tftp_server_root'
os.makedirs(server_root_dir, exist_ok=True)
with open(os.path.join(server_root_dir, 'testfile.txt'), 'w') as f:
f.write('Hello from TFTP server!')
def start_server():
print(f"Starting TFTP server on 0.0.0.0:6969 with root {server_root_dir}")
server = tftpy.TftpServer(server_root_dir)
# Use a non-privileged port for testing
try:
server.listen('0.0.0.0', 6969, timeout=2)
except Exception as e:
print(f"Server error: {e}")
finally:
print("Server stopped.")
os.remove(os.path.join(server_root_dir, 'testfile.txt'))
os.rmdir(server_root_dir)
server_thread = threading.Thread(target=start_server)
server_thread.daemon = True # Allow main program to exit even if server is running
server_thread.start()
# Give the server a moment to start
time.sleep(1)
# --- Client Usage ---
print("\n--- TFTP Client --- ")
client = tftpy.TftpClient('127.0.0.1', 6969)
local_download_path = 'downloaded_testfile.txt'
try:
print(f"Attempting to download 'testfile.txt' to '{local_download_path}'...")
client.download('testfile.txt', local_download_path)
if os.path.exists(local_download_path):
with open(local_download_path, 'r') as f:
content = f.read()
print(f"Download successful! Content: '{content}'")
os.remove(local_download_path)
else:
print("Download failed: file not found locally.")
except tftpy.TftpException as e:
print(f"TFTP Client Error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
print("Client operations complete.")