Source code for jgdv.mixins.annotate.aliaser

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5# Imports:
  6from __future__ import annotations
  7
  8# ##-- stdlib imports
  9import datetime
 10import enum
 11import functools as ftz
 12import itertools as itz
 13import logging as logmod
 14import pathlib as pl
 15import re
 16import time
 17import collections
 18import contextlib
 19import hashlib
 20from copy import deepcopy
 21from uuid import UUID, uuid1
 22from weakref import ref
 23import atexit # for @atexit.register
 24import faulthandler
 25# ##-- end stdlib imports
 26
 27from . import _interface as API # noqa: N812
 28
 29# ##-- types
 30# isort: off
 31# General
 32import abc
 33import collections.abc
 34import typing
 35import types
 36from types import GenericAlias
 37from typing import cast, assert_type, assert_never
 38from typing import Generic, NewType, Never
 39from typing import no_type_check, final, override, overload
 40# Protocols and Interfaces:
 41from typing import Protocol, runtime_checkable
 42if typing.TYPE_CHECKING:
 43    from typing import Final, ClassVar, Any, Self
 44    from typing import Literal, LiteralString
 45    from typing import TypeGuard
 46    from collections.abc import Iterable, Iterator, Callable, Generator
 47    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 48
 49    from jgdv import Maybe
 50
 51# isort: on
 52# ##-- end types
 53
 54##-- logging
 55logging = logmod.getLogger(__name__)
 56##-- end logging
 57
 58# Vars:
 59type AliasAnnotation = type|str|enum.Enum|Literal[False]|Literal[True]|tuple[AliasAnnotation, ...]
 60
 61# Body:
 62
[docs] 63class SubAlias_m: 64 """ A Mixin to manage generics that resolve to specific registered subclasses. 65 66 On class declaration, recognizes kwargs: 67 - fresh_registry:bool : use a separate registry for this class and subclasses 68 - accumulate:bool : annotations accumulate from their parent class 69 - strict:bool : error if a subclass tries to overwrite a registration 70 - default:bool : set this subclass as the default if no marks are specified when creating an instance 71 - annotation:Maybe[str|type|enum|tuple[...]] : the key to use for this subclass, if class_getitem hasn't been used 72 - no_register:bool : create the class, but don't register it 73 74 cls[val] -> GenericAlias(cls, val) 75 76 then: 77 78 class RealSub(cls[val]) ... 79 after which: 80 cls[val] is RealSub 81 82 Annotation Keys are stored in cls.__annotation__, 83 under the cls._annotate_to key name. 84 85 """ 86 __slots__ = () 87 _annotate_to : ClassVar[str] = API.AnnotationTarget 88 _default_k : ClassVar[str] = API.Default_K 89 # TODO make this a weakdict? 90 _registry : ClassVar[dict[AliasAnnotation, type]] = {} 91 _strict : ClassVar[bool] = False 92 _accumulator : ClassVar[bool] = False 93 94 def __init_subclass__(cls:type[Self], *args:Any, annotation:Maybe[AliasAnnotation]=None, fresh_registry:bool=False, **kwargs:Any) -> None: # noqa: ANN401 95 x : Any 96 overwrite : bool 97 # ensure a new annotations dict 98 # (if a subclass doesn't add *new* annotations, the super's is used. so if we modify it here, 99 # the subclass is effected) 100 cls.__annotations__ = cls.__annotations__.copy() 101 overwrite = kwargs.pop("overwrite", False) 102 if (strict:=kwargs.pop("strict", None)): 103 cls._strict = strict or cls._strict 104 if (accumulate:=kwargs.pop("accumulate", None)): 105 cls._accumulator = accumulate or cls._accumulator 106 107 if fresh_registry: 108 cls._registry = {} 109 110 if kwargs.pop("default", None) is True: 111 cls._registry[cls._default_k] = cls 112 113 # set the annotation target 114 match kwargs.pop(API.AnnotateKWD, None): 115 case str() as target: 116 logging.debug("Annotate Subclassing: %s : %s", cls, kwargs) 117 cls._annotate_to = target 118 setattr(cls, cls._annotate_to, None) 119 case None if not hasattr(cls, cls._annotate_to): 120 setattr(cls, cls._annotate_to, None) 121 case _: 122 pass 123 124 annotation = cls._build_annotation(annotation) 125 cls.__annotations__[cls._annotate_to] = annotation 126 if kwargs.pop("no_register", False): 127 return 128 129 match annotation, cls._registry.get(annotation, None): 130 case (), _: # No annotation 131 pass 132 case _, None: # No registered cls 133 cls._registry[annotation] = cls 134 case _, x if overwrite: 135 cls._registry[annotation] = cls 136 case _, x if cls._strict: # complain there s a cls 137 msg = "already has a registration" 138 raise TypeError(msg, x, cls, annotation, args, kwargs) 139 140 def __class_getitem__[K:AliasAnnotation](cls:type[Self], *key:K) -> type|GenericAlias: 141 return cls._retrieve_subtype(*key) 142
[docs] 143 @classmethod 144 def _retrieve_subtype[K:AliasAnnotation](cls:type[Self], key:K) -> type|GenericAlias: 145 use_key : AliasAnnotation = cls._build_annotation(key) 146 match cls._registry.get(use_key, None): 147 case type() as result: 148 return result 149 case None if use_key == () and cls._default_k in cls._registry: 150 return cls._registry[cls._default_k] 151 case _: 152 return GenericAlias(cls, use_key)
153
[docs] 154 @classmethod 155 def _clear_registry(cls) -> None: 156 cls._registry.clear()
157
[docs] 158 @classmethod 159 def cls_annotation(cls) -> tuple: 160 return cls.__annotations__.get(cls._annotate_to, ())
161
[docs] 162 @classmethod 163 def _build_annotation(cls, annotate:Maybe[AliasAnnotation]) -> AliasAnnotation: # noqa: PLR0912 164 """ single point of truth for determining annotations from a cls and provided annotation """ 165 x : Any 166 result : list[AliasAnnotation] = [] 167 assert(hasattr(cls, "cls_annotation")) 168 169 match cls.cls_annotation(): 170 case []: 171 pass 172 case tuple() as x: 173 result += x 174 175 match cls.__dict__.get(API.ORIG_BASES_K, []): 176 case _ if bool(result): 177 pass 178 case [GenericAlias() as x]: 179 result += x.__args__ 180 case _ if (annotated:=[x for x in cls.mro() if x is not cls and hasattr(x, "cls_annotation")]): 181 result += annotated[0].cls_annotation() 182 case _: 183 pass 184 185 match annotate: 186 case None: 187 pass 188 case [[*xs]] if cls._accumulator: 189 result += xs 190 case tuple() as x if cls._accumulator: 191 result += x 192 case x if cls._accumulator: 193 result.append(x) 194 case [[*xs]]: 195 result = [*xs] 196 case [*xs]: 197 result = [*xs] 198 case x: 199 result = [x] 200 201 ##--| 202 match result: 203 case []: 204 return () 205 case [x]: 206 return tuple([x]) 207 case [*xs]: 208 return tuple(xs) 209 case _: 210 return ()