Skip to content

Commit ff33baf

Browse files
Update support for newer clr-loader supporting net10 (#112)
1 parent 258ba5c commit ff33baf

File tree

4 files changed

+138
-30
lines changed

4 files changed

+138
-30
lines changed

pythonnet/__init__.py

Lines changed: 133 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,168 @@
1+
"""Python.NET runtime loading and configuration"""
2+
13
import sys
4+
from pathlib import Path
5+
from typing import Dict, Optional, Union, Any
26
import clr_loader
37

4-
_RUNTIME = None
5-
_LOADER_ASSEMBLY = None
6-
_FFI = None
7-
_LOADED = False
8+
__all__ = ["set_runtime", "set_runtime_from_env", "load", "unload", "get_runtime_info"]
9+
10+
_RUNTIME: Optional[clr_loader.Runtime] = None
11+
_LOADER_ASSEMBLY: Optional[clr_loader.Assembly] = None
12+
_LOADED: bool = False
13+
14+
15+
def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None:
16+
"""Set up a clr_loader runtime without loading it
817
18+
:param runtime:
19+
Either an already initialised `clr_loader` runtime, or one of netfx,
20+
coreclr, mono, or default. If a string parameter is given, the runtime
21+
will be created.
22+
"""
923

10-
def set_runtime(runtime):
1124
global _RUNTIME
1225
if _LOADED:
13-
raise RuntimeError("The runtime {} has already been loaded".format(_RUNTIME))
26+
raise RuntimeError(f"The runtime {_RUNTIME} has already been loaded")
1427

15-
_RUNTIME = runtime
28+
if isinstance(runtime, str):
29+
runtime = _create_runtime_from_spec(runtime, params)
1630

31+
_RUNTIME = runtime
1732

18-
def set_default_runtime() -> None:
19-
if sys.platform == "win32":
20-
set_runtime(clr_loader.get_netfx())
21-
else:
22-
set_runtime(clr_loader.get_mono())
2333

34+
def get_runtime_info() -> Optional[clr_loader.RuntimeInfo]:
35+
"""Retrieve information on the configured runtime"""
2436

25-
def load():
26-
global _FFI, _LOADED, _LOADER_ASSEMBLY
37+
if _RUNTIME is None:
38+
return None
39+
else:
40+
return _RUNTIME.info()
41+
42+
43+
def _get_params_from_env(prefix: str) -> Dict[str, str]:
44+
from os import environ
45+
46+
full_prefix = f"PYTHONNET_{prefix.upper()}_"
47+
len_ = len(full_prefix)
48+
49+
env_vars = {
50+
(k[len_:].lower()): v
51+
for k, v in environ.items()
52+
if k.upper().startswith(full_prefix)
53+
}
54+
55+
return env_vars
56+
57+
58+
def _create_runtime_from_spec(
59+
spec: str, params: Optional[Dict[str, Any]] = None
60+
) -> clr_loader.Runtime:
61+
was_default = False
62+
if spec == "default":
63+
was_default = True
64+
if sys.platform == "win32":
65+
spec = "netfx"
66+
else:
67+
spec = "mono"
68+
69+
params = params or _get_params_from_env(spec)
70+
71+
try:
72+
if spec == "netfx":
73+
return clr_loader.get_netfx(**params)
74+
elif spec == "mono":
75+
return clr_loader.get_mono(**params)
76+
elif spec == "coreclr":
77+
return clr_loader.get_coreclr(**params)
78+
else:
79+
raise RuntimeError(f"Invalid runtime name: '{spec}'")
80+
except Exception as exc:
81+
if was_default:
82+
raise RuntimeError(
83+
f"""Failed to create a default .NET runtime, which would
84+
have been "{spec}" on this system. Either install a
85+
compatible runtime or configure it explicitly via
86+
`set_runtime` or the `PYTHONNET_*` environment variables
87+
(see set_runtime_from_env)."""
88+
) from exc
89+
else:
90+
raise RuntimeError(
91+
f"""Failed to create a .NET runtime ({spec}) using the
92+
parameters {params}."""
93+
) from exc
94+
95+
96+
def set_runtime_from_env() -> None:
97+
"""Set up the runtime using the environment
98+
99+
This will use the environment variable PYTHONNET_RUNTIME to decide the
100+
runtime to use, which may be one of netfx, coreclr or mono. The parameters
101+
of the respective clr_loader.get_<runtime> functions can also be given as
102+
environment variables, named `PYTHONNET_<RUNTIME>_<PARAM_NAME>`. In
103+
particular, to use `PYTHONNET_RUNTIME=coreclr`, the variable
104+
`PYTHONNET_CORECLR_RUNTIME_CONFIG` has to be set to a valid
105+
`.runtimeconfig.json`.
106+
107+
If no environment variable is specified, a globally installed Mono is used
108+
for all environments but Windows, on Windows the legacy .NET Framework is
109+
used.
110+
"""
111+
from os import environ
112+
113+
spec = environ.get("PYTHONNET_RUNTIME", "default")
114+
runtime = _create_runtime_from_spec(spec)
115+
set_runtime(runtime)
116+
117+
118+
def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> None:
119+
"""Load Python.NET in the specified runtime
120+
121+
The same parameters as for `set_runtime` can be used. By default,
122+
`set_default_runtime` is called if no environment has been set yet and no
123+
parameters are passed.
124+
125+
After a successful call, further invocations will return immediately."""
126+
global _LOADED, _LOADER_ASSEMBLY
27127

28128
if _LOADED:
29129
return
30130

31-
from os.path import join, dirname
131+
if _RUNTIME is None:
132+
if runtime is None:
133+
set_runtime_from_env()
134+
else:
135+
set_runtime(runtime, **params)
32136

33137
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()
138+
raise RuntimeError("No valid runtime selected")
37139

38-
dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll")
140+
dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll"
39141

40-
_LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path)
142+
_LOADER_ASSEMBLY = assembly = _RUNTIME.get_assembly(str(dll_path))
143+
func = assembly.get_function("Python.Runtime.Loader.Initialize")
41144

