5
5
import os
6
6
import re
7
7
8
- PATTERN = re .compile (r"^(?P<impl>[a-zA-Z]+)?(?P<version>[0-9.]+)?(?:-(?P<arch>32|64))?$" )
8
+ PATTERN = re .compile (r"^(?P<impl>[a-zA-Z]+)?(?P<version>[0-9.]+)?(?P<threaded>t)?(? :-(?P<arch>32|64))?$" )
9
9
10
10
11
11
class PythonSpec :
@@ -20,18 +20,21 @@ def __init__( # noqa: PLR0913
20
20
micro : int | None ,
21
21
architecture : int | None ,
22
22
path : str | None ,
23
+ * ,
24
+ free_threaded : bool | None = None ,
23
25
) -> None :
24
26
self .str_spec = str_spec
25
27
self .implementation = implementation
26
28
self .major = major
27
29
self .minor = minor
28
30
self .micro = micro
31
+ self .free_threaded = free_threaded
29
32
self .architecture = architecture
30
33
self .path = path
31
34
32
35
@classmethod
33
36
def from_string_spec (cls , string_spec : str ): # noqa: C901, PLR0912
34
- impl , major , minor , micro , arch , path = None , None , None , None , None , None
37
+ impl , major , minor , micro , threaded , arch , path = None , None , None , None , None , None , None
35
38
if os .path .isabs (string_spec ): # noqa: PLR1702
36
39
path = string_spec
37
40
else :
@@ -58,6 +61,7 @@ def _int_or_none(val):
58
61
major = int (str (version_data )[0 ]) # first digit major
59
62
if version_data > 9 : # noqa: PLR2004
60
63
minor = int (str (version_data )[1 :])
64
+ threaded = bool (groups ["threaded" ])
61
65
ok = True
62
66
except ValueError :
63
67
pass
@@ -70,14 +74,15 @@ def _int_or_none(val):
70
74
if not ok :
71
75
path = string_spec
72
76
73
- return cls (string_spec , impl , major , minor , micro , arch , path )
77
+ return cls (string_spec , impl , major , minor , micro , arch , path , free_threaded = threaded )
74
78
75
79
def generate_re (self , * , windows : bool ) -> re .Pattern :
76
80
"""Generate a regular expression for matching against a filename."""
77
81
version = r"{}(\.{}(\.{})?)?" .format (
78
82
* (r"\d+" if v is None else v for v in (self .major , self .minor , self .micro ))
79
83
)
80
84
impl = "python" if self .implementation is None else f"python|{ re .escape (self .implementation )} "
85
+ mod = "t?" if self .free_threaded else ""
81
86
suffix = r"\.exe" if windows else ""
82
87
version_conditional = (
83
88
"?"
@@ -89,7 +94,7 @@ def generate_re(self, *, windows: bool) -> re.Pattern:
89
94
)
90
95
# Try matching `direct` first, so the `direct` group is filled when possible.
91
96
return re .compile (
92
- rf"(?P<impl>{ impl } )(?P<v>{ version } ){ version_conditional } { suffix } $" ,
97
+ rf"(?P<impl>{ impl } )(?P<v>{ version } { mod } ){ version_conditional } { suffix } $" ,
93
98
flags = re .IGNORECASE ,
94
99
)
95
100
@@ -105,6 +110,8 @@ def satisfies(self, spec):
105
110
return False
106
111
if spec .architecture is not None and spec .architecture != self .architecture :
107
112
return False
113
+ if spec .free_threaded is not None and spec .free_threaded != self .free_threaded :
114
+ return False
108
115
109
116
for our , req in zip ((self .major , self .minor , self .micro ), (spec .major , spec .minor , spec .micro )):
110
117
if req is not None and our is not None and our != req :
@@ -113,7 +120,7 @@ def satisfies(self, spec):
113
120
114
121
def __repr__ (self ) -> str :
115
122
name = type (self ).__name__
116
- params = "implementation" , "major" , "minor" , "micro" , "architecture" , "path"
123
+ params = "implementation" , "major" , "minor" , "micro" , "architecture" , "path" , "free_threaded"
117
124
return f"{ name } ({ ', ' .join (f'{ k } ={ getattr (self , k )} ' for k in params if getattr (self , k ) is not None )} )"
118
125
119
126
0 commit comments