Source code for jgdv.structs.dkey.dkey

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5
  6# Imports:
  7from __future__ import annotations
  8
  9# ##-- stdlib imports
 10import datetime
 11import enum
 12import functools as ftz
 13import itertools as itz
 14import logging as logmod
 15import pathlib as pl
 16import re
 17import time
 18import collections
 19import contextlib
 20import hashlib
 21from copy import deepcopy
 22from uuid import UUID, uuid1
 23from weakref import ref
 24# ##-- end stdlib imports
 25
 26from collections import ChainMap
 27from jgdv import Proto, Mixin
 28from jgdv.mixins.annotate import SubAlias_m
 29from jgdv.structs.strang import Strang
 30from ._util import _interface as ExpAPI # noqa: N812
 31from ._util.expander_stack import DKeyExpanderStack
 32from .processor import DKeyProcessor
 33
 34# ##-- types
 35# isort: off
 36# General
 37import abc
 38import collections.abc
 39import typing
 40import types
 41from typing import cast, assert_type, assert_never
 42from typing import Generic, NewType, Never
 43from typing import no_type_check, final, override, overload
 44# Protocols and Interfaces:
 45from typing import Protocol
 46from . import _interface as API # noqa: N812
 47
 48if typing.TYPE_CHECKING:
 49    from typing import Final, ClassVar, Any, Self
 50    from typing import Literal, LiteralString
 51    from typing import TypeGuard
 52    from collections.abc import Iterable, Iterator, Callable, Generator
 53    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 54
 55    from jgdv import Maybe, M_
 56    from ._util._interface import Expander_p
 57
 58# isort: on
 59# ##-- end types
 60
 61##-- logging
 62logging = logmod.getLogger(__name__)
 63##-- end logging
 64
 65# Vars:
 66CLASS_GETITEM_K : Final[str] = "__class_getitem__"
 67type DP_TYPE = API.DKeyMarkAbstract_e | type
 68# Body:
 69
