11mod detection;
22
3- pub use detection:: { find_bridge, has_windows_import_lib_support, upgrade_bridge_abi3 } ;
3+ pub use detection:: { find_bridge, has_windows_import_lib_support, upgrade_bridge_stable_abi } ;
44
55use std:: { fmt, str:: FromStr } ;
66
@@ -132,6 +132,23 @@ impl StableAbi {
132132 version : StableAbiVersion :: Version ( major, minor) ,
133133 }
134134 }
135+
136+ /// Create a StableAbi instance from a known abi3t version
137+ pub fn from_abi3t_version ( major : u8 , minor : u8 ) -> StableAbi {
138+ StableAbi {
139+ kind : StableAbiKind :: Abi3t ,
140+ version : StableAbiVersion :: Version ( major, minor) ,
141+ }
142+ }
143+ }
144+
145+ impl std:: fmt:: Display for StableAbi {
146+ fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
147+ match self . kind {
148+ StableAbiKind :: Abi3 => write ! ( f, "abi3" ) ,
149+ StableAbiKind :: Abi3t => write ! ( f, "abi3t" ) ,
150+ }
151+ }
135152}
136153
137154/// Python version to use as the abi3/abi3t target.
@@ -146,7 +163,7 @@ pub enum StableAbiVersion {
146163}
147164
148165impl StableAbiVersion {
149- /// Convert `StableAbiVersion` into an Option, where CurrentPython maps None
166+ /// Convert `StableAbiVersion` into an Option, where CurrentPython maps to None
150167 pub fn min_version ( & self ) -> Option < ( u8 , u8 ) > {
151168 match self {
152169 StableAbiVersion :: CurrentPython => None ,
@@ -160,12 +177,15 @@ impl StableAbiVersion {
160177pub enum StableAbiKind {
161178 /// The original stable ABI, supporting Python 3.2 and up
162179 Abi3 ,
180+ /// The free-threaded stable ABI, supporting Python 3.15 and up
181+ Abi3t ,
163182}
164183
165184impl fmt:: Display for StableAbiKind {
166185 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
167186 match self {
168187 StableAbiKind :: Abi3 => write ! ( f, "abi3" ) ,
188+ StableAbiKind :: Abi3t => write ! ( f, "abi3t" ) ,
169189 }
170190 }
171191}
@@ -175,6 +195,7 @@ impl StableAbiKind {
175195 pub fn wheel_tag ( & self ) -> & str {
176196 match self {
177197 StableAbiKind :: Abi3 => "abi3" ,
198+ StableAbiKind :: Abi3t => "abi3.abi3t" ,
178199 }
179200 }
180201}
@@ -340,32 +361,36 @@ impl BridgeModel {
340361 ///
341362 /// This is a project-level check — it does not consider whether a particular
342363 /// interpreter meets the abi3 minimum version. For per‑interpreter checks
343- /// use [`is_abi3_for_interpreter `](Self::is_abi3_for_interpreter ).
364+ /// use [`is_stable_abi_for_interpreter `](Self::is_stable_abi_for_interpreter ).
344365 pub fn has_stable_abi ( & self ) -> bool {
345366 self . pyo3 ( )
346367 . and_then ( |pyo3| pyo3. stable_abi . as_ref ( ) )
347368 . is_some ( )
348369 }
349370
350- /// Check whether abi3 should be enabled for a specific interpreter.
371+ /// Check whether an abi3 or abi3t build should be enabled for a specific interpreter.
351372 ///
352373 /// Returns `true` only when the bridge model has stable abi support **and**
353374 /// the given interpreter supports the stable ABI **and** meets the abi3
354375 /// minimum version. Version‑specific fallback builds (e.g. Python 3.10 when
355376 /// abi3 targets ≥ 3.11) return `false` so that `Py_LIMITED_API` is not
356377 /// defined and interpreter‑specific linker names are used.
357- pub fn is_abi3_for_interpreter ( & self , interpreter : & PythonInterpreter ) -> bool {
358- if !interpreter. has_stable_api ( ) {
359- return false ;
360- }
361-
378+ pub fn is_stable_abi_for_interpreter ( & self , interpreter : & PythonInterpreter ) -> bool {
362379 self . pyo3 ( )
363380 . and_then ( |pyo3| pyo3. stable_abi . as_ref ( ) )
364- . is_some_and ( |stable_abi| match stable_abi. version . min_version ( ) {
365- Some ( ( major, minor) ) => {
366- ( interpreter. major as u8 , interpreter. minor as u8 ) >= ( major, minor)
381+ . is_some_and ( |stable_abi| {
382+ if !interpreter. has_stable_api ( stable_abi. kind ) {
383+ return false ;
384+ }
385+ if matches ! ( stable_abi. kind, StableAbiKind :: Abi3 ) && interpreter. gil_disabled {
386+ return false ;
387+ } ;
388+ match stable_abi. version . min_version ( ) {
389+ Some ( ( major, minor) ) => {
390+ ( interpreter. major as u8 , interpreter. minor as u8 ) >= ( major, minor)
391+ }
392+ None => true , // CurrentPython → compatible when stable ABI is supported
367393 }
368- None => true , // CurrentPython → compatible when stable ABI is supported
369394 } )
370395 }
371396
@@ -392,3 +417,39 @@ impl fmt::Display for BridgeModel {
392417 }
393418 }
394419}
420+
421+ #[ cfg( test) ]
422+ mod tests {
423+ use super :: * ;
424+
425+ #[ test]
426+ fn stable_abi_kind_display ( ) {
427+ assert_eq ! ( StableAbiKind :: Abi3 . to_string( ) , "abi3" ) ;
428+ assert_eq ! ( StableAbiKind :: Abi3t . to_string( ) , "abi3t" ) ;
429+ }
430+
431+ #[ test]
432+ fn stable_abi_kind_wheel_tag ( ) {
433+ assert_eq ! ( StableAbiKind :: Abi3 . wheel_tag( ) , "abi3" ) ;
434+ // abi3t wheels are also importable on abi3-capable interpreters, so the
435+ // wheel tag is the compressed form `abi3.abi3t`.
436+ assert_eq ! ( StableAbiKind :: Abi3t . wheel_tag( ) , "abi3.abi3t" ) ;
437+ }
438+
439+ #[ test]
440+ fn stable_abi_display ( ) {
441+ assert_eq ! ( StableAbi :: from_abi3_version( 3 , 7 ) . to_string( ) , "abi3" ) ;
442+ assert_eq ! ( StableAbi :: from_abi3t_version( 3 , 15 ) . to_string( ) , "abi3t" ) ;
443+ }
444+
445+ #[ test]
446+ fn stable_abi_constructors ( ) {
447+ let abi3 = StableAbi :: from_abi3_version ( 3 , 9 ) ;
448+ assert_eq ! ( abi3. kind, StableAbiKind :: Abi3 ) ;
449+ assert_eq ! ( abi3. version, StableAbiVersion :: Version ( 3 , 9 ) ) ;
450+
451+ let abi3t = StableAbi :: from_abi3t_version ( 3 , 15 ) ;
452+ assert_eq ! ( abi3t. kind, StableAbiKind :: Abi3t ) ;
453+ assert_eq ! ( abi3t. version, StableAbiVersion :: Version ( 3 , 15 ) ) ;
454+ }
455+ }
0 commit comments