from__future__importannotationsimportabcfromcollections.abcimportIterablefromcollections.abcimportIteratorfromtypingimportAnyfromtypingimportCallablefromtypingimportClassVarfromtypingimportGenericfromtypingimportProtocolfromtypingimportTypeVarfromtypingimportruntime_checkablefrom.import_hypothesisfrom._utils.miscimportBoundTypefrom._utils.miscimportUnresolvedClassAttributefrom._utils.miscimportfully_qualified_namefrom._utils.miscimportis_not_known_mutable_typefrom._utils.miscimportis_subtypefrom._utils.miscimportresolve_class_attrfrom.boundsimportParserfrom.boundsimportget_bound_parserfrom.errorsimportBoundErrorfrom.predicatesimportPredicatefrom.schemaimportSchemaField@runtime_checkableclassInstanceCheckable(Protocol):def__instancecheck__(self,instance:object)->bool:...classPhantomMeta(abc.ABCMeta):""" Metaclass that defers __instancecheck__ to derived classes and prevents actual instance creation. """def__instancecheck__(self,instance:object)->bool:ifnotissubclass(self,InstanceCheckable):returnFalsereturnself.__instancecheck__(# type: ignore[no-any-return,attr-defined]instance,)# With the current level of metaclass support in mypy it's unlikely that we'll be# able to make this context typed, hence the ignores.def__call__(cls,instance):# type: ignore[no-untyped-def]returncls.parse(instance)# type: ignore[attr-defined]T=TypeVar("T",covariant=True)U=TypeVar("U")Derived=TypeVar("Derived",bound="PhantomBase")classPhantomBase(SchemaField,metaclass=PhantomMeta):@classmethoddefparse(cls:type[Derived],instance:object)->Derived:""" Parse an arbitrary value into a phantom type. :raises TypeError: """ifnotisinstance(instance,cls):raiseTypeError(f"Could not parse {fully_qualified_name(cls)} from {instance!r}")returninstance@classmethod@abc.abstractmethoddef__instancecheck__(cls,instance:object)->bool:...@classmethoddef__get_validators__(cls:type[Derived])->Iterator[Callable[[object],Derived]]:"""Hook that makes phantom types compatible with pydantic."""yieldcls.parseclassAbstractInstanceCheck(TypeError):...classMutableType(TypeError):...
[docs]classPhantom(PhantomBase,Generic[T]):""" Base class for predicate-based phantom types. **Class arguments** * ``predicate: Predicate[T] | None`` - Predicate function used for instance checks. Can be ``None`` if the type is abstract. * ``bound: type[T] | None`` - Bound used to check values before passing them to the type's predicate function. This will often but not always be the same as the runtime type that values of the phantom type are represented as. If this is not provided as a class argument, it's attempted to be resolved in order from an implicit bound (any bases of the type that come before ``Phantom``), or inherited from super phantom types that provide a bound. Can be ``None`` if the type is abstract. * ``abstract: bool`` - Set to ``True`` to create an abstract phantom type. This allows deferring definitions of ``predicate`` and ``bound`` to concrete subtypes. """__predicate__:Predicate[T]# The bound of a phantom type is the type that its values will have at# runtime, so when checking if a value is an instance of a phantom type,# it's first checked to be within its bounds, so that the value can be# safely passed as argument to the predicate function.## When subclassing, the bound of the new type must be a subtype of the bound# of the super class.__bound__:ClassVar[type]__abstract__:ClassVar[bool]def__init_subclass__(cls,predicate:Predicate[T]|None=None,bound:type[T]|None=None,abstract:bool=False,**kwargs:Any,)->None:super().__init_subclass__(**kwargs)resolve_class_attr(cls,"__abstract__",abstract)resolve_class_attr(cls,"__predicate__",predicate)cls._resolve_bound(bound)if_hypothesis.register_type_strategyisnotNoneandnotcls.__abstract__:strategy=cls.__register_strategy__()ifstrategyisnotNone:_hypothesis.register_type_strategy(cls,strategy)@classmethoddef_interpret_implicit_bound(cls)->BoundType:defdiscover_bounds()->Iterable[type]:fortype_incls.__mro__:iftype_iscls:continueifissubclass(type_,Phantom):breakyieldtype_else:# pragma: no coverraiseRuntimeError(f"{cls} is not a subclass of Phantom")types=tuple(discover_bounds())iflen(types)==1:returntypes[0]returntypes@classmethoddef_resolve_bound(cls,class_arg:Any)->None:inherited=getattr(cls,"__bound__",None)implicit=cls._interpret_implicit_bound()ifclass_argisnotNone:bound=class_argelifimplicit:bound=implicitelifinheritedisnotNone:bound=inheritedelifnotgetattr(cls,"__abstract__",False):raiseUnresolvedClassAttribute(f"Concrete phantom type {cls.__qualname__} must define class attribute "f"__bound__.")else:returnifinheritedisnotNoneandnotis_subtype(bound,inherited):raiseBoundError(f"The bound of {cls.__qualname__} is not compatible with its "f"inherited bounds.")ifnotis_not_known_mutable_type(bound):raiseMutableType(f"The bound of {cls.__qualname__} is mutable.")cls.__bound__=bound@classmethoddef__instancecheck__(cls,instance:object)->bool:ifcls.__abstract__:raiseAbstractInstanceCheck("Abstract phantom types cannot be used in instance checks")bound_parser:Parser[T]=get_bound_parser(cls.__bound__)try:instance=bound_parser(instance)exceptBoundError:returnFalsereturncls.__predicate__(instance)@classmethoddef__register_strategy__(cls)->_hypothesis.HypothesisStrategy|None:returnNone