[docs] 70@Proto(API.Key_p, check=False, mod_mro=False) 71class DKey[**K](Strang, fresh_registry=True): 72 """ A facade for DKeys and variants. 73 Implements __new__ to create the correct key type, from a string, dynamically. 74 75 kwargs: 76 explicit = insists that keys in the string are wrapped in braces '{akey} {anotherkey}'. 77 mark = pre-register expansion parameters / type etc 78 check = dictate a type that expanding this key must match 79 fparams = str formatting instructions for the key 80 81 Eg: 82 DKey('blah') 83 -> SingleDKey('blah') 84 -> SingleDKey('blah').format('w') 85 -> '{blah}' 86 -> [toml] aValue = '{blah}' 87 88 Because cls.__new__ calls __init__ automatically for return values of type cls, 89 DKey is the factory, but all DKeys are subclasses of DKeyBase, 90 to allow control over __init__. 91 92 Base class for implementing actual DKeys. 93 94 init takes kwargs: 95 fmt, mark, check, ctor, help, fallback, max_exp 96 97 on class definition, can register a 'mark', 'multi', and a conversion parameter str 98 """ 99 __slots__ = ("data",) 100 __match_args = () 101 _annotate_to : ClassVar[str] = "dkey_mark" 102 _processor : ClassVar = DKeyProcessor() 103 _sections : ClassVar = API.DKEY_SECTIONS 104 _expander : ClassVar[Expander_p] = cast("Expander_p", DKeyExpanderStack()) 105 _typevar : ClassVar = None 106 _extra_kwargs : ClassVar[set[str]] = set() 107 _extra_sources : ClassVar[list[ExpAPI.SourceBases]] = [] 108 Marks : ClassVar[API.DKeyMarkAbstract_e] = API.DKeyMark_e # type: ignore[assignment] 109 data : API.DKey_d 110 111 ##--| Class Utils 112
[docs] 113 @final 114 @staticmethod 115 def MarkOf[T](target:T|type[T]) -> API.KeyMark|tuple[API.KeyMark, ...]: # noqa: N802 116 """ Get the mark of the key type or instance """ 117 if not hasattr(target, "cls_annotation"): 118 return () 119 match target.cls_annotation(): # type: ignore[union-attr] 120 case None: 121 return () 122 case [x]: 123 return cast("API.KeyMark", x) 124 case xs: 125 return cast("tuple[API.KeyMark, ...]", xs)
126
[docs] 127 @classmethod 128 def add_sources(cls, *sources:dict) -> None: 129 """ register additional sources that are always included in expansion """ 130 cls._extra_sources += sources
131 132 @override 133 def __init_subclass__(cls, *args, **kwargs) -> None: # noqa: ANN002, ANN003 134 super().__init_subclass__(*args, annotation=kwargs.pop("mark", None), **kwargs) 135 cls._expander.set_ctor(DKey) # type: ignore[arg-type] 136 cls._processor.register_convert_param(cls, kwargs.pop("convert", None)) 137 138 ##--| Class Main 139 140 def __init__(self, *args:Any, **kwargs:Any) -> None: # noqa: ANN401 141 assert(not self.endswith(API.INDIRECT_SUFFIX)), self[:] 142 super().__init__(*args, **kwargs) 143 self.data = API.DKey_d(**kwargs) 144 145 def __call__(self, *args:Any, **kwargs:Any) -> Any: # noqa: ANN401 146 """ call expand on the key. 147 Args and kwargs are passed verbatim to expand() 148 """ 149 return self.expand(*args, **kwargs) 150 151 @override 152 def __eq__(self, other:object) -> bool: 153 match other: 154 case DKey() | str(): 155 return str.__eq__(self, other) 156 case _: 157 return NotImplemented 158 159 @override 160 def __hash__(self) -> int: 161 return hash(self[:]) 162 163 @override 164 def __format__(self, spec:str) -> str: 165 """ 166 Extends standard string format spec language: 167 [[fill]align][sign][z][#][0][width][grouping_option][. precision][type] 168 (https://docs.python.org/3/library/string.html#format-specification-mini-language) 169 170 Using the # alt form to declare keys are wrapped. 171 eg: for key = DKey('test'), ikey = DKey('test_') 172 f'{key}' -> 'test' 173 f'{key:w}' -> '{test}' 174 f'{key:i} -> 'test_' 175 f'{key:wi} -> '{test_}' 176 177 f'{ikey:d} -> 'test' 178 179 """ 180 result = self[:] 181 if not bool(spec): 182 return result 183 184 rem, wrap, direct = self._processor.consume_format_params(spec) 185 186 # format 187 if not direct: 188 result = f"{result}{API.INDIRECT_SUFFIX}" 189 190 if wrap: 191 result = "".join(["{", result, "}"]) # noqa: FLY002 192 193 return result 194 ##--| Utils 195
[docs] 196 def var_name(self) -> str: 197 """ When testing the dkey for its inclusion in a decorated functions signature, 198 this gives the 'named' val if its not None, otherwise the str of the key 199 """ 200 return self.data.name or str(self)
201
[docs] 202 def expand(self, *args:Any, **kwargs:Any) -> Maybe: # noqa: ANN401 203 kwargs.setdefault("limit", self.data.max_expansions) 204 assert(isinstance(self, API.Key_p)) 205 match self._expander.expand(self, *args, **kwargs): 206 case ExpAPI.ExpInst_d(value=val, literal=True): 207 return val 208 case _: 209 return None
210
[docs] 211 def redirect(self, *args:Any, **kwargs:Any) -> list[API.Key_p]: # noqa: ANN401 212 assert(isinstance(self, API.Key_p)) 213 result = [DKey(x.value) for x in self._expander.redirect(self, *args, **kwargs) if x is not None] 214 return result
215 216 ##--| expansion hooks
[docs] 217 def exp_extra_sources_h(self, current:ExpAPI.SourceChain_d) -> ExpAPI.SourceChain_d: 218 match self._extra_sources: 219 case [*xs]: 220 return current.extend(*xs) 221 case x: 222 raise TypeError(type(x))