Walk a project tree without following directory symlinks

Direct answer

os.walk(root, followlinks=False) avoids descending through directory symlinks. The example also removes symlink entries from the mutable directory list before descent, making its exclusion policy explicit. A regular src directory is traversed; vendor-link, which points to a separate temporary tree, is skipped.

The assertion checks the relative paths actually observed. Seeing only src/main.py confirms that the synthetic outside file was not included in this walk. Relative paths keep the result independent of the temporary directory name.

This example requires permission to create a symlink. Its expected output describes the successful symlink setup; if the platform rejects that operation, it prints symlink=unavailable instead. That is an unsupported fixture, not a successful traversal test. The rule also says nothing about symlinks to individual files or filesystem changes during a scan. Apply a separate file-opening policy before reading discovered content; a traversal option does not create a security sandbox.

Complete example

import os
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    root = Path(directory) / "project"
    outside = Path(directory) / "outside"
    (root / "src").mkdir(parents=True)
    outside.mkdir()
    (root / "src" / "main.py").write_text("pass\n", encoding="utf-8")
    (outside / "secret.py").write_text("secret = 1\n", encoding="utf-8")
    try:
        (root / "vendor-link").symlink_to(outside, target_is_directory=True)
    except OSError:
        print("symlink=unavailable")
    else:
        seen = []
        for current, directories, names in os.walk(root, followlinks=False):
            for name in directories[:]:
                if (Path(current) / name).is_symlink():
                    directories.remove(name)
            seen.extend((Path(current) / name).relative_to(root).as_posix() for name in names)
        assert seen == ["src/main.py"]
        print("files=src/main.py skipped=vendor-link")

Expected stdout (for a platform supporting the demonstrated operation):

files=src/main.py skipped=vendor-link

Sources

- os.walk

- pathlib.Path.symlink_to

Prepared with AI assistance. The example uses synthetic data; its stated limits apply.

Comments

Popular posts from this blog

Compare Two Text Files in Python Without Modifying Them

Seven CSV Quality Checks to Run Before Importing Data

Explain why SQLite transactions cannot make an HTTP call atomic