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

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)
```
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
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
nose coverage pytest
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/clld/clldutils.git@cebffac35a42bde46fcd32d2db4a0fd5464968a3#egg=clldutils
configparser==7.2.0
coverage==7.8.0
exceptiongroup==1.2.2
iniconfig==2.1.0
nose==1.3.7
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
python-dateutil==2.9.0.post0
six==1.17.0
tabulate==0.9.0
tomli==2.2.1

name: clldutils
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - configparser==7.2.0
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - nose==1.3.7
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - six==1.17.0
      - tabulate==0.9.0
      - tomli==2.2.1
prefix: /opt/conda/envs/clldutils

clldutils/tests/test_path.py::Tests::test_import_module
clldutils/tests/test_path.py::Tests::test_TemporaryDirectory clldutils/tests/test_path.py::Tests::test_as_posix clldutils/tests/test_path.py::Tests::test_copy clldutils/tests/test_path.py::Tests::test_copytree clldutils/tests/test_path.py::Tests::test_git_describe clldutils/tests/test_path.py::Tests::test_md5 clldutils/tests/test_path.py::Tests::test_move clldutils/tests/test_path.py::Tests::test_non_ascii clldutils/tests/test_path.py::Tests::test_remove clldutils/tests/test_path.py::Tests::test_rmtree clldutils/tests/test_path.py::Tests::test_walk
clldutils/misc.py clldutils/path.py
clldutils/misc.py clldutils/path.py