11import sys
2+ from pathlib import Path
3+ from typing import Dict , Optional , Union
24import clr_loader
35
4- _RUNTIME = None
5- _LOADER_ASSEMBLY = None
6- _FFI = None
7- _LOADED = False
6+ __all__ = ["set_runtime" , "set_default_runtime" , "load" ]
87
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."""
919
10- def set_runtime (runtime ):
1120 global _RUNTIME
1221 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 )
1426
1527 _RUNTIME = runtime
1628
1729
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 )
2162 else :
22- set_runtime ( clr_loader . get_mono () )
63+ raise RuntimeError ( f"Invalid runtime name: ' { spec } '" )
2364
2465
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
27100
28101 if _LOADED :
29102 return
30103
31- from os .path import join , dirname
104+ if _RUNTIME is None :
105+ set_runtime (runtime , ** params )
32106
33107 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" )
37109
38- dll_path = join ( dirname ( __file__ ), "runtime" , "Python.Runtime.dll" )
110+ dll_path = Path ( __file__ ). parent / "runtime" / "Python.Runtime.dll"
39111
40- _LOADER_ASSEMBLY = _RUNTIME .get_assembly (dll_path )
112+ _LOADER_ASSEMBLY = _RUNTIME .get_assembly (str ( dll_path ) )
41113
42114 func = _LOADER_ASSEMBLY ["Python.Runtime.Loader.Initialize" ]
43115 if func (b"" ) != 0 :
@@ -48,13 +120,17 @@ def load():
48120 atexit .register (unload )
49121
50122
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
53127 if _LOADER_ASSEMBLY is not None :
54128 func = _LOADER_ASSEMBLY ["Python.Runtime.Loader.Shutdown" ]
55129 if func (b"full_shutdown" ) != 0 :
56130 raise RuntimeError ("Failed to call Python.NET shutdown" )
57131
132+ _LOADER_ASSEMBLY = None
133+
58134 if _RUNTIME is not None :
59135 # TODO: Add explicit `close` to clr_loader
60136 _RUNTIME = None
0 commit comments