Importing a python file into another

davidcullen's Avatar

davidcullen

16 Aug, 2021 07:58 AM

Dear All,
Please excuse this newbie question. I’m self-taught with python/jython (mainly using Google).
I’m trying to import a python file into another, which is then run under the Moneydance MoneyBot console, but I get problems which I cannot solve.
I’m running Mac OS Big Sur 11.5.2 and Moneydance 2021.1 (3069).
I have two files in my Documents/md folder.
The first is vars.py :

def greet1(who):
  print "Hello {}".format(who)
The second file is test.py :
import vars
vars.greet1('David')
When I try to run test.py under the Moneydance MoneyBot console, I get:
AttributeError: 'module' object has no attribute ‘greet1'
When I run things in the OSX Terminal app, everything works correctly.
What am I doing incorrectly (or what am I not doing)?
Any help would be gratefully received.
David
  1. 1 Posted by Stuart Beesley ... on 16 Aug, 2021 08:27 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    Take a look at: 'print globals()'

    This code worked:

    import importlib, os, sys
    pyfilepath = "/Users/stu/Documents/Moneydance/My Python Scripts/test_var.py"
    if not os.path.exists(pyfilepath): raise Exception("ERROR")
    dirname, basename = os.path.split(pyfilepath) # pyfilepath: '/my/path/mymodule.py'
    sys.path.append(dirname) # only directories should be added to PYTHONPATH
    module_name = os.path.splitext(basename)[0] # '/my/path/mymodule.py' --> 'mymodule'
    module = importlib.import_module(module_name) # name space of defined module (otherwise we would literally look for "module_name")
    for var in globals(): print var
    print ""
    print module.greet1("Stuart")
    
  2. 2 Posted by davidcullen on 17 Aug, 2021 02:25 AM

    davidcullen's Avatar

    Thanks, Stuart (as always).
    I thought it might have something to do with the path, but didn’t know about import_module.
    I’ll explore things four then when I get home later today.
    Thanks again…..

  3. 3 Posted by Stuart Beesley ... on 17 Aug, 2021 02:32 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    I didn’t play with the std ‘import’ as you probably need to specify a file path and for this you need to call importlib (which ‘import’ actually calls). I have read something else where you can import a path using ‘.’s instead of ‘/‘s to separate the path folders, but haven’t tried. Good luck.

  4. 4 Posted by davidcullen on 17 Aug, 2021 07:55 AM

    davidcullen's Avatar

    Stuart,
    Thanks for your help. Things are working now, but not quite.
    When I import the module as you suggest, with

    module = importlib.import_module(module_name)
    
    it seems to work, and:
    print dir(module)
    
    shows the module function, which I have renamed as greet(who)
    This then works fine when calling:
    module.greet(“Stuart")
    
    My problem is that if I then rename the module function to, say, greet2(), the ** print dir(module)** command continue to show the (now renamed) greet() and not the new greet2().
    Calling module.greet(“Stuart") continues to work (even though the function has been renamed) but calling module.greet2(“Stuart” fails with:
    AttributeError: 'module' object has no attribute ‘greet2'
    
    It would seem that the old module has been cached? but I don’t know how to force it to refresh to reflect the new function edit?
  5. 5 Posted by Stuart Beesley ... on 17 Aug, 2021 08:04 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    I can't help without seeing your actual code. I suspect you are trying to import the same module twice, which won't work....

  6. 6 Posted by davidcullen on 18 Aug, 2021 02:40 AM

    davidcullen's Avatar

    Stuart,
    Thanks for the hint. After googling, I was able to solve things with:

    module = imp.reload(module)
    
    Not sure if there is a better (more correct) way.
  7. 7 Posted by Stuart Beesley ... on 18 Aug, 2021 04:42 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    I’m guessing that you would not really rename a module during the life of a runtime script? Without knowing what you are trying to do or why it’s difficult to comment further. As you say, reload, reruns the import file if that’s what you want to do.

  8. 8 Posted by davidcullen on 18 Aug, 2021 09:36 AM

    davidcullen's Avatar

    You are correct that I do not really want to rename a module during the life of a runtime script (once the module has been written).
    I am currently writing the module and testing it on the fly in Moneydance MoneyBot console and as I go I make editing changes to the module which I then test to see that they are working.
    Your earlier code has helped me with the initial loading of my module, but I need to call imp.reload(module) to test that my edit changes correctly flow though.
    Once things are finalised, I no longer need to reload things and your code then works fine.
    I hope this makes things clearer, Thanks for your guidance.
    PS: one final thing: my module depends on the moneydance variable (moneydance.getCurrentAccount().getAccountByName('name')) but I am getting an error message that global moneydance variable is not defined (even though (moneydance.getCurrentAccount().getAccountByName('name') works if on the main script (it only fails on my module). I worked around tis by passing moneydance to the module function (in addition to the account name).

  9. 9 Posted by Stuart Beesley ... on 18 Aug, 2021 10:06 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    That'll be a scope 'issue' of some sort.

    You could try doing this at the start of both your script, and also your module (outside any def statements).

    global moneydance
    

    and then see if your module can see it. Or you might have to try main.moneydance. Within your module do print globals() and see if it's there somewhere..

  10. 10 Posted by davidcullen on 19 Aug, 2021 05:47 AM

    davidcullen's Avatar

    Stuart,
    Thanks for your persistence. Here is my full code.
    The main file:

    #main.py
    #!/usr/bin/python
    #-- coding: UTF-8 --
    global moneydance
    import imp, importlib, os, sys
    script = "/Users/david/Documents/md/myModule.py"
    dirname, basename = os.path.split(script)
    sys.path.append(dirname)
    module_name = os.path.splitext(basename)[0]
    module = importlib.import_module(module_name)
    module = imp.reload(module) # reload to reflect module edits
    print module.getAcctForName('EM1')
    
    and the module:
    #myModule.py
    #!/usr/bin/python
    #-- coding: UTF-8 --
    global moneydance
     ''' Gets account from name; checks that it is valid. '''
      account = moneydance.getCurrentAccount().getAccountByName(name)
      assert type(account).name == 'Account', \
      "{} is not a valid Account - check spelling of FullAccountName".format(name)
      return account
    
    Adding global moneydance to either/both files does not seem to help. I still get an error: "NameError: global name 'moneydance' is not defined.

    If Ipass moneydance as a parameter when I call the module function, (ie changing things to:

    def getAcctForName(name, moneydance):
    
    everything works.
    I do not quite understand what you mean by try main.moneydance. When I try:
    account = moneydance.getCurrentAccount().getAccountByName(name)
    
    I get NameError: global name 'main' is not defined
    Finally, when I do print list(globals()) in myModule.py, I get:
    __file__ __name__ getAcctForName __builtins__ x __doc__ __package__
    but I do not see any reference to moneydance.
    I’d like to resolve things completely (so I understand more about Python), but if I cannot, then passing moneydance as a parameter (which works) is acceptable.
  11. 11 Posted by Stuart Beesley ... on 19 Aug, 2021 06:18 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    I do like the early morning tests...!

    It is a scope issue. Globals are global only to the module they were defined within. In Python 3 you can add the globals={} parameter to the importlib.import_module() call, but not in 2.7.

    You can cheat however, and given this is preset by MD anyway.... Stick this line in after your import statement, and before you call the module/function:

    module.moneydance = moneydance
    
    This will set the variable within your module.

    NOTE: I don't like this line:

    assert type(account).name == 'Account', "{} is not a valid Account - check spelling of FullAccountName".format(name)
    
    What are you trying to do? To check you have an account object do this:
    isinstance(account, Account)
    
    to make this work, you will need to do this at the beginning of your script:
    from com.infinitekind.moneydance.model import Account
    

    ... or

    account.getAccountName()
    
    for the name

    😉

  12. 12 Posted by davidcullen on 19 Aug, 2021 09:17 AM

    davidcullen's Avatar

    Stuart,
    This is really very helpful (and instructive). I have learned a lot from your guidance. I’ll play with things more tomorrow, and will change over to is instance(account, Account).

  13. 13 Posted by Stuart Beesley ... on 19 Aug, 2021 09:20 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    to clarify, it's if isinstance(): - the damn spell checker inserted a space..... 😊

  14. 14 Posted by davidcullen on 20 Aug, 2021 05:14 AM

    davidcullen's Avatar

    Changes made. All now working fine. Reload now with implib:

    reload(sys.modules[moduleName]) # reload to reflect any edits
    
    Thanks again......
  15. 15 Posted by davidcullen on 23 Aug, 2021 05:33 AM

    davidcullen's Avatar

    Stuart,
    A final note.
    Your earlier guidance has been really useful to me in understanding about sys.path. importlib (and imp ) as well as globals().
    Your code is very instructive/useful for importing a module (file) from a directory which is not already in PYTHONPATH.
    However, for importing a module in the same directory (already in PYTHONPATH I have solved things (probably obvious to a more experienced Python user then me), as follows:

    import sys, myModule
    reload(sys.modules[myModule.name])
    
    My main “problem” (now solved by your insight) was not realising that my module needs to be reloaded each time to reflect editing changes I am making “on the fly”.
  16. 16 Posted by Stuart Beesley ... on 23 Aug, 2021 05:49 AM

    Stuart Beesley - JUST A FELLOW USER and Toolbox ‘guy’'s Avatar

    Yes, it's not obvious that 'import' means run the file, and that if you call import again, Python remembers you've done this before and won't re-run the file....

    Also, note. Your imported files should always have this 'guard' in place wrapping any code that gets executed:

    if __name__ == "__main__":
       <execute some code>
    

    You don't need this if you are just defining things like variables, methods or classes. But you should if you have code that will/could execute..

    Google 'if name == main; for more details..... 😉

  17. davidcullen closed this discussion on 25 Aug, 2021 12:31 AM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac