Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable autoreload in interactive mode #41

Open
gpleiss opened this issue Mar 28, 2017 · 5 comments
Open

Enable autoreload in interactive mode #41

gpleiss opened this issue Mar 28, 2017 · 5 comments

Comments

@gpleiss
Copy link

gpleiss commented Mar 28, 2017

Is there a way to enable IPython autoreload when using --interactive mode? I've been using this mode frequently in development, and it'd be nice to easily test changes on the fly.

@jtratner
Copy link
Contributor

@dbieber - I wonder if this requires loading the module as __main__

@dbieber
Copy link
Member

dbieber commented Mar 28, 2017

@gpleiss
With some caveats, yes.

You can enable autoreload in the usual way after entering interactive mode:

%load_ext autoreload
%autoreload 2

This will successfully enable autoreload for everything that was imported, but it won't autoreload things in the __main__ module. So when you change things in the file in which fire.Fire() was called, they won't be reflected in IPython. <-- I imagine this is exactly what you want, though; is that right?

One workaround is to move fire.Fire() to a separate file where you import the things you care about, so that everything you might change is being imported and will be reloaded.

I wonder if there's a way to make autoreload think that things from the __main__ module (only those provided by Fire, not those created afterward) are actually from the module that corresponds to the file, so that when the file changes, these objects are updated.

If you're interested in exploring this, the place to start is interact.py:_EmbedIPython. The list of variables is variables, and you can check the module of certain types of objects with variable.__module__.

@jtratner can you elaborate?

@jtratner
Copy link
Contributor

@jtratner can you elaborate?

You basically covered it in your explanation, but for #29 - using load_module rather than load_source changes how autoreload would work.

@gpleiss
Copy link
Author

gpleiss commented Mar 29, 2017

Cool thanks! The separate file solution is a good workaround. I'll explore the __main__ module as well.

@dbieber
Copy link
Member

dbieber commented Mar 29, 2017

Thought I'd share some of my (failed) experimentation:

I modified interact.py adding the following methods:

def make_reloadable(variables, filename):
  module = reload_filename(filename)
  for name, variable in variables.items():
    variable.__module__ = module.__name__
    setattr(module, name, variable)

def new_reload(m):
  print('reloading module', m)
  try:
    return original_reload(m)
  except:
    return reload_filename(m.__file__)

def reload_filename(filename):
  module_name = os.path.splitext(os.path.basename(filename))[0]
  dirname = os.path.dirname(filename)
  print(dirname)
  try:
    fp, pathname, description = imp.find_module(module_name, [dirname])
  except ImportError:
    pass

  try:
    module = imp.load_module(module_name, fp, pathname, description)
  finally:
    if fp:
      fp.close()
  return module

Overwriting reload:

import __builtin__
original_reload = __builtin__.reload
__builtin__.reload = new_reload

And modifying this method:

def _EmbedIPython(variables, argv=None):
  """Drops into an IPython REPL with variables available for use.

  Args:
    variables: A dict of variables to make available. Keys are variable names.
        Values are variable values.
    argv: The argv to use for starting ipython. Defaults to an empty list.
  """
  argv = argv or []

  items = {}
  for name, variable in variables.items():
    try:
      if variable.__module__ == '__main__':
        items[name] = variable
    except AttributeError:
      pass

  main_module = sys.modules.get('__main__')
  make_reloadable(items, main_module.__file__)

  IPython.start_ipython(argv=argv, user_ns=variables)

I also updated autoreload.py (around line 375) to preserve __file__:

   # reload module
    try:
        # clear namespace first from old cruft
        old_dict = module.__dict__.copy()
        old_name = module.__name__
        old_file = module.__file__
        module.__dict__.clear()
        module.__dict__['__name__'] = old_name
        module.__dict__['__file__'] = old_file
        module.__dict__['__loader__'] = old_dict['__loader__']
    except (TypeError, AttributeError, KeyError):
        pass

Also tried adding this to autoreload after the call to reload(module).

for key in old_dict:
  if key not in module.__dict__:
    module.__dict__[key] = old_dict[key]

No luck. autoreload's update_generic is being called on the objects we want to reload, so not sure what the problem is at the moment. Going to take a break from digging now. Maybe someone will find this useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants