Repository: clld/clldutils

Issue:
context manager to temporarily adapt `sys.path`
For several projects we want to be able to import custom code not within a python package. The most convenient (and portable) way to this seems to be via a context manager, adapting `sys.path` appropriately:
```python
@contextmanager
def with_sys_path(d):
    p = d.as_posix()
    sys.path.append(p)
    yield
    if sys.path[-1] == p:
        sys.path.pop()
```
Alternatively, the complete functionality, i.e. importing a module by filesystem path could be provided:
```python
        with with_sys_path(path.parent):
            return import_module(path.name)
```

Discussion:

Patch:
diff --git a/clldutils/misc.py b/clldutils/misc.py
index 7979814..c97f5b3 100644
--- a/clldutils/misc.py
+++ b/clldutils/misc.py
@@ -9,9 +9,6 @@ from string import ascii_letters
 from six import PY3, string_types, text_type, binary_type
 
 
-# FIXME: add: badge, Stats
-
-
 def nfilter(seq):
     """Replacement for python 2's filter(None, seq).
 
diff --git a/clldutils/path.py b/clldutils/path.py
index 05c0b19..bd6888d 100644
--- a/clldutils/path.py
+++ b/clldutils/path.py
@@ -1,10 +1,13 @@
 # coding: utf8
 from __future__ import unicode_literals
 import os
+import sys
 import shutil
 import tempfile
 import subprocess
 import hashlib
+from contextlib import contextmanager
+import importlib
 
 from six import PY3, string_types, binary_type, text_type
 
@@ -17,6 +20,20 @@ else:
 Path = pathlib.Path
 
 
+@contextmanager
+def sys_path(p):
+    p = Path(p).as_posix()
+    sys.path.append(p)
+    yield
+    if sys.path[-1] == p:
+        sys.path.pop()
+
+
+def import_module(p):
+    with sys_path(p.parent):
+        return importlib.import_module(p.stem)
+
+
 # In python 3, pathlib treats path components and string-like representations or
 # attributes of paths (like name and stem) as unicode strings. Unfortunately this is not
 # true for pathlib under python 2.7. So as workaround for the case of using non-ASCII


Test patch:
diff --git a/clldutils/tests/test_path.py b/clldutils/tests/test_path.py
index ac10a6d..e208515 100644
--- a/clldutils/tests/test_path.py
+++ b/clldutils/tests/test_path.py
@@ -14,6 +14,21 @@ class Tests(WithTempDir):
             fp.write('test')
         return path
 
+    def test_import_module(self):
+        from clldutils.path import import_module
+
+        with self.tmp_path('__init__.py').open('w', encoding='ascii') as fp:
+            fp.write('A = [1, 2, 3]')
+
+        m = import_module(self.tmp_path())
+        self.assertEqual(len(m.A), 3)
+
+        with self.tmp_path('mod.py').open('w', encoding='ascii') as fp:
+            fp.write('A = [1, 2, 3]')
+
+        m = import_module(self.tmp_path('mod.py'))
+        self.assertEqual(len(m.A), 3)
+
     def test_non_ascii(self):
         from clldutils.path import Path, path_component, as_unicode