diff --git a/.travis.yml b/.travis.yml index c2ba68b8..3c9e01f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,10 +74,10 @@ script: # "py,py27" below would be redundant when the main interpreter is # Python 2.7 but it simplifies the CI setup. - if [ "$CROSS_VERSION" = "1" ]; then - $PYTHON -m tox -e py,py27 -- -v; + $PYTHON -m tox -e py,py27 -- -sv; fi - - PYJULIA_TEST_REBUILD=yes $PYTHON -m tox -- --cov=julia -v + - PYJULIA_TEST_REBUILD=yes $PYTHON -m tox -- --cov=julia -sv after_success: - coveralls diff --git a/appveyor.yml b/appveyor.yml index ee850d6f..2e909ddb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -91,9 +91,9 @@ test_script: # Run cross-version tests but ignore the failures (from Python 2). # Once cross-version in Windows is fmixed, stop using # Invoke-Expression (which ignores the exit status). - - ps: if ($env:CROSS_VERSION -eq 1) { Invoke-Expression "tox -- -v" } - # - ps: if ($env:CROSS_VERSION -eq 1) { tox -- -v } + - ps: if ($env:CROSS_VERSION -eq 1) { Invoke-Expression "tox -- -sv" } + # - ps: if ($env:CROSS_VERSION -eq 1) { tox -- -sv } # Rebuild PyCall.ji for each Python interpreter before testing: - "SET PYJULIA_TEST_REBUILD=yes" - - tox -- -v + - tox -- -sv diff --git a/julia/core.py b/julia/core.py index 302672f6..e801628b 100644 --- a/julia/core.py +++ b/julia/core.py @@ -15,14 +15,17 @@ # Stdlib from __future__ import print_function, absolute_import + +from logging import getLogger +# Not importing `logging` module here so that using `logging.debug` +# instead of `logger.debug` becomes an error. + import atexit import ctypes import ctypes.util import os import sys -import keyword import subprocess -import time import warnings from collections import namedtuple @@ -38,7 +41,7 @@ from distutils.spawn import find_executable as which # this is python 3.3 specific -from types import ModuleType, FunctionType +from types import ModuleType from .find_libpython import find_libpython, linked_libpython, normalize_path @@ -47,10 +50,37 @@ #----------------------------------------------------------------------------- python_version = sys.version_info -if python_version.major == 3: - def iteritems(d): return iter(d.items()) -else: - iteritems = dict.iteritems + +logger = getLogger("julia") +_loghandler = None + + +def get_loghandler(): + """ + Get `logging.StreamHandler` private to PyJulia. + """ + global _loghandler + if _loghandler is None: + import logging + + formatter = logging.Formatter("%(levelname)s %(message)s") + + _loghandler = logging.StreamHandler() + _loghandler.setFormatter(formatter) + + logger.addHandler(_loghandler) + return _loghandler + + +def set_loglevel(level): + import logging + + get_loghandler() + logger.setLevel(getattr(logging, level, level)) + + +def enable_debug(): + set_loglevel("DEBUG") # As setting up Julia modifies os.environ, we need to cache it for @@ -333,7 +363,7 @@ def is_same_path(a, b): return a == b -def is_compatible_exe(jlinfo, _debug=lambda *_: None): +def is_compatible_exe(jlinfo): """ Determine if Python used by PyCall.jl is compatible with this Python. @@ -347,11 +377,11 @@ def is_compatible_exe(jlinfo, _debug=lambda *_: None): jlinfo : JuliaInfo A `JuliaInfo` object returned by `juliainfo` function. """ - _debug("jlinfo.libpython =", jlinfo.libpython) + logger.debug("jlinfo.libpython = %s", jlinfo.libpython) py_libpython = linked_libpython() jl_libpython = normalize_path(jlinfo.libpython) - _debug("py_libpython =", py_libpython) - _debug("jl_libpython =", jl_libpython) + logger.debug("py_libpython = %s", py_libpython) + logger.debug("jl_libpython = %s", jl_libpython) dynamically_linked = py_libpython is not None return dynamically_linked and py_libpython == jl_libpython # `py_libpython is not None` here for checking if this Python @@ -480,7 +510,9 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None, to avoid re-initializing it. The purpose of the flag is only to manage situations when Julia was initialized from outside this code. """ - self.is_debugging = debug + + if debug: + enable_debug() # Ugly hack to register the julia interpreter globally so we can reload # this extension without trying to re-open the shared lib, which kills @@ -509,14 +541,14 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None, raise TypeError( "Both `runtime` and `jl_runtime_path` are specified.") - self._debug() # so that debug message is shown nicely w/ pytest + logger.debug("") # so that debug message is shown nicely w/ pytest if init_julia: jlinfo = juliainfo(runtime) JULIA_HOME, libjulia_path, image_file, depsjlexe = jlinfo[:4] - self._debug("pyprogramname =", depsjlexe) - self._debug("sys.executable =", sys.executable) - self._debug("JULIA_HOME = %s, libjulia_path = %s" % (JULIA_HOME, libjulia_path)) + logger.debug("pyprogramname = %s", depsjlexe) + logger.debug("sys.executable = %s", sys.executable) + logger.debug("JULIA_HOME = %s, libjulia_path = %s", JULIA_HOME, libjulia_path) if not os.path.exists(libjulia_path): raise JuliaError("Julia library (\"libjulia\") not found! {}".format(libjulia_path)) @@ -533,8 +565,8 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None, else: jl_init_path = JULIA_HOME.encode("utf-8") # initialize with JULIA_HOME - use_separate_cache = not is_compatible_exe(jlinfo, _debug=self._debug) - self._debug("use_separate_cache =", use_separate_cache) + use_separate_cache = not is_compatible_exe(jlinfo) + logger.debug("use_separate_cache = %s", use_separate_cache) if use_separate_cache: PYCALL_JULIA_HOME = os.path.join( os.path.dirname(os.path.realpath(__file__)),"fake-julia").replace("\\","\\\\") @@ -548,9 +580,9 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None, else: raise ImportError("No libjulia entrypoint found! (tried jl_init_with_image and jl_init_with_image__threading)") self.api.jl_init_with_image.argtypes = [char_p, char_p] - self._debug("calling jl_init_with_image(%s, %s)" % (jl_init_path, image_file)) + logger.debug("calling jl_init_with_image(%s, %s)", jl_init_path, image_file) self.api.jl_init_with_image(jl_init_path, image_file.encode("utf-8")) - self._debug("seems to work...") + logger.debug("seems to work...") else: # we're assuming here we're fully inside a running Julia process, @@ -680,14 +712,6 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None, self.eval("@eval Main import Base.MainInclude: eval, include") # https://github.com/JuliaLang/julia/issues/28825 - def _debug(self, *msg): - """ - Print some debugging stuff, if enabled - """ - if self.is_debugging: - print(*msg, file=sys.stderr) - sys.stderr.flush() - def _call(self, src): """ Low-level call to execute a snippet of Julia source. @@ -697,7 +721,7 @@ def _call(self, src): management. It should never be used for returning the result of Julia expressions, only to execute statements. """ - # self._debug("_call(%s)" % src) + # logger.debug("_call(%s)", src) ans = self.api.jl_eval_string(src.encode('utf-8')) self.check_exception(src) @@ -728,9 +752,9 @@ def _unbox_as(self, pointer, c_type): def check_exception(self, src=""): exoc = self.api.jl_exception_occurred() - self._debug("exception occured? " + str(exoc)) + logger.debug("exception occured? %s", str(exoc)) if not exoc: - # self._debug("No Exception") + # logger.debug("No Exception") self.api.jl_exception_clear() return diff --git a/test/test_compatible_exe.py b/test/test_compatible_exe.py index 0b53eece..1f2dc90e 100644 --- a/test/test_compatible_exe.py +++ b/test/test_compatible_exe.py @@ -166,12 +166,14 @@ def test_statically_linked(python): """ from __future__ import print_function from julia.find_libpython import find_libpython - from julia.core import is_compatible_exe + from julia.core import is_compatible_exe, enable_debug + + enable_debug() class jlinfo: libpython = find_libpython() - assert not is_compatible_exe(jlinfo, _debug=print) + assert not is_compatible_exe(jlinfo) """, check=True, )