diff --git a/README.md b/README.md index 8d16df1..36efe9e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # rpyutils Various utility types and functions for Python + +## API + +- Context manager for adding DLLs to the Windows search path. + Only applies to Python 3.8 or newer: + - `add_dll_directories_from_env` diff --git a/rpyutils/__init__.py b/rpyutils/__init__.py index 0ad61e7..ff705a3 100644 --- a/rpyutils/__init__.py +++ b/rpyutils/__init__.py @@ -12,5 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from .add_dll_directories import add_dll_directories_from_env + + __all__ = [ + 'add_dll_directories_from_env', ] diff --git a/rpyutils/add_dll_directories.py b/rpyutils/add_dll_directories.py new file mode 100644 index 0000000..cd2d2d3 --- /dev/null +++ b/rpyutils/add_dll_directories.py @@ -0,0 +1,55 @@ +# Copyright 2020 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager +import os +import sys + + +@contextmanager +def add_dll_directories_from_env(env_name: str): + """ + Add a list of directories from an environment variable to the DLL search path on Windows. + + Each directory in the environment variable that exists is passed to + :func:`os.add_dll_directory`. + + If this function is called on a system other than Windows, then nothing happens and + an empty list is returned. + If this function is called with a version of Python less than 3.8, then nothing happens and + an empty list is returned. + + Example usage:: + + with add_dll_directories_from_env('PATH'): + importlib.import_module('foo', package='bar') + + :param env_name: The name of the environment variable with DLL search paths. + :return: A list of handles to directories. + """ + dll_dir_handles = [] + # This function only makes sense on Windows and if the function 'add_dll_directory' exists + if sys.platform == 'win32' and hasattr(os, 'add_dll_directory'): + env_value = os.environ.get(env_name) + path_list = env_value.split(os.pathsep) if env_value is not None else [] + for prefix_path in path_list: + # Only add directories that exist + if os.path.isdir(prefix_path): + dll_dir_handles.append(os.add_dll_directory(prefix_path)) + + try: + yield dll_dir_handles + finally: + for handle in dll_dir_handles: + handle.close() diff --git a/test/rpyutils/test_add_dll_directories.py b/test/rpyutils/test_add_dll_directories.py new file mode 100644 index 0000000..fa2a48f --- /dev/null +++ b/test/rpyutils/test_add_dll_directories.py @@ -0,0 +1,36 @@ +# Copyright 2020 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rpyutils import add_dll_directories_from_env + + +def test_add_dll_direcotires_from_env(monkeypatch, tmp_path): + # Test with empty value + monkeypatch.delenv('TEST_ENV', raising=False) + with add_dll_directories_from_env('TEST_ENV'): + pass + + # Test with one path + monkeypatch.setenv('TEST_ENV', tmp_path.name) + with add_dll_directories_from_env('TEST_ENV'): + pass + + # Test with multiple paths + dir1 = tmp_path / 'subdir1' + dir2 = tmp_path / 'subdir2' + dir1.mkdir() + dir2.mkdir() + monkeypatch.setenv('TEST_ENV', f'{dir1.name};{dir2.name}') + with add_dll_directories_from_env('TEST_ENV'): + pass