1# Copyright 2019 The Cirq Developers 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""A more flexible abstract base class metaclass ABCMetaImplementAnyOneOf.""" 15 16import abc 17import functools 18from typing import cast, Callable, Set, TypeVar 19 20# Required due to PEP 560 21try: 22 # python 3.6 class for generic metaclasses 23 from typing import GenericMeta # type: ignore 24except ImportError: 25 # In python 3.7, GenericMeta doesn't exist but we don't need it 26 class GenericMeta(type): # type: ignore 27 pass 28 29 30T = TypeVar('T') 31 32 33def alternative(*, requires: str, implementation: T) -> Callable[[T], T]: 34 """A decorator indicating an abstract method with an alternative default 35 implementation. 36 37 This decorator may be used multiple times on the same function to specify 38 multiple alternatives. If multiple alternatives are available, the 39 outermost (lowest line number) alternative is used. 40 41 Usage: 42 class Parent(metaclass=ABCMetaImplementAnyOneOf): 43 def _default_do_a_using_b(self, ...): 44 ... 45 def _default_do_a_using_c(self, ...): 46 ... 47 48 # Abstract method with alternatives 49 @alternative(requires='do_b', implementation=_default_do_a_using_b) 50 @alternative(requires='do_c', implementation=_default_do_a_using_c) 51 def do_a(self, ...): 52 '''Method docstring.''' 53 54 # Abstract or concrete methods `do_b` and `do_c`: 55 ... 56 57 class Child(Parent): 58 def do_b(self): 59 ... 60 61 child = Child() 62 child.do_a(...) 63 64 Arguments: 65 requires: The name of another abstract method in the same class that 66 `implementation` needs to be implemented. 67 implementation: A function that uses the method named by requires to 68 implement the default behavior of the wrapped abstract method. This 69 function must have the same signature as the decorated function. 70 """ 71 72 def decorator(func: T) -> T: 73 alternatives = getattr(func, '_abstract_alternatives_', []) 74 alternatives.insert(0, (requires, implementation)) 75 setattr(func, '_abstract_alternatives_', alternatives) 76 return func 77 78 return decorator 79 80 81class ABCMetaImplementAnyOneOf(abc.ABCMeta): 82 """A metaclass extending `abc.ABCMeta` for defining abstract base classes 83 (ABCs) with more flexibility in which methods must be overridden. 84 85 Use this metaclass in the same way as `abc.ABCMeta` to create an ABC. 86 87 In addition to the decorators in the` abc` module, the decorator 88 `@alternative(...)` may be used. 89 """ 90 91 def __new__(mcls, name, bases, namespace, **kwargs): 92 cls = super().__new__(mcls, name, bases, namespace, **kwargs) 93 implemented_by = {} 94 95 def has_some_implementation(name: str) -> bool: 96 if name in implemented_by: 97 return True 98 try: 99 value = getattr(cls, name) 100 except AttributeError: 101 raise TypeError( 102 'A method named \'{}\' was listed as a possible ' 103 'implementation alternative but it does not exist in the ' 104 'definition of {!r}.'.format(name, cls) 105 ) 106 if getattr(value, '__isabstractmethod__', False): 107 return False 108 if hasattr(value, '_abstract_alternatives_'): 109 return False 110 return True 111 112 def find_next_implementations(all_names: Set[str]) -> bool: 113 next_implemented_by = {} 114 for name in all_names: 115 if has_some_implementation(name): 116 continue 117 value = getattr(cls, name, None) 118 if not hasattr(value, '_abstract_alternatives_'): 119 continue 120 for alt_name, impl in getattr(value, '_abstract_alternatives_'): 121 if has_some_implementation(alt_name): 122 next_implemented_by[name] = impl 123 break 124 implemented_by.update(next_implemented_by) 125 return bool(next_implemented_by) 126 127 # Find all abstract methods (methods that haven't been implemented or 128 # don't have an implemented alternative). 129 all_names = set(alt_name for alt_name in namespace.keys() if hasattr(cls, alt_name)) 130 for base in bases: 131 all_names.update(getattr(base, '__abstractmethods__', set())) 132 all_names.update( 133 alt_name for alt_name, _ in getattr(base, '_implemented_by_', {}).items() 134 ) 135 while find_next_implementations(all_names): 136 pass 137 abstracts = frozenset(name for name in all_names if not has_some_implementation(name)) 138 # Replace the abstract methods with their default implementations. 139 for name, default_impl in implemented_by.items(): 140 abstract_method = getattr(cls, name) 141 142 def wrap_scope(impl: T) -> T: 143 def impl_of_abstract(*args, **kwargs): 144 return impl(*args, **kwargs) 145 146 functools.update_wrapper(impl_of_abstract, abstract_method) 147 return cast(T, impl_of_abstract) 148 149 impl_of_abstract = wrap_scope(default_impl) 150 impl_of_abstract._abstract_alternatives_ = abstract_method._abstract_alternatives_ 151 setattr(cls, name, impl_of_abstract) 152 # If __abstractmethods__ is non-empty, this is an abstract class and 153 # can't be instantiated. 154 cls.__abstractmethods__ |= abstracts # Add to the set made by ABCMeta 155 cls._implemented_by_ = implemented_by 156 return cls 157 158 159class GenericMetaImplementAnyOneOf(GenericMeta, ABCMetaImplementAnyOneOf): 160 """Generic version of ABCMetaImplementAnyOneOf. 161 162 Classes which inherit from Generic[T] must use this type instead of 163 ABCMetaImplementAnyOneOf due to https://github.com/python/typing/issues/449. 164 165 This issue is specific to python3.6; this class can be removed when Cirq 166 python3.6 support is turned down. 167 """ 168