Skip to content

Paths

With the path extra (pip install tftp[path]), TFTP files work with pathapi paths:

import tftp
from uripath import URIPath

client = tftp.TFTPClient("192.0.2.1", windowsize=8)
config = client.path("pxelinux.cfg", "default")
print(config.read_text())
config.with_name("default.bak").write_text(new_menu)

kernel = URIPath("tftp://192.0.2.1/images/vmlinuz")
print(kernel.stat().st_size)            # a size probe: nothing is transferred
kernel.copy("s3://boot-images/vmlinuz") # any uripath scheme

TFTP moves whole files, so open() (read or write), read_*/write_*, stat(), exists() and copy() work against any server, copy(dest, overwrite=True) included, and move() works to a TFTP path (a TFTP source cannot be deleted, so move() from one raises NotImplementedError before it copies anything). An upload that fails before it is closed sends nothing: a with block over open("w") or open("wb") that raises, a text write that cannot be encoded and a file collected unclosed leave the server's file as it was. stat() has size_known False when the server sent no tsize.

Against a pytftp server with listing allowed (pytftp serve --listing, or TFTPServerOptions(allowed=STANDARD_OPTIONS | LISTING_OPTIONS)), directories work too: iterdir(), is_dir(), walk(), glob("**/*.cfg"), copy(dest, recursive=True), and stat().st_mtime. Each directory costs one listing transfer, whose entries already carry type, size and time. Other servers report a directory as missing (FileNotFoundError).

Deleting (unlink()), renaming, creating directories, setting times and permissions are not implemented and raise NotImplementedError; ask first with pathapi.protocols.supports(path, Remove) or capabilities(path), which lists Stat, BinaryOpen, Listing, Identity and Copy. supports(path, Listing) asks the server (one probe of its root, kept for the client). Reads and writes stream through a bounded buffer, and errors arrive as the usual FileNotFoundError, PermissionError, FileExistsError.

TFTPURIPath is registered with uripath (the pathapi-uri distribution) through an entry point: URIPath("tftp://...") finds it without importing tftp. Tune the client with path.with_options(blksize=8192, windowsize=16).

Options in a URL

A tftp:// URL carries transfer options in either of two spellings. Whichever of ? and ; comes first after the file name decides how the rest is read: after ?, name=value pairs separated by &; after ;, name=value pairs separated by ;. These two URLs are equal:

from tftp import TFTPURL

a = TFTPURL.parse("tftp://192.0.2.1/images/vmlinuz?blksize=1428&windowsize=16")
b = TFTPURL.parse("tftp://192.0.2.1/images/vmlinuz;blksize=1428;windowsize=16")
assert a == b and a.options == {"blksize": "1428", "windowsize": "16"}
print(a)  # tftp://192.0.2.1/images/vmlinuz;blksize=1428;windowsize=16

str(url) always writes the ; spelling, mode first and only when it is not octet, then the options in name order, so a URL with a mode and no options is exactly RFC 3617's form. Port 0 means the default port, 69.

Options in a URL are this library's extension. RFC 3617 defines ;mode= and nothing else, curl looks for ;mode= only, and another tool reads the text after ? as part of the file name: a URL with options is for this library's own readers (TFTPURL, download_url, upload_url, pytftp get|put|ls and TFTPURIPath).

Name Meaning Value
mode the transfer mode octet or netascii
blksize TFTPClient(blksize=) mtu, or digits within 8..65464
windowsize TFTPClient(windowsize=) digits, 1..65535
timeout TFTPClient(timeout=) seconds, above 0
tsize TFTPClient(tsize=) 1, 0, true or false
rollover TFTPClient(rollover=) 0 or 1
any other requested verbatim (extra_options) any text

Names are compared without case. A value is percent-encoded text: write ;, &, ?, =, # and % inside a value as %3B, %26, %3F, %3D, %23 and %25. The other spelling's delimiter inside a list is refused (a literal ? after ;, or a literal ; after ?), as are a fragment, a repeated name, an empty pair, a pair with no = and a value the name cannot read.

An explicit keyword wins over the URL's option of the same name: download_url(url, dst, blksize=512), path.with_options(blksize=512) and pytftp get URL -b 512 all request 512 whatever the URL says. Extra options merge name by name the same way.