Source code for gaitutils.envutils

# -*- coding: utf-8 -*-
"""
Created on Tue Mar 17 14:41:31 2015

Stuff related to Python environment

@author: Jussi (jnu@iki.fi)
"""

import sys
import traceback
import subprocess
from ulstools import env
from pkg_resources import resource_filename
import logging
import hashlib
import os
import tempfile
from pathlib import Path
from functools import lru_cache

from .gui._windows import error_exit


logger = logging.getLogger(__name__)


pkg_dir = Path(resource_filename('gaitutils', ''))  # package directory
pkg_parent = pkg_dir.parent
# True if package was imported from a git repository
git_mode = (pkg_parent / '.git').is_dir()


[docs]class GaitDataError(Exception): """Custom exception class to indicate gait data related errors"""
def _ipython_setup(): """Performs some IPython magic if we are running in IPython""" try: __IPYTHON__ except NameError: return from IPython import get_ipython ip = get_ipython() # ip.magic("gui qt5") # needed for mayavi plots # ip.magic("matplotlib qt") # do mpl plots in separate windows ip.magic("reload_ext autoreload") # these will enable module autoreloading ip.magic("autoreload 2") # print('warning: setting precision=3 for numpy array printing') # np.set_printoptions(precision=3) def _make_gaitutils_shortcut(): """Makes a desktop shortcut to gaitmenu gui.""" env.make_shortcut('gaitutils', 'gui/gaitmenu.py', 'gaitutils menu') def _git_update(): """Update the package git repository. This works, if the package was installed into user directory by cloning the git repository and running 'python setup.py develop'. In this case, updating the cloned repository will effectively update the package. The normal way to install the package is via pip install. In this case, the package must be updated manually by pip. Since this update mechanism is a bit fragile, it is not used by default. Return True if update was ran, else False. """ if git_mode: logger.info('running git update') try: startupinfo = None if os.name == 'nt': # hides the console on Windows startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW o = subprocess.check_output( ['git', 'pull'], cwd=pkg_parent, encoding='utf-8', startupinfo=startupinfo, ) except subprocess.CalledProcessError: status = (False, 'cannot retrieve or merge update') if 'lready' in o: status = (False, 'package already up to date') else: status = (True, o) else: # not a git repo status = (False, 'cannot update, gaitutils is not installed in git mode') return status def _register_gui_exception_handler(full_traceback=False): """Registers an exception handler that reports exceptions via GUI""" from .config import cfg def _my_excepthook(type_, value, tback): """Custom exception handler for fatal (unhandled) exceptions: report to user via GUI and terminate.""" # exception and message, but no traceback tbackstr = tback if full_traceback else '' msg = ''.join(traceback.format_exception(type_, value, tbackstr)) error_exit(msg) # just the message (e.g. ValueError: "blah" -> "blah") # may sometimes be confusing, since type of exception is not printed # error_exit(value) # sys.__excepthook__(type_, value, tback) sys.exit() if cfg.general.gui_exceptions: sys.excepthook = _my_excepthook
[docs]def lru_cache_checkfile(fun): """Cache function results, unless the argument file has changed. A lru_cache -style decorator for functions that take a file name argument. Makes sense for functions that read a file and take a long time to process the data (anything that takes significantly longer than the md5 digest). Works by computing the md5 digest for the input file and passing that on to lru_cache, so the cache is invalidated if file contents have changed. Parameters ---------- fun : function The function to cache. Returns ------- function The cached function. """ @lru_cache() def cached_fun(filename, md5sum): return fun(filename) def wrapper(filename): with open(filename, 'rb') as f: data = f.read() md5sum = hashlib.md5(data).hexdigest() return cached_fun(filename, md5sum) return wrapper
def _named_tempfile(suffix=None): """Return a name for a temporary file. Does not open the file. Cross-platform. Intended to replace tempfile.NamedTemporaryFile which behaves strangely on Windows. """ LEN = 12 # length of basename if suffix is None: suffix = '' elif suffix[0] != '.': raise ValueError('Invalid suffix, must start with dot') basename = os.urandom(LEN) # get random bytes # convert to hex string basename = basename.hex() return tempfile.gettempdir() / Path(basename).with_suffix(suffix)