views:

266

answers:

5

Hi folks. I'm working on an auto-reload feature for WHIFF http://whiff.sourceforge.net (so you have to restart the HTTP server less often, ideally never).

I have the following code to reload a package module "location" if a file is added to the package directory. It doesn't work on Windows XP. How can I fix it? I think the problem is that getmtime(dir) doesn't change on Windows when the directory content changes? I'd really rather not compare an os.listdir(dir) with the last directory content every time I access the package...

                if not do_reload and hasattr(location, "__path__"):
                    path0 = location.__path__[0]
                    if os.path.exists(path0):
                        dir_mtime = int( os.path.getmtime(path0) )
                        if fn_mtime<dir_mtime:
                            print "dir change: reloading package root", location
                            do_reload = True
                            md_mtime = dir_mtime

In the code the "fn_mtime" is the recorded mtime from the last (re)load.

... added comment: I came up with the following work around, which I think may work, but I don't care for it too much since it involves code generation. I dynamically generate a code fragment to load a module and if it fails it tries again after a reload. Not tested yet.

GET_MODULE_FUNCTION = """
def f():
    import %(parent)s
    try:
        from %(parent)s import %(child)s
    except ImportError:
        # one more time...
        reload(%(parent)s)
        from %(parent)s import %(child)s
    return %(child)s
"""

def my_import(partname, parent):
    f = None # for pychecker
    parentname = parent.__name__
    defn = GET_MODULE_FUNCTION % {"parent": parentname, "child": partname}
    #pr "executing"
    #pr defn
    try:
        exec(defn) # defines function f()
    except SyntaxError:
        raise ImportError, "bad function name "+repr(partname)+"?"
    partmodule = f()
    #pr "got", partmodule
    setattr(parent, partname, partmodule)
    #pr "setattr", parent, ".", partname, "=", getattr(parent, partname)
    return partmodule

Other suggestions welcome. I'm not happy about this...

A: 

you can try using getatime() instead.

ghostdog74
It doesn't look like it. The atime is actually *before* the mtime >>> os.path.getatime("root") 1247371200.0 >>> os.path.getmtime("root") 1238624574.0
Aaron Watters
I'm sorry to object, but your atime is younger than your mtime:1247371200.0 - 1238624574.0 = 8746626.0Since these are seconds since the epoch, larger means younger, so atime was _after_ mtime.
ThomasH
whoops. Nevertheless when I create a file in the directory the atime doesn't change.
Aaron Watters
What about the mtime? This seems to be more relevant. Here is what I get on my XPsp2 machine:In [24]: t1=os.path.getmtime("d:/tmp")In [25]: t1Out[25]: 1247511520.796875In [26]: shutil.copyfile("d:/tmp/test.txt", "d:/tmp/test1.txt")In [27]: t2=os.path.getmtime("d:/tmp")In [28]: t2Out[28]: 1247511548.140625In [29]: t2 - t1Out[29]: 27.34375Looks perfect to me. It might not be necessary to read the directory in order to add an entry, so the atime need not change.
ThomasH
As I said, the mtime() for the directory doesn't change for the version of Windows XP I'm using, when you add or delete a file from the directory (at the top level).
Aaron Watters
Mh, which version of Python are you using? Could you repeat the steps from my previous comment in your local Python shell (sorry for the clutter but all the newlines got lost when posting the comment), and post the outcome?!
ThomasH
A: 

Maybe this will help you http://tgolden.sc.sabren.com/python/win32_how_do_i/watch_directory_for_changes.html

Cristian Ciupitu
Aaron Watters
I don't understand why do you want to limit yourself to a plain `listdir`. You could have a wrapper that uses the appropriate API for the current platform, e.g. `FindFirstChangeNotification` under win32 and `inotify` under Linux. By doing this, at least you're not reading from disk, unless you have to.
Cristian Ciupitu
I haven't looked into it but it looks to me that FindFirstChangeNotification modifies some hidden datastructure for the process with unknown implications --that's scary to me; maybe I'm just paranoid.I just want to look at a time stamp for the directoryto see if a file has been added or deleted since last time.Remember that the process may be accessing thousands ofmodules like this, conceivably.
Aaron Watters
I haven't noticed anything wrong/dangerous at this function. I suggest doing a Google code search to see how it's used in other programs so you'll get an idea of how dangerous it is. Btw http://www.kalab.com/freeware/pylogviewer/pylogviewer.htm seems to use it.
Cristian Ciupitu
A: 

I'm not understanding your question completely...

Are you calling getmtime() on a directory or an individual file?

CMB
I'm calling getmtime on a directory which is the package directoryfor a module.
Aaron Watters
and looking to see if any item in the directory was modified....and desiring a universal pythonic solution.Meh..either the above win32 specific solution (blech code to maintain, additional complexity to break things)..or ..an os.listdir accounting type system (unelegent, could be universal)Maybe you could stat the directory or (contents if need be) ...assuming os.stat works as desired (i.e. contents of directories affect the attribute of the directory itself)Good luck
CMB
Actually I only need to know if a file was added (maybe deleted).os.stat(dir) doesn't help. I tried it and it returned exactly the same values before and after adding the file to the directory.
Aaron Watters
Using os.stat(dir).st_mtime should give the same result as os.path.getmtime(). Just looking at os.stat(dir) gives abbreviated values.
ThomasH
As I said the stat() value for the directory doesn't change when you add a file to the directory at the top level on the version of Windows XP that is running on my laptop.
Aaron Watters
+2  A: 

Hi Aaron, long time no see. I'm not sure exactly what you're doing, but the equivalent of your code:

GET_MODULE_FUNCTION = """
def f():
    import %(parent)s
    try:
        from %(parent)s import %(child)s
    except ImportError:
        # one more time...
        reload(%(parent)s)
        from %(parent)s import %(child)s
    return %(child)s
"""

to be execed with:

defn = GET_MODULE_FUNCTION % {"parent": parentname, "child": partname}
exec(defn)

is (per the docs), assuming parentname names a package and partname names a module in that package (if partname is a top-level name of the parentname package, such as a function or class, you'll have to use a getattr at the end):

import sys

def f(parentname, partname):
    name = '%s.%s' % (parentname, partname)
    try:
        __import__(name)
    except ImportError:
        parent = __import__(parentname)
        reload(parent)
        __import__(name)
    return sys.modules[name]

without exec or anything weird, just call this f appropriately.

Alex Martelli
Thanks Alex. I think this is the work around I need. What I am doing is trying to make it so that if you have a WHIFF directory installed under (say) Apache/mod_wsgi you never need to restart Apache or mod_wsgi even if you add a new module. For packages checking the directory __mtime__ doesn't work for this on at least one version of Windows XP (the one on my laptop). So I guess if I get an import error I have to try a dynamic import. For other cases I can check the mtime of the source file and that seems to work.
Aaron Watters
Hi Aaron, yep looks like my workaround could work -- otherwise let us know, but if you can verify that it does work, then pls accept my answer as per normal SO etiquette!-)
Alex Martelli
A: 

There are two things about your first code snippet that concern me:

  • You cast the float from getmtime to int. Dependening on the frequency this code is run, you might get unreliable results.

  • At the end of the code you assign dir_mtime to a variable md_mtime. fn_mtime, which you check against, seems not to be updated.

ThomasH