Skip to content

Should astropy work with py2exe?#960

Closed
embray wants to merge 26 commits intoastropy:masterfrom
embray:issue-960
Closed

Should astropy work with py2exe?#960
embray wants to merge 26 commits intoastropy:masterfrom
embray:issue-960

Conversation

@embray
Copy link
Member

@embray embray commented Nov 27, 2013

This may be outside the scope of the project, but I'm trying to make a standalone Windows application (with py2exe) that uses astropy. I don't get very far, as trying to import astropy triggers complaints about configuration.

Here's a simple version of the problem:

in main.py:

import astropy

in setup.py

import sys
from distutils.core import setup
import py2exe
import numpy

#directory within WinSxS containing MSVCP90.dll - system dependent 
sys.path.append('C:\\Windows\\WinSxS\\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.21022.8_none_bcb86ed6ac711f91')

setup(console=['main.py'], 
      options={'py2exe':{'excludes':['zmq']}},
      )

This is enough to run python setup.py py2exe and create dist/main.exe on my machine, but running it gives:

Traceback (most recent call last):
  File "main.py", line 1, in <module>
  File "astropy\__init__.pyc", line 109, in <module>
  File "astropy\logger.pyc", line 15, in <module>
  File "astropy\utils\console.pyc", line 35, in <module>
  File "astropy\config\configuration.pyc", line 128, in __init__
RuntimeError: Cannot automatically determine get_config module, because it is not called from inside a valid module

@astrofrog
Copy link
Member

I think we want this to work.

@eteq - do you know why this is happening?

@astrofrog
Copy link
Member

Given this and other issues with the configuration, I wonder whether we want to consider trying to refactor it to make it more robust? Otherwise we're just constantly adding workarounds.

@eteq
Copy link
Member

eteq commented Apr 9, 2013

Well, I'm certainly interested in specific suggestions for how to make it more robust @astrofrog (the best would probably be to get away from configobj, on reflection... but that's quite a bit of work now).

But this part of the code isn't really related to the config module directly - the error message means simply that find_current_module couldn't figure out where it is, which should be impossible if the python stdlib is behaving correctly. I suspect py2exe does something tricky with the call stack that's confusing the stdlib inspect.

@ChrisBeaumont - can you try putting some temporary diagnostic print statements in your astropy.utils.misc.find_current_module function? perhaps print frm in the for loop that begins with for i in range(depth):. You might also trigger an exception there (a quick 1/0 or something) so we can see if py2exe is somehow screwing with the stack ordering.

@ChrisBeaumont
Copy link
Author

Sure. The changes:

diff --git a/astropy/utils/misc.py b/astropy/utils/misc.py
index d8559fa..a949098 100644
--- a/astropy/utils/misc.py
+++ b/astropy/utils/misc.py
@@ -102,11 +102,18 @@ def find_current_module(depth=1, finddiff=False):

     # using a patched version of getmodule because the py 3.1 and 3.2 stdlib
     # is broken if the list of modules changes during import
+
     from .compat import inspect_getmodule
+    from traceback import format_stack
+    print '***********\nBegin Traceback'
+    print ''.join(format_stack())
+    print '************\nEnd Traceback'

     frm = currentframe()
+    print frm
     for i in range(depth):
         frm = frm.f_back
+       print frm
         if frm is None:
             return None

What it prints normally:

PS C:\Users\beaumont\astropy_test> python main.py
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
22, in <module>
    from .version import version as __version__
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\version.py", line 7
, in <module>
    version = update_git_devstr(_last_generated_version)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\version_helpers.py"
, line 49, in update_git_devstr
    devstr = get_git_devstr(sha=True, show_warning=False, path=path)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\version_helpers.py"
, line 104, in get_git_devstr
    mod = find_current_module(1, finddiff=True)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000001E7EB28>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
22, in <module>
    from .version import version as __version__
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\version.py", line 8
, in <module>
    githash = get_git_devstr(sha=True, show_warning=False)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\version_helpers.py"
, line 104, in get_git_devstr
    mod = find_current_module(1, finddiff=True)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000001E7EB28>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 15