42-
func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"]
43145
if func(b"") != 0:
44146
raise RuntimeError("Failed to initialize Python.Runtime.dll")
147+
148+
_LOADED = True
45149

46150
import atexit
47151

48152
atexit.register(unload)
49153

50154

51-
def unload():
52-
global _RUNTIME
155+
def unload() -> None:
156+
"""Explicitly unload a loaded runtime and shut down Python.NET"""
157+
158+
global _RUNTIME, _LOADER_ASSEMBLY
53159
if _LOADER_ASSEMBLY is not None:
54-
func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"]
160+
func = _LOADER_ASSEMBLY.get_function("Python.Runtime.Loader.Shutdown")
55161
if func(b"full_shutdown") != 0:
56162
raise RuntimeError("Failed to call Python.NET shutdown")
57163

164+
_LOADER_ASSEMBLY = None
165+
58166
if _RUNTIME is not None:
59-
# TODO: Add explicit `close` to clr_loader
167+
_RUNTIME.shutdown()
60168
_RUNTIME = None

src/perf_tests/Python.PerformanceTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
16-
<PackageReference Include="quantconnect.pythonnet" Version="2.0.51" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.52" GeneratePathProperty="true">
1717
<IncludeAssets>compile</IncludeAssets>
1818
</PackageReference>
1919
</ItemGroup>
@@ -25,7 +25,7 @@
2525
</Target>
2626

2727
<Target Name="CopyBaseline" AfterTargets="Build">
28-
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.51\lib\net10.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
28+
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.52\lib\net10.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
2929
</Target>
3030

3131
<Target Name="CopyNewBuild" AfterTargets="Build">

src/runtime/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
55
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
66

7-
[assembly: AssemblyVersion("2.0.51")]
8-
[assembly: AssemblyFileVersion("2.0.51")]
7+
[assembly: AssemblyVersion("2.0.52")]
8+
[assembly: AssemblyFileVersion("2.0.52")]

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
77
<PackageId>QuantConnect.pythonnet</PackageId>
8-
<Version>2.0.51</Version>
8+
<Version>2.0.52</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

0 commit comments

Comments
 (0)