pythonpython-watchdog

Python Script won't run against all files, and won't end when I hit Ctrl-C


I'm new to Python, and I'm having a problem with executing some code with Watchdog. The code is supposed to copy files to their respective folders when they're modified or created. It will work against one file, but then it quits if there are more files matching. I also can't stop the program with Ctrl-C for some reason. Full code below:

import os
import os.path
import shutil
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from watchdog.events import PatternMatchingEventHandler

sourcepath='C:/Users/bhart/Downloads/'
sourcefiles = os.listdir(sourcepath)
destinationpath = 'C:/Users/bhart/Downloads/xls'
destinationpathcsv = 'C:/Users/bhart/Downloads/csv'
destinationpathtxt = 'C:/Users/bhart/Downloads/txt'
destinationpathpdf = 'C:/Users/bhart/Downloads/pdf'
path = sourcepath
event_handler = FileSystemEventHandler()






def on_created(event):

    

    for file in sourcefiles:
            if os.path.exists(file):
                if file.endswith('.xls') or file.endswith('.xlsx'):
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpath,file))
                if file.endswith('.csv'):
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpathcsv,file))
                    print("CSV file moved.")
                if file.endswith('.txt'):
                    print("TXT file moved")
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpathtxt,file))
                if file.endswith('.pdf'):
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpathpdf,file))


def on_modified(event):



    for file in sourcefiles:
            if os.path.exists(file):
                if file.endswith('.xls') or file.endswith('.xlsx'):
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpath,file))
                if file.endswith('.csv'):
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpathcsv,file))
                if file.endswith('.txt'):
                    print("TXT file moved")
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpathtxt,file))
                if file.endswith('.pdf'):
                    shutil.move(os.path.join(sourcepath,file), os.path.join(destinationpathpdf,file))



if __name__ == "__main__":

    event_handler.on_modified = on_modified
    observer = Observer()
    observer.start()
    observer.schedule(event_handler, path, recursive=True)
    observer.join()

    event_handler.on_created = on_created
    observer = Observer()
    observer.start()
    observer.schedule(event_handler, path, recursive=True)
    observer.join()

    try:
        print("test")
    except KeyboardInterrupt:
        exit()

Solution

  • I don't know if I resolve all problems but:


    listdir() gives filenames without directory and you have to use os.path.join() even when you check os.path.exists()

    if os.path.exists( os.path.join(sourcepath, file) ):
    

    listdir() gives filenames only once and you have to use it inside for-loop to get new filenames.

    def on_created(event):
    
        sourcefilenames = os.listdir(sourcepath)
    
        for filename in sourcefilenames:
    
            src = os.path.join(sourcepath, filename)
    
            if os.path.exists(src):
    
                 # ... code ...
    
    
    def on_modified(event):
    
        sourcefilenames = os.listdir(sourcepath)
    
        for filename in sourcefilenames:
    
            src = os.path.join(sourcepath, filename)
    
            if os.path.exists(src):
    
                 # ... code ...
    

    .join() blocks code and wait until you close program so it create first Observer and wait for its end before it create second Observer - but you could do all with one Observer

    It seems you have the same code in on_created and on_modified so you could use one function for both situations

    def move_it(event):
        sourcefilenames = os.listdir(sourcepath)
    
        for filename in sourcefilenames:
    
            src = os.path.join(sourcepath, filename)
    
            if os.path.exists(src):
    
                 # ... code ...
    
    if __name__ == "__main__":
    
        event_handler = FileSystemEventHandler()
        event_handler.on_modified = move_it
        event_handler.on_created  = move_it
        
        observer = Observer()
        observer.start()
        observer.schedule(event_handler, sourcepath, recursive=True)
        observer.join()
    

    If you want to catch Ctrl+C then you should put all code in try/except (or at least put join() inside try/except).

    I don't know what problem you have with Ctrl+C but it works for me on Linux.

    if __name__ == "__main__":
    
        try:
            event_handler = FileSystemEventHandler()
            event_handler.on_modified = move_it
            event_handler.on_created  = move_it
            
            observer = Observer()
            observer.start()
            observer.schedule(event_handler, sourcepath, recursive=True)
            observer.join()
        except KeyboardInterrupt:
            print('Stopped by Ctrl+C')
    

    One suggestion:

    Code can be much simpler and more universal if you will use dictionary

    {
       ".xls": "C:/.../xls", 
       ".xlsx": "C:/.../xls", 
       # ...
    }
    

    This way you can use for-loop to check all extensions. And you can always add new extension to dictionary without changing code in functions.

    import os
    import shutil
    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
    
    sourcepath = 'C:/Users/bhart/Downloads/'
    
    destinationpath = {
        '.xls' : 'C:/Users/bhart/Downloads/xls',
        '.xlsx': 'C:/Users/bhart/Downloads/xls',    
        '.csv' : 'C:/Users/bhart/Downloads/csv',
        '.txt' : 'C:/Users/bhart/Downloads/txt',
        '.pdf' : 'C:/Users/bhart/Downloads/pdf',
    }
    
    def move_it(event):
    
        sourcefilenames = os.listdir(sourcepath)
    
        for filename in sourcefilenames:
            src = os.path.join(sourcepath, filename)
            if os.path.exists(src):
                for ext, dst in destinationpath.items():
                    if filename.lower().endswith(ext):
                        print('move:', filename, '->', dst)
                        shutil.move(src, os.path.join(dst, filename))
    
    if __name__ == "__main__":
    
        try:
            event_handler = FileSystemEventHandler()
            event_handler.on_modified = move_it
            event_handler.on_created  = move_it
            
            observer = Observer()
            observer.start()
            observer.schedule(event_handler, sourcepath, recursive=True)
            observer.join()
        except KeyboardInterrupt:
            print('Stopped by Ctrl+C')
    

    EDIT:

    event gives event.src_path, event.event_type, ect. and you could use it instead of listdir() to get path to file.

    import os
    import shutil
    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
    
    sourcepath = 'C:/Users/bhart/Downloads/'
    
    destinationpath = {
        '.xls' : 'C:/Users/bhart/Downloads/xls',
        '.xlsx': 'C:/Users/bhart/Downloads/xls',    
        '.csv' : 'C:/Users/bhart/Downloads/csv',
        '.txt' : 'C:/Users/bhart/Downloads/txt',
        '.pdf' : 'C:/Users/bhart/Downloads/pdf',
    }
    
    def move_it(event):
        #print(dir(event))
        #print('event:', event)
        #print('event_type:', event.event_type)
        #print('is_directory:', event.is_directory)
        #print('src_path:', event.src_path)
        #print('key:', event.key)
        #print('----')
    
        if not event.is_directory:
    
            parts = os.path.split(event.src_path)
            #print('parts:', parts)
            filename = parts[-1]
            
            for ext, dst in destinationpath.items():
                if filename.lower().endswith(ext):
                    shutil.move(event.src_path, os.path.join(dst, filename))
                    print('move:', filename, '->', dst)
    
    if __name__ == "__main__":
    
        try:
            event_handler = FileSystemEventHandler()
            event_handler.on_modified = move_it
            event_handler.on_created  = move_it
            #event_handler.on_moved    = move_it  # ie. rename (but this need to check `dest_path`)
            
            observer = Observer()
            observer.start()
            observer.schedule(event_handler, sourcepath, recursive=True)
            observer.join()
        except KeyboardInterrupt:
            print('Stopped by Ctrl+C')