, in <module>
    from .utils.console import color_print
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\console.py",
line 36, in <module>
    'When True, use ANSI color escape sequences when writing to the console.')
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x000000000215CC98>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 15
, in <module>
    from .utils.console import color_print
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\console.py",
line 39, in <module>
    'Use Unicode characters when drawing progress bars etc. at the console.')
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x000000000215CC98>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 45
, in <module>
    "Threshold for the logging messages. Logging "
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 51
, in <module>
    "Whether to use color for the level names")
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 54
, in <module>
    "Whether to log warnings.warn calls")
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 57
, in <module>
    "Whether to log exceptions before raising "
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 61
, in <module>
    "Whether to always log messages to a log "
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 65
, in <module>
    "The file to log messages to. When '', "
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 70
, in <module>
    "Threshold for logging messages to "
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>
***********
Begin Traceback
  File "main.py", line 1, in <module>
    import astropy
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\__init__.py", line
126, in <module>
    from .logger import _init_log
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\logger.py", line 75
, in <module>
    "Format for log file entries")
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\config\configuratio
n.py", line 125, in __init__
    module = find_current_module(2)
  File "C:\Users\beaumont\Anaconda\lib\site-packages\astropy-0.3.dev3723-py2.7-win-amd64.egg\astropy\utils\misc.py", lin
e 109, in find_current_module
    print ''.join(format_stack())

************
End Traceback
<frame object at 0x0000000001ED90F8>
<frame object at 0x0000000002113AD8>
<frame object at 0x00000000020F21A8>

And from within py2exe

PS C:\Users\beaumont\astropy_test> dist/main.exe
***********
Begin Traceback
  File "main.py", line 1, in <module>
  File "astropy\__init__.pyc", line 22, in <module>
  File "astropy\version.pyc", line 7, in <module>
  File "astropy\version_helpers.pyc", line 49, in update_git_devstr
  File "astropy\version_helpers.pyc", line 104, in get_git_devstr
  File "astropy\utils\misc.pyc", line 109, in find_current_module

************
End Traceback
<frame object at 0x0000000001D988A8>
<frame object at 0x000000000045D418>
***********
Begin Traceback
  File "main.py", line 1, in <module>
  File "astropy\__init__.pyc", line 22, in <module>
  File "astropy\version.pyc", line 8, in <module>
  File "astropy\version_helpers.pyc", line 104, in get_git_devstr
  File "astropy\utils\misc.pyc", line 109, in find_current_module

************
End Traceback
<frame object at 0x0000000001D988A8>
<frame object at 0x000000000045D418>
***********
Begin Traceback
  File "main.py", line 1, in <module>
  File "astropy\__init__.pyc", line 126, in <module>
  File "astropy\logger.pyc", line 15, in <module>
  File "astropy\utils\console.pyc", line 36, in <module>
  File "astropy\config\configuration.pyc", line 125, in __init__
  File "astropy\utils\misc.pyc", line 109, in find_current_module

************
End Traceback
<frame object at 0x0000000001D988A8>
<frame object at 0x0000000001BDC858>
<frame object at 0x0000000001BE2038>
Traceback (most recent call last):
  File "main.py", line 1, in <module>
    import astropy
  File "astropy\__init__.pyc", line 126, in <module>
  File "astropy\logger.pyc", line 15, in <module>
  File "astropy\utils\console.pyc", line 36, in <module>
  File "astropy\config\configuration.pyc", line 129, in __init__
RuntimeError: Cannot automatically determine get_config module, because it is not called from inside a valid module

@embray
Copy link
Member

embray commented Apr 10, 2013

Yeah I don't think the problem here has to do with the config system directly, and more to do with the fact that several places in Astropy assume that the modules are being imported from files on the file system and not more exotic importers.

@ChrisBeaumont
Copy link
Author

I see. So should I not expect Astropy to support py2exe integration? Are there other modules (besides the config system) that also rely on import nuances?

@embray
Copy link
Member

embray commented Apr 10, 2013

There are. I don't know that this is a closed door though. I've used py2exe before but not in a while so I'm not exactly sure what it's doing here. But I think it's putting the modules in a zip file?

I'd have to look into how other projects with the same types of issues integrate with py2exe if at all.

@ChrisBeaumont
Copy link
Author

Ok, keep me posted. This is something I'd be willing to work on as well.

@embray
Copy link
Member

embray commented Apr 12, 2013

I set up a test py2exe project where I was able to reproduce this, so I'll try to poke at it a bit as well.

@ChrisBeaumont
Copy link
Author

Thanks. I'm also looking into PyInstaller, which looks like it might be more maintained.

