Skip to content

Commit e0897bd

Browse files
authored
Merge pull request #107 from pythonnet/modernize
Modernise the codebase a bit
2 parents 58d3f55 + ac7b66b commit e0897bd

18 files changed

Lines changed: 173 additions & 641 deletions

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
# - Update macos images to at least 14 as 13 is retired
5050
# - Update ubuntu images
5151
os: [ubuntu-22.04, ubuntu-22.04-arm, windows-latest, macos-13]
52-
python: ['3.14', '3.13', '3.12', '3.11', '3.10', '3.9', '3.8'] # pypy3
52+
python: ['3.14', '3.13', '3.12', '3.11', '3.10'] # pypy3
5353

5454
steps:
5555
- name: Set Environment on macOS
@@ -61,7 +61,7 @@ jobs:
6161
- name: Setup .NET
6262
uses: actions/setup-dotnet@v5
6363
with:
64-
dotnet-version: '8.0.x'
64+
dotnet-version: '10.0'
6565

6666
- name: Set up Python ${{ matrix.python }}
6767
uses: astral-sh/setup-uv@v7

clr_loader/__init__.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
from collections.abc import Sequence
12
from pathlib import Path
23
from tempfile import TemporaryDirectory
3-
from typing import Dict, Optional, Sequence
44

5-
from .types import Assembly, Runtime, RuntimeInfo
6-
from .util import StrOrPath
5+
from .types import Assembly, Runtime, RuntimeInfo, StrOrPath
76
from .util.find import find_dotnet_root, find_libmono, find_runtimes
87
from .util.runtime_spec import DotnetCoreRuntimeSpec
98

