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 ()