@badders
Copy link

badders commented Sep 25, 2013

Just wanted to say that it fails the same way as py2exe when using pyinstaller (on my mac), exactly the same backtrace about the configuration. It does seem to work fine when using py2app though. If the problem is to do with the library being in a zip file, isnt this one of the standard methods of packaging python libraries with python 3 now anyway that setuptools/distribute/whatever else have become more standardised with upstream python?

@embray
Copy link
Member

embray commented Sep 26, 2013

It really works with py2app? Because I had a different report that it fails with py2app, also with the same error. And yes, the problem has to do with the fact that py2exe and friends places all the libraries in a zip file. And no, this is no impact on setuptools. Although it is possible to import setuptools egg packages (which are zip files), Astropy (and actually probably the majority of other packages you'll find that use setuptools) set the zip_safe=False flag which causes them to be extracted to directories instead.

eggs are being slowly deprecated anyways in favor of the wheel binary distribution format which doesn't share these issues since they're not designed (at the moment) to be importable.

@badders
Copy link

badders commented Sep 27, 2013

I have it working fine with py2app here: https://github.com/badders/pyfitsview
You can build with 'python setup-mac.py py2app' and get a nice working app bundle

@embray
Copy link
Member

embray commented Nov 26, 2013

I've looked into this a little more, finally. Still haven't tried py2app--I've heard some reports of it working, and some of it not. I think this might actually relate to the Python version...?

This ultimately comes down to inspect.getmodule which appears to not work properly with modules imported from a zip file. I think this may be a bug in Python--one could consider it a bug in the zipimporter or in the inspect module depending on how you choose to look at it, but ultimately the two are incompatible. I need to investigate whether this is a known bug in Python but I suspect it probably is.

@embray
Copy link
Member

embray commented Nov 26, 2013

Here's the issue (at least so far) in all its gory details:

in our find_current_module function we pass in a frame object to inspect.getmodule(). This function knows, in theory, how to find the module (in sys.modules) in which the function the current stack from is in is defined. It does this by looking at the code object associated with the given frame. It then loops through sys.modules and finds the module whose __file__ matches the code.co_filename attribute (if one exists).

Normally this works fine--code.co_filename and module.__file__ both contain the absolute path of the source file in which a particular function is defined. However, while the zipimporter sets module.__file__ to an absolute pathname like /full/path/to/libraries.zip/path/within/zipfile/of/module.pyc, when it compiles the module code objects it just uses the relative pathname within the zipfile, like path/within/zipfile/of/module.pyc.

That's why inspect.getmodule() falls over--the filename associated with the code object and the filename associated with the module object don't match up, so its method of finding the correct module in sys.modules fails.

I believe it should be easy enough to find a workaround for this, but bear with me...

@embray
Copy link
Member

embray commented Nov 27, 2013

With the attached changes I've gotten this working so far as import astropy will work in a py2exe bundle. This is a workaround to what I would say is a bug in Python (for which I plan to submit a patch).

That said, with this patch this still does not mean Astropy will work with py2exe. Take for example this example script:

import astropy
from astropy.time import Time

print "Hello world!"
print astropy.__version__
print "The time is", Time.now()
print "The UT1 - UTC offset is", Time.now().delta_ut1_utc

It works up to the last line, where it bombs out with this traceback:

Hello world!
0.4.dev6658
The time is 2013-11-27 19:29:12.481000
The UT1 - UTC offset isERROR: IOError: source code not available [inspect]
Traceback (most recent call last):
  File "hello.py", line 7, in <module>
    print "The UT1 - UTC offset is", Time.now().delta_ut1_utc
  File "astropy\time\core.pyc", line 660, in _get_delta_ut1_utc
  File "astropy\utils\iers\__init__.pyc", line 4, in <module>
  File "astropy\utils\iers\iers.pyc", line 57, in <module>
  File "astropy\table\__init__.pyc", line 2, in <module>
  File "astropy\table\table.pyc", line 21, in <module>
  File "astropy\units\__init__.pyc", line 17, in <module>
  File "astropy\units\si.pyc", line 230, in <module>
  File "astropy\units\utils.pyc", line 115, in generate_unit_summary
  File "astropy\units\core.pyc", line 577, in __format__
  File "astropy\units\core.pyc", line 489, in __unicode__
  File "astropy\units\format\generic.pyc", line 31, in __init__
  File "astropy\units\format\generic.pyc", line 319, in _make_parser
  File "astropy\extern\ply\yacc.pyc", line 3096, in yacc
  File "astropy\extern\ply\yacc.pyc", line 2792, in validate_all
  File "astropy\extern\ply\yacc.pyc", line 2832, in validate_modules
  File "inspect.pyc", line 690, in getsourcelines

  File "inspect.pyc", line 529, in findsource

IOError: source code not available

This is an issue particular to ply, but there are other issues like this that are more particular to Astropy itself (for example I tried a script that runs astropy.test() and that does not work at all. The main problem here is that py2exe tries to be "smart"--it builds up a dependency graph of exactly the Python modules you need for your script and only those modules. But obviously it's imperfect and any code that does anything weird (of which we have at least some) throws it off. It also doesn't automatically include any non-Python data files required by the module.

I'm trying to figure out if there is any way to tell it very forcefully: I want you to just include all files under this package. But I have no idea. Might have to give up on py2exe. While it still seems to have an active user base, it hasn't been updated since 2008. PyInstaller seems to be considered the wave of the future for this sort of thing, and is being actively developed. Though it might have some of the same issues--I don't know.

@embray
Copy link
Member

embray commented Nov 27, 2013

I discovered looking here that matplotlib provides a matplotlib.get_py2exe_datafiles function specifically for this purpose. We might be able to get somewhere if we provide something similar....

@embray
Copy link
Member

embray commented Nov 27, 2013

I examined this a little more, and discovered that there's blame to go around. If I modify py2exe slightly to recompile the pyc files so that the code objects serialized therein have source filenames relative to the zip file--like library.zip/astropy/__init__.py--that significantly helps matters. Because now when importing a module from the zip file it's as if the zipfile contained the source .py file and compiled the .pyc from it. This is what actually happens in Python's zipimporter if you import a zipfile containing .py source files and not .pyc files.

However, the whole thing still breaks down if you run the executable created by py2exe from any directory other than the one that contains it. Then the inspect module still doesn't correctly figure out the module from the file name due to careless use of os.path.abspath 😦

@eteq
Copy link
Member

eteq commented Nov 29, 2013

I have no specific suggestions, but I just want to be sure to offer @embray some encouragement - it would be great to get this working, but it sounds like it's really in the weeds!

@embray
Copy link
Member

embray commented Nov 29, 2013

I talked to Marshall about this the other day (he brought up this same issue but in the context of py2app, which is mostly the same as py2exe). He said he doesn't care what utility he uses to bundle his application so long as it works of course. It seems like most activity in Python app bundling is in PyInstaller these days, so I'm going to see if I can have any better luck with that. If so, we might then recommend developers who want to bundle Astropy with their application use PyInstaller, and I would then offer documentation on how best to do that with Astropy.

It's probably possible to get things working with py2exe/app too, but I think it's better to focus on PyInstaller first...

@embray
Copy link
Member

embray commented Dec 3, 2013

With the latest changes importing Astropy from a zipfile mostly works now for most use cases. That's importing only.

But that's only the tip of the iceberg. Almost everything that's relying on __file__ to locate data files is going to be broken. This too can be fixed, however. We need a utility function for getting data files relative to a module. In the general case this can rely on __file__ as we currently do, but otherwise we can rely on the module loader API to get the data for that file and write it to a temp file, then return the path to the temp file.

@embray
Copy link
Member

embray commented Dec 3, 2013

Turns out we do already have functions related to getting package data files--I thought I remembered something like that. But not ever package is using them consistently. And we still need to modify them to support loading data from a zip file, but that can be done.

@embray
Copy link
Member

embray commented Dec 3, 2013

I fixed up the get_pkg_data_ family of utility functions to be able to load data files when astropy is imported from a zip file. There's still a fair bit of overhead, some of which we may able to reduce by extracting multiple files simultaneously when it's expected they will all be used. But the general principle is sound.

I fixed up a few bits in the code that weren't using get_pkg_data_* where it would be appropriate. There are still a few other bits that use __file__ directly but most of them do no matter for normal runtime functionality.

@embray
Copy link
Member

embray commented Dec 3, 2013

So now astropy can work if, for example, it's imported from an egg archive. You can try this out yourself by running ./setup.py bdist_egg (which will make a .egg zip archive even though we set zip_safe=False), then cd into the dist directory that the egg is created in, fire up a Python interpreter, and:

import sys
sys.path.insert(0, egg_filename)
import astropy

and try things out. We still want to leave zip_safe=False since that's a preferable default. But the fact that this does mostly work will be a big help for getting Astropy working with things like PyInstaller. Possibly even py2exe and friends, though I know PyInstaller has full support for eggs.

@embray
Copy link
Member

embray commented Dec 3, 2013

Welp. Except that it doesn't seem to be working on Windows....

@embray
Copy link
Member

embray commented Dec 4, 2013

Okay, fixed the Windows thing.

@embray
Copy link
Member

embray commented Dec 4, 2013

At this point it should be just about possible to bundle Astropy in an application built with, for example PyInstaller.

There are a few areas where things are still a little dicey--in particular imports that you need that PyInstaller doesn't pick up automatically. These could be handled by a hook script for PyInstaller which I think we can put together. But I'd rather wait to work with someone on an actual application that uses this before spending much more time on it, as it will be easier to uncover all the issues with a real application.

@astrofrog
Copy link
Member

@embray - is this ready for final review? It should probably be merged soon to avoid rebasing given the extensive changes, right?

@embray
Copy link
Member

embray commented Dec 9, 2013

No, I still want to put a call out to @ChrisBeaumont and @mperrin (the only people I specifically know of so far who have tried to bundle Astropy in an application) to either test this out, or point me to their application source code so that I can try to keep working on it.

This also deserves further documentation on how to bundle Astropy in an application. Currently I would recommend only using PyInstaller over py2exe or py2app, and this should be documented. Currently also Astropy will only work if bundled in it entirely as an .egg archive. It would still be good to write a hook script for PyInstaller to inform it how to correctly pick up all the files that Astropy needs to run.

@embray
Copy link
Member

embray commented Dec 9, 2013

To amend my last comment: It's probably better if someone give me some code to try to work with this on; I've already gotten some simple programs working with Astropy so I have a basic idea of what's required. I wouldn't want anyone else to waste a lot of time on it until I've been able to document what's required.

@embray
Copy link
Member

embray commented Aug 5, 2016

Marshall and I got this working once aeons ago for webbpsf. It required a few custom handlers for Astropy plus the tweaks in this branch, but it worked. This branch is way out of date though and would be a tough problem to rebase.

@embray
Copy link
Member

embray commented Jan 24, 2017

This came up again on StackOverflow. Would like to try to find the time to move it forward again sometime if I can--maybe even add a continuous integration build based on a 'frozen' astropy to keep that working. Need to rebase again, and also need to incorporate the solution from #4531 or something like it.

@maartenbreddels
Copy link

FWIW, I set up this repo:
https://github.com/maartenbreddels/frozen_astropy
which (seems to) work with astropy+pyinstaller. The trick I use is to include astropy as a directory, and monkey patch inspect.getabsfile. I use this trick in vaex as well.
A similar trick I use for py2exe in vaex where I tell it to not package the astropy package.
Would be good to have this working with travis, and have it execute 'frozen_astropy test'.
I'm not sure py2exe has a future anymore, development seems to have stopped (correct me if I'm wrong).

@fockez
Copy link
Contributor

fockez commented Feb 15, 2017

I want to know what's the status now? I try to bundle my pipeline on Windows for specific project using windows, and python is easy to distribute. But I tried py2exe pyinstaller and mainly cx_freeze, no luck. I can skip the "six" error by adding astropy.extern.bundle.six manually to the setup script. But, still face the problem of get_config error.

Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\cx_Freeze\initscripts\__startup__.py", line 12, in <module>
    __import__(name + "__init__")
  File "C:\Python27\lib\site-packages\cx_Freeze\initscripts\Console.py", line 24, in <module>
    exec(code, m.__dict__)
  File "timviewer.py", line 17, in <module>
    from astropy.io import fits as pyfits
  File "C:\Python27\lib\site-packages\astropy\__init__.py", line 121, in <module>
    class Conf(_config.ConfigNamespace):
  File "C:\Python27\lib\site-packages\astropy\__init__.py", line 128, in Conf
    'When True, use Unicode characters when outputting values, and '
  File "C:\Python27\lib\site-packages\astropy\config\configuration.py", line 236, in __init__
    module = module.__name__
RuntimeError: Cannot automatically determine get_config module, because it is not called from inside a valid module

I hope this can be solved no matter how.
Thanks.

@maartenbreddels
Copy link

I did it the same as described above, don't 'pack' it, but make sure astropy gets placed in a directory, I believe it is the skip_archive option. Let me know if it worked.

@fockez
Copy link
Contributor

fockez commented Feb 16, 2017

I used cx_freeze and all the modules are in directory, not one package. Still no luck. Even includes astropy manually. The problem is that find_current_module(2) always return None, and I don't know why.

@maartenbreddels
Copy link

@fockez
Copy link
Contributor

fockez commented Feb 16, 2017

if hasattr( sys, "frozen" ):
    sys.path.insert( 2, os.path.join( os.path.dirname( sys.executable ), "astropy" ) )
    old_getabsfile = inspect.getabsfile
    def inspect_getabsfile_wrapper(*args, **kwargs):
        path = old_getabsfile(*args, **kwargs)
        print path
        last_part = re.sub("(.*?yportsa).*", r"\1", path[::-1])[::-1]
        print last_part
        return os.path.join( os.path.dirname( sys.executable ), last_part )
    inspect.getabsfile = inspect_getabsfile_wrapper

add astropy to sys.path[0] may cause io and gzip error, so I change to [2], but still get the same error of get_config.

@fockez
Copy link
Contributor

fockez commented Feb 17, 2017

i don't think it's the problem of getabsfile, i found that i can get the right return of getabsfile, but i always get none return of getmodule function.

@maartenbreddels
Copy link

Are you sure, since what I wrote (I cannot remember all the details):
https://github.com/maartenbreddels/frozen_astropy/blob/master/frozen_astropy.py#L16

inspect.getabsfile will return /usr/lib/python2.6/site-packages/astropy/init.pyc
and inspect.getmodule will not find that file in sys.modules, so we rewrite path
How I found out is by using the debugger, for windows you can try making a console application, do import pdb; pdb.set_trace(); and from there on set a break on getmodule, and continue.

@fockez
Copy link
Contributor

fockez commented Feb 19, 2017

Sorry for the carelessness, I didn't check the whole output including "astropy". Under windows, all module list using lower case and getabsfile return .py not .pyc, but cx_freeze only packing .pyc.
So I change it to :

if hasattr( sys, "frozen" ):
    sys.path.insert( -1, os.path.join( os.path.dirname( sys.executable ), "astropy" ) )
    old_getabsfile = inspect.getabsfile
    def inspect_getabsfile_wrapper( *args, **kwargs ):
        path = old_getabsfile( *args, **kwargs )
        last_part = re.sub( "(.*?yportsa).*", r"\1", path[::-1] )[::-1]
        if os.path.splitext( last_part )[1] == '.py':
            last_part = os.path.splitext( last_part )[0] + '.pyc'
        return os.path.join( os.path.dirname( sys.executable ), last_part ).lower()
    inspect.getabsfile = inspect_getabsfile_wrapper

Also add path to the end to avoid path conflicts. This is ugly but can work. I hope this will be resolved permanently in the future. At least using bundled inspect module?

@embray
Copy link
Member

embray commented Mar 30, 2017

I think it would be great to include a test in the appveyor build matrix for bundling Astropy with pyinstaller or something, so that we can keep this working in the future.

@astropy-bot
Copy link

astropy-bot bot commented Sep 25, 2017

Hi there @embray 👋 - thanks for the pull request! I'm just a friendly 🤖 that checks for issues related to the changelog and making sure that this pull request is milestoned and labelled correctly. This is mainly intended for the maintainers, so if you are not a maintainer you can ignore this, and a maintainer will let you know if any action is required on your part 😃.

I see this is an experimental pull request. I'll report back on the checks once the PR discussion in settled.

@bsipocz bsipocz removed this from the Future milestone Sep 25, 2017
@astropy-bot
Copy link

astropy-bot bot commented Sep 28, 2017

Hi humans 👋 - this pull request hasn't had any new commits for approximately 1 year, 7 months. I plan to close this in a month if the pull request doesn't have any new commits by then.

In lieu of a stalled pull request, please close this and open an issue instead to revisit in the future. Maintainers may also choose to add keep-open label to keep this PR open but it is discouraged unless absolutely necessary.

If you believe I commented on this issue incorrectly, please report this here.

@astrofrog
Copy link
Member

astrofrog commented Sep 28, 2017

@maartenbreddels @embray @fockez @josePhoenix @badders - I think it would be good to understand what the latest status is here. I think all of you used various tools (py2app, py2exe, pyinstaller). Could you report back as to what currently works/doesn't work with the developer version of astropy? Note that the developer version of Astropy doesn't include bundled versions of six or pytest anymore, which were causing issues for some people before.

I think this PR may include some useful things we should incorporate anyway, but it's going to be incredibly difficult to rebase given how old it is and changes in e.g. the testing infrastructure. It might make more sense to just open a new issue and point to this PR in case someone wants to start again (unless, @embray, you want to try rebasing this!).

It would also be helpful if someone could point out how we could test this in our continuous integration - what would a minimal project look like, and how would we test that it works?

@fockez
Copy link
Contributor

fockez commented Oct 18, 2017

Recently, I switch my code from py2 to py3, and this cause new trouble. Under PyInstaller, it always report when loading built app:
ImportError: Astropy requires the 'six' module of minimum version 1.10; normally this is bundled with the astropy package so if you get this warning consult the packager of your Astropy distribution.
And, with cx_freeze which works under py2 now report:
ValueError: 'm / (s)' did not parse as unit: invalid syntax (generic_lextab.py, line 4).

So, when the 3.0 of astropy will be released or at least beta version, totally no need of six package.

@fridgerator
Copy link

@fockez I was able to get this to work using PyInstaller with the following pyinstaller options --exclude-module astropy and --add-data path_to/astropy:astropy

@astrofrog
Copy link
Member

@fridgerator - ok that sounds pretty straightforward. So no need for the changes in this PR?

@fridgerator
Copy link

@astrofrog Correct, I didn't change anything else or use anything from this PR

@astropy-bot
Copy link

astropy-bot bot commented Nov 21, 2017

⏰ Time's up! ⏰

I'm going to close this pull request as per my previous message. If you think what is being added/fixed here is still important, please remember to open an issue to keep track of it. Thanks!

If this is the first time I am commenting on this issue, or if you believe I closed this issue incorrectly, please report this here.

@astropy-bot astropy-bot bot closed this Nov 21, 2017
@astrofrog astrofrog added the closed-by-bot Closed by stale bot label Nov 21, 2017
@lzkelley
Copy link
Contributor

lzkelley commented Jan 5, 2018

I'm trying to use the exclude/include trick from above. It got rid of the ImportError: Astropy requires the 'six' module of minimum version 1.10 error, but now I get:

Traceback (most recent call last):
  File "pyapi.py", line 10, in <module>
    import cosmopy
  File "/Users/lzkelley/Programs/cosmo/ve_test/vetest/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages/cosmopy/__init__.py", line 9, in <module>
  File "/var/folders/wr/_dhmn18x1dj6t8ytjjd9rsxh0000gn/T/_MEIhGawWS/astropy/constants/__init__.py", line 28, in <module>
    from .constant import Constant, EMConstant
  File "/var/folders/wr/_dhmn18x1dj6t8ytjjd9rsxh0000gn/T/_MEIhGawWS/astropy/constants/constant.py", line 11, in <module>
    from ..units.core import Unit, UnitsError
  File "/var/folders/wr/_dhmn18x1dj6t8ytjjd9rsxh0000gn/T/_MEIhGawWS/astropy/units/__init__.py", line 14, in <module>
    from .core import *
  File "/var/folders/wr/_dhmn18x1dj6t8ytjjd9rsxh0000gn/T/_MEIhGawWS/astropy/units/core.py", line 23, in <module>
    from .utils import (is_effectively_unity, sanitize_scale, validate_power,
  File "/var/folders/wr/_dhmn18x1dj6t8ytjjd9rsxh0000gn/T/_MEIhGawWS/astropy/units/utils.py", line 16, in <module>
    from fractions import Fraction
ImportError: No module named 'fractions'
[2338] Failed to execute script pyapi

I'm using a virtualenv named vetest in the directory ve_test, trying to run with the command:
pyinstaller -F pyapi.py --exclude-module astropy --add-data /Users/lzkelley/Programs/cosmo/ve_test/vetest/lib/python3.5/site-packages/astropy:astropy

(the produced executable is also >200MB, is that normal?)

The full output from pyinstaller is attached.

out.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.