Recursively listing the content of an FTP server Today I found myself in need of programmatically getting a recursive list of the files on a remote FTP server to look for some data archives. Much to my surprise, I could not find this functionality implemented in some kind of Python library. So I decided to write a simple recursive version of ftplib.mlsd, that can be found below:

ftphelper.py

def recursive_mlsd(ftp_object, path="", maxdepth=None):
    """Run the FTP's MLSD command recursively

    The MLSD is returned as a list of tuples with (name, properties) for each
    object found on the FTP server. This function adds the non-standard
    property "children" which is then again an MLSD listing, possibly with more
    "children".

    Parameters
    ----------
    ftp_object: ftplib.FTP or ftplib.FTP_TLS
        the (authenticated) FTP client object used to make the calls to the
        server
    path: str
        path to start the recursive listing from
    maxdepth: {None, int}, optional
        maximum recursion depth, has to be &gt;= 0 or None (i.e. no limit).

    Returns
    -------
    list
        the recursive directory listing

    See also
    --------
    ftplib.FTP.mlsd : the non-recursive version of this function
    """
    if maxdepth is not None:
        maxdepth = int(maxdepth)
        if maxdepth &lt; 0:
            raise ValueError("maxdepth is supposed to be &gt;= 0")

    def _inner(path_, depth_):
        if maxdepth is not None and depth_ &gt; maxdepth:
            return

        inner_mlsd = list(ftp_object.mlsd(path=path_))
        for name, properties in inner_mlsd:
            if properties["type"] == "dir":
                rec_path = path_+"/"+name if path_ else name
                res = _inner(rec_path, depth_+1)
                if res is not None:
                    properties["children"] = res

        return inner_mlsd

    return _inner(path, 0)


A very basic example usage could be like:

import ftplib
import getpass
import pprint
import sys

import ftphelper


host = "ftp1.at.proftpd.org"   # from the Python docs
directory = "historic"
print(f"Login to '{host}' to list '{directory}'", file=sys.stderr)

default_user = ""   # getpass.getuser() could also be used where appropriate
prompt = f"Username (default: '{default_user}'): "
if sys.stdout.isatty():
    user = input(prompt)
else:
    print(prompt, end="", file=sys.stderr)
    user = input()
user = user.strip()
user = user if user else default_user

with ftplib.FTP(host=host) as ftp:
    ftp.login(user=user, passwd=getpass.getpass())
    listing = ftphelper.recursive_mlsd(ftp, directory)

pp = pprint.PrettyPrinter(indent=2)
pp.pprint(listing)


As always, every kind of feedback is welcome and much appreciated.
Seems good except for a few small improvements:


path_+"/"+name is better perceived with f-string formatting: f'{path_}/{name} 
user if user else default_user is a verbose version of user or default_user