| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,7 +45,7 @@ jobs: | |||
| 45 | 45 | run: python -m pytest --runtime mono | |
| 46 | 46 | ||
| 47 | 47 | - name: Python Tests (.NET Core) | |
| 48 | - run: python -m pytest --runtime netcore | ||
| 48 | + run: python -m pytest --runtime coreclr | ||
| 49 | 49 | ||
| 50 | 50 | - name: Python tests run from .NET | |
| 51 | 51 | run: dotnet test src/python_tests_runner/ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,4 @@ | |||
| 1 | - name: Main (x64) | ||
| 1 | + name: Main | ||
| 2 | 2 | ||
| 3 | 3 | on: | |
| 4 | 4 | push: | |
@@ -73,9 +73,10 @@ jobs: | |||
| 73 | 73 | if: ${{ matrix.os != 'windows' }} | |
| 74 | 74 | run: pytest --runtime mono | |
| 75 | 75 | ||
| 76 | + # TODO: Run these tests on Windows x86 | ||
| 76 | 77 | - name: Python Tests (.NET Core) | |
| 77 | 78 | if: ${{ matrix.platform == 'x64' }} | |
| 78 | - run: pytest --runtime netcore | ||
| 79 | + run: pytest --runtime coreclr | ||
| 79 | 80 | ||
| 80 | 81 | - name: Python Tests (.NET Framework) | |
| 81 | 82 | if: ${{ matrix.os == 'windows' }} | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,6 +29,7 @@ and other `PyObject` derived types when called from Python. | |||
| 29 | 29 | - .NET classes, that have `__call__` method are callable from Python | |
| 30 | 30 | - `PyIterable` type, that wraps any iterable object in Python | |
| 31 | 31 | - `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` | |
| 32 | + - The runtime that is loaded on `import clr` can now be configured via environment variables | ||
| 32 | 33 | ||
| 33 | 34 | ||
| 34 | 35 | ### Changed | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,43 +1,115 @@ | |||
| 1 | 1 | import sys | |
| 2 | + from pathlib import Path | ||
| 3 | + from typing import Dict, Optional, Union | ||
| 2 | 4 | import clr_loader | |
| 3 | 5 | ||
| 4 | - _RUNTIME = None | ||
| 5 | - _LOADER_ASSEMBLY = None | ||
| 6 | - _FFI = None | ||
| 7 | - _LOADED = False | ||
| 6 | + __all__ = ["set_runtime", "set_default_runtime", "load"] | ||
| 8 | 7 | ||
| 8 | + _RUNTIME: Optional[clr_loader.Runtime] = None | ||
| 9 | + _LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None | ||
| 10 | + _LOADED: bool = False | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: | ||
| 14 | + """Set up a clr_loader runtime without loading it | ||
| 15 | + | ||
| 16 | + :param runtime: Either an already initialised `clr_loader` runtime, or one | ||
| 17 | + of netfx, coreclr, mono, or default. If a string parameter is given, the | ||
| 18 | + runtime will be created.""" | ||
| 9 | 19 | ||
| 10 | - def set_runtime(runtime): | ||
| 11 | 20 | global _RUNTIME | |
| 12 | 21 | if _LOADED: | |
| 13 | - raise RuntimeError("The runtime {} has already been loaded".format(_RUNTIME)) | ||
| 22 | + raise RuntimeError(f"The runtime {_RUNTIME} has already been loaded") | ||
| 23 | + | ||
| 24 | + if isinstance(runtime, str): | ||
| 25 | + runtime = _create_runtime_from_spec(runtime, params) | ||
| 14 | 26 | ||
| 15 | 27 | _RUNTIME = runtime | |
| 16 | 28 | ||
| 17 | 29 | ||
| 18 | - def set_default_runtime() -> None: | ||
| 19 | - if sys.platform == "win32": | ||
| 20 | - set_runtime(clr_loader.get_netfx()) | ||
| 30 | + def _get_params_from_env(prefix: str) -> Dict[str, str]: | ||
| 31 | + from os import environ | ||
| 32 | + | ||
| 33 | + full_prefix = f"PYTHONNET_{prefix.upper()}" | ||
| 34 | + len_ = len(full_prefix) | ||
| 35 | + | ||
| 36 | + env_vars = { | ||
| 37 | + (k[len_:].lower()): v | ||
| 38 | + for k, v in environ.items() | ||
| 39 | + if k.upper().startswith(full_prefix) | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + return env_vars | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + def _create_runtime_from_spec( | ||
| 46 | + spec: str, params: Optional[Dict[str, str]] = None | ||
| 47 | + ) -> clr_loader.Runtime: | ||
| 48 | + if spec == "default": | ||
| 49 | + if sys.platform == "win32": | ||
| 50 | + spec = "netfx" | ||
| 51 | + else: | ||
| 52 | + spec = "mono" | ||
| 53 | + | ||
| 54 | + params = params or _get_params_from_env(spec) | ||
| 55 | + | ||
| 56 | + if spec == "netfx": | ||
| 57 | + return clr_loader.get_netfx(**params) | ||
| 58 | + elif spec == "mono": | ||
| 59 | + return clr_loader.get_mono(**params) | ||
| 60 | + elif spec == "coreclr": | ||
| 61 | + return clr_loader.get_coreclr(**params) | ||
| 21 | 62 | else: | |
| 22 | - set_runtime(clr_loader.get_mono()) | ||
| 63 | + raise RuntimeError(f"Invalid runtime name: '{spec}'") | ||
| 23 | 64 | ||
| 24 | 65 | ||
| 25 | - def load(): | ||
| 26 | - global _FFI, _LOADED, _LOADER_ASSEMBLY | ||
| 66 | + def set_default_runtime() -> None: | ||
| 67 | + """Set up the default runtime | ||
| 68 | + | ||
| 69 | + This will use the environment variable PYTHONNET_RUNTIME to decide the | ||
| 70 | + runtime to use, which may be one of netfx, coreclr or mono. The parameters | ||
| 71 | + of the respective clr_loader.get_<runtime> functions can also be given as | ||
| 72 | + environment variables, named `PYTHONNET_<RUNTIME>_<PARAM_NAME>`. In | ||
| 73 | + particular, to use `PYTHONNET_RUNTIME=coreclr`, the variable | ||
| 74 | + `PYTHONNET_CORECLR_RUNTIME_CONFIG` has to be set to a valid | ||
| 75 | + `.runtimeconfig.json`. | ||
| 76 | + | ||
| 77 | + If no environment variable is specified, a globally installed Mono is used | ||
| 78 | + for all environments but Windows, on Windows the legacy .NET Framework is | ||
| 79 | + used. | ||
| 80 | + """ | ||
| 81 | + from os import environ | ||
| 82 | + | ||
| 83 | + print("Set default RUNTIME") | ||
| 84 | + raise RuntimeError("Shouldn't be called here") | ||
| 85 | + | ||
| 86 | + spec = environ.get("PYTHONNET_RUNTIME", "default") | ||
| 87 | + runtime = _create_runtime_from_spec(spec) | ||
| 88 | + set_runtime(runtime) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | + def load( | ||
| 92 | + runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str] | ||
| 93 | + ) -> None: | ||
| 94 | + """Load Python.NET in the specified runtime | ||
| 95 | + | ||
| 96 | + The same parameters as for `set_runtime` can be used. By default, | ||
| 97 | + `set_default_runtime` is called if no environment has been set yet and no | ||
| 98 | + parameters are passed.""" | ||
| 99 | + global _LOADED, _LOADER_ASSEMBLY | ||
| 27 | 100 | ||
| 28 | 101 | if _LOADED: | |
| 29 | 102 | return | |
| 30 | 103 | ||
| 31 | - from os.path import join, dirname | ||
| 104 | + if _RUNTIME is None: | ||
| 105 | + set_runtime(runtime, **params) | ||
| 32 | 106 | ||
| 33 | 107 | if _RUNTIME is None: | |
| 34 | - # TODO: Warn, in the future the runtime must be set explicitly, either | ||
| 35 | - # as a config/env variable or via set_runtime | ||
| 36 | - set_default_runtime() | ||
| 108 | + raise RuntimeError("No valid runtime selected") | ||
| 37 | 109 | ||
| 38 | - dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll") | ||
| 110 | + dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll" | ||
| 39 | 111 | ||
| 40 | - _LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path) | ||
| 112 | + _LOADER_ASSEMBLY = _RUNTIME.get_assembly(str(dll_path)) | ||
| 41 | 113 | ||
| 42 | 114 | func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] | |
| 43 | 115 | if func(b"") != 0: | |
@@ -48,13 +120,17 @@ def load(): | |||
| 48 | 120 | atexit.register(unload) | |
| 49 | 121 | ||
| 50 | 122 | ||
| 51 | - def unload(): | ||
| 52 | - global _RUNTIME | ||
| 123 | + def unload() -> None: | ||
| 124 | + """Explicitly unload a laoded runtime and shut down Python.NET""" | ||
| 125 | + | ||
| 126 | + global _RUNTIME, _LOADER_ASSEMBLY | ||
| 53 | 127 | if _LOADER_ASSEMBLY is not None: | |
| 54 | 128 | func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] | |
| 55 | 129 | if func(b"full_shutdown") != 0: | |
| 56 | 130 | raise RuntimeError("Failed to call Python.NET shutdown") | |
| 57 | 131 | ||
| 132 | + _LOADER_ASSEMBLY = None | ||
| 133 | + | ||
| 58 | 134 | if _RUNTIME is not None: | |
| 59 | 135 | # TODO: Add explicit `close` to clr_loader | |
| 60 | 136 | _RUNTIME = None | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,91 +8,83 @@ | |||
| 8 | 8 | import os | |
| 9 | 9 | import sys | |
| 10 | 10 | import sysconfig | |
| 11 | + from pathlib import Path | ||
| 11 | 12 | from subprocess import check_call | |
| 12 | 13 | from tempfile import mkdtemp | |
| 13 | 14 | import shutil | |
| 14 | 15 | ||
| 15 | 16 | import pytest | |
| 16 | 17 | ||
| 17 | - from pythonnet import set_runtime | ||
| 18 | - | ||
| 19 | 18 | # Add path for `Python.Test` | |
| 20 | - cwd = os.path.dirname(__file__) | ||
| 21 | - fixtures_path = os.path.join(cwd, "fixtures") | ||
| 22 | - sys.path.append(fixtures_path) | ||
| 19 | + cwd = Path(__file__).parent | ||
| 20 | + fixtures_path = cwd / "fixtures" | ||
| 21 | + sys.path.append(str(fixtures_path)) | ||
| 22 | + | ||
| 23 | 23 | ||
| 24 | 24 | def pytest_addoption(parser): | |
| 25 | 25 | parser.addoption( | |
| 26 | 26 | "--runtime", | |
| 27 | 27 | action="store", | |
| 28 | 28 | default="default", | |
| 29 | - help="Must be one of default, netcore, netfx and mono" | ||
| 29 | + help="Must be one of default, coreclr, netfx and mono", | ||
| 30 | 30 | ) | |
| 31 | 31 | ||
| 32 | + | ||
| 32 | 33 | collect_ignore = [] | |
| 33 | 34 | ||
| 35 | + | ||
| 34 | 36 | def pytest_configure(config): | |
| 35 | 37 | global bin_path | |
| 36 | 38 | if "clr" in sys.modules: | |
| 37 | 39 | # Already loaded (e.g. by the C# test runner), skip build | |
| 38 | 40 | import clr | |
| 41 | + | ||
| 39 | 42 | clr.AddReference("Python.Test") | |
| 40 | 43 | return | |
| 41 | 44 | ||
| 42 | 45 | runtime_opt = config.getoption("runtime") | |
| 43 | - | ||
| 44 | - test_proj_path = os.path.join(cwd, "..", "src", "testing") | ||
| 45 | - | ||
| 46 | - if runtime_opt not in ["netcore", "netfx", "mono", "default"]: | ||
| 46 | + if runtime_opt not in ["coreclr", "netfx", "mono", "default"]: | ||
| 47 | 47 | raise RuntimeError(f"Invalid runtime: {runtime_opt}") | |
| 48 | 48 | ||
| 49 | - bin_path = mkdtemp() | ||
| 50 | - | ||
| 51 | - # tmpdir_factory.mktemp(f"pythonnet-{runtime_opt}") | ||
| 52 | - | ||
| 53 | - fw = "net6.0" if runtime_opt == "netcore" else "netstandard2.0" | ||
| 54 | - | ||
| 55 | - check_call(["dotnet", "publish", "-f", fw, "-o", bin_path, test_proj_path]) | ||
| 56 | - | ||
| 57 | - sys.path.append(bin_path) | ||
| 58 | - | ||
| 59 | - if runtime_opt == "default": | ||
| 60 | - pass | ||
| 61 | - elif runtime_opt == "netfx": | ||
| 62 | - from clr_loader import get_netfx | ||
| 63 | - runtime = get_netfx() | ||
| 64 | - set_runtime(runtime) | ||
| 65 | - elif runtime_opt == "mono": | ||
| 66 | - from clr_loader import get_mono | ||
| 67 | - runtime = get_mono() | ||
| 68 | - set_runtime(runtime) | ||
| 69 | - elif runtime_opt == "netcore": | ||
| 70 | - from clr_loader import get_coreclr | ||
| 71 | - rt_config_path = os.path.join(bin_path, "Python.Test.runtimeconfig.json") | ||
| 72 | - runtime = get_coreclr(rt_config_path) | ||
| 73 | - set_runtime(runtime) | ||
| 74 | - | ||
| 75 | - import clr | ||
| 76 | - clr.AddReference("Python.Test") | ||
| 49 | + test_proj_path = cwd.parent / "src" / "testing" | ||
| 50 | + bin_path = Path(mkdtemp()) | ||
| 77 | 51 | ||
| 78 | - soft_mode = False | ||
| 79 | - try: | ||
| 80 | - os.environ['PYTHONNET_SHUTDOWN_MODE'] == 'Soft' | ||
| 81 | - except: pass | ||
| 52 | + fw = "netstandard2.0" | ||
| 53 | + runtime_params = {} | ||
| 82 | 54 | ||
| 83 | - if config.getoption("--runtime") == "netcore" or soft_mode\ | ||
| 84 | - : | ||
| 55 | + if runtime_opt == "coreclr": | ||
| 56 | + fw = "net6.0" | ||
| 57 | + runtime_params["runtime_config"] = str( | ||
| 58 | + bin_path / "Python.Test.runtimeconfig.json" | ||
| 59 | + ) | ||
| 85 | 60 | collect_ignore.append("domain_tests/test_domain_reload.py") | |
| 86 | 61 | else: | |
| 87 | - domain_tests_dir = os.path.join(os.path.dirname(__file__), "domain_tests") | ||
| 88 | - bin_path = os.path.join(domain_tests_dir, "bin") | ||
| 89 | - build_cmd = ["dotnet", "build", domain_tests_dir, "-o", bin_path] | ||
| 62 | + domain_tests_dir = cwd / "domain_tests" | ||
| 63 | + domain_bin_path = domain_tests_dir / "bin" | ||
| 64 | + build_cmd = [ | ||
| 65 | + "dotnet", | ||
| 66 | + "build", | ||
| 67 | + str(domain_tests_dir), | ||
| 68 | + "-o", | ||
| 69 | + str(domain_bin_path), | ||
| 70 | + ] | ||
| 90 | 71 | is_64bits = sys.maxsize > 2**32 | |
| 91 | 72 | if not is_64bits: | |
| 92 | 73 | build_cmd += ["/p:Prefer32Bit=True"] | |
| 93 | 74 | check_call(build_cmd) | |
| 94 | 75 | ||
| 76 | + check_call( | ||
| 77 | + ["dotnet", "publish", "-f", fw, "-o", str(bin_path), str(test_proj_path)] | ||
| 78 | + ) | ||
| 79 | + | ||
| 80 | + from pythonnet import load | ||
| 81 | + | ||
| 82 | + load(runtime_opt, **runtime_params) | ||
| 83 | + | ||
| 84 | + import clr | ||
| 95 | 85 | ||
| 86 | + sys.path.append(str(bin_path)) | ||
| 87 | + clr.AddReference("Python.Test") | ||
| 96 | 88 | ||
| 97 | 89 | ||
| 98 | 90 | def pytest_unconfigure(config): | |
@@ -102,18 +94,16 @@ def pytest_unconfigure(config): | |||
| 102 | 94 | except Exception: | |
| 103 | 95 | pass | |
| 104 | 96 | ||
| 97 | + | ||
| 105 | 98 | def pytest_report_header(config): | |
| 106 | 99 | """Generate extra report headers""" | |
| 107 | 100 | # FIXME: https://github.com/pytest-dev/pytest/issues/2257 | |
| 108 | 101 | is_64bits = sys.maxsize > 2**32 | |
| 109 | 102 | arch = "x64" if is_64bits else "x86" | |
| 110 | 103 | ucs = ctypes.sizeof(ctypes.c_wchar) | |
| 111 | 104 | libdir = sysconfig.get_config_var("LIBDIR") | |
| 112 | - shared = bool(sysconfig.get_config_var("Py_ENABLE_SHARED")) | ||
| 113 | 105 | ||
| 114 | - header = ("Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}, " | ||
| 115 | - "Py_ENABLE_SHARED: {shared}".format(**locals())) | ||
| 116 | - return header | ||
| 106 | + return f"Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}" | ||
| 117 | 107 | ||
| 118 | 108 | ||
| 119 | 109 | @pytest.fixture() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments