diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index 66f68366d..af257bcb8 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -36,7 +36,7 @@ jobs: - name: Set Python DLL path (non Windows) run: | - python -m pythonnet.find_libpython --export >> $GITHUB_ENV + echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - name: Embedding tests run: dotnet test --logger "console;verbosity=detailed" src/embed_tests/ @@ -45,7 +45,7 @@ jobs: run: python -m pytest --runtime mono - name: Python Tests (.NET Core) - run: python -m pytest --runtime netcore + run: python -m pytest --runtime coreclr - name: Python tests run from .NET run: dotnet test src/python_tests_runner/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a77682f7..2cc793621 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Main (x64) +name: Main on: push: @@ -57,12 +57,12 @@ jobs: - name: Set Python DLL path (non Windows) if: ${{ matrix.os != 'windows' }} run: | - python -m pythonnet.find_libpython --export >> $GITHUB_ENV + echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - name: Set Python DLL path (Windows) if: ${{ matrix.os == 'windows' }} run: | - python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONNET_PYDLL=$(python -m find_libpython)" - name: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/ @@ -73,9 +73,10 @@ jobs: if: ${{ matrix.os != 'windows' }} run: pytest --runtime mono + # TODO: Run these tests on Windows x86 - name: Python Tests (.NET Core) if: ${{ matrix.platform == 'x64' }} - run: pytest --runtime netcore + run: pytest --runtime coreclr - name: Python Tests (.NET Framework) if: ${{ matrix.os == 'windows' }} diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index 1dfa17d5a..d652f4b1e 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -46,7 +46,7 @@ jobs: - name: Set Python DLL path (non Windows) if: ${{ matrix.os != 'windows' }} run: | - python -m pythonnet.find_libpython --export >> $GITHUB_ENV + echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - name: Python Tests run: pytest diff --git a/CHANGELOG.md b/CHANGELOG.md index 069629021..52ee08484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and other `PyObject` derived types when called from Python. - .NET classes, that have `__call__` method are callable from Python - `PyIterable` type, that wraps any iterable object in Python - `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` +- The runtime that is loaded on `import clr` can now be configured via environment variables ### Changed diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 10dc403e4..fa6ed45cf 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -1,43 +1,115 @@ import sys +from pathlib import Path +from typing import Dict, Optional, Union import clr_loader -_RUNTIME = None -_LOADER_ASSEMBLY = None -_FFI = None -_LOADED = False +__all__ = ["set_runtime", "set_default_runtime", "load"] +_RUNTIME: Optional[clr_loader.Runtime] = None +_LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None +_LOADED: bool = False + + +def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: + """Set up a clr_loader runtime without loading it + + :param runtime: Either an already initialised `clr_loader` runtime, or one + of netfx, coreclr, mono, or default. If a string parameter is given, the + runtime will be created.""" -def set_runtime(runtime): global _RUNTIME if _LOADED: - raise RuntimeError("The runtime {} has already been loaded".format(_RUNTIME)) + raise RuntimeError(f"The runtime {_RUNTIME} has already been loaded") + + if isinstance(runtime, str): + runtime = _create_runtime_from_spec(runtime, params) _RUNTIME = runtime -def set_default_runtime() -> None: - if sys.platform == "win32": - set_runtime(clr_loader.get_netfx()) +def _get_params_from_env(prefix: str) -> Dict[str, str]: + from os import environ + + full_prefix = f"PYTHONNET_{prefix.upper()}" + len_ = len(full_prefix) + + env_vars = { + (k[len_:].lower()): v + for k, v in environ.items() + if k.upper().startswith(full_prefix) + } + + return env_vars + + +def _create_runtime_from_spec( + spec: str, params: Optional[Dict[str, str]] = None +) -> clr_loader.Runtime: + if spec == "default": + if sys.platform == "win32": + spec = "netfx" + else: + spec = "mono" + + params = params or _get_params_from_env(spec) + + if spec == "netfx": + return clr_loader.get_netfx(**params) + elif spec == "mono": + return clr_loader.get_mono(**params) + elif spec == "coreclr": + return clr_loader.get_coreclr(**params) else: - set_runtime(clr_loader.get_mono()) + raise RuntimeError(f"Invalid runtime name: '{spec}'") -def load(): - global _FFI, _LOADED, _LOADER_ASSEMBLY +def set_default_runtime() -> None: + """Set up the default runtime + + This will use the environment variable PYTHONNET_RUNTIME to decide the + runtime to use, which may be one of netfx, coreclr or mono. The parameters + of the respective clr_loader.get_ functions can also be given as + environment variables, named `PYTHONNET__`. In + particular, to use `PYTHONNET_RUNTIME=coreclr`, the variable + `PYTHONNET_CORECLR_RUNTIME_CONFIG` has to be set to a valid + `.runtimeconfig.json`. + + If no environment variable is specified, a globally installed Mono is used + for all environments but Windows, on Windows the legacy .NET Framework is + used. + """ + from os import environ + + print("Set default RUNTIME") + raise RuntimeError("Shouldn't be called here") + + spec = environ.get("PYTHONNET_RUNTIME", "default") + runtime = _create_runtime_from_spec(spec) + set_runtime(runtime) + + +def load( + runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str] +) -> None: + """Load Python.NET in the specified runtime + + The same parameters as for `set_runtime` can be used. By default, + `set_default_runtime` is called if no environment has been set yet and no + parameters are passed.""" + global _LOADED, _LOADER_ASSEMBLY if _LOADED: return - from os.path import join, dirname + if _RUNTIME is None: + set_runtime(runtime, **params) if _RUNTIME is None: - # TODO: Warn, in the future the runtime must be set explicitly, either - # as a config/env variable or via set_runtime - set_default_runtime() + raise RuntimeError("No valid runtime selected") - dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll") + dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll" - _LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path) + _LOADER_ASSEMBLY = _RUNTIME.get_assembly(str(dll_path)) func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] if func(b"") != 0: @@ -48,13 +120,17 @@ def load(): atexit.register(unload) -def unload(): - global _RUNTIME +def unload() -> None: + """Explicitly unload a laoded runtime and shut down Python.NET""" + + global _RUNTIME, _LOADER_ASSEMBLY if _LOADER_ASSEMBLY is not None: func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] if func(b"full_shutdown") != 0: raise RuntimeError("Failed to call Python.NET shutdown") + _LOADER_ASSEMBLY = None + if _RUNTIME is not None: # TODO: Add explicit `close` to clr_loader _RUNTIME = None diff --git a/pythonnet/find_libpython/__init__.py b/pythonnet/find_libpython/__init__.py deleted file mode 100644 index 3ae28970e..000000000 --- a/pythonnet/find_libpython/__init__.py +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env python - -""" -Locate libpython associated with this Python executable. -""" - -# License -# -# Copyright 2018, Takafumi Arakaki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -from __future__ import print_function, absolute_import - -from logging import getLogger -import ctypes.util -import functools -import os -import sys -import sysconfig - -logger = getLogger("find_libpython") - -is_windows = os.name == "nt" -is_apple = sys.platform == "darwin" - -SHLIB_SUFFIX = sysconfig.get_config_var("SHLIB_SUFFIX") -if SHLIB_SUFFIX is None: - if is_windows: - SHLIB_SUFFIX = ".dll" - else: - SHLIB_SUFFIX = ".so" -if is_apple: - # sysconfig.get_config_var("SHLIB_SUFFIX") can be ".so" in macOS. - # Let's not use the value from sysconfig. - SHLIB_SUFFIX = ".dylib" - - -def linked_libpython(): - """ - Find the linked libpython using dladdr (in *nix). - - Returns - ------- - path : str or None - A path to linked libpython. Return `None` if statically linked. - """ - if is_windows: - return _linked_libpython_windows() - return _linked_libpython_unix() - - -class Dl_info(ctypes.Structure): - _fields_ = [ - ("dli_fname", ctypes.c_char_p), - ("dli_fbase", ctypes.c_void_p), - ("dli_sname", ctypes.c_char_p), - ("dli_saddr", ctypes.c_void_p), - ] - - -def _linked_libpython_unix(): - if not sysconfig.get_config_var("Py_ENABLE_SHARED"): - return None - - libdl = ctypes.CDLL(ctypes.util.find_library("dl")) - libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)] - libdl.dladdr.restype = ctypes.c_int - - dlinfo = Dl_info() - retcode = libdl.dladdr( - ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), - ctypes.pointer(dlinfo)) - if retcode == 0: # means error - return None - path = os.path.realpath(dlinfo.dli_fname.decode()) - return path - - -def _linked_libpython_windows(): - """ - Based on: https://stackoverflow.com/a/16659821 - """ - from ctypes.wintypes import HANDLE, LPWSTR, DWORD - - GetModuleFileName = ctypes.windll.kernel32.GetModuleFileNameW - GetModuleFileName.argtypes = [HANDLE, LPWSTR, DWORD] - GetModuleFileName.restype = DWORD - - MAX_PATH = 260 - try: - buf = ctypes.create_unicode_buffer(MAX_PATH) - GetModuleFileName(ctypes.pythonapi._handle, buf, MAX_PATH) - return buf.value - except (ValueError, OSError): - return None - - - -def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows): - """ - Convert a file basename `name` to a library name (no "lib" and ".so" etc.) - - >>> library_name("libpython3.7m.so") # doctest: +SKIP - 'python3.7m' - >>> library_name("libpython3.7m.so", suffix=".so", is_windows=False) - 'python3.7m' - >>> library_name("libpython3.7m.dylib", suffix=".dylib", is_windows=False) - 'python3.7m' - >>> library_name("python37.dll", suffix=".dll", is_windows=True) - 'python37' - """ - if not is_windows and name.startswith("lib"): - name = name[len("lib"):] - if suffix and name.endswith(suffix): - name = name[:-len(suffix)] - return name - - -def append_truthy(list, item): - if item: - list.append(item) - - -def uniquifying(items): - """ - Yield items while excluding the duplicates and preserving the order. - - >>> list(uniquifying([1, 2, 1, 2, 3])) - [1, 2, 3] - """ - seen = set() - for x in items: - if x not in seen: - yield x - seen.add(x) - - -def uniquified(func): - """ Wrap iterator returned from `func` by `uniquifying`. """ - @functools.wraps(func) - def wrapper(*args, **kwds): - return uniquifying(func(*args, **kwds)) - return wrapper - - -@uniquified -def candidate_names(suffix=SHLIB_SUFFIX): - """ - Iterate over candidate file names of libpython. - - Yields - ------ - name : str - Candidate name libpython. - """ - LDLIBRARY = sysconfig.get_config_var("LDLIBRARY") - if LDLIBRARY and not LDLIBRARY.endswith(".a"): - yield LDLIBRARY - - LIBRARY = sysconfig.get_config_var("LIBRARY") - if LIBRARY and not LIBRARY.endswith(".a"): - yield os.path.splitext(LIBRARY)[0] + suffix - - dlprefix = "" if is_windows else "lib" - sysdata = dict( - v=sys.version_info, - # VERSION is X.Y in Linux/macOS and XY in Windows: - VERSION=(sysconfig.get_python_version() or - "{v.major}.{v.minor}".format(v=sys.version_info) or - sysconfig.get_config_var("VERSION")), - ABIFLAGS=(sysconfig.get_config_var("ABIFLAGS") or - sysconfig.get_config_var("abiflags") or ""), - ) - - for stem in [ - "python{VERSION}{ABIFLAGS}".format(**sysdata), - "python{VERSION}".format(**sysdata), - "python{v.major}".format(**sysdata), - "python", - ]: - yield dlprefix + stem + suffix - - - -@uniquified -def candidate_paths(suffix=SHLIB_SUFFIX): - """ - Iterate over candidate paths of libpython. - - Yields - ------ - path : str or None - Candidate path to libpython. The path may not be a fullpath - and may not exist. - """ - - yield linked_libpython() - - # List candidates for directories in which libpython may exist - lib_dirs = [] - append_truthy(lib_dirs, sysconfig.get_config_var('LIBPL')) - append_truthy(lib_dirs, sysconfig.get_config_var('srcdir')) - append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR")) - - # LIBPL seems to be the right config_var to use. It is the one - # used in python-config when shared library is not enabled: - # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57 - # - # But we try other places just in case. - - if is_windows: - lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) - else: - lib_dirs.append(os.path.join( - os.path.dirname(os.path.dirname(sys.executable)), - "lib")) - - # For macOS: - append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX")) - - lib_dirs.append(sys.exec_prefix) - lib_dirs.append(os.path.join(sys.exec_prefix, "lib")) - - lib_basenames = list(candidate_names(suffix=suffix)) - - for directory in lib_dirs: - for basename in lib_basenames: - yield os.path.join(directory, basename) - - # In macOS and Windows, ctypes.util.find_library returns a full path: - for basename in lib_basenames: - yield ctypes.util.find_library(library_name(basename)) - -# Possibly useful links: -# * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist -# * https://github.com/Valloric/ycmd/issues/518 -# * https://github.com/Valloric/ycmd/pull/519 - - -def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple): - """ - Normalize shared library `path` to a real path. - - If `path` is not a full path, `None` is returned. If `path` does - not exists, append `SHLIB_SUFFIX` and check if it exists. - Finally, the path is canonicalized by following the symlinks. - - Parameters - ---------- - path : str ot None - A candidate path to a shared library. - """ - if not path: - return None - if not os.path.isabs(path): - return None - if os.path.exists(path): - return os.path.realpath(path) - if os.path.exists(path + suffix): - return os.path.realpath(path + suffix) - if is_apple: - return normalize_path(_remove_suffix_apple(path), - suffix=".so", is_apple=False) - return None - - -def _remove_suffix_apple(path): - """ - Strip off .so or .dylib. - - >>> _remove_suffix_apple("libpython.so") - 'libpython' - >>> _remove_suffix_apple("libpython.dylib") - 'libpython' - >>> _remove_suffix_apple("libpython3.7") - 'libpython3.7' - """ - if path.endswith(".dylib"): - return path[:-len(".dylib")] - if path.endswith(".so"): - return path[:-len(".so")] - return path - - -@uniquified -def finding_libpython(): - """ - Iterate over existing libpython paths. - - The first item is likely to be the best one. - - Yields - ------ - path : str - Existing path to a libpython. - """ - logger.debug("is_windows = %s", is_windows) - logger.debug("is_apple = %s", is_apple) - for path in candidate_paths(): - logger.debug("Candidate: %s", path) - normalized = normalize_path(path) - if normalized: - logger.debug("Found: %s", normalized) - yield normalized - else: - logger.debug("Not found.") - - -def find_libpython(): - """ - Return a path (`str`) to libpython or `None` if not found. - - Parameters - ---------- - path : str or None - Existing path to the (supposedly) correct libpython. - """ - for path in finding_libpython(): - return os.path.realpath(path) - - -def print_all(items): - for x in items: - print(x) - - -def cli_find_libpython(cli_op, verbose, export): - import logging - # Importing `logging` module here so that using `logging.debug` - # instead of `logger.debug` outside of this function becomes an - # error. - - if verbose: - logging.basicConfig( - format="%(levelname)s %(message)s", - level=logging.DEBUG) - - if cli_op == "list-all": - print_all(finding_libpython()) - elif cli_op == "candidate-names": - print_all(candidate_names()) - elif cli_op == "candidate-paths": - print_all(p for p in candidate_paths() if p and os.path.isabs(p)) - else: - path = find_libpython() - if path is None: - return 1 - if export: - print(f"PYTHONNET_PYDLL={path}") - else: - print(path, end="") - - -def main(args=None): - import argparse - parser = argparse.ArgumentParser( - description=__doc__) - parser.add_argument( - "--verbose", "-v", action="store_true", - help="Print debugging information.") - - group = parser.add_mutually_exclusive_group() - group.add_argument( - "--list-all", - action="store_const", dest="cli_op", const="list-all", - help="Print list of all paths found.") - group.add_argument( - "--candidate-names", - action="store_const", dest="cli_op", const="candidate-names", - help="Print list of candidate names of libpython.") - group.add_argument( - "--candidate-paths", - action="store_const", dest="cli_op", const="candidate-paths", - help="Print list of candidate paths of libpython.") - group.add_argument( - "--export", - action="store_true", - help="Print as an environment export expression" - ) - - ns = parser.parse_args(args) - parser.exit(cli_find_libpython(**vars(ns))) diff --git a/pythonnet/find_libpython/__main__.py b/pythonnet/find_libpython/__main__.py deleted file mode 100644 index 031df43e1..000000000 --- a/pythonnet/find_libpython/__main__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import main -main() diff --git a/pythonnet/util/__init__.py b/pythonnet/util/__init__.py deleted file mode 100644 index 75d4bad8c..000000000 --- a/pythonnet/util/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .find_libpython import find_libpython diff --git a/requirements.txt b/requirements.txt index f5aedfc3f..8e911ef5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,6 @@ wheel pycparser setuptools clr-loader + +# Discover libpython +find_libpython \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 89db46eca..fcd1d224a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,91 +8,83 @@ import os import sys import sysconfig +from pathlib import Path from subprocess import check_call from tempfile import mkdtemp import shutil import pytest -from pythonnet import set_runtime - # Add path for `Python.Test` -cwd = os.path.dirname(__file__) -fixtures_path = os.path.join(cwd, "fixtures") -sys.path.append(fixtures_path) +cwd = Path(__file__).parent +fixtures_path = cwd / "fixtures" +sys.path.append(str(fixtures_path)) + def pytest_addoption(parser): parser.addoption( "--runtime", action="store", default="default", - help="Must be one of default, netcore, netfx and mono" + help="Must be one of default, coreclr, netfx and mono", ) + collect_ignore = [] + def pytest_configure(config): global bin_path if "clr" in sys.modules: # Already loaded (e.g. by the C# test runner), skip build import clr + clr.AddReference("Python.Test") return runtime_opt = config.getoption("runtime") - - test_proj_path = os.path.join(cwd, "..", "src", "testing") - - if runtime_opt not in ["netcore", "netfx", "mono", "default"]: + if runtime_opt not in ["coreclr", "netfx", "mono", "default"]: raise RuntimeError(f"Invalid runtime: {runtime_opt}") - bin_path = mkdtemp() - - # tmpdir_factory.mktemp(f"pythonnet-{runtime_opt}") - - fw = "net6.0" if runtime_opt == "netcore" else "netstandard2.0" - - check_call(["dotnet", "publish", "-f", fw, "-o", bin_path, test_proj_path]) - - sys.path.append(bin_path) - - if runtime_opt == "default": - pass - elif runtime_opt == "netfx": - from clr_loader import get_netfx - runtime = get_netfx() - set_runtime(runtime) - elif runtime_opt == "mono": - from clr_loader import get_mono - runtime = get_mono() - set_runtime(runtime) - elif runtime_opt == "netcore": - from clr_loader import get_coreclr - rt_config_path = os.path.join(bin_path, "Python.Test.runtimeconfig.json") - runtime = get_coreclr(rt_config_path) - set_runtime(runtime) - - import clr - clr.AddReference("Python.Test") + test_proj_path = cwd.parent / "src" / "testing" + bin_path = Path(mkdtemp()) - soft_mode = False - try: - os.environ['PYTHONNET_SHUTDOWN_MODE'] == 'Soft' - except: pass + fw = "netstandard2.0" + runtime_params = {} - if config.getoption("--runtime") == "netcore" or soft_mode\ - : + if runtime_opt == "coreclr": + fw = "net6.0" + runtime_params["runtime_config"] = str( + bin_path / "Python.Test.runtimeconfig.json" + ) collect_ignore.append("domain_tests/test_domain_reload.py") else: - domain_tests_dir = os.path.join(os.path.dirname(__file__), "domain_tests") - bin_path = os.path.join(domain_tests_dir, "bin") - build_cmd = ["dotnet", "build", domain_tests_dir, "-o", bin_path] + domain_tests_dir = cwd / "domain_tests" + domain_bin_path = domain_tests_dir / "bin" + build_cmd = [ + "dotnet", + "build", + str(domain_tests_dir), + "-o", + str(domain_bin_path), + ] is_64bits = sys.maxsize > 2**32 if not is_64bits: build_cmd += ["/p:Prefer32Bit=True"] check_call(build_cmd) + check_call( + ["dotnet", "publish", "-f", fw, "-o", str(bin_path), str(test_proj_path)] + ) + + from pythonnet import load + + load(runtime_opt, **runtime_params) + + import clr + sys.path.append(str(bin_path)) + clr.AddReference("Python.Test") def pytest_unconfigure(config): @@ -102,6 +94,7 @@ def pytest_unconfigure(config): except Exception: pass + def pytest_report_header(config): """Generate extra report headers""" # FIXME: https://github.com/pytest-dev/pytest/issues/2257 @@ -109,11 +102,8 @@ def pytest_report_header(config): arch = "x64" if is_64bits else "x86" ucs = ctypes.sizeof(ctypes.c_wchar) libdir = sysconfig.get_config_var("LIBDIR") - shared = bool(sysconfig.get_config_var("Py_ENABLE_SHARED")) - header = ("Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}, " - "Py_ENABLE_SHARED: {shared}".format(**locals())) - return header + return f"Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}" @pytest.fixture() diff --git a/tests/domain_tests/test_domain_reload.py b/tests/domain_tests/test_domain_reload.py index d04d5a1f6..8999e481b 100644 --- a/tests/domain_tests/test_domain_reload.py +++ b/tests/domain_tests/test_domain_reload.py @@ -4,7 +4,7 @@ import pytest -from pythonnet.find_libpython import find_libpython +from find_libpython import find_libpython libpython = find_libpython() pytestmark = pytest.mark.xfail(libpython is None, reason="Can't find suitable libpython")