Skip to content
Python Basics: Inventory Directory Entries Without Following Symlinks

Python Basics: Inventory Directory Entries Without Following Symlinks

October 30, 2026

An inventory should state whether a symbolic link counts as a file or as a link. This example keeps those categories separate and sorts names so the report is repeatable.

Prepare a scratch directory

Tested on Linux with CPython 3.12.14. You need Bash, Python, a text editor, and the standard mktemp, rm, and rmdir utilities. No external packages are required. Run without -O so assertions remain enabled.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as scandir-symlinks-demo.py:

import os
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory(prefix="inventory-") as directory:
    root = Path(directory)
    (root / "report.txt").write_bytes(b"ready\n")
    (root / "archive").mkdir()
    (root / "latest").symlink_to("report.txt")
    (root / "missing").symlink_to("absent.txt")

    with os.scandir(root) as entries:
        for entry in sorted(entries, key=lambda item: item.name):
            if entry.is_symlink():
                kind = "symlink"
            elif entry.is_file(follow_symlinks=False):
                kind = f"file ({entry.stat(follow_symlinks=False).st_size} bytes)"
            elif entry.is_dir(follow_symlinks=False):
                kind = "directory"
            else:
                kind = "other"
            print(f"{entry.name}: {kind}")

    with os.scandir(root) as entries:
        links = {entry.name: entry for entry in entries if entry.is_symlink()}
        print("latest resolves to file:", links["latest"].is_file())
        print("missing resolves to file:", links["missing"].is_file())

Run it:

python3 -B scandir-symlinks-demo.py

Observed output:

archive: directory
latest: symlink
missing: symlink
report.txt: file (6 bytes)
latest resolves to file: True
missing resolves to file: False

Read the result

Both latest and the dangling missing entry remain links in the inventory. The later checks intentionally use the default link-following behavior: one reaches the regular file and the other has no target. The six-byte size includes the newline written to report.txt.

Apply it carefully

Directory iteration order is unspecified, so sort when a stable report matters. A DirEntry can cache metadata; do not retain entries as a permanently current view. Concurrent deletion or permission changes can cause errors, and checking a path before opening it does not prevent a race. This is an inventory of a private fixture, not a secure file-opening policy.

The official os.scandir reference documents the APIs used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory:

rm -- scandir-symlinks-demo.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on