@@ -24,17 +23,17 @@
2423
def get_mono(
2524
*,
2625
# domain: Optional[str] = None,
27-
config_file: Optional[StrOrPath] = None,
28-
global_config_file: Optional[StrOrPath] = None,
29-
libmono: Optional[StrOrPath] = None,
26+
config_file: StrOrPath | None = None,
27+
global_config_file: StrOrPath | None = None,
28+
libmono: StrOrPath | None = None,
3029
sgen: bool = True,
3130
debug: bool = False,
32-
jit_options: Optional[Sequence[str]] = None,
33-
assembly_dir: Optional[str] = None,
34-
config_dir: Optional[str] = None,
31+
jit_options: Sequence[str] | None = None,
32+
assembly_dir: StrOrPath | None = None,
33+
config_dir: StrOrPath | None = None,
3534
set_signal_chaining: bool = False,
36-
trace_mask: Optional[str] = None,
37-
trace_level: Optional[str] = None,
35+
trace_mask: str | None = None,
36+
trace_level: str | None = None,
3837
) -> Runtime:
3938
"""Get a Mono runtime instance
4039
@@ -92,11 +91,12 @@ def get_mono(
9291

9392
def get_coreclr(
9493
*,
95-
runtime_config: Optional[StrOrPath] = None,
96-
entry_dll: Optional[StrOrPath] = None,
97-
dotnet_root: Optional[StrOrPath] = None,
98-
properties: Optional[Dict[str, str]] = None,
99-
runtime_spec: Optional[DotnetCoreRuntimeSpec] = None,
94+
runtime_config: StrOrPath | None = None,
95+
entry_dll: StrOrPath | None = None,
96+
dotnet_root: StrOrPath | None = None,
97+
properties: dict[str, str] | None = None,
98+
runtime_spec: DotnetCoreRuntimeSpec | None = None,
99+
runtime_version: str | None = None,
100100
) -> Runtime:
101101
"""Get a CoreCLR (.NET Core) runtime instance
102102
@@ -122,7 +122,11 @@ def get_coreclr(
122122
:param runtime_spec:
123123
If the ``runtime_config`` is not specified, the concrete runtime to use
124124
can be controlled by passing this parameter. Possible values can be
125-
retrieved using :py:func:`find_runtimes`."""
125+
retrieved using :py:func:`find_runtimes`.
126+
:param runtime_version:
127+
If neither the ``runtime_config`` nor the ``runtime_spec`` are specified,
128+
the concrete runtime version to use can be controlled by passing the
129+
major.minor version to this parameter."""
126130

127131
from .hostfxr import DotnetCoreRuntime
128132

@@ -139,6 +143,12 @@ def get_coreclr(
139143
candidates = [
140144
rt for rt in find_runtimes() if rt.name == "Microsoft.NETCore.App"
141145
]
146+
if runtime_version is not None:
147+
candidates = [
148+
rt
149+
for rt in candidates
150+
if f"{rt.version_info[0]}.{rt.version_info[1]}" == runtime_version
151+
]
142152
candidates.sort(key=lambda spec: spec.version_info, reverse=True)
143153
if not candidates:
144154
raise RuntimeError("Failed to find a suitable runtime")
@@ -168,7 +178,7 @@ def get_coreclr(
168178

169179

170180
def get_netfx(
171-
*, domain: Optional[str] = None, config_file: Optional[StrOrPath] = None
181+
*, domain: str | None = None, config_file: StrOrPath | None = None
172182
) -> Runtime:
173183
"""Get a .NET Framework runtime instance
174184
@@ -186,7 +196,7 @@ def get_netfx(
186196
return impl
187197

188198

189-
def _maybe_path(p: Optional[StrOrPath]) -> Optional[Path]:
199+
def _maybe_path(p: StrOrPath | None) -> Path | None:
190200
if p is None:
191201
return None
192202
else:

clr_loader/ffi/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import sys
22
from pathlib import Path
3-
from typing import Optional, Tuple
43

5-
import cffi # type: ignore
4+
import cffi
65

76
from . import hostfxr, mono, netfx
87

98
__all__ = ["ffi", "load_hostfxr", "load_mono", "load_netfx"]
109

11-
ffi = cffi.FFI() # type: ignore
10+
ffi = cffi.FFI()
1211

1312
for cdef in hostfxr.cdef + mono.cdef + netfx.cdef:
1413
ffi.cdef(cdef)
@@ -22,7 +21,7 @@ def load_hostfxr(dotnet_root: Path):
2221
hostfxr_path = dotnet_root / "host" / "fxr"
2322
hostfxr_paths = hostfxr_path.glob(f"*/{hostfxr_name}")
2423

25-
error_report = list()
24+
error_report: list[str] = []
2625

2726
for hostfxr_path in reversed(sorted(hostfxr_paths, key=_path_to_version)):
2827
try:
@@ -41,7 +40,7 @@ def load_hostfxr(dotnet_root: Path):
4140
)
4241

4342

44-
def load_mono(path: Optional[Path] = None):
43+
def load_mono(path: Path | None = None):
4544
# Preload C++ standard library, Mono needs that and doesn't properly link against it
4645
if sys.platform == "linux":
4746
ffi.dlopen("stdc++", ffi.RTLD_GLOBAL)
@@ -65,7 +64,7 @@ def load_netfx():
6564
return ffi.dlopen(str(path))
6665

6766

68-
def _path_to_version(path: Path) -> Tuple[int, int, int]:
67+
def _path_to_version(path: Path) -> tuple[int, int, int]:
6968
name = path.parent.name
7069
try:
7170
# Handle pre-release versions like "10.0.0-rc.1" by taking only the version part

clr_loader/hostfxr.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from pathlib import Path
3-
from typing import Generator, Tuple, Optional
3+
from collections.abc import Generator
44

55
from .ffi import ffi, load_hostfxr
66
from .types import Runtime, RuntimeInfo, StrOrPath
@@ -18,8 +18,8 @@ def __init__(
1818
self,
1919
*,
2020
dotnet_root: Path,
21-
runtime_config: Optional[Path] = None,
22-
entry_dll: Optional[Path] = None,
21+
runtime_config: Path | None = None,
22+
entry_dll: Path | None = None,
2323
**params: str,
2424
):
2525
self._handle = None
@@ -81,7 +81,7 @@ def __setitem__(self, key: str, value: str) -> None:
8181
)
8282
check_result(res)
8383

84-
def __iter__(self) -> Generator[Tuple[str, str], None, None]:
84+
def __iter__(self) -> Generator[tuple[str, str], None, None]:
8585
if self.is_shutdown:
8686
raise RuntimeError("Runtime is shut down")
8787
max_size = 100

clr_loader/mono.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import atexit
22
import re
33
from pathlib import Path
4-
from typing import Any, Dict, Optional, Sequence
4+
from typing import Any
5+
from collections.abc import Sequence
56

67
from .ffi import ffi, load_mono
78
from .types import Runtime, RuntimeInfo, StrOrPath
@@ -17,20 +18,20 @@
1718
class Mono(Runtime):
1819
def __init__(
1920
self,
20-
libmono: Optional[Path],
21+
libmono: Path | None,
2122
*,
22-
domain: Optional[str] = None,
23+
domain: str | None = None,
2324
debug: bool = False,
24-
jit_options: Optional[Sequence[str]] = None,
25-
config_file: Optional[Path] = None,
26-
global_config_file: Optional[Path] = None,
27-
assembly_dir: Optional[str] = None,
28-
config_dir: Optional[str] = None,
25+
jit_options: Sequence[str] | None = None,
26+
config_file: Path | None = None,
27+
global_config_file: Path | None = None,
28+
assembly_dir: StrOrPath | None = None,
29+
config_dir: StrOrPath | None = None,
2930
set_signal_chaining: bool = False,
30-
trace_mask: Optional[str] = None,
31-
trace_level: Optional[str] = None,
31+
trace_mask: str | None = None,
32+
trace_level: str | None = None,
3233
):
33-
self._assemblies: Dict[Path, Any] = {}
34+
self._assemblies: dict[Path, Any] = {}
3435

3536
self._version: str = initialize(
3637
config_file=optional_path_as_string(config_file),
@@ -129,16 +130,16 @@ def __call__(self, ptr, size):
129130

130131

131132
def initialize(
132-
libmono: Optional[Path],
133+
libmono: Path | None,
133134
debug: bool = False,
134-
jit_options: Optional[Sequence[str]] = None,
135-
config_file: Optional[str] = None,
136-
global_config_file: Optional[str] = None,
137-
assembly_dir: Optional[str] = None,
138-
config_dir: Optional[str] = None,
135+
jit_options: Sequence[str] | None = None,
136+
config_file: str | None = None,
137+
global_config_file: str | None = None,
138+
assembly_dir: StrOrPath | None = None,
139+
config_dir: StrOrPath | None = None,
139140
set_signal_chaining: bool = False,
140-
trace_mask: Optional[str] = None,
141-
trace_level: Optional[str] = None,
141+
trace_mask: str | None = None,
142+
trace_level: str | None = None,
142143
) -> str:
143144
global _MONO, _ROOT_DOMAIN
144145
if _MONO is None:
@@ -151,7 +152,10 @@ def initialize(
151152
_MONO.mono_trace_set_level_string(trace_level.encode("utf8"))
152153

153154
if assembly_dir is not None and config_dir is not None:
154-
_MONO.mono_set_dirs(assembly_dir.encode("utf8"), config_dir.encode("utf8"))
155+
_MONO.mono_set_dirs(
156+
path_as_string(assembly_dir).encode("utf8"),
157+
path_as_string(config_dir).encode("utf8"),
158+
)
155159

156160
# Load in global config (i.e /etc/mono/config)
157161
global_encoded = global_config_file or ffi.NULL

clr_loader/netfx.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import atexit
22
from pathlib import Path
3-
from typing import Any, Optional
3+
from typing import Any
44

55
from .ffi import ffi, load_netfx
66
from .types import Runtime, RuntimeInfo, StrOrPath
@@ -9,10 +9,8 @@
99

1010

1111
class NetFx(Runtime):
12-
def __init__(
13-
self, domain: Optional[str] = None, config_file: Optional[Path] = None
14-
):
15-
self._domain: Optional[str] = None
12+
def __init__(self, domain: str | None = None, config_file: Path | None = None):
13+
self._domain: str | None = None
1614

1715
initialize()
1816
if config_file is not None:
@@ -22,8 +20,8 @@ def __init__(
2220

2321
domain_s = domain.encode("utf8") if domain else ffi.NULL
2422

25-
self._domain_name: Optional[str] = domain
26-
self._config_file: Optional[Path] = config_file
23+
self._domain_name: str | None = domain
24+
self._config_file: Path | None = config_file
2725
self._domain = _FW.pyclr_create_appdomain(domain_s, config_file_s)
2826

2927
def info(self) -> RuntimeInfo:

clr_loader/types.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
from abc import ABCMeta, abstractmethod
22
from dataclasses import dataclass, field
33
from os import PathLike
4-
from typing import Any, Callable, Dict, Optional, Union
4+
from typing import Any
5+
from collections.abc import Callable
56

67
__all__ = ["StrOrPath"]
78

8-
try:
9-
StrOrPath = Union[str, PathLike[str]]
10-
except TypeError:
11-
StrOrPath = Union[str, PathLike]
9+
StrOrPath = str | PathLike[str]
1210

1311

1412
@dataclass
@@ -33,7 +31,7 @@ class RuntimeInfo:
3331
version: str
3432
initialized: bool
3533
shutdown: bool
36-
properties: Dict[str, str] = field(repr=False)
34+
properties: dict[str, str] = field(repr=False)
3735

3836
def __str__(self) -> str:
3937
return (
@@ -75,7 +73,7 @@ def __init__(self, runtime: "Runtime", path: StrOrPath):
7573
self._runtime: "Runtime" = runtime
7674
self._path: StrOrPath = path
7775

78-
def get_function(self, name: str, func: Optional[str] = None) -> ClrFunction:
76+
def get_function(self, name: str, func: str | None = None) -> ClrFunction:
7977
"""Get a wrapped .NET function instance
8078
8179
The function must be ``static``, and it must have the signature

clr_loader/util/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from pathlib import Path
2-
from typing import Optional
32

43
from ..types import StrOrPath
54
from .clr_error import ClrError
@@ -15,7 +14,7 @@
1514
]
1615

1716

18-
def optional_path_as_string(path: Optional[StrOrPath]) -> Optional[str]:
17+
def optional_path_as_string(path: StrOrPath | None) -> str | None:
1918
if path is None:
2019
return None
2120
return path_as_string(path)

clr_loader/util/clr_error.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
from typing import Optional
2-
3-
41
class ClrError(Exception):
52
def __init__(
63
self,
74
hresult: int,
8-
name: Optional[str] = None,
9-
message: Optional[str] = None,
10-
comment: Optional[str] = None,
5+
name: str | None = None,
6+
message: str | None = None,
7+
comment: str | None = None,
118
):
129
self.hresult = hresult
1310
self.name = name

0 commit comments

Comments
 (0)