Danylo Fedorov
Danylo Fedorov

Reputation: 1091

Easy way to determine a type of file (regular file, directory, symlink etc.) by path in python

I wanted an easy way to determine a type of path so I googled alot and then I wrote this:

from stat import S_ISREG, S_ISDIR, S_ISLNK
from os import stat, lstat
from os.path import isfile, islink, isdir, lexists, exists
from enum import Enum, auto

class FileTypes(Enum):
    FILE = auto()
    LINK_TO_FILE = auto()
    DIR = auto()
    LINK_TO_DIR = auto()
    BROKEN_LINK = auto()
    NO_SUCH = auto()
    UNDEFINED = auto()

def file_type(filename):
    if lexists(filename):
        if isfile(filename):
            if islink(filename):
                return FileTypes.LINK_TO_FILE
            else:
                return FileTypes.FILE
        else:
            if isdir(filename):
                if islink(filename):
                    return FileTypes.LINK_TO_DIR
                else:
                    return FileTypes.DIR
            else:
                if islink(filename):
                    return FileTypes.BROKEN_LINK
                else:
                    return FileTypes.UNDEFINED
    else:
        return FileTypes.NO_SUCH

Then I googled more and wrote this:

def file_type2(filename):
    if lexists(filename):
        if exists(filename):
            mode = stat(filename).st_mode
            lmode = lstat(filename).st_mode # os.lstat doesn't follow symlinks
            if S_ISREG(mode) and S_ISREG(lmode):
                return FileTypes.FILE
            elif S_ISREG(mode) and S_ISLNK(lmode):
                return FileTypes.LINK_TO_FILE
            elif S_ISDIR(mode) and S_ISDIR(lmode):
                return FileTypes.DIR
            elif S_ISDIR(mode) and S_ISLNK(lmode):
                return FileTypes.LINK_TO_DIR
            else:
                return FileTypes.UNDEFINED
        else:
            return FileTypes.BROKEN_LINK
    else:
        return FileTypes.NO_SUCH

Both functions do what I want, but look kinda ugly and I think that I'm missing a simpler solution hiding in some cool python lib.

Question is: Is there a better way to do this?

Upvotes: 0

Views: 786

Answers (2)

Tomasz Plaskota
Tomasz Plaskota

Reputation: 1367

We can make it more consise with utilizing bitmasking inside enum:

Let's assume first value describes if file exists: 0 for existing and 1 for not existing, second one will be symlink: 1 for links and 0 for non-links, third for directory: 1 if it is directory and 0 if it is not, and the last for file in hte same menner.

So if we wanted to describe file that existsand is a symlink to file, we would use 0(exists)1(link)0(non-dir)1(file)

With using meaningful values we can now consisely chain those values together with results returned from python stat wrapper.

class FileTypes(Enum):
    FILE = 1 #0001
    LINK_TO_FILE = 5 #0101
    DIR = 2 #0010
    LINK_TO_DIR = 6 #0110
    BROKEN_LINK = 4 #0100
    NO_SUCH = 0 #1000
    UNDEFINED = #0000

def file_type(filepath):
    return FileTypes.NO_SUCH if lexists(filepath) else 
        Filetypes(int(
            str(int(islink(filepath)))
          + str(int(isdir(filepath)))
          + str(int(isfile(filepath)))))

Obviously there is issue of some illegal states like if something would report that it is both directory and file, at that point this will raise exception, it can be modified for different behaviour, but raising exception seems perfectly valid.

Also I've used pretty ugly way of adding together this value, but this is for sake of readibility. You always could do ''.join(map(str,map(int,[islink(filepath),isdir(filepath),isfile(filepath)]))) or even shorter ways

Upvotes: 0

Eugene Yarmash
Eugene Yarmash

Reputation: 149736

You can try the pathlib module which has been in stdlib since Python 3.4 (for older pythons use pip install pathlib). It defines the Path class which contains methods for both checking types of files as well as resolving symlinks. Besides, it provides a pretty convenient API:

>>> from pathlib import Path
>>> path = Path("/etc/") / "passwd"
>>> path
PosixPath('/etc/passwd')
>>> path.is_file()
True

Upvotes: 1

Related Questions