mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	bpo-44356: [Enum] allow multiple data-type mixins if they are all the same (GH-26649) (GH-26652)
This enables, for example, two base Enums to both inherit from `str`, and then both be mixed into the same final Enum:
    class Str1Enum(str, Enum):
        GH- some behavior here
    class Str2Enum(str, Enum):
        GH- some more behavior here
    class FinalStrEnum(Str1Enum, Str2Enum):
        GH- this now works
(cherry picked from commit 8a4f0850d7)
Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
			
			
This commit is contained in:
		
							parent
							
								
									175ebc60d5
								
							
						
					
					
						commit
						304ec53b53
					
				
					 3 changed files with 52 additions and 4 deletions
				
			
		|  | @ -556,7 +556,7 @@ def _get_mixins_(class_name, bases): | ||||||
|             return object, Enum |             return object, Enum | ||||||
| 
 | 
 | ||||||
|         def _find_data_type(bases): |         def _find_data_type(bases): | ||||||
|             data_types = [] |             data_types = set() | ||||||
|             for chain in bases: |             for chain in bases: | ||||||
|                 candidate = None |                 candidate = None | ||||||
|                 for base in chain.__mro__: |                 for base in chain.__mro__: | ||||||
|  | @ -564,19 +564,19 @@ def _find_data_type(bases): | ||||||
|                         continue |                         continue | ||||||
|                     elif issubclass(base, Enum): |                     elif issubclass(base, Enum): | ||||||
|                         if base._member_type_ is not object: |                         if base._member_type_ is not object: | ||||||
|                             data_types.append(base._member_type_) |                             data_types.add(base._member_type_) | ||||||
|                             break |                             break | ||||||
|                     elif '__new__' in base.__dict__: |                     elif '__new__' in base.__dict__: | ||||||
|                         if issubclass(base, Enum): |                         if issubclass(base, Enum): | ||||||
|                             continue |                             continue | ||||||
|                         data_types.append(candidate or base) |                         data_types.add(candidate or base) | ||||||
|                         break |                         break | ||||||
|                     else: |                     else: | ||||||
|                         candidate = base |                         candidate = base | ||||||
|             if len(data_types) > 1: |             if len(data_types) > 1: | ||||||
|                 raise TypeError('%r: too many data types: %r' % (class_name, data_types)) |                 raise TypeError('%r: too many data types: %r' % (class_name, data_types)) | ||||||
|             elif data_types: |             elif data_types: | ||||||
|                 return data_types[0] |                 return data_types.pop() | ||||||
|             else: |             else: | ||||||
|                 return None |                 return None | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2072,6 +2072,53 @@ def __new__(cls, value): | ||||||
|                 return member |                 return member | ||||||
|         self.assertEqual(Fee.TEST, 2) |         self.assertEqual(Fee.TEST, 2) | ||||||
| 
 | 
 | ||||||
|  |     def test_miltuple_mixin_with_common_data_type(self): | ||||||
|  |         class CaseInsensitiveStrEnum(str, Enum): | ||||||
|  |             @classmethod | ||||||
|  |             def _missing_(cls, value): | ||||||
|  |                 for member in cls._member_map_.values(): | ||||||
|  |                     if member._value_.lower() == value.lower(): | ||||||
|  |                         return member | ||||||
|  |                 return super()._missing_(value) | ||||||
|  |         # | ||||||
|  |         class LenientStrEnum(str, Enum): | ||||||
|  |             def __init__(self, *args): | ||||||
|  |                 self._valid = True | ||||||
|  |             @classmethod | ||||||
|  |             def _missing_(cls, value): | ||||||
|  |                 # encountered an unknown value! | ||||||
|  |                 # Luckily I'm a LenientStrEnum, so I won't crash just yet. | ||||||
|  |                 # You might want to add a new case though. | ||||||
|  |                 unknown = cls._member_type_.__new__(cls, value) | ||||||
|  |                 unknown._valid = False | ||||||
|  |                 unknown._name_ = value.upper() | ||||||
|  |                 unknown._value_ = value | ||||||
|  |                 cls._member_map_[value] = unknown | ||||||
|  |                 return unknown | ||||||
|  |             @property | ||||||
|  |             def valid(self): | ||||||
|  |                 return self._valid | ||||||
|  |         # | ||||||
|  |         class JobStatus(CaseInsensitiveStrEnum, LenientStrEnum): | ||||||
|  |             ACTIVE = "active" | ||||||
|  |             PENDING = "pending" | ||||||
|  |             TERMINATED = "terminated" | ||||||
|  |         # | ||||||
|  |         JS = JobStatus | ||||||
|  |         self.assertEqual(list(JobStatus), [JS.ACTIVE, JS.PENDING, JS.TERMINATED]) | ||||||
|  |         self.assertEqual(JS.ACTIVE, 'active') | ||||||
|  |         self.assertEqual(JS.ACTIVE.value, 'active') | ||||||
|  |         self.assertIs(JS('Active'), JS.ACTIVE) | ||||||
|  |         self.assertTrue(JS.ACTIVE.valid) | ||||||
|  |         missing = JS('missing') | ||||||
|  |         self.assertEqual(list(JobStatus), [JS.ACTIVE, JS.PENDING, JS.TERMINATED]) | ||||||
|  |         self.assertEqual(JS.ACTIVE, 'active') | ||||||
|  |         self.assertEqual(JS.ACTIVE.value, 'active') | ||||||
|  |         self.assertIs(JS('Active'), JS.ACTIVE) | ||||||
|  |         self.assertTrue(JS.ACTIVE.valid) | ||||||
|  |         self.assertTrue(isinstance(missing, JS)) | ||||||
|  |         self.assertFalse(missing.valid) | ||||||
|  | 
 | ||||||
|     def test_empty_globals(self): |     def test_empty_globals(self): | ||||||
|         # bpo-35717: sys._getframe(2).f_globals['__name__'] fails with KeyError |         # bpo-35717: sys._getframe(2).f_globals['__name__'] fails with KeyError | ||||||
|         # when using compile and exec because f_globals is empty |         # when using compile and exec because f_globals is empty | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | [Enum] Allow multiple data-type mixins if they are all the same. | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Miss Islington (bot)
						Miss Islington (bot)