This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Init

author
syvlorg
date (May 17, 2025, 1:26 PM -0400) commit f1386192
+10630
+22
.gitignore
··· 1 + # Python-generated files 2 + __pycache__/ 3 + *.py[oc] 4 + build/ 5 + dist/ 6 + wheels/ 7 + *.egg-info 8 + 9 + # Virtual environments 10 + .venv 11 + 12 + # Ruff 13 + .ruff_cache 14 + 15 + # Hypothesis 16 + .hypothesis 17 + 18 + # Pytest 19 + .pytest_cache 20 + 21 + # Random Tests 22 + /test*.py
+1
.python-version
··· 1 + 3.14
README.md

This is a binary file and will not be displayed.

+40
conftest.py
··· 1 + # Adapted From: https://github.com/pytest-dev/pytest/issues/2269#issue-209278464 2 + import os.path as op 3 + import sys 4 + 5 + sys.path.remove(op.dirname(op.abspath(__file__))) 6 + 7 + from pytest import hookimpl 8 + from hypothesis import HealthCheck 9 + from hypothesis import settings 10 + from functools import partial 11 + 12 + # Adapted From: https://docs.pytest.org/en/stable/example/simple.html 13 + # And: https://github.com/pytest-dev/pytest/issues/5024#issuecomment-2078698395 14 + # And: https://pytest-with-eric.com/hooks/pytest-configure/ 15 + name = "oreo" 16 + kwargs = { 17 + "deadline": None, 18 + "derandomize": True, 19 + "suppress_health_check": ( 20 + HealthCheck.differing_executors, 21 + HealthCheck.filter_too_much, 22 + HealthCheck.function_scoped_fixture, 23 + HealthCheck.too_slow, 24 + ), 25 + } 26 + 27 + 28 + def pytest_addoption(parser): 29 + parser.addoption("--more-tests", action="store", default=False, type=bool) 30 + parser.addoption("--verbose-debug", action="store", default=False, type=bool) 31 + 32 + 33 + @hookimpl(tryfirst=True) 34 + def pytest_configure(config): 35 + if config.getoption("--more-tests"): 36 + kwargs["max_examples"] = 1000 37 + if config.getoption("--verbose-debug"): 38 + kwargs["verbosity"] = Verbosity.debug 39 + settings.register_profile(name, **kwargs) 40 + settings.load_profile("oreo")
+1
packages/bundle/.python-version
··· 1 + 3.14
packages/bundle/README.md

This is a binary file and will not be displayed.

+25
packages/bundle/pyproject.toml
··· 1 + [project] 2 + name = "bundle" 3 + version = "0.1.0" 4 + description = "A syvl.org typed CLI system!" 5 + readme = "README.md" 6 + authors = [ 7 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 8 + ] 9 + maintainers = [ 10 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 11 + ] 12 + requires-python = ">=3.14" 13 + license = "MIT" 14 + urls."Source code" = "https://tangled.sh/@syvl.org/bundle" 15 + dependencies = [ "jugulis" ] 16 + 17 + [build-system] 18 + requires = ["hatchling"] 19 + build-backend = "hatchling.build" 20 + 21 + [tool.hatch.build.targets.wheel] 22 + packages = [ "bundle" ] 23 + 24 + [tool.uv.sources] 25 + jugulis = { workspace = true }
+1
packages/hydrox/.python-version
··· 1 + 3.14
packages/hydrox/README.md

This is a binary file and will not be displayed.

+3
packages/hydrox/hydrox/__init__.py
··· 1 + from rich.traceback import install 2 + 3 + install(show_locals=True)
+78
packages/hydrox/hydrox/helpers/__init__.py
··· 1 + # Adapted From: 2 + # Answer 1: https://stackoverflow.com/a/46980829 3 + # User 1: https://stackoverflow.com/users/510937/bakuriu 4 + # Answer 2: https://stackoverflow.com/a/43059528 5 + # User 2: https://stackoverflow.com/users/5249307/donkopotamus 6 + from importlib import import_module 7 + from pathlib import Path 8 + 9 + modules = [] 10 + for file in Path(__file__).parent.iterdir(): 11 + stem = file.stem 12 + if not ( 13 + file.suffix != ".py" 14 + or stem.startswith("__") 15 + or stem.endswith("__") 16 + or stem in "hypothesis" 17 + ): 18 + modules.append(import_module("." + stem, package=__name__)) 19 + # module = import_module("." + stem, package=__name__) 20 + # globals().update( 21 + # { 22 + # k: v 23 + # for k, v in module.__dict__.items() 24 + # if not (k.startswith("__") or k.endswith("__") or len(k) == 1) 25 + # } 26 + # ) 27 + 28 + import sys 29 + from . import tests 30 + from .helpers import ModuleCaller, inf, aleph, ninf, naleph 31 + 32 + # Adapted From: 33 + # Question: https://stackoverflow.com/questions/56786604/import-modules-that-dont-exist-yet 34 + # User: https://stackoverflow.com/users/10827766/shadowrylander 35 + 36 + 37 + def helper_creator(name): 38 + def __getattr__(self, t: str): 39 + if t.startswith("__"): 40 + raise AttributeError(t) 41 + if t in ("tests", "hypothesis"): 42 + # return import_module("." + t, package=__name__) 43 + return eval(t) 44 + if t in ("_inf", "_aleph", "_ninf", "_naleph"): 45 + return eval(t.lstrip("_")) 46 + if t.startswith("infm"): 47 + return inf("-" + t.removeprefix("infm")) 48 + if t.startswith("inf"): 49 + return inf(t.removeprefix("infp").removeprefix("inf") or 0) 50 + if t.startswith("alephm"): 51 + return aleph("-" + t.removeprefix("alephm")) 52 + if t.startswith("aleph"): 53 + return aleph(t.removeprefix("alephp").removeprefix("aleph") or 0) 54 + if t.startswith("ninfm"): 55 + return ninf("-" + t.removeprefix("ninfm")) 56 + if t.startswith("ninf"): 57 + return ninf(t.removeprefix("ninfp").removeprefix("ninf") or 0) 58 + if t.startswith("nalephm"): 59 + return naleph("-" + t.removeprefix("nalephm")) 60 + if t.startswith("naleph"): 61 + return naleph(t.removeprefix("nalephp").removeprefix("naleph") or 0) 62 + for module in modules: 63 + if hasattr(module, t): 64 + return getattr(module, t) 65 + raise AttributeError(t) 66 + 67 + return type( 68 + name, 69 + (ModuleCaller,), 70 + { 71 + "__getattr__": __getattr__, 72 + "__getitem__": lambda self, t: self.__getattr__(t), 73 + "__all__": [var for var in vars() if var not in ("__qualname__",)], 74 + }, 75 + )() 76 + 77 + 78 + sys.modules[__name__] = helper_creator(__name__)
+323
packages/hydrox/hydrox/helpers/cache.py
··· 1 + # Adapted From: https://github.com/python/cpython/blob/7bb41aef4b7b8f3c3f07c11b801c5b7f8afaac7f/Lib/functools.py#L513-L741 2 + 3 + import collections.abc as abc 4 + from .decorator import Decorator 5 + from .helpers import zipsuppress 6 + from contextlib import suppress 7 + from functools import partial, wraps, cache as defcache 8 + from inspect import isclass, getattr_static 9 + from rich import print 10 + from rich.rule import Rule 11 + 12 + sentinel = object() 13 + 14 + 15 + @defcache 16 + def cachehash(obj): 17 + return hash(obj) 18 + 19 + 20 + class CacheError(Exception): 21 + pass 22 + 23 + 24 + class _HashedSeq(tuple): 25 + """This class guarantees that hash() will be called no more than once 26 + per element. This is important because the lru_cache() will hash 27 + the key multiple times on a cache miss. 28 + 29 + """ 30 + 31 + def __new__(cls, tup): 32 + result = tuple.__new__(cls, tup) 33 + result.hashvalue = cachehash(tup) 34 + return result 35 + 36 + def __hash__(self): 37 + return self.hashvalue 38 + 39 + 40 + def _make_key(args, kwds, hashed): 41 + """Make a cache key from optionally typed positional and keyword arguments 42 + 43 + The key is constructed in a way that is flat as possible rather than 44 + as a nested structure that would take more memory. 45 + 46 + If there is only a single argument and its data type is known to cache 47 + its hash value, then that argument is returned without a wrapper. This 48 + saves space and improves lookup speed. 49 + 50 + """ 51 + 52 + # Adapted From: https://github.com/Textualize/rich/issues/2207 53 + # And: https://github.com/Textualize/rich/pull/2226 54 + _rich_traceback_omit = True 55 + 56 + # All of code below relies on kwds preserving the order input by the user. 57 + # Formerly, we sorted() the kwds before looping. The new way is *much* 58 + # faster; however, it means that f(x=1, y=2) will now be treated as a 59 + # distinct call from f(y=2, x=1) which will be cached separately. 60 + key = args 61 + if kwds: 62 + for item in kwds.items(): 63 + key += item 64 + elif len(key) == 1: 65 + first = key[0] 66 + if hashed: 67 + cachehash(first) 68 + return first 69 + return _HashedSeq(key) if hashed else key 70 + 71 + 72 + def nocache(func): 73 + func.__nocache__ = True 74 + return func 75 + 76 + 77 + def cachecheck(value): 78 + return ( 79 + callable(value) 80 + and not getattr(value, "__nocache__", False) 81 + and getattr(value, "cache", None) is None 82 + ) 83 + 84 + 85 + def cache_get(func, _cache, hashed, cls, args, kwargs): 86 + # Adapted From: https://github.com/Textualize/rich/issues/2207 87 + # And: https://github.com/Textualize/rich/pull/2226 88 + _rich_traceback_omit = True 89 + 90 + if _cache is None: 91 + return func() 92 + try: 93 + key = _make_key(args[int(cls) :], kwargs, hashed) 94 + except TypeError as e: 95 + if "unhashable type" not in str(e): 96 + raise e 97 + return func() 98 + try: 99 + return _cache[key] 100 + except CacheError: 101 + return func() 102 + except KeyError: 103 + result = _cache[key] = func() 104 + return result 105 + 106 + 107 + @Decorator 108 + def cache(func, _cache=None, /, *, hashed=True, cls=False): 109 + if isclass(func): 110 + metaclass = type(func) 111 + namespace = {} 112 + for k in dir(func): 113 + if cachecheck(v := getattr_static(func, k)): 114 + v = cache(v, _cache, hashed=hashed, cls=not isinstance(v, staticmethod)) 115 + namespace[k] = v 116 + return metaclass.__new__(metaclass, func.__name__, func.__bases__, namespace) 117 + _cache = {} if _cache is None else _cache 118 + part_args = tuple() 119 + part_kwargs = {} 120 + while isinstance(func, partial): 121 + part_args = func.args + part_args 122 + part_kwargs = func.keywords | part_kwargs 123 + func = func.func 124 + 125 + @wraps(func) 126 + def wrapper(*args, **kwargs): 127 + # Adapted From: https://github.com/Textualize/rich/issues/2207 128 + # And: https://github.com/Textualize/rich/pull/2226 129 + _rich_traceback_omit = True 130 + 131 + return cache_get( 132 + partial(func, *args, **kwargs), _cache, hashed, cls, args, kwargs 133 + ) 134 + 135 + if part_args or part_kwargs: 136 + wrapper = partial(wrapper, *part_args, **part_kwargs) 137 + 138 + wrapper.cache = _cache 139 + return wrapper 140 + 141 + 142 + class cacheclass(type): 143 + class __prepare__(dict): 144 + def __init__(self, name, bases, **kwargs): 145 + kwargs.pop("cls", None) 146 + self.cache = kwargs.pop("cache", {}) 147 + self.kwargs = kwargs 148 + 149 + def __setitem__(self, key, value): 150 + if cachecheck(value): 151 + value = cache( 152 + value, 153 + self.cache, 154 + cls=not isinstance(value, staticmethod), 155 + **self.kwargs, 156 + ) 157 + super().__setitem__(key, value) 158 + 159 + 160 + class EQDict: 161 + __slots__ = ("_keys", "_values") 162 + 163 + def __init__(self, _keys=None, _values=None, _items=None): 164 + self._keys = [] if _keys is None else list(_keys) 165 + self._values = [] if _values is None else list(_values) 166 + if (_keys or _values) and (len(_keys) != len(_values)): 167 + raise ValueError("'_keys' and '_values' must be of the same length") 168 + if _items: 169 + _items = dict(_items) 170 + self._keys.extend(_items.keys()) 171 + self._values.extend(_items.values()) 172 + 173 + def __contains__(self, key): 174 + return key in self._keys 175 + 176 + def __delattr__(self, attr): 177 + del self[attr] 178 + 179 + def __delitem__(self, item): 180 + try: 181 + index = self._keys.index(item) 182 + except ValueError: 183 + raise KeyError(item) 184 + else: 185 + del self._keys[index] 186 + del self._values[index] 187 + 188 + def __eq__(self, other): 189 + if isinstance(other, EQDict): 190 + return (self._keys == other._keys) and (self._values == other._values) 191 + if isinstance(other, abc.Mapping): 192 + with zipsuppress(): 193 + return all( 194 + False 195 + for ks, ko in zip(self._keys, other.keys(), strict=True) 196 + if ks != ko 197 + ) and all( 198 + False 199 + for vs, vo in zip(self._values, other.values(), strict=True) 200 + if vs != vo 201 + ) 202 + return NotImplemented 203 + 204 + def __getattr__(self, attr): 205 + return self[attr] 206 + 207 + def __getitem__(self, item): 208 + try: 209 + return self._values[self._keys.index(item)] 210 + except ValueError: 211 + raise KeyError(item) 212 + 213 + def __ior__(self, other): 214 + self.update(other) 215 + return self 216 + 217 + def __iter__(self): 218 + yield from self._keys 219 + 220 + def __len__(self): 221 + return len(self._keys) 222 + 223 + def __or__(self, other): 224 + if isinstance(other, (EQDict, abc.Mapping)): 225 + result = self.copy() 226 + result |= other 227 + return result 228 + raise TypeError( 229 + "cannot convert dictionary update sequence element #0 to a sequence" 230 + ) 231 + 232 + def __repr__(self): 233 + return repr(self.items()) 234 + 235 + def __rich_repr__(self): 236 + for k, v in self.items(): 237 + yield str(k), v 238 + 239 + def __ror__(self, other): 240 + if isinstance(other, EQDict): 241 + result = other.copy() 242 + result |= self 243 + return result 244 + if isinstance(other, abc.Mapping): 245 + result = other.copy() 246 + result |= zip(self._keys, self._values, strict=True) 247 + return result 248 + raise TypeError( 249 + "cannot convert dictionary update sequence element #0 to a sequence" 250 + ) 251 + 252 + def __setattr__(self, attr, value): 253 + if attr.startswith("_"): 254 + return object.__setattr__(self, attr, value) 255 + self[attr] = value 256 + 257 + def __setitem__(self, key, value): 258 + self._keys.append(key) 259 + self._values.append(value) 260 + 261 + def clear(self): 262 + self._keys = type(self._keys)() 263 + self._values = type(self._values)() 264 + 265 + @classmethod 266 + def fromkeys(cls, keys, value=None): 267 + keys = list(keys) 268 + return cls(_keys=keys, _values=[value] * len(keys)) 269 + 270 + def copy(self): 271 + return self.__class__(_keys=self._keys, _values=self._values) 272 + 273 + def get(self, key, default=None): 274 + try: 275 + return self[key] 276 + except KeyError: 277 + return default 278 + 279 + def items(self): 280 + return [(k, v) for k, v in zip(self._keys, self._values, strict=True)] 281 + 282 + def keys(self): 283 + return self._keys 284 + 285 + def pop(self, key, default=sentinel): 286 + try: 287 + value = self._values[self._keys.index(key)] 288 + except ValueError: 289 + if default is sentinel: 290 + raise KeyError(key) 291 + return default 292 + else: 293 + del self[key] 294 + return value 295 + 296 + def popitem(self): 297 + if self._keys: 298 + self._keys.pop() 299 + return self._values.pop() 300 + raise KeyError("'popitem(): EQDict is empty'") 301 + 302 + def setdefault(self, key, value=None): 303 + try: 304 + value = self._values[self._keys.index(key)] 305 + except ValueError: 306 + self._keys.append(key) 307 + self._values.append(value) 308 + return value 309 + 310 + def update(self, other): 311 + if isinstance(other, (EQDict, abc.Mapping)): 312 + self._keys.extend(other.keys()) 313 + self._values.extend(other.values()) 314 + raise TypeError( 315 + "cannot convert dictionary update sequence element #0 to a sequence" 316 + ) 317 + 318 + def values(self): 319 + return self._values 320 + 321 + 322 + def eqcache(func): 323 + return cache(func, EQDict(), False)
+40
packages/hydrox/hydrox/helpers/decorator.py
··· 1 + from .helpers import as_decorator 2 + 3 + # Adapted From: https://chatgpt.com/c/638a98d4-5547-4651-a94b-952317209721 4 + query = "@.*?\(" 5 + 6 + 7 + # Inspired By: https://github.com/micheles/decorator 8 + class Decorator: 9 + __slots__ = "func" 10 + 11 + def __init__(self, func): 12 + self.func = func 13 + 14 + def __call__(self, *args, **kwargs): 15 + if as_decorator(query=query): 16 + 17 + def wrapper(func): 18 + return self.func(func, *args, **kwargs) 19 + 20 + return wrapper 21 + 22 + return self.func(*args, **kwargs) 23 + 24 + 25 + # Inspired By: https://github.com/micheles/decorator 26 + class ClassDecorator: 27 + __slots__ = "func" 28 + 29 + def __init__(self, func): 30 + self.func = func 31 + 32 + def __call__(self, cls, *args, **kwargs): 33 + if as_decorator(query=query): 34 + 35 + def wrapper(func): 36 + return self.func(cls, func, *args, **kwargs) 37 + 38 + return wrapper 39 + 40 + return self.func(cls, *args, **kwargs)
+801
packages/hydrox/hydrox/helpers/helpers.py
··· 1 + import builtins 2 + import collections.abc as abc 3 + import linecache 4 + import math 5 + import re 6 + import typing 7 + import types 8 + 9 + from contextlib import suppress, contextmanager 10 + from functools import partial 11 + from inspect import ( 12 + currentframe, 13 + getframeinfo, 14 + getmodule, 15 + ) 16 + from itertools import permutations 17 + from more_itertools import unique_everseen, powerset 18 + 19 + BString: typing.TypeAlias = str | bytes | bytearray 20 + 21 + 22 + def unwrap(func): 23 + return getattr(func, "__wrapped__", func) 24 + 25 + 26 + def ziperr(err): 27 + err_msg = err if isinstance(err, str) else str(err) 28 + return err_msg in ( 29 + "zip() argument 2 is longer than argument 1", 30 + "zip() argument 2 is shorter than argument 1", 31 + ) 32 + 33 + 34 + @contextmanager 35 + def zipsuppress(): 36 + try: 37 + yield 38 + except ValueError as e: 39 + if not ziperr(e): 40 + raise e 41 + 42 + 43 + def reversecut(l, i=None): 44 + # length = len(l) 45 + # if i is None: 46 + # i = length 47 + # return l[::-1] if i == length else l[: length - i - 1 : -1] 48 + return l[::-1][: len(l) if i is None else i] 49 + 50 + 51 + def cutreverse(l, i=None): 52 + return l[: len(l) if i is None else i][::-1] 53 + 54 + 55 + # Adapted From: 56 + # Answer: https://stackoverflow.com/a/34445090/10827766 57 + # User: https://stackoverflow.com/users/168717/akiross 58 + def findall(p, s): 59 + """Yields all the positions of 60 + the pattern p in the string s.""" 61 + i = s.find(p) 62 + while i != -1: 63 + yield i 64 + i = s.find(p, i + 1) 65 + 66 + 67 + class CollectionMeta(type): 68 + def __instancecheck__(cls, instance): 69 + return not ( 70 + typing.get_origin(instance) or isinstance(instance, BString) 71 + ) and isinstance(instance, abc.Iterable) 72 + 73 + 74 + class Collection(metaclass=CollectionMeta): 75 + __class_getitem__ = classmethod(types.GenericAlias) 76 + 77 + 78 + class Flattery: 79 + def __init__(self, instance, iterables, **kwargs): 80 + self.instance = instance 81 + self.iterables = [iterables] 82 + self.kwargs = kwargs 83 + self.call = partial(self.instance, **self.kwargs) 84 + 85 + def __iter__(self): 86 + yield from self.call(self.iterables) 87 + 88 + def __call__(self, *iterables, **kwargs): 89 + yield from self.call(*self.iterables, *iterables, **kwargs) 90 + 91 + def bake(self, *iterables): 92 + self.iterables.append(iterables) 93 + return self 94 + 95 + def __getattr__(self, attr): 96 + def wrapper(*iterables, **kwargs): 97 + return getattr(self.instance, attr)( 98 + *self.iterables, *iterables, **(self.kwargs | kwargs) 99 + ) 100 + 101 + return wrapper 102 + 103 + 104 + class FlattenMeta(type): 105 + def __call__(self, *iterables, use=builtins.tuple(), levels=None, current_level=0): 106 + for iterable in use or iterables: 107 + if isinstance(iterable, Collection) and ( 108 + (levels is None) or (current_level < levels) 109 + ): 110 + yield from self( 111 + use=iterable, levels=levels, current_level=current_level + 1 112 + ) 113 + else: 114 + yield iterable 115 + 116 + def bake(self, *iterables, **kwargs): 117 + return Flattery(self, iterables, **kwargs) 118 + 119 + def list(self, *iterables, use=builtins.tuple(), levels=None, current_level=0): 120 + new_iterables = [] 121 + for iterable in use or iterables: 122 + if isinstance(iterable, Collection) and ( 123 + (levels is None) or (current_level < levels) 124 + ): 125 + new_iterables.extend( 126 + self.list( 127 + use=iterable, levels=levels, current_level=current_level + 1 128 + ) 129 + ) 130 + else: 131 + new_iterables.append(iterable) 132 + return new_iterables 133 + 134 + def set(self, *iterables, use=builtins.tuple(), levels=None, current_level=0): 135 + new_iterables = set() 136 + for iterable in use or iterables: 137 + if isinstance(iterable, Collection) and ( 138 + (levels is None) or (current_level < levels) 139 + ): 140 + new_iterables.update( 141 + self.set( 142 + use=iterable, levels=levels, current_level=current_level + 1 143 + ) 144 + ) 145 + else: 146 + new_iterables.add(iterable) 147 + return new_iterables 148 + 149 + def tuple(self, *iterables, use=builtins.tuple(), levels=None): 150 + return tuple(self(use=use or iterables, levels=levels)) 151 + 152 + def frozenset(self, *iterables, use=builtins.tuple(), levels=None): 153 + return frozenset(self.set(use=use or iterables, levels=levels)) 154 + 155 + def __getattr__(self, attr): 156 + def wrapper(*iterables, use=builtins.tuple(), levels=None): 157 + return getattr(builtins, attr)( 158 + self.list(use=use or iterables, levels=levels) 159 + ) 160 + 161 + return wrapper 162 + 163 + 164 + class flatten(metaclass=FlattenMeta): 165 + pass 166 + 167 + 168 + # Adapted From: 169 + # Answer: https://stackoverflow.com/a/61618555/10827766 170 + # User: https://stackoverflow.com/users/11769765/friedrich 171 + class ModuleCaller: ... 172 + 173 + 174 + def superpowerset(*iterables): 175 + return unique_everseen( 176 + flatten((powerset(p) for p in permutations(flatten(iterables))), levels=2) 177 + ) 178 + 179 + 180 + # Adapted From: 181 + # Answer 1: https://stackoverflow.com/a/60778287/10827766 182 + # User 1: https://stackoverflow.com/users/2729778/changaco 183 + # Answer 2: https://stackoverflow.com/a/9947093 184 + # User 2: https://stackoverflow.com/users/916657/niklas-b 185 + def as_decorator(default=False, query="@.*"): 186 + """This function tries to determine how its caller was called. 187 + 188 + The value returned by this function should not be blindly trusted, it can 189 + sometimes be inaccurate. 190 + 191 + Arguments: 192 + default (bool): the fallback value to return when we're unable to determine 193 + how the function was called 194 + 195 + >>> def f(*args): 196 + ... if as_decorator(): 197 + ... print("called as decorator with args {!r}".format(args)) 198 + ... if len(args) == 1: 199 + ... return args[0] 200 + ... return f 201 + ... else: 202 + ... print("called normally with args {!r}".format(args)) 203 + ... 204 + >>> f() 205 + called normally with args () 206 + >>> @f #doctest: +ELLIPSIS 207 + ... def g(): pass 208 + ... 209 + called as decorator with args (<function g at ...>,) 210 + >>> @f() 211 + ... class Foobar: pass 212 + ... 213 + called as decorator with args () 214 + called as decorator with args (<class 'state_chain.Foobar'>,) 215 + >>> @f( #doctest: +ELLIPSIS 216 + ... 'one long argument', 217 + ... 'another long argument', 218 + ... ) 219 + ... def g(): pass 220 + ... 221 + called as decorator with args ('one long argument', 'another long argument') 222 + called as decorator with args (<function g at ...>,) 223 + >>> @f('one long argument', #doctest: +ELLIPSIS 224 + ... 'another long argument') 225 + ... def g(): pass 226 + ... 227 + called as decorator with args ('one long argument', 'another long argument') 228 + called as decorator with args (<function g at ...>,) 229 + >>> @f( #doctest: +ELLIPSIS 230 + ... # A weirdly placed comment 231 + ... ) 232 + ... @f 233 + ... def g(): pass 234 + ... 235 + called as decorator with args () 236 + called as decorator with args (<function g at ...>,) 237 + 238 + """ 239 + 240 + def get_indentation(line): 241 + for i, c in enumerate(line): 242 + if not c.isspace(): 243 + break 244 + return line[:i] 245 + 246 + def get_call_module(frame): 247 + with suppress(AttributeError): 248 + return getmodule(frame).__spec__.name.split(".")[0] 249 + 250 + # First, we try to look at the line where Python says the function call is. 251 + # Unfortunately, Python doesn't always give us the line we're interested in. 252 + call_frame = currentframe().f_back.f_back 253 + 254 + # TODO: Add `hydreigon` to the multiple dispatch modules as well. 255 + while get_call_module(call_frame) in ("plum", "multimethod", "multipledispatch"): 256 + call_frame = call_frame.f_back 257 + 258 + call_info = getframeinfo(call_frame, context=0) 259 + source_lines = linecache.getlines(call_info.filename) 260 + if not source_lines: 261 + # Reading the source code failed, return the fallback value. 262 + return default 263 + try: 264 + call_line = source_lines[call_info.lineno - 1] 265 + except IndexError: 266 + # The source file seems to have been modified. 267 + return default 268 + call_line_ls = call_line.lstrip() 269 + if re.match(query, call_line_ls): 270 + # Note: there is a small probability of false positive here, if the 271 + # function call is on the same line as a decorator call. 272 + return True 273 + if call_line_ls.startswith("class ") or call_line_ls.startswith("def "): 274 + # Note: there is a small probability of false positive here, if the 275 + # function call is on the same line as a `class` or `def` keyword. 276 + return True 277 + # Next, we try to find and examine the line after the function call. 278 + # If that line doesn't start with a `class` or `def` keyword, then the 279 + # function isn't being called as a decorator. 280 + def_lineno = call_info.lineno 281 + while True: 282 + try: 283 + def_line = source_lines[def_lineno] 284 + except IndexError: 285 + # We've reached the end of the file. 286 + return False 287 + def_line_ls = def_line.lstrip() 288 + if def_line_ls[:1] in (")", "#", "@", ""): 289 + def_lineno += 1 290 + continue 291 + break 292 + if not (def_line_ls.startswith("class") or def_line_ls.startswith("def")): 293 + # Note: there is a small probability of false negative here, as we might 294 + # be looking at the wrong line. 295 + return False 296 + # Finally, we look at the lines above, taking advantage of the fact that a 297 + # decorator call is at the same level of indentation as the function or 298 + # class being decorated. 299 + def_line_indentation = get_indentation(def_line) 300 + for lineno in range(call_info.lineno - 1, 0, -1): 301 + line = source_lines[lineno - 1] 302 + line_indentation = get_indentation(line) 303 + if line_indentation == def_line_indentation: 304 + line_ls = line.lstrip() 305 + if line_ls[:1] in (")", ","): 306 + continue 307 + return re.match(query, line_ls) or False 308 + elif len(line_indentation) < len(def_line_indentation): 309 + break 310 + return default 311 + 312 + 313 + minf = math.inf 314 + 315 + 316 + # TODO: Test these using integers and floats; account for `0`. 317 + class subfloat(float): 318 + def __add__(self, value): 319 + if isinstance(value, float) and not isinstance(value, subfloat): 320 + return value 321 + if isinstance(value, self.__class__): 322 + return self.__class__(super().__add__(value)) 323 + return value 324 + 325 + def __bool__(self): 326 + return True 327 + 328 + def __ceil__(self): 329 + return self.__class__(super().__ceil__()) 330 + 331 + # TODO 332 + def __divmod__(self, value): ... 333 + 334 + def __eq__(self, value): 335 + return isinstance(value, self.__class__) and super().__eq__(value) 336 + 337 + def __floor__(self): 338 + return self.__class__(super().__floor__()) 339 + 340 + def __floordiv__(self, value): 341 + if value: 342 + if isinstance(value, self.__class__): 343 + try: 344 + return self.__class__(super().__floordiv__(value)) 345 + except ZeroDivisionError: 346 + name = self.__class__.__name__ 347 + raise ZeroDivisionError(f"{name} division by {name} zero") 348 + return value 349 + raise ZeroDivisionError("division by zero") 350 + 351 + def __hash__(self): 352 + return hash((self.__class__.__name__, super().__hash__())) 353 + 354 + def __int__(self): 355 + return self.__floor__() 356 + 357 + # TODO 358 + def __mod__(self, value): ... 359 + 360 + def __mul__(self, value): 361 + if value: 362 + if isinstance(value, float) and not isinstance(value, subfloat): 363 + return value 364 + if isinstance(value, self.__class__): 365 + return self.__class__(super().__mul__(value)) 366 + return value 367 + return 0 368 + 369 + def __ne__(self, value): 370 + return not (self == value) 371 + 372 + def __neg__(self): 373 + return self.__class__(super().__neg__()) 374 + 375 + def __pow__(self, value): 376 + if isinstance(value, float) and not isinstance(value, subfloat): 377 + return value 378 + if isinstance(value, self.__class__): 379 + return self.__class__(super().__pow__(value)) 380 + return value 381 + 382 + def __radd__(self, value): 383 + return self.__add__(value) 384 + 385 + def __rdivmod__(self, value): 386 + return self.__class__(value).__divmod__(self) 387 + 388 + def __repr__(self): 389 + name = self.__class__.__name__ 390 + if super().__bool__(): 391 + return name + super().__repr__().removesuffix(".0") 392 + return name 393 + 394 + def __rfloordiv__(self, value): 395 + return self.__class__(value).__rfloordiv__(self) 396 + 397 + # TODO 398 + def __rmod__(self, value): ... 399 + 400 + def __rmul__(self, value): 401 + return self.__class__(value).__mul__(self) 402 + 403 + def __round__(self, ndigits=None): 404 + return self.__class__(super().__round__(ndigits)) 405 + 406 + def __rpow__(self, value): 407 + return self.__class__(value).__rpow__(self) 408 + 409 + def __rsub__(self, value): 410 + return -self.__sub__(value) 411 + # return self.__class__(value).__sub__(self) 412 + 413 + def __rtruediv__(self, value): 414 + return self.__class__(value).__truediv__(self) 415 + 416 + def __sub__(self, value): 417 + if isinstance(value, float) and not isinstance(value, subfloat): 418 + return value 419 + if isinstance(value, self.__class__): 420 + return self.__class__(super().__sub__(value)) 421 + return value 422 + 423 + def __truediv__(self, value): 424 + if value: 425 + if isinstance(value, float) and not isinstance(value, subfloat): 426 + return value 427 + if isinstance(value, self.__class__): 428 + try: 429 + return self.__class__(super().__truediv__(value)) 430 + except ZeroDivisionError: 431 + name = self.__class__.__name__ 432 + raise ZeroDivisionError(f"{name} division by {name} zero") 433 + return value 434 + raise ZeroDivisionError("division by zero") 435 + 436 + def __trunc__(self): 437 + return self.__class__(super().__trunc__()) 438 + 439 + 440 + mninf = -minf 441 + 442 + 443 + class ninfmeta(type): 444 + def __instancecheck__(self, instance): 445 + return instance == mninf 446 + 447 + 448 + class ninf(subfloat, metaclass=ninfmeta): 449 + def __new__(cls, value=0): 450 + return super().__new__(cls, 0 if value == mninf else value) 451 + 452 + def __add__(self, value): 453 + if isinstance(value, naleph): 454 + return self 455 + return super().__add__(value) 456 + 457 + # TODO 458 + def __divmod__(self, value): ... 459 + 460 + def __eq__(self, value): 461 + if value == mninf: 462 + return super().__eq__(0) 463 + return super().__eq__(value) 464 + 465 + def __ge__(self, value): 466 + if value == mninf: 467 + return super().__ge__(0) 468 + result = super().__ge__(value) 469 + if isinstance(value, subfloat): 470 + if isinstance(value, naleph): 471 + return True 472 + return result 473 + return NotImplemented 474 + 475 + def __gt__(self, value): 476 + if value == mninf: 477 + return super().__gt__(0) 478 + result = super().__gt__(value) 479 + if isinstance(value, subfloat): 480 + if isinstance(value, naleph): 481 + return True 482 + return result 483 + return NotImplemented 484 + 485 + def __le__(self, value): 486 + if value == mninf: 487 + return super().__le__(0) 488 + result = super().__le__(value) 489 + if isinstance(value, subfloat): 490 + if isinstance(value, naleph): 491 + return False 492 + return result 493 + if result is NotImplemented: 494 + return NotImplemented 495 + return True 496 + 497 + def __lt__(self, value): 498 + if value == mninf: 499 + return super().__lt__(0) 500 + result = super().__lt__(value) 501 + if isinstance(value, subfloat): 502 + if isinstance(value, naleph): 503 + return False 504 + return result 505 + if result is NotImplemented: 506 + return NotImplemented 507 + return True 508 + 509 + # TODO 510 + def __mod__(self, value): ... 511 + 512 + def __mul__(self, value): 513 + if isinstance(value, naleph): 514 + return self 515 + if value == mninf: 516 + return self.__class__() 517 + return super().__mul__(value) 518 + 519 + def __pow__(self, value): 520 + if isinstance(value, naleph): 521 + return self 522 + if value == mninf: 523 + return self.__class__(1) 524 + return super().__pow__(value) 525 + 526 + def __sub__(self, value): 527 + if isinstance(value, naleph): 528 + return self 529 + return super().__sub__(value) 530 + 531 + def __truediv__(self, value): 532 + if isinstance(value, naleph): 533 + return self 534 + if value == mninf: 535 + raise ZeroDivisionError("inf division by inf zero") 536 + return super().__truediv__(value) 537 + 538 + 539 + class naleph(subfloat): 540 + def __ge__(self, value): 541 + result = super().__ge__(value) 542 + if isinstance(value, self.__class__): 543 + return result 544 + return NotImplemented 545 + 546 + def __gt__(self, value): 547 + result = super().__gt__(value) 548 + if isinstance(value, self.__class__): 549 + return result 550 + return NotImplemented 551 + 552 + def __le__(self, value): 553 + result = super().__le__(value) 554 + if isinstance(value, self.__class__): 555 + return result 556 + if result is NotImplemented: 557 + return NotImplemented 558 + return True 559 + 560 + def __lt__(self, value): 561 + result = super().__lt__(value) 562 + if isinstance(value, self.__class__): 563 + return result 564 + if result is NotImplemented: 565 + return NotImplemented 566 + return True 567 + 568 + 569 + class superfloat(float): 570 + def __add__(self, value): 571 + if isinstance(value, self.__class__): 572 + return self.__class__(super().__add__(value)) 573 + return self 574 + 575 + def __bool__(self): 576 + return True 577 + 578 + def __ceil__(self): 579 + return self.__class__(super().__ceil__()) 580 + 581 + # TODO 582 + def __divmod__(self, value): ... 583 + 584 + def __eq__(self, value): 585 + return isinstance(value, self.__class__) and super().__eq__(value) 586 + 587 + def __floor__(self): 588 + return self.__class__(super().__floor__()) 589 + 590 + def __floordiv__(self, value): 591 + if value: 592 + if isinstance(value, self.__class__): 593 + try: 594 + return self.__class__(super().__floordiv__(value)) 595 + except ZeroDivisionError: 596 + name = self.__class__.__name__ 597 + raise ZeroDivisionError(f"{name} division by {name} zero") 598 + return self 599 + raise ZeroDivisionError("division by zero") 600 + 601 + def __hash__(self): 602 + return hash((self.__class__.__name__, super().__hash__())) 603 + 604 + def __int__(self): 605 + return self.__floor__() 606 + 607 + # TODO 608 + def __mod__(self, value): ... 609 + 610 + def __mul__(self, value): 611 + if value: 612 + if isinstance(value, self.__class__): 613 + return self.__class__(super().__mul__(value)) 614 + return self 615 + return 0 616 + 617 + def __ne__(self, value): 618 + return not (self == value) 619 + 620 + def __neg__(self): 621 + return self.__class__(super().__neg__()) 622 + 623 + def __pow__(self, value): 624 + if value: 625 + if isinstance(value, self.__class__): 626 + return self.__class__(super().__pow__(value)) 627 + return self 628 + return 1 629 + 630 + def __radd__(self, value): 631 + return self.__add__(value) 632 + 633 + def __rdivmod__(self, value): 634 + return self.__class__(value).__divmod__(self) 635 + 636 + def __repr__(self): 637 + name = self.__class__.__name__ 638 + if super().__bool__(): 639 + return name + super().__repr__().removesuffix(".0") 640 + return name 641 + 642 + def __rfloordiv__(self, value): 643 + return self.__class__(value).__rfloordiv__(self) 644 + 645 + # TODO 646 + def __rmod__(self, value): ... 647 + 648 + def __rmul__(self, value): 649 + return self.__class__(value).__mul__(self) 650 + 651 + def __round__(self, ndigits=None): 652 + return self.__class__(super().__round__(ndigits)) 653 + 654 + def __rpow__(self, value): 655 + return self.__class__(value).__rpow__(self) 656 + 657 + def __rsub__(self, value): 658 + return -self.__sub__(value) 659 + # return self.__class__(value).__sub__(self) 660 + 661 + def __rtruediv__(self, value): 662 + return self.__class__(value).__truediv__(self) 663 + 664 + def __sub__(self, value): 665 + if isinstance(value, self.__class__): 666 + return self.__class__(super().__sub__(value)) 667 + return self 668 + 669 + def __truediv__(self, value): 670 + if value: 671 + if isinstance(value, self.__class__): 672 + try: 673 + return self.__class__(super().__truediv__(value)) 674 + except ZeroDivisionError: 675 + name = self.__class__.__name__ 676 + raise ZeroDivisionError(f"{name} division by {name} zero") 677 + return self 678 + raise ZeroDivisionError("division by zero") 679 + 680 + def __trunc__(self): 681 + return self.__class__(super().__trunc__()) 682 + 683 + 684 + class infmeta(type): 685 + def __instancecheck__(self, instance): 686 + return instance == minf 687 + 688 + 689 + class inf(superfloat, metaclass=infmeta): 690 + def __new__(cls, value=0): 691 + return super().__new__(cls, 0 if value == minf else value) 692 + 693 + def __add__(self, value): 694 + if isinstance(value, aleph): 695 + return value 696 + return super().__add__(value) 697 + 698 + # TODO 699 + def __divmod__(self, value): ... 700 + 701 + def __eq__(self, value): 702 + if value == minf: 703 + return super().__eq__(0) 704 + return super().__eq__(value) 705 + 706 + def __ge__(self, value): 707 + if isinstance(value, aleph): 708 + return False 709 + if value == minf: 710 + return super().__ge__(0) 711 + result = super().__ge__(value) 712 + if isinstance(value, self.__class__): 713 + return result 714 + return NotImplemented 715 + 716 + def __gt__(self, value): 717 + if isinstance(value, aleph): 718 + return False 719 + if value == minf: 720 + return super().__gt__(0) 721 + result = super().__gt__(value) 722 + if isinstance(value, self.__class__): 723 + return result 724 + return NotImplemented 725 + 726 + def __le__(self, value): 727 + if isinstance(value, aleph): 728 + return True 729 + if value == minf: 730 + return super().__le__(0) 731 + result = super().__le__(value) 732 + if isinstance(value, self.__class__): 733 + return result 734 + return NotImplemented 735 + 736 + def __lt__(self, value): 737 + if isinstance(value, aleph): 738 + return True 739 + if value == minf: 740 + return super().__lt__(0) 741 + result = super().__lt__(value) 742 + if isinstance(value, self.__class__): 743 + return result 744 + return NotImplemented 745 + 746 + # TODO 747 + def __mod__(self, value): ... 748 + 749 + def __mul__(self, value): 750 + if isinstance(value, aleph): 751 + return value 752 + if value == minf: 753 + return self.__class__() 754 + return super().__mul__(value) 755 + 756 + def __pow__(self, value): 757 + if isinstance(value, aleph): 758 + return value 759 + if value == minf: 760 + return self.__class__(1) 761 + return super().__pow__(value) 762 + 763 + def __sub__(self, value): 764 + if isinstance(value, aleph): 765 + return value 766 + return super().__sub__(value) 767 + 768 + def __truediv__(self, value): 769 + if isinstance(value, aleph): 770 + return value 771 + if value == minf: 772 + raise ZeroDivisionError("inf division by inf zero") 773 + return super().__truediv__(value) 774 + 775 + 776 + class aleph(superfloat): 777 + def __ge__(self, value): 778 + result = super().__ge__(value) 779 + if isinstance(value, self.__class__): 780 + return result 781 + if result is NotImplemented: 782 + return NotImplemented 783 + return True 784 + 785 + def __gt__(self, value): 786 + result = super().__gt__(value) 787 + if isinstance(value, self.__class__): 788 + return result 789 + if result is NotImplemented: 790 + return NotImplemented 791 + return True 792 + 793 + def __le__(self, value): 794 + if isinstance(value, self.__class__): 795 + return super().__le__(value) 796 + return NotImplemented 797 + 798 + def __lt__(self, value): 799 + if isinstance(value, self.__class__): 800 + return super().__lt__(value) 801 + return NotImplemented
+73
packages/hydrox/hydrox/helpers/normalize.py
··· 1 + import inspect 2 + from more_itertools import collapse 3 + 4 + 5 + def coflag(varargs=False, varkw=False): 6 + if varargs and varkw: 7 + return 15 8 + if varargs: 9 + return 7 10 + if varkw: 11 + return 11 12 + return 3 13 + 14 + 15 + # Adapted From: 16 + # Answer 1: https://stackoverflow.com/a/11292547 17 + # User 1: https://stackoverflow.com/users/534790/ahsan 18 + # Answer 2: https://stackoverflow.com/a/77155447 19 + # User 2: https://stackoverflow.com/users/6890912/blhsing 20 + # Answer 3: https://stackoverflow.com/a/73607744 21 + # User 3: https://stackoverflow.com/users/3936044/mandera 22 + # Answer 4: https://stackoverflow.com/a/17625756 23 + # User 4: https://stackoverflow.com/users/908494/abarnert 24 + # And: https://chatgpt.com/c/7a03b03f-8150-43cb-9ae0-e6fd111a05b6 25 + def function(func, include=tuple(), exclude=tuple()): 26 + sig = inspect.signature(func, eval_str=True) 27 + co_params = [param for param in sig.parameters if param not in exclude] + list( 28 + include 29 + ) 30 + co_argcount = len(co_params) 31 + co_varnames = (*co_params, *func.__code__.co_varnames[co_argcount:]) 32 + 33 + # NOTE: For documentation purposes. 34 + # return FunctionType( 35 + # CodeType( 36 + # co_argcount=co_argcount, 37 + # co_posonlyargcount=0, 38 + # co_kwonlyargcount=0, 39 + # co_nlocals=func.__code__.co_nlocals, 40 + # co_stacksize=func.__code__.co_stacksize, 41 + # co_flags=coflag(), 42 + # co_code=func.__code__.co_code, 43 + # co_consts=func.__code__.co_consts, 44 + # co_names=func.__code__.co_names, 45 + # co_varnames=(*co_params, *func.__code__.co_varnames[co_argcount:]), 46 + # co_filename=func.__code__.co_filename, 47 + # co_name=func.__code__.co_name, 48 + # co_qualname=func.__code__.co_qualname, 49 + # co_firstlineno=func.__code__.co_firstlineno, 50 + # co_linetable=func.__code__.co_linetable, 51 + # co_exceptiontable=func.__code__.co_exceptiontable, 52 + # co_freevars=func.__code__.co_freevars, 53 + # co_cellvars=func.__code__.co_cellvars, 54 + # ), 55 + # func.__globals__, 56 + # func.__qualname__, 57 + # ) 58 + 59 + func.__code__ = func.__code__.replace( 60 + co_argcount=co_argcount, 61 + co_posonlyargcount=0, 62 + co_kwonlyargcount=0, 63 + co_flags=coflag(), 64 + co_varnames=co_varnames, 65 + # TODO 66 + # co_nlocals=len(co_varnames) 67 + ) 68 + return func 69 + 70 + 71 + def multiline(string): 72 + split = string.strip().split("\n") 73 + return " ".join(filter(None, collapse(line.split(" ") for line in split)))
packages/hydrox/hydrox/helpers/tests/__init__.py

This is a binary file and will not be displayed.

+114
packages/hydrox/hydrox/helpers/tests/test_flatten.py
··· 1 + import builtins 2 + import hydrox.helpers as hh 3 + from hydrox.hypothesis import rand_strat 4 + from hypothesis import given, example, strategies as st 5 + from more_itertools import collapse 6 + from random import choice 7 + 8 + attrs = [ 9 + attr 10 + for attr in dir(hh.FlattenMeta) 11 + if not (attr.startswith("__") or attr.endswith("__") or attr in ("bake", "mro")) 12 + ] 13 + 14 + 15 + class TestFlatten: 16 + @given(iterable=rand_strat(levels=10), t=st.sampled_from(attrs)) 17 + @example(iterable=(1, (2, (3, (4,)))), t=choice(attrs)) 18 + @example(iterable=(1, 2, (3, (4,))), t=choice(attrs)) 19 + @example(iterable=(1, 2, 3, (4,)), t=choice(attrs)) 20 + @example(iterable=(1, 2, 3, 4), t=choice(attrs)) 21 + def test_iterable(self, iterable, t): 22 + assert getattr(hh.flatten, t)(iterable) == getattr(builtins, t)( 23 + collapse((iterable,)) 24 + ) 25 + 26 + @given(i=..., t=st.sampled_from(attrs)) 27 + def test_single(self, i: int, t): 28 + assert getattr(hh.flatten, t)(i) == getattr(builtins, t)((i,)) 29 + 30 + 31 + class TestFlattenedBaking: 32 + @given(iterable=rand_strat(levels=10), t=st.sampled_from(attrs)) 33 + @example(iterable=(1, (2, (3, (4,)))), t=choice(attrs)) 34 + @example(iterable=(1, 2, (3, (4,))), t=choice(attrs)) 35 + @example(iterable=(1, 2, 3, (4,)), t=choice(attrs)) 36 + @example(iterable=(1, 2, 3, 4), t=choice(attrs)) 37 + def test_iterable(self, iterable, t): 38 + bakery = hh.flatten.bake(iterable) 39 + assert getattr(bakery, t)() == getattr(builtins, t)(collapse((iterable,))) 40 + 41 + @given(i=..., t=st.sampled_from(attrs)) 42 + def test_single(self, i: int, t): 43 + bakery = hh.flatten.bake(i) 44 + assert getattr(bakery, t)() == getattr(builtins, t)((i,)) 45 + 46 + 47 + class TestFlattenGenerator: 48 + @given(iterable=rand_strat(levels=10)) 49 + @example(iterable=(1, (2, (3, (4,))))) 50 + @example(iterable=(1, 2, (3, (4,)))) 51 + @example(iterable=(1, 2, 3, (4,))) 52 + @example(iterable=(1, 2, 3, 4)) 53 + def test_iterable(self, iterable): 54 + try: 55 + for l, r in zip(hh.flatten(iterable), collapse((iterable,)), strict=True): 56 + assert l == r 57 + except ValueError: 58 + assert False 59 + 60 + @given(i=...) 61 + def test_single(self, i: int): 62 + try: 63 + for l, r in zip(hh.flatten(i), (i,), strict=True): 64 + assert l == r 65 + except ValueError: 66 + assert False 67 + 68 + 69 + class TestFlattenedGeneratorBaking: 70 + @given(iterable=rand_strat(levels=10)) 71 + @example(iterable=(1, (2, (3, (4,))))) 72 + @example(iterable=(1, 2, (3, (4,)))) 73 + @example(iterable=(1, 2, 3, (4,))) 74 + @example(iterable=(1, 2, 3, 4)) 75 + def test_iterable(self, iterable): 76 + bakery = hh.flatten.bake(iterable) 77 + try: 78 + for l, r in zip(bakery(), collapse((iterable,)), strict=True): 79 + assert l == r 80 + except ValueError: 81 + assert False 82 + 83 + @given(i=...) 84 + def test_single(self, i: int): 85 + bakery = hh.flatten.bake(i) 86 + try: 87 + for l, r in zip(bakery(), (i,), strict=True): 88 + assert l == r 89 + except ValueError: 90 + assert False 91 + 92 + 93 + class TestFlattenedGeneratorFlattery: 94 + @given(iterable=rand_strat(levels=10)) 95 + @example(iterable=(1, (2, (3, (4,))))) 96 + @example(iterable=(1, 2, (3, (4,)))) 97 + @example(iterable=(1, 2, 3, (4,))) 98 + @example(iterable=(1, 2, 3, 4)) 99 + def test_iterable(self, iterable): 100 + bakery = hh.flatten.bake(iterable) 101 + try: 102 + for l, r in zip(bakery, collapse((iterable,)), strict=True): 103 + assert l == r 104 + except ValueError: 105 + assert False 106 + 107 + @given(i=...) 108 + def test_single(self, i: int): 109 + bakery = hh.flatten.bake(i) 110 + try: 111 + for l, r in zip(bakery, (i,), strict=True): 112 + assert l == r 113 + except ValueError: 114 + assert False
+763
packages/hydrox/hydrox/hypothesis/__init__.py
··· 1 + import builtins 2 + import collections.abc as abc 3 + import hypothesis 4 + import types 5 + import typing 6 + import warnings 7 + from .. import typing as ht 8 + from ..helpers import flatten 9 + from ..helpers.decorator import Decorator 10 + from ..helpers.helpers import flatten 11 + from ..helpers.normalize import coflag 12 + from collections import defaultdict 13 + from contextlib import suppress 14 + from email.headerregistry import Address 15 + from functools import partial, cache as defcache 16 + from hydrox.helpers import Collection 17 + from hypothesis import strategies as st, assume 18 + from hypothesis.errors import InvalidArgument, Unsatisfiable 19 + from inspect import getfullargspec, isclass, signature 20 + from inspect import Parameter 21 + from rich.pretty import pprint 22 + from rich.rule import Rule 23 + from rich import print 24 + from string import ascii_lowercase as lowercase 25 + 26 + sentinel = object() 27 + 28 + ht.create_types(abc.Iterable, recurse=True) 29 + for name in dir(builtins): 30 + if name.islower() and name not in ("object",): 31 + attr = getattr(builtins, name, None) 32 + if isinstance(attr, ht.Annotation): 33 + ht.create_types(attr, recurse=True) 34 + 35 + for t in ( 36 + "NoneType", 37 + "EllipsisType", 38 + "Generator", 39 + "GeneratorType", 40 + ): 41 + ht.Generalize(t) 42 + 43 + generic_keys = tuple(ht.Generics) 44 + generic_values = tuple(ht.Generics.values()) 45 + 46 + strat_default_kwargs = {"allow_nan": False} 47 + 48 + 49 + def apply_kwargs(strat, kwargs=None, args=tuple()): 50 + kwargs = kwargs or {} 51 + spec = getfullargspec(strat) 52 + params = signature(strat).parameters 53 + if ( 54 + all(False for param in params.values() if param.default is Parameter.empty) 55 + or spec.varargs 56 + ): 57 + return strat( 58 + *args, 59 + **{k: v for k, v in (strat_default_kwargs | kwargs).items() if k in params}, 60 + ) 61 + 62 + 63 + # Adapted From: https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings 64 + with warnings.catch_warnings(): 65 + warnings.simplefilter("ignore") 66 + hashable_strats = [] 67 + for attr in dir(st): 68 + if not ( 69 + attr.startswith("_") 70 + or attr.startswith("@") 71 + or attr in ("builds", "one_of", "runner") 72 + ): 73 + strat = getattr(st, attr) 74 + if not isclass(strat): 75 + with suppress(InvalidArgument, Unsatisfiable): 76 + try: 77 + hash(apply_kwargs(strat).example()) 78 + except TypeError as e: 79 + if not "unhashable type:" in str(e): 80 + raise e 81 + except AttributeError as e: 82 + if str(e) != "'NoneType' object has no attribute 'example'": 83 + raise e 84 + else: 85 + hashable_strats.append(strat) 86 + 87 + 88 + def get_strat(strat, kwargs): 89 + if isinstance(strat, str): 90 + return apply_kwargs(getattr(st, strat), kwargs) 91 + if isinstance(strat, st.SearchStrategy): 92 + return apply_kwargs(strat, kwargs) 93 + if isinstance(strat, st._internal.lazy.LazyStrategy): 94 + return strat 95 + return st.from_type(strat) 96 + 97 + 98 + # Adapted From: https://chatgpt.com/c/e267b2c6-0b30-4a7c-9b9c-fa7b30a8278a 99 + # Answer 1: https://stackoverflow.com/a/43285138 100 + # User 1: https://stackoverflow.com/users/223424/9000 101 + # Answer 2: https://stackoverflow.com/a/52435332 102 + # User 2: https://stackoverflow.com/users/9297601/zac-hatfield-dodds 103 + # TODO: Vastly simplify this. 104 + @st.composite 105 + def rand_strat( 106 + draw, 107 + *ignores, 108 + only=tuple(), 109 + strats=tuple(), 110 + iterable_only_strats=tuple(), 111 + levels=1, 112 + iterable=False, 113 + only_iterables=tuple(), 114 + only_in_iterables=tuple(), 115 + hashable_only=False, 116 + hashable_only_in_iterable=False, 117 + **kwargs, 118 + ): 119 + ignores = [ 120 + i 121 + if isinstance(i, str) 122 + else i.function.__name__ 123 + if isinstance(i, st.SearchStrategy) 124 + else st.from_type(i).function.__name__ 125 + for i in flatten(ignores) 126 + ] 127 + only = flatten.tuple(only) 128 + hashable_only_strats = [ 129 + apply_kwargs(s, kwargs) 130 + for s in hashable_strats 131 + if ((only and s.__name__ in only) or True) and s.__name__ not in ignores 132 + ] 133 + only_iterables = flatten.tuple(only_iterables) 134 + valid_strats = flatten.list(strats) 135 + if hashable_only: 136 + valid_strats += hashable_only_strats 137 + iterable_strats = [] 138 + if not valid_strats: 139 + if only: 140 + valid_strats = [get_strat(s, kwargs) for s in only] 141 + else: 142 + for attr in only_iterables or dir(st): 143 + if not ( 144 + attr.startswith("_") 145 + or attr.startswith("@") 146 + or attr 147 + in ( 148 + "builds", 149 + "one_of", 150 + "runner", 151 + *ignores, 152 + ) 153 + ): 154 + strat = getattr(st, attr) 155 + if ( 156 + attr 157 + in ( 158 + "sets", 159 + "frozensets", 160 + "lists", 161 + "tuples", 162 + "dictionaries", 163 + ) 164 + and levels >= 0 165 + ): 166 + randint = draw(st.randoms()).randint 167 + min_size = kwargs.get("min_size", randint(0, 10)) 168 + max_size = kwargs.get("max_size", randint(min_size, 10)) 169 + iterable_only_strats = flatten.list( 170 + iterable_only_strats, 171 + (get_strat(s, kwargs) for s in flatten(only_in_iterables)), 172 + ) 173 + if hashable_only_in_iterable: 174 + iterable_only_strats += hashable_only_strats 175 + rand_strat_part = partial( 176 + rand_strat, 177 + *ignores, 178 + levels=levels - 1, 179 + strats=iterable_only_strats, 180 + **kwargs, 181 + ) 182 + hashable_strats_part = rand_strat_part( 183 + strats=iterable_only_strats or hashable_only_strats 184 + ) 185 + strat_part = partial( 186 + strat, min_size=min_size, max_size=max_size 187 + ) 188 + match attr: 189 + case "sets" | "frozensets": 190 + iterable_strats.append(strat_part(hashable_strats_part)) 191 + case "lists": 192 + iterable_strats.append(strat_part(rand_strat_part())) 193 + case "tuples": 194 + iterable_strats.append( 195 + strat( 196 + *[rand_strat_part()] 197 + * randint(min_size, max_size) 198 + ) 199 + ) 200 + case "dictionaries": 201 + iterable_strats.append( 202 + strat_part( 203 + hashable_strats_part, 204 + rand_strat_part(), 205 + ) 206 + ) 207 + elif not (isclass(strat) or iterable or only_iterables): 208 + # Adapted From: https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings 209 + with warnings.catch_warnings(): 210 + warnings.simplefilter("ignore") 211 + if valid_strat := apply_kwargs(strat, kwargs): 212 + valid_strats.append(valid_strat) 213 + return draw( 214 + st.one_of( 215 + iterable_strats 216 + if (iterable or only_iterables) 217 + else flatten.tuple(valid_strats, iterable_strats) 218 + ) 219 + ) 220 + 221 + 222 + @st.composite 223 + def rand_literal(draw, *ignores, **kwargs): 224 + only = ( 225 + "binary", 226 + "booleans", 227 + "characters", 228 + "integers", 229 + "none", 230 + "text", 231 + kwargs.pop("only", tuple()), 232 + ) 233 + return draw( 234 + rand_strat( 235 + *ignores, 236 + only=only, 237 + **kwargs, 238 + ) 239 + ) 240 + 241 + 242 + # Adapted From: https://chatgpt.com/c/e267b2c6-0b30-4a7c-9b9c-fa7b30a8278a 243 + @st.composite 244 + def literal_strat(draw): 245 + # Step 1: Generate a list of random objects 246 + random_list = draw( 247 + st.lists(rand_literal(), min_size=1) 248 + ) # Ensure at least one item for subset selection 249 + 250 + # Step 2: Create a subset of the list 251 + subset = draw( 252 + st.lists(st.sampled_from(random_list), min_size=1, max_size=len(random_list)) 253 + ) 254 + 255 + # Step 3: Select a random item from the original list 256 + random_item = draw(st.sampled_from(random_list)) 257 + 258 + # Return the generated data 259 + return random_list, subset, random_item 260 + 261 + 262 + class Data: 263 + pass 264 + 265 + 266 + class Args: 267 + pass 268 + 269 + 270 + class SubArgs: 271 + pass 272 + 273 + 274 + class AllGeneric: 275 + __class_getitem__ = classmethod(types.GenericAlias) 276 + 277 + 278 + class AllGenericAlias: 279 + pass 280 + 281 + 282 + class HTGeneric: 283 + __class_getitem__ = classmethod(types.GenericAlias) 284 + 285 + 286 + class HTGenericAlias: 287 + pass 288 + 289 + 290 + generics = defaultdict(list) 291 + generics["generic_subclasses"] = defaultdict(list) 292 + generics["generic_superclasses"] = defaultdict(list) 293 + generics["all_generic_subclasses"] = defaultdict(list) 294 + generics["all_generic_superclasses"] = defaultdict(list) 295 + 296 + 297 + def subgen_strat(generic, ungen=False): 298 + return st.sampled_from(generics["all"]).filter( 299 + lambda g: ungen ^ ht.isinmrosubclass.genericcheck(g, generic) 300 + ) 301 + 302 + 303 + def supergen_strat(generic, ungen=False): 304 + return st.sampled_from(generics["all"]).filter( 305 + lambda g: ungen ^ ht.isinmrosubclass.genericcheck(generic, g) 306 + ) 307 + 308 + 309 + @defcache 310 + def generic_check(generic): 311 + return not ( 312 + generic 313 + in ( 314 + ht.Generic, 315 + ht.GenericAlias, 316 + type, 317 + types.GeneratorType, 318 + typing.Union, 319 + typing.Optional, 320 + typing.Any, 321 + ) 322 + or isinstance(getattr(generic, "__generic__", sentinel), typing._SpecialForm) 323 + ) and getattr(generic, "__generics__", tuple()) 324 + 325 + 326 + generics["all"] = set() 327 + # for generic in sample(generic_values, len(generic_values) // 2): 328 + for generic in generic_values: 329 + if generic_check(generic): 330 + generics["class"].append(generic) 331 + generics["all"].add(generic) 332 + generics["all"].update(generic.__generics__) 333 + 334 + generics["all"] = tuple(generics["all"]) 335 + 336 + # cached_get_args = cache(ht.get_args) 337 + cached_get_args = ht.get_args 338 + 339 + 340 + def subscript(t, s, i=None): 341 + if i is None: 342 + i = ht.get_generic_subscription_size(t, rand=True) 343 + if not i: 344 + return t 345 + return ht.format_args(t, [s] * i) 346 + 347 + 348 + @st.composite 349 + def subscript_generic_strat(draw, provided_generics, *types, i=None): 350 + return draw( 351 + st.builds( 352 + subscript, 353 + st.sampled_from(provided_generics), 354 + st.sampled_from(flatten.tuple(types)), 355 + i=st.just(i), 356 + ) 357 + ) 358 + 359 + 360 + def flat_args_strat(): 361 + return st.lists( 362 + st.from_type(HTGeneric), 363 + min_size=ht.max_subscription_size, 364 + max_size=ht.max_subscription_size, 365 + ) 366 + 367 + 368 + def arg_strat(provided_generics=tuple(), flat=False): 369 + return ( 370 + st.sampled_from(provided_generics or generics["all"]) 371 + if flat 372 + else subscript_generic_strat( 373 + provided_generics or generics["all"], 374 + *generics["all"], 375 + ) 376 + ) 377 + 378 + 379 + @st.composite 380 + def subarg_strat( 381 + draw, 382 + alias=None, 383 + provided_generics=tuple(), 384 + flat=False, 385 + just_sub_alias=False, 386 + ungen=False, 387 + ): 388 + alias = alias or draw(arg_strat(provided_generics=provided_generics, flat=flat)) 389 + origin_strat = subgen_strat(ht.get_origin(alias), ungen=ungen) 390 + if args := ht.get_args(alias): 391 + sub_alias = draw( 392 + st.builds( 393 + ht.format_args, 394 + origin_strat, 395 + st.lists( 396 + st.sampled_from(args), 397 + min_size=ht.max_subscription_size, 398 + max_size=ht.max_subscription_size, 399 + ), 400 + ).filter( 401 + lambda subalias: ungen 402 + ^ ht.isinmrosubclass.genericcheck(subalias, alias) 403 + ) 404 + ) 405 + else: 406 + sub_alias = draw(origin_strat) 407 + if just_sub_alias: 408 + return sub_alias 409 + return ht.generalize(alias), sub_alias 410 + 411 + 412 + def args_strat(provided_generics=tuple(), flat=False): 413 + return st.lists( 414 + arg_strat(provided_generics=provided_generics, flat=flat), 415 + min_size=ht.max_subscription_size, 416 + max_size=ht.max_subscription_size, 417 + ) 418 + 419 + 420 + @st.composite 421 + def subargs_strat( 422 + draw, 423 + aliases=tuple(), 424 + exact_aliases=tuple(), 425 + just_sub_aliases=False, 426 + ungen=False, 427 + **kwargs, 428 + ): 429 + list_strat_part = partial( 430 + st.lists, 431 + min_size=ht.max_subscription_size, 432 + max_size=ht.max_subscription_size, 433 + ) 434 + if aliases: 435 + aliases = draw( 436 + list_strat_part(st.sampled_from(aliases)) 437 + if aliases 438 + else args_strat(**kwargs) 439 + ) 440 + sub_aliases = [ 441 + draw(subarg_strat(alias, just_sub_alias=True, ungen=ungen)) 442 + for alias in aliases 443 + ] 444 + if just_sub_aliases: 445 + return sub_aliases 446 + return [ht.generalize(alias) for alias in aliases], sub_aliases 447 + elif exact_aliases: 448 + sub_aliases = [ 449 + draw(subarg_strat(alias, just_sub_alias=True, ungen=ungen)) 450 + for alias in exact_aliases 451 + ] 452 + if just_sub_aliases: 453 + return sub_aliases 454 + return exact_aliases, sub_aliases 455 + else: 456 + if just_sub_aliases: 457 + return draw(list_strat_part(subarg_strat(just_sub_alias=True, ungen=ungen))) 458 + # Adapted From: https://www.reddit.com/r/learnpython/comments/ykohm7/comment/iuuwysr/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button 459 + return tuple(zip(*draw(list_strat_part(subarg_strat(ungen=ungen))))) 460 + 461 + 462 + def recursive_type_var_strat(T, levels=None): 463 + levels_was_None = levels is None 464 + levels = 4 if levels is None else levels 465 + origin = st.one_of( 466 + st.just(T), st.from_type(AllGenericAlias if levels_was_None else AllGeneric) 467 + ) 468 + arg = recursive_type_var_strat(T, levels=levels - 1) if levels else origin 469 + strat = st.builds( 470 + ht.format_args, 471 + origin, 472 + st.lists( 473 + st.one_of(origin, arg), 474 + min_size=ht.max_subscription_size, 475 + max_size=ht.max_subscription_size, 476 + ), 477 + ) 478 + if levels_was_None: 479 + return strat.filter( 480 + lambda annotation: any( 481 + True for a in ht.flatten_args(annotation, False) if a == T 482 + ) 483 + ) 484 + return strat 485 + 486 + 487 + @st.composite 488 + def constrained_type_var_strat(draw, annotations=tuple()): 489 + tv = draw(st.from_type(AllGeneric[typing.TypeVar])) 490 + if annotations: 491 + sample_strat = st.sampled_from(annotations) 492 + T = draw( 493 + st.builds(tv, st.characters(), sample_strat, sample_strat, sample_strat) 494 + ) 495 + else: 496 + T = draw( 497 + st.builds( 498 + tv, 499 + st.characters(), 500 + st.from_type(AllGeneric), 501 + st.from_type(AllGeneric), 502 + st.from_type(AllGeneric), 503 + ) 504 + ) 505 + return ( 506 + draw(recursive_type_var_strat(T)), 507 + T, 508 + draw(st.sampled_from(T.__constraints__)), 509 + ) 510 + 511 + 512 + @st.composite 513 + def bound_type_var_strat(draw, annotations=tuple()): 514 + tv = draw(st.from_type(AllGeneric[typing.TypeVar])) 515 + if annotations: 516 + T = draw( 517 + st.builds( 518 + tv, 519 + st.characters(), 520 + bound=st.sampled_from(annotations), 521 + ) 522 + ) 523 + else: 524 + T = draw(st.builds(tv, st.characters(), bound=st.from_type(AllGeneric))) 525 + return draw(recursive_type_var_strat(T)), T, T.__bound__ 526 + 527 + 528 + def create_generic_func(generic): 529 + origin = ht.get_origin(generic) 530 + args = ht.get_args(generic) 531 + co_argcount = len(args) 532 + co_varnames = tuple(lowercase[:co_argcount]) 533 + 534 + def func(): ... 535 + 536 + # Adapted From: 537 + # Answer 1: https://stackoverflow.com/a/11292547 538 + # User 1: https://stackoverflow.com/users/534790/ahsan 539 + # Answer 2: https://stackoverflow.com/a/77155447 540 + # User 2: https://stackoverflow.com/users/6890912/blhsing 541 + # Answer 3: https://stackoverflow.com/a/73607744 542 + # User 3: https://stackoverflow.com/users/3936044/mandera 543 + # Answer 4: https://stackoverflow.com/a/17625756 544 + # User 4: https://stackoverflow.com/users/908494/abarnert 545 + # And: https://chatgpt.com/c/7a03b03f-8150-43cb-9ae0-e6fd111a05b6 546 + func.__code__ = func.__code__.replace( 547 + co_argcount=co_argcount, 548 + co_posonlyargcount=0, 549 + co_kwonlyargcount=0, 550 + co_nlocals=co_argcount, 551 + co_flags=coflag(), 552 + co_varnames=co_varnames, 553 + ) 554 + func.__annotations__ = dict(zip(co_varnames, args, strict=True)) | { 555 + "return": origin 556 + } 557 + return func 558 + 559 + 560 + @st.composite 561 + def typevar_func_strat(draw, bound=False): 562 + generic, T, bc = draw( 563 + bound_type_var_strat() if bound else constrained_type_var_strat() 564 + ) 565 + return create_generic_func(generic), T, bc 566 + 567 + 568 + def process_typevar_annotation(T, annotation, /, *constraints, bound=None): 569 + if annotation in constraints or (bound and ht.isinmrosubclass(annotation, bound)): 570 + return T 571 + origin = ht.get_origin(annotation) 572 + if args := ht.get_args(annotation): 573 + return ht.format_args( 574 + origin, 575 + [ 576 + process_typevar_annotation(T, arg, *constraints, bound=bound) 577 + for arg in args 578 + ], 579 + ) 580 + return origin 581 + 582 + 583 + # TODO: Allow for multiple TypeVar instances and test this scenario. 584 + @st.composite 585 + def typevar_instance_strat(draw, bound=False): 586 + obj = draw(rand_strat()) 587 + t = ht.get_type(obj) 588 + assume( 589 + annotations := [arg for arg in ht.flatten_args(t, False) if generic_check(arg)] 590 + ) 591 + generic, T, bc = draw( 592 + bound_type_var_strat(annotations) 593 + if bound 594 + else constrained_type_var_strat(annotations) 595 + ) 596 + return ( 597 + create_generic_func(generic), 598 + T, 599 + bc, 600 + process_typevar_annotation(T, t, bound=bc) 601 + if bound 602 + else process_typevar_annotation(T, t, bc), 603 + obj, 604 + ) 605 + 606 + 607 + st.register_type_strategy(str, st.one_of(st.text(), st.characters())) 608 + st.register_type_strategy(Data, st.data()) 609 + st.register_type_strategy(Args, args_strat()) 610 + st.register_type_strategy(SubArgs, subargs_strat()) 611 + st.register_type_strategy( 612 + HTGenericAlias, st.sampled_from(generics["class"]).filter(ht.is_subscriptable) 613 + ) 614 + st.register_type_strategy( 615 + AllGenericAlias, st.sampled_from(generics["all"]).filter(ht.is_subscriptable) 616 + ) 617 + st.register_type_strategy(Address, st.emails) 618 + 619 + 620 + def collection_strat(t): 621 + args = typing.get_args(t) 622 + if args and args[0] is not typing.Any: 623 + return rand_strat( 624 + iterable=True, 625 + iterable_only_strats=[st.from_type(arg) for arg in args], 626 + min_size=1, 627 + ) 628 + return rand_strat( 629 + iterable=True, 630 + min_size=bool(args and args[0] is typing.Any), 631 + ) 632 + 633 + 634 + st.register_type_strategy(Collection, collection_strat) 635 + 636 + 637 + def generic_strat(t): 638 + args = typing.get_args(t) 639 + if args: 640 + arg = args[0] 641 + if arg == ht.ParamSpec: 642 + return st.sampled_from( 643 + (typing.ParamSpec("P"), ht.ParamSpec("P"), ht.paramspec("p")) 644 + ) 645 + return st.sampled_from(ht.get_all_ht_generics(arg) or args) 646 + return st.sampled_from(generics["all"]) 647 + 648 + 649 + st.register_type_strategy(AllGeneric, generic_strat) 650 + 651 + 652 + def generic_ht_strat(t): 653 + args = typing.get_args(t) 654 + if args: 655 + arg = args[0] 656 + if arg == ht.ParamSpec: 657 + return st.sampled_from((ht.ParamSpec("P"), ht.paramspec("p"))) 658 + return st.sampled_from(ht.get_all_ht(arg) or args) 659 + return st.sampled_from(generics["class"]) 660 + 661 + 662 + st.register_type_strategy(HTGeneric, generic_ht_strat) 663 + 664 + 665 + # NOTE: Also allowed: 666 + # - collections.abc.Iterable 667 + # - datetime.date 668 + # - datetime.datetime 669 + # - datetime.time 670 + # - datetime.timedelta 671 + # - datetime.timezone 672 + # - decimal.Decimal 673 + # - fraction.Fraction 674 + # - ipaddress.IPv4Address 675 + # - ipaddress.IPv6Address 676 + # - random.Random 677 + # - types.FunctionType 678 + # - uuid.UUID 679 + 680 + 681 + def nohypo(func): 682 + func.__nohypo__ = True 683 + return func 684 + 685 + 686 + def hypocheck(value): 687 + return ( 688 + callable(value) 689 + and not getattr(value, "__nohypo__", False) 690 + and not getattr(value, "is_hypothesis_test", False) 691 + and getattr(value, "_pytestfixturefunction", None) is None 692 + ) 693 + 694 + 695 + # NOTE: This can be used to convert regular functions into tests as well. 696 + # TODO: Implement contracts somehow. 697 + @Decorator 698 + def given(func, settings=None, **kwargs): 699 + if isclass(func): 700 + metaclass = type(func) 701 + namespace = {} 702 + for k in dir(func): 703 + if hypocheck(v := getattr(func, k)): 704 + v = given(v, settings=settings, **kwargs) 705 + namespace[k] = v 706 + return metaclass.__new__(metaclass, func.__name__, func.__bases__, namespace) 707 + else: 708 + param: Parameter 709 + sig = signature(func) 710 + settings = settings or {} 711 + strats = {} 712 + for index, (name, param) in enumerate(sig.parameters.items()): 713 + if (not index and name == "self") or name in kwargs: 714 + continue 715 + annotation = param.annotation 716 + if annotation is Parameter.empty: 717 + if param.default is Parameter.empty: 718 + continue 719 + annotation = ht.get_type(param.default) 720 + elif annotation == typing.Self: 721 + continue 722 + 723 + if ht.annotation_is_generic(annotation) and hasattr( 724 + annotation, "__strategy__" 725 + ): 726 + strat = annotation.__strategy__(st) 727 + else: 728 + strat = st.from_type(annotation) 729 + if isinstance(strat, st._internal.deferred.DeferredStrategy): 730 + raise TypeError( 731 + f'parameter "{name}" of type "{annotation}" has no associated strategy' 732 + ) 733 + if settings: 734 + strats[name] = apply_kwargs( 735 + strat.function, 736 + strat._LazyStrategy__kwargs | settings, 737 + strat._LazyStrategy__args, 738 + ) 739 + else: 740 + strats[name] = strat 741 + strats |= kwargs 742 + if hasattr(func, "hypothesis_explicit_examples"): 743 + for example in func.hypothesis_explicit_examples: 744 + # Adapted From: https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings 745 + with warnings.catch_warnings(): 746 + warnings.simplefilter("ignore") 747 + example.kwargs |= { 748 + k: v.example() 749 + for k, v in strats.items() 750 + if k not in example.kwargs 751 + } 752 + return hypothesis.given(**strats)(func) 753 + 754 + 755 + class Given(type): 756 + class __prepare__(dict): 757 + def __init__(self, name, bases, **kwargs): 758 + self.kwargs = kwargs 759 + 760 + def __setitem__(self, key, value): 761 + if hypocheck(value): 762 + value = given(value, **self.kwargs) 763 + super().__setitem__(key, value)
+38
packages/hydrox/hydrox/typing/__init__.py
··· 1 + import sys 2 + from . import generic 3 + 4 + # NOTE: Needed for setup purposes. 5 + from . import types, tests 6 + 7 + from ..helpers.helpers import ModuleCaller 8 + 9 + # Adapted From: 10 + # Question: https://stackoverflow.com/questions/56786604/import-modules-that-dont-exist-yet 11 + # User: https://stackoverflow.com/users/10827766/shadowrylander 12 + 13 + 14 + def type_creator(name): 15 + def __getattr__(self, t: str): 16 + if t.startswith("__"): 17 + raise AttributeError(t) 18 + elif t in ("tests",): 19 + return eval(t) 20 + if hasattr(generic, t): 21 + return getattr(generic, t) 22 + try: 23 + return generic.Generics[t] 24 + except KeyError: 25 + raise AttributeError(t) 26 + 27 + return type( 28 + name, 29 + (ModuleCaller,), 30 + { 31 + "__getattr__": __getattr__, 32 + "__getitem__": lambda self, t: self.__getattr__(t), 33 + "__all__": [var for var in vars() if var not in ("__qualname__",)], 34 + }, 35 + )() 36 + 37 + 38 + sys.modules[__name__] = type_creator(__name__)
+2435
packages/hydrox/hydrox/typing/generic.py
··· 1 + # TODO: Split this file into functions, variables, and classes, 2 + # with classes in their own directory in their own files. 3 + 4 + import builtins 5 + import collections.abc as abc 6 + import collections 7 + import types 8 + import typing 9 + import gc 10 + import inspect 11 + import operator 12 + import os 13 + import re 14 + import sys 15 + 16 + from ..helpers import reversecut, Collection, flatten, zipsuppress, ziperr, unwrap 17 + 18 + from ..helpers.cache import CacheError, cache, cachehash 19 + from ..helpers.decorator import Decorator 20 + from ..variables import unique_attributes 21 + from abc import ABCMeta 22 + from contextlib import suppress 23 + from collections import defaultdict 24 + from functools import partial, reduce, cache as defcache 25 + from inspect import Parameter 26 + from inspect import getmro, stack, FrameInfo 27 + from more_itertools import partition, unique_everseen 28 + from random import randint, sample 29 + from rich import print 30 + from rich.traceback import Traceback 31 + from rich.rule import Rule 32 + from rich.pretty import pprint 33 + from rich.table import Table 34 + from rich.console import Console 35 + from warnings import warn 36 + 37 + 38 + console = Console() 39 + 40 + # debug = False 41 + 42 + # # Adapted From: https://martinheinz.dev/blog/66 43 + # def exception_hook(exc_type, exc_value, tb): 44 + # global debug 45 + # debug = True 46 + # console.print(Traceback.from_exception(exc_type, exc_value, tb, show_locals=True)) 47 + 48 + # sys.excepthook = exception_hook 49 + 50 + sentinel = object() 51 + 52 + FunctionTypes: typing.TypeAlias = types.FunctionType | types.MethodType 53 + 54 + TypingInstance: typing.TypeAlias = typing.NewType | typing.ParamSpec | typing.TypeVar 55 + 56 + Annotation: typing.TypeAlias = ( 57 + type 58 + | typing._BaseGenericAlias 59 + | typing._SpecialForm 60 + | types.EllipsisType 61 + | types.GenericAlias 62 + | types.NoneType 63 + | types.UnionType 64 + | typing.ForwardRef 65 + | TypingInstance 66 + ) 67 + 68 + AnnotationString: typing.TypeAlias = Annotation | str 69 + 70 + 71 + # NOTE: Can't cache this; it will return the used-up iterable. 72 + def flatten_args( 73 + annotation: Annotation, origin: bool = True 74 + ) -> collections.abc.Generator[Annotation]: 75 + """Flatten a type annotation into its origin and arguments. 76 + 77 + Args: 78 + annotation (Annotation): The annotation to be flattened. 79 + origin (bool): Whether the origin should be included. 80 + Will always be `True` if there are no args. 81 + Defaults to `True`. 82 + 83 + Returns: 84 + collections.abc.Generator[Annotation]: An un-nested generator of type annotations. 85 + """ 86 + args = get_args(annotation) 87 + return flatten( 88 + get_origin(annotation) if origin or not args else tuple(), 89 + (flatten_args(arg, origin) for arg in args), 90 + ) 91 + 92 + 93 + # TODO 94 + # @defcache 95 + @cache 96 + def get_name(annotation: AnnotationString) -> str: 97 + """Get the name or representation of a type annotation. 98 + 99 + Args: 100 + annotation (AnnotationString): The annotation to get the name or representation of. 101 + 102 + Returns: 103 + str: The name or representation of the annotation. 104 + """ 105 + if isinstance(annotation, str): 106 + return annotation 107 + if annotation is types.EllipsisType: 108 + return "EllipsisType" 109 + if annotation is types.GeneratorType: 110 + return "GeneratorType" 111 + if (name := getattr(annotation, "__name__", "")) and ("." not in name): 112 + return name 113 + if (qualname := getattr(annotation, "__qualname__", "")) and ("." not in qualname): 114 + return qualname 115 + return repr(annotation) 116 + 117 + 118 + @defcache 119 + def get_name_lowered(annotation: AnnotationString) -> str: 120 + """Get the lowercase name or representation of a type annotation. 121 + 122 + Args: 123 + annotation (AnnotationString): The annotation to get the lowercase name or representation of. 124 + 125 + Returns: 126 + str: The lowercase name of the annnotation. 127 + """ 128 + return get_name(annotation).lower() 129 + 130 + 131 + NameCheck: typing.TypeAlias = collections.abc.Callable[[str, str], bool] 132 + 133 + 134 + @defcache 135 + def get_name_check( 136 + a: AnnotationString, 137 + /, 138 + *bs: AnnotationString, 139 + checks: NameCheck | collections.abc.Iterable[NameCheck] = tuple(), 140 + ) -> bool: 141 + # TODO: Revise. 142 + """Check whether a type annotation returns _True_ for any of the provided functions. 143 + 144 + Args: 145 + a (AnnotationString): The annotation to check. 146 + *bs (AnnotationString): The annotation to check _against_. 147 + checks (Check | collections.abc.Iterable[Check], optional): 148 + An iterable of check functions that take two strings and return a boolean. 149 + Defaults to an empty tuple. 150 + 151 + Returns: 152 + bool: _True_ if any of the provided functions returns _True_ else _False_. 153 + """ 154 + a = get_name_lowered(a) 155 + return any( 156 + True 157 + for check in flatten(checks or str.__eq__) 158 + for b in bs 159 + if check(a, get_name_lowered(b)) 160 + ) 161 + 162 + 163 + @defcache 164 + def get_union_check(annotation: AnnotationString) -> bool: 165 + """Checks whether a type annotation is a _Union_ or _UnionType_. 166 + 167 + Args: 168 + annotation (AnnotationString): The annotation to check. 169 + 170 + Returns: 171 + bool: _True_ if the annotation is a _Union_ or a _UnionType_ else _False_. 172 + """ 173 + return get_name_check(annotation, "union", "uniontype") 174 + 175 + 176 + @defcache 177 + def get_name_remove_suffix(annotation: AnnotationString, suffix: str) -> str: 178 + """Removes a suffix from a type annotation name or representation. 179 + 180 + Args: 181 + annotation (AnnotationString): The annotation to remove the suffix from. 182 + suffix (str): The suffix to remove. 183 + 184 + Returns: 185 + str: The annotation name or representation without the specified suffix. 186 + """ 187 + return get_name(annotation).removesuffix(suffix) 188 + 189 + 190 + # TODO: Test this. 191 + @cache 192 + def get_origin_default(annotation: Annotation) -> Annotation: 193 + """Get the origin of a type annotation. 194 + 195 + Args: 196 + annotation (Annotation): The annotation to get the origin of. 197 + 198 + Returns: 199 + Annotation: The origin of the annotation. 200 + """ 201 + if getattr(annotation, "__module__", None) == "typing": 202 + return getattr(typing, get_name(annotation)) 203 + return typing.get_origin(annotation) 204 + 205 + 206 + # TODO: Test this. 207 + @cache 208 + def get_origin(annotation: Annotation) -> Annotation: 209 + """Get the origin of a type annotation or the annotation if the origin is _None_. 210 + 211 + Args: 212 + annotation (Annotation): The annotation to get the origin of or to return. 213 + 214 + Returns: 215 + Annotation: The origin of the annotation or the annotation itself. 216 + """ 217 + return get_origin_default(annotation) or annotation 218 + 219 + 220 + # NOTE: Can't flatten this; it might be used with `Literal`, for example. 221 + @cache 222 + def get_args(annotation): 223 + args = typing.get_args(annotation) 224 + if args and isinstance(args[0], list): 225 + return annotation.__args__ 226 + return args 227 + 228 + 229 + @defcache 230 + def add_camel_check(name): 231 + name: str = get_name(name) 232 + return name.isalpha() and name[0].isupper() 233 + 234 + 235 + @defcache 236 + def dirattr(module): 237 + names = dir(module) 238 + attrs = set() 239 + for name in names: 240 + attr = getattr(module, name) 241 + if isinstance(attr, Annotation): 242 + attrs.add(get_name(attr)) 243 + return flatten(names, attrs) 244 + 245 + 246 + modules = { 247 + get_name(module): module for module in (builtins, collections, abc, types, typing) 248 + } 249 + 250 + 251 + # Adapted From: 252 + # Answer: https://stackoverflow.com/a/39375731/10827766 253 + # User: https://stackoverflow.com/users/541136/aaron-hall 254 + class CamelizedNames(dict): 255 + def __init__(self, *names, base=None, **kwargs): 256 + super().__init__(base or {}, **kwargs) 257 + self.extend(*names) 258 + 259 + def extend(self, *names): 260 + for name in flatten(names): 261 + self.append(name) 262 + 263 + def append(self, name): 264 + name: str = get_name(name) 265 + if name.startswith("__"): 266 + raise TypeError( 267 + f"name {name} must be an alphanumeric capitalized or camelcased string" 268 + ) 269 + else: 270 + stripped_name = ( 271 + name if all(False for c in name if c != "_") else name.lstrip("_") 272 + ) 273 + lowered = name.lower() 274 + if name in self: 275 + return lowered, super().__getitem__(lowered) 276 + elif stripped_name[0].isupper(): 277 + self[name] = name 278 + # Adapted From: 279 + # Comment: https://stackoverflow.com/questions/2277352/split-a-string-at-uppercase-letters#comment87244231_2277363 280 + # User: https://stackoverflow.com/users/1570408/ulysses 281 + current_subname = "" 282 + for subname in re.findall("^[a-z]+|[A-Z][^A-Z]*", stripped_name): 283 + if subname != stripped_name: 284 + if len(subname) == 1: 285 + current_subname += subname 286 + else: 287 + if current_subname: 288 + subname = current_subname 289 + current_subname = "" 290 + self[subname] = subname 291 + else: 292 + if current_subname: 293 + self[current_subname] = current_subname 294 + return lowered, name 295 + else: 296 + # Adapted From: 297 + # Answer: https://stackoverflow.com/a/43958170/10827766 298 + # User: https://stackoverflow.com/users/6622817/taku 299 + Name = ( 300 + "_" * (len(name) - len(stripped_name)) 301 + ) + stripped_name.capitalize() 302 + self[name] = Name 303 + return name, Name 304 + 305 + def __contains__(self, key): 306 + return super().__contains__(get_name_lowered(key)) 307 + 308 + def __delattr__(self, key): 309 + del self[key] 310 + 311 + def __delitem__(self, key): 312 + super().__delitem__(get_name_lowered(key)) 313 + 314 + def __getattr__(self, key): 315 + return self[key] 316 + 317 + def __getitem__(self, key): 318 + return self.append(key)[1] 319 + 320 + def __ior__(self, other): 321 + self.update(other) 322 + return self 323 + 324 + def __or__(self, other): 325 + result = self.copy() 326 + result.update(other) 327 + return result 328 + 329 + def __ror__(self, other): 330 + result = self.__class__(base=other) 331 + result.update(self) 332 + return result 333 + 334 + def __setattr__(self, key, value): 335 + if key.startswith("_"): 336 + return object.__setattr__(self, key, value) 337 + self[key] = value 338 + 339 + def __setitem__(self, key, value): 340 + super().__setitem__(get_name_lowered(key), value) 341 + 342 + def copy(self): # don't delegate w/ super - dict.copy() -> dict :( 343 + return self.__class__(base=self) 344 + 345 + @classmethod 346 + def fromkeys(cls, keys): 347 + return cls(keys) 348 + 349 + def get(self, key, default=None): 350 + try: 351 + return self[key] 352 + except TypeError: 353 + return default 354 + 355 + def pop(self, key, default=sentinel): 356 + lowered = get_name_lowered(key) 357 + if default is sentinel: 358 + return super().pop(lowered) 359 + return default 360 + 361 + def update(self, other=(), **kwargs): 362 + if isinstance(other, abc.Mapping): 363 + super().update(other) 364 + else: 365 + super().update(other) 366 + super().update(kwargs) 367 + 368 + 369 + camelized_names = CamelizedNames( 370 + filter(add_camel_check, flatten(dirattr(m) for m in modules.values())), 371 + "ByteArray", 372 + "ClassMethod", 373 + "FrozenSet", 374 + "MemoryView", 375 + "StaticMethod", 376 + ) 377 + 378 + 379 + class DeletionError(Exception): 380 + pass 381 + 382 + 383 + # Adapted From: 384 + # Answer: https://stackoverflow.com/a/39375731/10827766 385 + # User: https://stackoverflow.com/users/541136/aaron-hall 386 + class GenericsDict(dict): 387 + def __init__(self, base=None, **kwargs): 388 + super().__init__(base or {}, **kwargs) 389 + 390 + def __contains__(self, key): 391 + return super().__contains__(get_name(key)) 392 + 393 + def __delattr__(self, key): 394 + del self[key] 395 + 396 + def __delitem__(self, key): 397 + super().__delitem__(get_name(key)) 398 + 399 + def __getattr__(self, key): 400 + return self[key] 401 + 402 + def __getitem__(self, key): 403 + if typing.get_args(key) or annotation_is_typing_instance(key): 404 + return GenericArgs[key] 405 + if not (isinstance(key, str) or isinstance(key, Annotation)): 406 + raise CacheError() 407 + key = get_name(key) 408 + try: 409 + return super().__getitem__(key) 410 + except KeyError: 411 + if generics := get_all_generics(key): 412 + for g in generics: 413 + create_types(g) 414 + return super().__getitem__(key) 415 + raise KeyError 416 + 417 + def __ior__(self, other): 418 + self.update(other) 419 + return self 420 + 421 + def __or__(self, other): 422 + result = self.copy() 423 + result.update(other) 424 + return result 425 + 426 + def __ror__(self, other): 427 + result = self.__class__(base=other) 428 + result.update(self) 429 + return result 430 + 431 + def __setattr__(self, key, value): 432 + if key.startswith("_"): 433 + return object.__setattr__(self, key, value) 434 + self[key] = value 435 + 436 + def __setitem__(self, key, value): 437 + if ( 438 + typing.get_args(key) 439 + or typing.get_args(value) 440 + or annotation_is_typing_instance(key) 441 + or annotation_is_typing_instance(value) 442 + ): 443 + return GenericArgs.__setitem__(key, value) 444 + super().__setitem__(get_name(key), value) 445 + 446 + def _process_dict(self, other): 447 + return {get_name(k): v for k, v in other.items()} 448 + 449 + def copy(self): # don't delegate w/ super - dict.copy() -> dict :( 450 + return self.__class__(base=self) 451 + 452 + @classmethod 453 + def fromkeys(cls, keys, value=None): 454 + result = cls() 455 + for k in keys: 456 + result[k] = value 457 + return result 458 + 459 + def get(self, key, default=None): 460 + try: 461 + return self[key] 462 + except KeyError: 463 + return default 464 + 465 + def pop(self, key, default=sentinel): 466 + if default is sentinel: 467 + return super().pop(get_name(key)) 468 + return super().pop(get_name(key), default) 469 + 470 + def update(self, other=(), **kwargs): 471 + if isinstance(other, abc.Mapping): 472 + super().update(self._process_dict(other)) 473 + else: 474 + super().update(other) 475 + super().update(self._process_dict(kwargs)) 476 + 477 + 478 + Generics = GenericsDict() 479 + GenericAliases = GenericsDict() 480 + GenericMetas = GenericsDict() 481 + GenericArgs = {} 482 + 483 + 484 + test_subscription_size = 10 485 + fallback_subscription_size = 3 486 + max_subscription_size = 0 487 + default_subscription_size = -2 488 + 489 + 490 + @defcache 491 + def trysubscriptionsize(g): 492 + try: 493 + g[*[int] * test_subscription_size] 494 + except TypeError as e: 495 + chk_msgs = ( 496 + f"actual {test_subscription_size}, expected at least ", 497 + f"actual {test_subscription_size}, expected ", 498 + ) 499 + err_msg = str(e) 500 + for chk_msg in chk_msgs: 501 + if chk_msg in err_msg: 502 + return int(err_msg.split(chk_msg)[1]) 503 + if "single type" in err_msg: 504 + return 1 505 + if ("is not subscriptable" in err_msg) or ("is not a generic class" in err_msg): 506 + return 0 507 + if "must be used as" in err_msg: 508 + # return len(err_msg[err_msg.index("[") + 1 : -2].rsplit(", ", 1)) 509 + return -1 510 + if ( 511 + "Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got" 512 + in err_msg 513 + ) or ( 514 + "must all be type variables or parameter specification variables." 515 + in err_msg 516 + ): 517 + return -1 518 + raise e 519 + except KeyError: 520 + return 0 521 + else: 522 + return default_subscription_size 523 + 524 + 525 + @defcache 526 + def get_subscription_size(g): 527 + if isinstance(g, str): 528 + return default_subscription_size 529 + if annotation_is_generic(g): 530 + return getattr(g, "__subscriptions__", 0) 531 + args = typing.get_args(g) 532 + if args: 533 + if geeq(g, collections.abc.Callable): 534 + first_arg = args[0] 535 + return len(first_arg) if isinstance(first_arg, list) else -1 536 + return len(args) 537 + return trysubscriptionsize(g) 538 + 539 + 540 + @cache 541 + def trymro(cls): 542 + try: 543 + return getmro(cls) 544 + except AttributeError: 545 + for g in get_all_generics(cls): 546 + with suppress(AttributeError): 547 + return getmro(g) 548 + return tuple() 549 + 550 + 551 + @defcache 552 + def trymro_set(cls): 553 + if cls is typing.Set or cls is collections.abc.Set: 554 + return getmro(set) 555 + return trymro(cls) 556 + 557 + 558 + @defcache 559 + def trymro_generic(cls): 560 + return [m for m in trymro_set(cls) if not geeq(m, typing.Generic)] 561 + 562 + 563 + @cache 564 + def trybases(cls): 565 + with suppress(AttributeError): 566 + return cls.__bases__ 567 + return tuple() 568 + 569 + 570 + @defcache 571 + def check_subscription_size(size): 572 + return size > default_subscription_size 573 + 574 + 575 + @defcache 576 + def random_subscription_size(rand, size): 577 + global max_subscription_size 578 + if size == -1 and rand: 579 + return randint(2, max_subscription_size or fallback_subscription_size) 580 + if size > max_subscription_size: 581 + max_subscription_size = size 582 + return size 583 + 584 + 585 + @defcache 586 + def get_inner_subscription_size(rand, generic, bases=tuple()): 587 + randinner = partial(random_subscription_size, rand) 588 + bases = bases or trybases(generic) 589 + if object not in bases and Generic not in bases: 590 + for base in bases: 591 + if check_subscription_size(size := get_subscription_size(base)): 592 + return randinner(size) 593 + for g in get_all_generics(base): 594 + if check_subscription_size(size := get_subscription_size(g)): 595 + return randinner(size) 596 + if check_subscription_size(size := get_inner_subscription_size(rand, base)): 597 + return randinner(size) 598 + for g in get_all_generics(base): 599 + if check_subscription_size( 600 + size := get_inner_subscription_size(rand, g) 601 + ): 602 + return randinner(size) 603 + 604 + return default_subscription_size 605 + 606 + 607 + # TODO: How did ht.UserDict originally get the 2, then...? 608 + @defcache 609 + def get_generic_subscription_size(t, /, *, root_bases=tuple(), rand=False): 610 + randinner = partial(random_subscription_size, rand) 611 + 612 + name = get_name(t) 613 + 614 + # TODO: Should these be incorporated into the Generics themselves? 615 + if name in ("generator", "GeneratorType"): 616 + return 0 617 + if name in ("Generator",): 618 + if check_subscription_size(size := get_subscription_size(t)): 619 + return randinner(size) 620 + for g in get_generics(t): 621 + if check_subscription_size(size := get_subscription_size(g)): 622 + return randinner(size) 623 + 624 + lowered = name.lower() 625 + if lowered in Generics: 626 + return randinner(Generics[lowered].__subscriptions__) 627 + 628 + if check_subscription_size(size := get_subscription_size(t)): 629 + return randinner(size) 630 + for g in get_all_generics(t): 631 + if check_subscription_size(size := get_subscription_size(g)): 632 + return randinner(size) 633 + 634 + if check_subscription_size( 635 + size := get_inner_subscription_size(rand, t, root_bases) 636 + ): 637 + return randinner(size) 638 + for g in get_all_generics(t): 639 + if check_subscription_size(size := get_inner_subscription_size(rand, g)): 640 + return randinner(size) 641 + 642 + return 1 643 + 644 + 645 + @defcache 646 + def is_subscriptable(t): 647 + if isinstance(t, GenericMeta): 648 + return bool(t.__subscriptions__) 649 + return isinstance(t, GenericAlias) or bool(trysubscriptionsize(t)) 650 + 651 + 652 + recursive_classes = (types.GenericAlias,) 653 + non_generic_classes = (object, None, ...) + recursive_classes 654 + 655 + 656 + @defcache 657 + def get_named(n: str, getter): 658 + match n: 659 + case "Union" | "UnionType": 660 + return getter("Union") + getter("UnionType") 661 + case "Generator" | "GeneratorType": 662 + return getter("Generator") + getter("GeneratorType") 663 + case _: 664 + return getter(n) 665 + 666 + 667 + @defcache 668 + def get_named_generics(n: str): 669 + inner_generics = [] 670 + for module in modules.values(): 671 + if hasattr(module, n): 672 + g = getattr(module, n) 673 + if (g in non_generic_classes) or (not isinstance(g, Annotation)): 674 + continue 675 + inner_generics.append(g) 676 + return tuple(inner_generics) 677 + 678 + 679 + @defcache 680 + def get_named_ht(n: str): 681 + try: 682 + return (Generics[n],) 683 + except KeyError: 684 + return tuple() 685 + 686 + 687 + # TODO: Implement TypeVar, NewType, and ParamSpec support. 688 + @cache 689 + def get_generics(generic, /, *, getter=get_named_generics): 690 + generics = get_named(get_name(generic), getter) 691 + if args := typing.get_args(generic): 692 + return tuple([format_args(g, args) for g in generics]) 693 + return generics 694 + 695 + 696 + # TODO: Implement TypeVar, NewType, and ParamSpec support. 697 + @cache 698 + def get_ht(generic): 699 + return get_generics(generic, getter=get_named_ht) 700 + 701 + 702 + @cache 703 + def get_ht_generics(generic): 704 + return get_generics(generic) + get_ht(generic) 705 + 706 + 707 + @cache 708 + def get_all_generics(generic, /, *, getter=get_generics): 709 + if isinstance(generic, Collection): 710 + raise TypeError(f'generic "{generic}" cannot be an iterable') 711 + if annotation_is_generic(generic): 712 + return getattr(generic, "__generics__", tuple()) 713 + generics = tuple() 714 + for name in camelized_names.append(get_name(get_origin(generic))): 715 + generics += getter(name) 716 + if args := typing.get_args(generic): 717 + return tuple([format_args(g, args) for g in generics]) 718 + return generics 719 + 720 + 721 + @cache 722 + def get_all_ht(generic): 723 + return get_all_generics(generic, getter=get_ht) 724 + 725 + 726 + @cache 727 + def get_all_ht_generics(generic): 728 + return get_all_generics(generic) + get_all_ht(generic) 729 + 730 + 731 + # TODO: Implement the cache additions in `GenericMeta` or the `get` functions for the `has` functions as well. 732 + @defcache 733 + def has_named_generics(n): 734 + return tuple([module for module in modules.values() if hasattr(module, n)]) 735 + 736 + 737 + @defcache 738 + def has_named_ht(n): 739 + return (Generics,) if n in Generics else tuple() 740 + 741 + 742 + @cache 743 + def has_generics(generic, /, *, getter=has_named_generics): 744 + return getter(get_name(generic), getter) 745 + 746 + 747 + @cache 748 + def has_ht(generic): 749 + return has_generics(generic, getter=has_named_ht) 750 + 751 + 752 + @cache 753 + def has_ht_generics(generic): 754 + return has_generics(generic) + has_ht(generic) 755 + 756 + 757 + @cache 758 + def has_all_generics(generic, /, *, getter=has_generics): 759 + if isinstance(generic, Collection): 760 + raise TypeError(f'generic "{generic}" cannot be an iterable') 761 + generics = tuple() 762 + for name in camelized_names.append(get_name(get_origin(generic))): 763 + generics += getter(name) 764 + return generics 765 + 766 + 767 + @cache 768 + def has_all_ht(generic): 769 + return has_all_generics(generic, getter=has_ht) 770 + 771 + 772 + @cache 773 + def has_all_ht_generics(generic): 774 + return has_all_generics(generic) + has_all_ht(generic) 775 + 776 + 777 + @defcache 778 + def not_generic_class_error(error): 779 + error = error if isinstance(error, str) else str(error) 780 + return error not in ( 781 + "issubclass() arg 1 must be a class", 782 + "issubclass() arg 2 must be a class, a tuple of classes, or a union", 783 + "issubclass() argument 2 cannot be a parameterized generic", 784 + "Subscripted generics cannot be used with class and instance checks", 785 + ) and all( 786 + part not in error 787 + for part in ("cannot be used with issubclass()", "is not a generic class") 788 + ) 789 + 790 + 791 + def get_singular_subclasses(cls): 792 + with suppress(TypeError, AttributeError): 793 + return cls.__subclasses__() 794 + with suppress(TypeError): 795 + return type.__subclasses__(cls) 796 + return [] 797 + 798 + 799 + @cache 800 + def check_module(cls, modules): 801 + modules = [get_name_lowered(m) for m in modules] 802 + return ( 803 + modules 804 + and ( 805 + cls.__module__ in modules 806 + # Adapted From: 807 + # Answer: https://stackoverflow.com/a/7810592 808 + # User: https://stackoverflow.com/users/68998/newtover 809 + or sys.modules[__name__].__package__ in cls.__module__ 810 + ) 811 + ) or not modules 812 + 813 + 814 + # NOTE: Can't cache this; classes might get new subclasses. 815 + def get_subclasses( 816 + cls, add_self=False, ignored=tuple(), modules=tuple(), gsc=None, inner=None 817 + ): 818 + ignored = [get_name_lowered(i) for i in ignored] 819 + if not inner: 820 + 821 + @defcache 822 + def inner(camel): 823 + return [ 824 + scls 825 + for scls in get_singular_subclasses(camel) 826 + if get_name_lowered(scls) not in ignored and check_module(scls, modules) 827 + ] 828 + 829 + gsc = gsc or defcache( 830 + partial(get_subclasses, ignored=ignored, modules=modules, inner=inner) 831 + ) 832 + if get_name_lowered(cls) not in ignored: 833 + classes = inner(cls) 834 + if isinstance(cls, GenericMeta): 835 + inner_classes = inner(cls.__camel__) 836 + 837 + # TODO: Simplify this. 838 + if cls is Generics.set: 839 + classes += [ 840 + scls for scls in inner_classes if scls != Generics.MutableSet 841 + ] 842 + 843 + else: 844 + classes += inner_classes 845 + classes.extend( 846 + dict.fromkeys( 847 + flatten( 848 + gsc(scls, gsc=gsc) 849 + for scls in classes 850 + if get_name_lowered(scls) not in ignored 851 + and check_module(scls, modules) 852 + ) 853 + ) 854 + ) 855 + return ([cls] if add_self else []) + classes 856 + 857 + 858 + @defcache 859 + def get_superclasses(cls, /, *, add_self=False, ignored=tuple()): 860 + ignored = [get_name_lowered(i) for i in ignored] 861 + return [t for t in trymro(cls)[int(not add_self) :] if get_name(t) not in ignored] 862 + 863 + 864 + @cache 865 + def callable_first_arg_no_list(arg): 866 + arg = get_origin(arg) 867 + return ( 868 + arg is ... or geeq(arg, typing.Concatenate) or geeq(type(arg), typing.ParamSpec) 869 + ) 870 + 871 + 872 + @cache 873 + def callable_first_arg(arg): 874 + return isinstance(arg, list) or callable_first_arg_no_list(arg) 875 + 876 + 877 + @cache 878 + def unionize(t, args): 879 + if t is types.UnionType: 880 + return reduce(operator.or_, args) 881 + return t[*args] 882 + 883 + 884 + @cache 885 + def unpack(origin, args): 886 + if len(args) == 1: 887 + return origin[args[0]] 888 + return origin[*args] 889 + 890 + 891 + # TODO: Make this faster somehow. 892 + @cache 893 + def format_args(origin, args): 894 + if not (subscription_size := get_generic_subscription_size(origin)): 895 + return origin 896 + args = flatten.list(args) 897 + if geeq(origin, typing.Union): 898 + return unionize(origin, args) 899 + if geeq(origin, typing.Optional) and len(args) > 1: 900 + with suppress(ValueError): 901 + args.remove(Generics.NoneType) 902 + if subscription_size > -1: 903 + args = args[:subscription_size] 904 + if args: 905 + if geeq(origin, collections.abc.Callable): 906 + first_arg = args[0] 907 + if callable_first_arg_no_list(first_arg) and not isinstance( 908 + origin, GenericMeta 909 + ): 910 + return origin[officiate(first_arg), args[-1]] 911 + elif isinstance(first_arg, list): 912 + return origin[first_arg, args[-1]] 913 + else: 914 + return origin[args[:-1], args[-1]] 915 + try: 916 + return unpack(origin, args) 917 + except KeyError: 918 + pass 919 + except TypeError as e: 920 + err_msg = str(e) 921 + if not ("Too few arguments" in err_msg or "Too many arguments" in err_msg): 922 + raise e 923 + return origin 924 + 925 + 926 + # TODO: Account for `ForwardRefs`. 927 + @cache(Generics) 928 + def generalize(annotation): 929 + # TODO: Is this necessary? 930 + # if annotation is None: 931 + # return Generics.NoneType 932 + 933 + if annotation_is_generic(annotation): 934 + return annotation 935 + if isinstance(annotation, typing.TypeVar): 936 + return Generics.TypeVar( 937 + annotation.__name__, 938 + *annotation.__constraints__, 939 + **{ 940 + k: getattr(annotation, f"__{k}__") 941 + for k in ( 942 + "bound", 943 + "covariant", 944 + "contravariant", 945 + # "infer_variance", 946 + # "default", 947 + ) 948 + }, 949 + ) 950 + if isinstance(annotation, typing.NewType): 951 + return Generics.NewType(annotation.__name__, annotation.__supertype__) 952 + if isinstance(annotation, typing.ParamSpec): 953 + return Generics.ParamSpec( 954 + annotation.__name__, 955 + **{ 956 + k: getattr(annotation, f"__{k}__") 957 + for k in ( 958 + "bound", 959 + "covariant", 960 + "contravariant", 961 + # "default", 962 + ) 963 + }, 964 + ) 965 + origin = get_origin(annotation) 966 + name = get_name(origin) 967 + try: 968 + generic = Generics[name] 969 + except KeyError: 970 + return annotation 971 + if args := get_args(annotation): 972 + return format_args(generic, [generalize(arg) for arg in args]) 973 + return generic 974 + 975 + 976 + # TODO: Test this. 977 + # Does this make testing slower? 978 + @cache 979 + def officiate(annotation): 980 + if annotation_is_generic(annotation): 981 + return getattr(annotation, "__generic__", sentinel) 982 + if annotation and isinstance(annotation, str): 983 + if generics := get_all_generics(annotation): 984 + return generics[0] 985 + if args := get_args(annotation): 986 + return format_args(get_origin(annotation), [officiate(arg) for arg in args]) 987 + return annotation 988 + 989 + 990 + # NOTE: Can't cache this; the functions might have defaults with integers, booleans, floats, or strings. 991 + def signature(sig: inspect.Signature, t=typing.Any): 992 + if not isinstance(sig, inspect.Signature): 993 + sig = inspect.signature(sig, eval_str=True) 994 + parameters = [] 995 + for param in sig.parameters.values(): 996 + if param.annotation is inspect.Parameter.empty: 997 + if param.kind in ( 998 + inspect.Parameter.VAR_POSITIONAL, 999 + inspect.Parameter.VAR_KEYWORD, 1000 + ): 1001 + parameters.append(param) 1002 + else: 1003 + parameters.append( 1004 + param.replace( 1005 + annotation=generalize( 1006 + t 1007 + if param.default is inspect.Parameter.empty 1008 + else get_type(param.default) 1009 + ) 1010 + ) 1011 + ) 1012 + else: 1013 + parameters.append(param.replace(annotation=generalize(param.annotation))) 1014 + return sig.replace( 1015 + return_annotation=generalize( 1016 + t 1017 + if sig.return_annotation is inspect.Parameter.empty 1018 + else sig.return_annotation 1019 + ), 1020 + parameters=parameters, 1021 + ) 1022 + 1023 + 1024 + # TODO: Revise and see if I can just use `generalize` 1025 + # on the final product instead of accessing `Generics` so many times. 1026 + # TODO: Why is `t` here again...? 1027 + # NOTE: Can't cache this; integers, booleans, strings, and floats can sometimes have the same value. 1028 + def get_type(obj: typing.Any) -> Annotation: # , t=abc.Callable): 1029 + # TODO: Implement this in `GenericMeta` and `GenericAlias`. 1030 + if hasattr(obj, "__genericsupertype__"): 1031 + try: 1032 + return obj.__genericsupertype__() 1033 + except TypeError as e: 1034 + if not "missing 1 required positional argument: 'cls'" in str(e): 1035 + raise e 1036 + return obj.__genericsupertype__(obj) 1037 + 1038 + ot = type(obj) 1039 + bt = Generics.get(ot, ot) 1040 + if ( 1041 + not obj 1042 + or isinstance(obj, Annotation) 1043 + or isinstance(obj, abc.Iterator) 1044 + or isinstance(obj, Generic) 1045 + or trysubclass(obj, GenericMeta) 1046 + ): 1047 + return bt 1048 + inner = partial(get_type) # , t=t) 1049 + if isinstance(obj, tuple): 1050 + return generalize(format_args(tuple, [inner(item) for item in obj])) 1051 + if isinstance(obj, dict): 1052 + return generalize( 1053 + format_args( 1054 + dict, 1055 + [ 1056 + unionize(types.UnionType, [inner(item) for item in obj.keys()]), 1057 + unionize(types.UnionType, [inner(item) for item in obj.values()]), 1058 + ], 1059 + ) 1060 + ) 1061 + if isinstance(obj, Collection): 1062 + cls_types = [inner(item) for item in obj] 1063 + try: 1064 + return bt[ 1065 + cls_types[0] 1066 + if len(cls_types) == 1 1067 + else generalize(unionize(types.UnionType, cls_types)) 1068 + ] 1069 + except TypeError as e: 1070 + if "is not subscriptable" in str(e): 1071 + return bt 1072 + # TODO: 1073 + if isinstance(obj, FunctionTypes): 1074 + # if isinstance(obj, abc.Callable): # t): 1075 + with suppress(TypeError, ValueError): 1076 + sig = signature(obj) 1077 + params: list[Annotation] = [] 1078 + variable = False 1079 + for param in sig.parameters.values(): 1080 + if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): 1081 + variable = True 1082 + continue 1083 + params.append(param.annotation) 1084 + if variable: 1085 + params.append(...) 1086 + 1087 + return generalize( 1088 + format_args(collections.abc.Callable, [params, sig.return_annotation]) 1089 + ) 1090 + return bt 1091 + 1092 + 1093 + # NOTE: Can't cache this; integers, booleans, strings, and floats can sometimes have the same value. 1094 + def supertype(*args, **kwargs): 1095 + # Adapted From: https://snarky.ca/unravelling-pythons-classes/ 1096 + def inner(_name, _bases, _ns, **kwds): 1097 + bases = types.resolve_bases(_bases if isinstance(_bases, tuple) else (_bases,)) 1098 + meta, ns, kwds = types.prepare_class(_name, bases, kwds) 1099 + 1100 + # NOTE: This cannot be changed to `update` as 1101 + # regular dictionaries don't return anything 1102 + # with said function. 1103 + ns |= _ns or {} 1104 + 1105 + return meta( 1106 + _name, 1107 + bases, 1108 + {k: v for k, v in ns.items() if k not in unique_attributes}, 1109 + ) 1110 + 1111 + match len(args): 1112 + case 1: 1113 + return get_type(args[0]) # , t=types.FunctionType) 1114 + case 2: 1115 + return inner(get_name(args[0]), args[1], args[0].__dict__, **kwargs) 1116 + case 3: 1117 + if kwargs: 1118 + return inner(*args, **kwargs) 1119 + return builtins.type(*args) 1120 + case _: 1121 + raise TypeError("type() takes between 1 and 3 arguments") 1122 + 1123 + 1124 + @defcache 1125 + def get_generic_names(generics): 1126 + return {get_name_lowered(g) for g in generics} 1127 + 1128 + 1129 + def any_generic_instance(instance, generic): 1130 + return isinmrosubclass(instance, get_all_ht_generics(generic)) 1131 + 1132 + 1133 + @cache 1134 + def trysubclasscheck(left, right): 1135 + if annotation_is_Generic(right): 1136 + return right.__subclasscheck__(left) 1137 + return False 1138 + 1139 + 1140 + @cache 1141 + def left_right_annotation_check(left, right): 1142 + return isinstance(left, Annotation) and isinstance(right, Annotation) 1143 + 1144 + 1145 + @cache 1146 + def left_right_collection_check(left, right): 1147 + return left_right_annotation_check(left, right) or isinstance(right, Collection) 1148 + 1149 + 1150 + class TypeWarning(Warning): ... 1151 + 1152 + 1153 + @cache 1154 + def _trybase(left, right, check): 1155 + if isinstance(right, abc.Iterator): 1156 + warn(f'variable "right" of value "{right}" will be used up', TypeWarning) 1157 + if not left_right_collection_check(left, right): 1158 + return False 1159 + if left == right: 1160 + return True 1161 + mutableset = Generics.MutableSet 1162 + left_is_mutableset = left == mutableset 1163 + left_name = get_name(left) 1164 + left_is_Set = left_name == "Set" 1165 + left_is_set = left_name == "set" 1166 + for r in flatten(right): 1167 + r_name = get_name(r) 1168 + r_is_mutableset = r == mutableset 1169 + r_is_Set = r_name == "Set" 1170 + r_is_set = r_name == "set" 1171 + if ( 1172 + (not isinstance(r, (Annotation, Collection))) 1173 + or (left_is_Set and r_is_mutableset) 1174 + or (left_is_mutableset and r_is_set) 1175 + ): 1176 + continue 1177 + if ( 1178 + ( 1179 + (left_is_Set or left_is_set) 1180 + and (r_is_Set or r_is_set) 1181 + and not typing.get_args(r) 1182 + ) 1183 + or (left_is_set and r_is_mutableset) 1184 + or (left_is_mutableset and r_is_Set) 1185 + ): 1186 + return True 1187 + return check(left, r) 1188 + return False 1189 + 1190 + 1191 + @defcache 1192 + def _trysubclass(left, right): 1193 + try: 1194 + return builtins.issubclass(left, right) 1195 + except TypeError as e: 1196 + if not_generic_class_error(e): 1197 + raise e 1198 + return False 1199 + 1200 + 1201 + @cache 1202 + def trysubclass(left, right): 1203 + return _trybase(left, right, _trysubclass) 1204 + 1205 + 1206 + @defcache 1207 + def get_all_set_generics(annotation): 1208 + return (get_generics if geeq(get_origin(annotation), set) else get_all_generics)( 1209 + annotation 1210 + ) 1211 + 1212 + 1213 + @defcache 1214 + def _tryinmro(left, right): 1215 + return right in trymro(left) 1216 + 1217 + 1218 + @cache 1219 + def tryinmro(left, right): 1220 + return _trybase(left, right, _tryinmro) 1221 + 1222 + 1223 + @defcache 1224 + def _tryinmrosubclass(left, right): 1225 + return tryinmro(left, right) or trysubclass(left, right) 1226 + 1227 + 1228 + @cache 1229 + def tryinmrosubclass(left, right): 1230 + return _trybase(left, right, _tryinmrosubclass) 1231 + 1232 + 1233 + @cache 1234 + def annotation_is_not_generic(annotation): 1235 + return not annotation_is_generic(annotation) 1236 + 1237 + 1238 + # NOTE: Can't cache this; it will return the used-up iterable. 1239 + # TODO: Or maybe that wouldn't be an issue if it's converted to a tuple...? 1240 + def generic_flatten(annotation): 1241 + return flatten( 1242 + partition( 1243 + annotation_is_not_generic, 1244 + unique_everseen( 1245 + flatten( 1246 + annotation, 1247 + map( 1248 + get_all_set_generics, 1249 + flatten(annotation), 1250 + ), 1251 + ) 1252 + ), 1253 + ) 1254 + ) 1255 + 1256 + def unwrap_check(cls): 1257 + if has_custom_init(cls): 1258 + cls = type(cls) 1259 + return unwrap(getattr(cls, "__subclasscheck__", None)) in ( 1260 + unwrap(GenericMeta.__subclasscheck__), 1261 + unwrap(GenericAlias.__subclasscheck__), 1262 + ) 1263 + 1264 + # TODO: This is going to trip up with types that have the same name as the official generics, 1265 + # but don't aren't connected to them, such as `hh.Collection` and `abc.Collection`. 1266 + @cache 1267 + def tryingenericmrosubclass(left, right): 1268 + if left_right_collection_check(left, right): 1269 + if isinstance(right, abc.Iterator): 1270 + warn(f'variable "right" of value "{right}" will be used up', TypeWarning) 1271 + else: 1272 + if unwrap_check(right): 1273 + generic = officiate(right) 1274 + else: 1275 + generic = right 1276 + for l in generic_flatten(left): 1277 + for r in generic_flatten(right): 1278 + if tryinmro(l, r): 1279 + return l, r 1280 + for r in generic_flatten(generic): 1281 + if trysubclass(l, r): 1282 + return l, r 1283 + return tuple() 1284 + 1285 + 1286 + @defcache 1287 + def argsinmrosubclass(left_args, right_args, strict): 1288 + with zipsuppress(): 1289 + return all( 1290 + False 1291 + for la, ra in zip(left_args, right_args, strict=strict) 1292 + if not isinmrosubclass(la, ra) 1293 + ) 1294 + return False 1295 + 1296 + 1297 + @cache 1298 + def isinmrosubclass(left, right): 1299 + if isinstance(right, abc.Iterator): 1300 + warn(f'variable "right" of value "{right}" will be used up', TypeWarning) 1301 + if not left_right_collection_check(left, right): 1302 + return False 1303 + return ( 1304 + tryingenericmrosubclass(left, right) or scoresubclass(left, right) 1305 + ) is not None 1306 + 1307 + 1308 + isinmrosubclass.genericcheck = cache( 1309 + lambda left, right: bool(tryingenericmrosubclass(left, right)) 1310 + ) 1311 + 1312 + 1313 + # TODO 1314 + def debugmrosubclass(left, right): 1315 + table = Table(title=f"issubclass({left}, {right})") 1316 + table.add_column("Check") 1317 + table.add_column("Result") 1318 + table.add_row("tryinmro", str(tryinmro(left, right))) 1319 + table.add_row("trysubclass", str(trysubclass(left, right))) 1320 + table.add_row( 1321 + "isinmrosubclass.genericcheck", 1322 + str(isinmrosubclass.genericcheck(left, right)), 1323 + ) 1324 + table.add_row("isinmrosubclass", str(isinmrosubclass(left, right))) 1325 + console.print(table) 1326 + 1327 + 1328 + def isinmroinstance(left, right): 1329 + return ( 1330 + tryingenericmrosubclass(get_type(left), right) or scoreinstance(left, right) 1331 + ) is not None 1332 + 1333 + 1334 + summation_suffixes: tuple[str, str, str, str] = ( 1335 + "'int' and 'NoneType'", 1336 + "'float' and 'NoneType'", 1337 + "'NoneType' and 'int'", 1338 + "'NoneType' and 'float'", 1339 + ) 1340 + 1341 + 1342 + def sumscore(*scores): 1343 + try: 1344 + return sum(flatten(scores)) 1345 + except TypeError as e: 1346 + err_msg = str(e) 1347 + if not ( 1348 + err_msg.startswith("unsupported operand type(s) for") 1349 + or any(True for suffix in summation_suffixes if err_msg.endswith(suffix)) 1350 + ): 1351 + raise e 1352 + except ValueError as e: 1353 + if not ziperr(e): 1354 + raise e 1355 + return None 1356 + 1357 + 1358 + max_prefixes: tuple[str, str, str, str] = ( 1359 + "'>' not supported between instances of", 1360 + "'>=' not supported between instances of", 1361 + "'<' not supported between instances of", 1362 + "'<=' not supported between instances of", 1363 + ) 1364 + 1365 + 1366 + def maxscore(*scores): 1367 + try: 1368 + return max([i for i in flatten(scores) if i is not None]) 1369 + except ValueError as e: 1370 + err_msg = str(e) 1371 + if not (err_msg == "max() iterable argument is empty" or ziperr(err_msg)): 1372 + raise e 1373 + return None 1374 + 1375 + 1376 + @defcache 1377 + def scoremro(left, right): 1378 + if left == right: 1379 + return 1 1380 + if geeq(left, typing.Generic) or geeq(left, types.GenericAlias): 1381 + # TODO: Will `object` match everything, like `typing.Any`? 1382 + mro = (left, object) 1383 + else: 1384 + mro = trymro_generic(left) 1385 + try: 1386 + return 1 - mro.index(right) 1387 + except ValueError as e: 1388 + if "not in" in str(e): 1389 + return -(len(mro) + 1) 1390 + raise e 1391 + 1392 + 1393 + @defcache 1394 + def score(left, right): 1395 + if result := tryingenericmrosubclass(left, right): 1396 + return scoremro(*result) 1397 + 1398 + 1399 + any_score = 1 1400 + 1401 + 1402 + @defcache 1403 + def scoreargs(left_args, right_args, strict): 1404 + return sumscore( 1405 + scoresubclass(la, ra) for la, ra in zip(left_args, right_args, strict=strict) 1406 + ) 1407 + 1408 + 1409 + @defcache 1410 + def _scoresubclass(left, right): 1411 + left_origin, right_origin = get_origin(left), get_origin(right) 1412 + right_args = typing.get_args(right) 1413 + if left_args := typing.get_args(left): 1414 + origin_score = scoresubclass(left_origin, right_origin) 1415 + if origin_score is not None: 1416 + if right_args: 1417 + if ... in right_args: 1418 + if len(right_args) == 1: 1419 + return origin_score + any_score 1420 + pre_right_args, post_right_args = partition_ellipsis(right_args) 1421 + 1422 + if ... in left_args: 1423 + if len(left_args) == 1: 1424 + return None 1425 + pre_left_args, post_left_args = partition_ellipsis(left_args) 1426 + pre_score = scoreargs( 1427 + pre_left_args, 1428 + pre_right_args, 1429 + len(pre_right_args) > len(pre_left_args), 1430 + ) 1431 + if pre_score is None: 1432 + return None 1433 + if post_right_args: 1434 + # TODO: Is it really ambiguous when the length of `post_right_args` is less then 1435 + # or equal to the length of `post_left_args`? 1436 + if post_left_args: 1437 + return sumscore( 1438 + origin_score, 1439 + pre_score, 1440 + scoreargs( 1441 + post_left_args[::-1], 1442 + post_right_args[::-1], 1443 + len(post_right_args) > len(post_left_args), 1444 + ), 1445 + ) 1446 + return None 1447 + 1448 + return origin_score + pre_score 1449 + 1450 + # TODO: For the scoring system, `[str]` can match `[str, ...]` AND `[..., str]`; 1451 + # prefer the former over the latter, and users can choose which they prefer 1452 + # by adding an extra type to the one they don't want, such as `typing.Any` 1453 + # if they want `[..., str]`, which would then become `[..., typing.Any, str]` 1454 + # or `[..., str, typing.Any]`. 1455 + # TODO: How do I test this scenario? 1456 + if pre_right_args: 1457 + pre_args_length = len(pre_right_args) 1458 + pre_left_args = left_args[:pre_args_length] 1459 + pre_score = scoreargs(pre_left_args, pre_right_args, True) 1460 + if pre_score is None: 1461 + return None 1462 + if post_right_args: 1463 + post_args_length = len(post_right_args) 1464 + if len(left_args) >= (pre_args_length + post_args_length): 1465 + return sumscore( 1466 + origin_score, 1467 + pre_score, 1468 + scoreargs( 1469 + reversecut(left_args, post_args_length), 1470 + post_right_args[::-1], 1471 + True, 1472 + ), 1473 + ) 1474 + return None 1475 + return origin_score + pre_score 1476 + if post_right_args: 1477 + # NOTE: This prefers `[str, ...]` over `[..., str]`. 1478 + # TODO: Is this better, or should `1` be added to the `pre_right_args` scores? 1479 + return sumscore( 1480 + origin_score, 1481 + scoreargs( 1482 + reversecut(left_args, len(post_right_args)), 1483 + post_right_args[::-1], 1484 + True, 1485 + ), 1486 + -1, 1487 + ) 1488 + 1489 + return sumscore(origin_score, scoreargs(left_args, right_args, True)) 1490 + return origin_score 1491 + elif right_args: 1492 + return None 1493 + else: 1494 + return score(left_origin, right_origin) 1495 + 1496 + 1497 + @defcache 1498 + def is_custom_init(init): 1499 + return init not in (object.__init__, None) 1500 + 1501 + 1502 + @defcache 1503 + def has_custom_init(cls): 1504 + return ( 1505 + cls.__custom_init__ 1506 + if isinstance(cls, GenericMeta) 1507 + else is_custom_init(getattr(cls, "__init__", None)) 1508 + ) 1509 + 1510 + 1511 + @defcache 1512 + def scoresubclass(left, right): 1513 + left, right = generalize(left), generalize(right) 1514 + if hasattr(right, "__subclassscore__"): 1515 + if has_custom_init(right): 1516 + cls = type(right) 1517 + if hasattr(cls, "__subclassscore__"): 1518 + return maxscore(cls.__subclassscore__(right, left), score(left, right)) 1519 + else: 1520 + return maxscore(right.__subclassscore__(left), score(left, right)) 1521 + return _scoresubclass(left, right) 1522 + 1523 + 1524 + def anything(annotation): 1525 + if isinstance(annotation, Annotation): 1526 + if annotation is ...: 1527 + return True 1528 + for generic in (object, typing.Any, typing.Union): 1529 + if geeq(annotation, generic): 1530 + return True 1531 + return False 1532 + 1533 + 1534 + # NOTE: Can't cache this; booleans, floats, strings, and integers sometimes have the same hash values. 1535 + def scoreinstance(left, right): 1536 + if anything(right): 1537 + return any_score 1538 + right = generalize(right) 1539 + if hasattr(right, "__instancescore__"): 1540 + if has_custom_init(right): 1541 + cls = type(right) 1542 + if hasattr(cls, "__instancescore__"): 1543 + return cls.__instancescore__(right, left) 1544 + else: 1545 + return right.__instancescore__(left) 1546 + return scoresubclass(get_type(left), right) 1547 + 1548 + 1549 + @cache 1550 + def annotation_is_Generic(annotation): 1551 + return isinstance(annotation, (GenericMeta, GenericAlias)) 1552 + 1553 + 1554 + @cache 1555 + def annotation_is_generic(annotation): 1556 + return annotation_is_Generic(annotation) or isinstance(annotation, Generic) 1557 + 1558 + 1559 + @cache 1560 + def annotation_is_typing_instance(annotation): 1561 + return isinstance(annotation, (Generic, TypingInstance)) 1562 + 1563 + 1564 + def call(cls, *args, **kwargs): 1565 + errors = [] 1566 + for generic in cls.__generics__: 1567 + try: 1568 + return generic(*args, **kwargs) 1569 + except Exception as e: 1570 + errors.append(e) 1571 + if isinstance(cls, GenericMeta): 1572 + for supercls in get_superclasses(cls): 1573 + try: 1574 + return supercls.__call__(*args, **kwargs) 1575 + except Exception as e: 1576 + errors.append(e) 1577 + # errors.append(ValueError(cls)) 1578 + # errors.append(ValueError(args)) 1579 + # errors.append(ValueError(kwargs)) 1580 + if len(errors) == 1: 1581 + raise errors[0] 1582 + if len(errors) == 2: 1583 + raise errors[1] from errors[0] 1584 + if len(errors) > 2: 1585 + raise ExceptionGroup("multiple errors occurred:", errors) 1586 + raise TypeError(f"'{cls}' object is not callable") 1587 + 1588 + 1589 + # TODO: Can't cache this; it might get non-truthy objects. 1590 + def format_name(obj): 1591 + if isinstance(obj, list): 1592 + return f"[{', '.join([format_name(a) for a in obj])}]" 1593 + if obj is ...: 1594 + return "..." 1595 + # if obj is None: 1596 + # return "None" 1597 + # if geeq(obj, typing.Union): 1598 + # return "Union" 1599 + 1600 + r = repr(obj) 1601 + if ("<" in r or ">" in r) and "->" not in r and "<lambda>(" not in r: 1602 + if isinstance(obj, FunctionTypes): 1603 + last_kind = None 1604 + paramessages = [] 1605 + sig: inspect.Signature = getattr(obj, "__signature__", signature(obj)) 1606 + params = sig.parameters 1607 + length_of_params = len(params) 1608 + starred = False 1609 + for index, (name, param) in enumerate(params.items(), 1): 1610 + kind = param.kind 1611 + not_last_kind = kind is not last_kind 1612 + if not_last_kind and (last_kind is Parameter.POSITIONAL_ONLY): 1613 + paramessages.append("/") 1614 + if param.annotation is not Parameter.empty: 1615 + formatted_name = format_name(param.annotation) 1616 + if param.default is Parameter.empty: 1617 + if kind is Parameter.VAR_POSITIONAL: 1618 + prefix = "*" 1619 + starred = True 1620 + elif kind is Parameter.VAR_KEYWORD: 1621 + prefix = "**" 1622 + else: 1623 + prefix = "" 1624 + message = prefix + f"{name}: {formatted_name}" 1625 + else: 1626 + message = f"{name}: {formatted_name} = {param.default}" 1627 + else: 1628 + if param.default is Parameter.empty: 1629 + if kind is Parameter.VAR_POSITIONAL: 1630 + prefix = "*" 1631 + starred = True 1632 + elif kind is Parameter.VAR_KEYWORD: 1633 + prefix = "**" 1634 + else: 1635 + prefix = "" 1636 + message = prefix + name 1637 + else: 1638 + message = f"{name} = {param.default}" 1639 + if (not starred) and ( 1640 + (not_last_kind and (last_kind is Parameter.POSITIONAL_OR_KEYWORD)) 1641 + or ((kind is Parameter.KEYWORD_ONLY) and (last_kind is None)) 1642 + ): 1643 + paramessages.append("*") 1644 + starred = True 1645 + paramessages.append(message) 1646 + if (kind is Parameter.POSITIONAL_ONLY) and (index == length_of_params): 1647 + paramessages.append("/") 1648 + last_kind = kind 1649 + return ( 1650 + f"{obj.__name__}(" 1651 + + ", ".join(paramessages) 1652 + + f") -> {format_name(sig.return_annotation)}" 1653 + ) 1654 + n = get_name(obj) 1655 + m = getattr(obj, "__module__", "") 1656 + if m and m != "builtins": 1657 + return f"{m}.{n}" 1658 + return n 1659 + return r 1660 + 1661 + 1662 + @cache 1663 + def geeq(left, right): 1664 + if not left_right_annotation_check(left, right): 1665 + return False 1666 + return any( 1667 + True 1668 + for s in get_all_ht_generics(left) 1669 + for o in get_all_ht_generics(right) 1670 + if s is o 1671 + ) 1672 + 1673 + 1674 + # Adapted From: 1675 + # Answer: https://stackoverflow.com/a/27952689/10827766 1676 + # User: https://stackoverflow.com/users/1774667/yakk-adam-nevraumont 1677 + @defcache 1678 + def reducecachehash(a, b): 1679 + return (cachehash(a) * 3) + cachehash(b) 1680 + 1681 + 1682 + def reducehash(*objs): 1683 + return reduce(reducecachehash, flatten(objs), sentinel) 1684 + 1685 + 1686 + class FormatMeta(type): 1687 + def __repr__(cls): 1688 + # frames: list[FrameInfo] = stack() 1689 + # n: int = 35 1690 + # if ((len(frames) >= n) and (frames[n] == "showtraceback")) or ( 1691 + # "hydrox/hydrox/typing/tests" in os.environ.get("PYTEST_CURRENT_TEST", "") 1692 + # ): 1693 + # return f"{''.join(part[0] for part in cls.__module__.split('.')[:2])}.{cls.__name__}" 1694 + # if annotation_is_generic(cls) or isinstance(cls, FormatMeta): 1695 + # if getattr(cls, "__generic__", sentinel) is sentinel: 1696 + # return f"{''.join(part[0] for part in cls.__module__.split('.')[:2])}.{cls.__name__}" 1697 + # return format_name(cls.__generic__) 1698 + # return format_name(cls) 1699 + return f"{''.join(part[0] for part in cls.__module__.split('.')[:2])}.{cls.__name__}" 1700 + 1701 + @staticmethod 1702 + def __format_type__(C): 1703 + if isinmrosubclass(C, GenericAlias): 1704 + return FormatMeta.__repr__(C) 1705 + return repr(C) 1706 + 1707 + # # TODO 1708 + # def __instancecheck__(cls, instance: typing.Any): 1709 + # if typing.get_args(instance) or not isinstance(instance, Annotation): 1710 + # return False 1711 + # # name, Name = camelized_names.append(instance) 1712 + # # return any( 1713 + # # True 1714 + # # for g in (get_generics(name) + get_generics(Name)) 1715 + # # if tryinmrosubclass(type(g), cls) 1716 + # # ) 1717 + # return isinmrosubclass(instance, cls.__instance__) 1718 + 1719 + 1720 + class GenericMeta(ABCMeta, metaclass=FormatMeta): 1721 + # For Documentation Purposes: 1722 + # Answer: https://stackoverflow.com/a/43779009 1723 + # User: https://stackoverflow.com/users/100297/martijn-pieters 1724 + def __new__( 1725 + cls, 1726 + name, 1727 + bases, 1728 + ns, 1729 + generic=False, 1730 + subscriptions=None, 1731 + final=None, 1732 + force=False, 1733 + ): 1734 + defaults = { 1735 + "name": name, 1736 + "bases": bases, 1737 + "ns": ns, 1738 + "generic": generic, 1739 + "subscriptions": subscriptions, 1740 + "final": final, 1741 + "force": force, 1742 + } 1743 + 1744 + name, Name = camelized_names.append(name) 1745 + if not force: 1746 + for b in bases: 1747 + if getattr(b, "__final__", False): 1748 + raise TypeError(f"type '{b}' is not an acceptable base type") 1749 + 1750 + # TODO: Can this be switched to `if cls is GenericMeta`? 1751 + # If so, remove `generic` and rename `orig_generic` to `generic`. 1752 + # Therefore, subclasses, such as `list`, would only need `metaclass=GenericMeta`. 1753 + # However, how will the block after the conditional be reached? 1754 + # TODO: `generic` is still needed for subclasses like `list`. 1755 + # Or maybe there's another way to determine if something is meant to be a subclass? 1756 + if generic: 1757 + metaclass = super().__new__ 1758 + 1759 + def inner_result(n): 1760 + if n in Generics: 1761 + old_generic = Generics[n] 1762 + for func in ( 1763 + officiate, 1764 + get_ht, 1765 + get_generics, 1766 + get_ht_generics, 1767 + get_all_ht, 1768 + get_all_generics, 1769 + get_all_ht_generics, 1770 + ): 1771 + del func.cache[old_generic] 1772 + meta_namespace = {} 1773 + alias_namespace = {} 1774 + namespace = {} 1775 + for k, v in ns.items(): 1776 + if ( 1777 + k.startswith("__meta") 1778 + and k.endswith("__") 1779 + and k not in ("__metaclass__",) 1780 + ): 1781 + meta_namespace[k.replace("__meta", "__")] = v 1782 + elif ( 1783 + k.startswith("__alias") 1784 + and k.endswith("__") 1785 + and k not in ("__alias__",) 1786 + ): 1787 + alias_namespace[k.replace("__alias", "__")] = v 1788 + else: 1789 + namespace[k] = v 1790 + 1791 + # if ( 1792 + # name == "none" 1793 + # and "__bool__" not in meta_namespace 1794 + # ): 1795 + # meta_namespace["__bool__"] = lambda cls: False 1796 + 1797 + if "__aliaseq__" in ns and "__aliasne__" not in ns: 1798 + # Adapted From: 1799 + # Answer: https://stackoverflow.com/a/30676267 1800 + # User: https://stackoverflow.com/users/541136/aaron-hall 1801 + alias_namespace["__ne__"] = lambda self, other: not (self == other) 1802 + 1803 + custom_init = is_custom_init(ns.get("__init__")) 1804 + if custom_init: 1805 + meta_namespace["__call__"] = ABCMeta.__call__ 1806 + 1807 + GenericMetas[n] = Meta = type(f"{n}Meta", (cls,), meta_namespace) 1808 + 1809 + r = metaclass(Meta, n, bases, namespace) 1810 + Meta.__instance__ = r 1811 + r.__custom_init__ = custom_init 1812 + r.__final__ = False 1813 + 1814 + generics = r.__generics__ = get_all_generics(n) 1815 + 1816 + current_generics = get_generics(n) 1817 + 1818 + if current_generics: 1819 + r.__generic__ = current_generics[0] 1820 + elif generics: 1821 + r.__generic__ = generics[0] 1822 + else: 1823 + r.__generic__ = sentinel 1824 + 1825 + for g in generics: 1826 + try: 1827 + temp = types.new_class("Test", (g,)) 1828 + except TypeError as e: 1829 + err_msg = str(e) 1830 + 1831 + # Adapted From: 1832 + # Answer: https://stackoverflow.com/a/68938778 1833 + # User: https://stackoverflow.com/users/14030287/ariadne-paradis 1834 + if any( 1835 + True 1836 + for part in ( 1837 + "is not an acceptable base type", 1838 + "Cannot subclass", 1839 + "takes no arguments", 1840 + ) 1841 + if part in err_msg 1842 + ): 1843 + r.__final__ = True 1844 + break 1845 + 1846 + else: 1847 + del temp 1848 + gc.collect() 1849 + nonlocal subscriptions 1850 + if subscriptions is None: 1851 + s = get_generic_subscription_size(n, root_bases=bases) 1852 + else: 1853 + s = subscriptions 1854 + if s: 1855 + if "__class_getitem__" not in ns: 1856 + if ( 1857 + "__eq__" in alias_namespace 1858 + and "__hash__" not in alias_namespace 1859 + ): 1860 + alias_namespace["__hash__"] = GenericAlias.__hash__ 1861 + 1862 + def __getattribute__(self, attr): 1863 + if attr == "__camel__": 1864 + return format_args(r.__camel__, get_args(self)) 1865 + 1866 + # Adapted From: 1867 + # Answer: https://stackoverflow.com/a/900404 1868 + # User: https://stackoverflow.com/users/40005/ayman-hourieh 1869 + if ( 1870 + attr == "__origin__" 1871 + and geeq( 1872 + object.__getattribute__(self, "__origin__"), 1873 + collections.abc.Callable, 1874 + ) 1875 + and stack()[1].function 1876 + == "_should_unflatten_callable_args" 1877 + ): 1878 + return abc.Callable 1879 + 1880 + try: 1881 + return object.__getattribute__(self, attr) 1882 + except AttributeError: 1883 + return getattr(r, attr) 1884 + 1885 + GenericAliases[n] = alias = Meta.__alias__ = type( 1886 + f"{n}Alias", 1887 + (cls.__alias__,), 1888 + { 1889 + "__origin__": r, 1890 + "__getattribute__": __getattribute__, 1891 + } 1892 + | alias_namespace, 1893 + ) 1894 + alias.__generic__ = alias 1895 + 1896 + # NOTE: This cannot be cached, as it may receive booleans, floats, and integers. 1897 + # TODO: Can this be worked around? 1898 + def class_getitem(origin, args): 1899 + # TODO: Vastly simplify this. 1900 + old_args = flatten.tuple( 1901 + ns.get("__aliasargsprocessor__", lambda x, y: y)( 1902 + origin, args 1903 + ) 1904 + ) 1905 + args = tuple( 1906 + ns["__aliasargsfullprocessor__"](origin, args) 1907 + if "__aliasargsfullprocessor__" in ns 1908 + else [ 1909 + ns.get("__aliasargsmapper__", generalize)(arg) 1910 + for arg in old_args 1911 + ] 1912 + ) 1913 + # nonlocal s 1914 + args_length = len(args) 1915 + 1916 + if s not in (args_length, -1): 1917 + raise TypeError( 1918 + f"Too {'many' if args_length > s else 'few'} arguments for {format_name(origin)}; actual {args_length}, expected {s}" 1919 + ) 1920 + 1921 + if "__aliasprocessor__" in ns: 1922 + alias_result = ns["__aliasprocessor__"]( 1923 + origin, args, alias 1924 + ) 1925 + if not isinstance(alias_result, GenericAlias): 1926 + return alias_result 1927 + else: 1928 + alias_result = alias( 1929 + origin, 1930 + args, 1931 + ) 1932 + 1933 + if "__aliasofficialprocessor__" in ns: 1934 + official_args = ns["__aliasofficialprocessor__"]( 1935 + old_args 1936 + ) 1937 + else: 1938 + official_args = [officiate(arg) for arg in old_args] 1939 + alias_result.__generics__ = tuple( 1940 + [format_args(g, official_args) for g in generics] 1941 + ) 1942 + 1943 + current_alias_generics = tuple( 1944 + [ 1945 + format_args(g, official_args) 1946 + for g in current_generics 1947 + ] 1948 + ) 1949 + 1950 + # TODO: Test this. 1951 + if current_alias_generics: 1952 + alias_result.__generic__ = current_alias_generics[0] 1953 + elif alias_result.__generics__: 1954 + alias_result.__generic__ = alias_result.__generics__[0] 1955 + else: 1956 + alias_result.__generic__ = sentinel 1957 + 1958 + try: 1959 + alias_result.__hash_value__ = reducehash( 1960 + origin, 1961 + ( 1962 + tuple(arg.items()) 1963 + if isinstance(arg, abc.Mapping) 1964 + else tuple(arg) 1965 + if isinstance(arg, list) 1966 + else arg 1967 + for arg in args 1968 + ), 1969 + ) 1970 + 1971 + GenericArgs[alias_result] = alias_result 1972 + 1973 + officiate.cache[alias_result] = alias_result.__generic__ 1974 + 1975 + current_generic_alias_results = ( 1976 + current_alias_generics + (alias_result,) 1977 + ) 1978 + for gar in current_generic_alias_results: 1979 + get_ht.cache[gar] = (alias_result,) 1980 + get_generics.cache[gar] = current_alias_generics 1981 + get_ht_generics.cache[gar] = ( 1982 + current_generic_alias_results 1983 + ) 1984 + 1985 + except TypeError as e: 1986 + if "unhashable type" not in str(e): 1987 + raise e 1988 + 1989 + alias_result.__repr_message__ = f"{origin}[{', '.join([format_name(arg) for arg in args])}]" 1990 + 1991 + return alias_result 1992 + 1993 + r.__class_getitem__ = classmethod(class_getitem) 1994 + else: 1995 + 1996 + def no_class_getitem(*args): 1997 + raise TypeError(f"type '{format_name(r)}' is not subscriptable") 1998 + 1999 + r.__class_getitem__ = no_class_getitem 2000 + r.__subscriptions__ = s 2001 + new_defaults = defaults | {"name": n, "subscriptions": s} 2002 + del new_defaults["ns"] 2003 + r.__hash_value__ = reducehash( 2004 + sys.modules[__name__].__package__, 2005 + n, 2006 + new_defaults.items(), 2007 + generics, 2008 + ) 2009 + 2010 + # TODO: Put these in `Generalize` or similar. 2011 + # Although, if I do that, subclasses of Generics won't be added... 2012 + for rn in (r, n): 2013 + officiate.cache[rn] = r.__generic__ 2014 + 2015 + current_generic_results = current_generics + (r,) 2016 + for gr in current_generic_results + (n,): 2017 + get_ht.cache[gr] = (r,) 2018 + get_generics.cache[gr] = current_generics 2019 + get_ht_generics.cache[gr] = current_generic_results 2020 + 2021 + r.__repr_message__ = FormatMeta.__repr__(r) 2022 + 2023 + return r 2024 + 2025 + result = inner_result(name) 2026 + Result = inner_result(Name) 2027 + else: 2028 + new_bases = tuple( 2029 + [base if base is Generic else base.__generic__ for base in bases] 2030 + ) 2031 + new_metaclasses = [type(b) for b in new_bases] 2032 + metaclass = partial(super().__new__, cls) 2033 + base = bases[0] 2034 + for base, new_base in zip(bases, new_bases): 2035 + meta = type(base) 2036 + new_meta = type(new_base) 2037 + if not builtins.issubclass(new_meta, meta): 2038 + if metaclass in new_metaclasses: 2039 + raise TypeError( 2040 + f"metaclass conflict: the metaclass {new_meta} " 2041 + f"of the derived class {new_base} " 2042 + f"must be a (non-strict) subclass of the metaclasses {new_metaclasses} " 2043 + f"of all its bases {new_bases}" 2044 + ) 2045 + metaclass = new_meta 2046 + base = new_base 2047 + result = metaclass(name, new_bases, ns) 2048 + Result = metaclass(Name, new_bases, ns) 2049 + Generics[name] = Result.__camel__ = result 2050 + Generics[Name] = result.__camel__ = Result 2051 + 2052 + results = (result, Result) 2053 + generic_results = result.__generics__ + results 2054 + for gr in generic_results + (name, Name): 2055 + get_all_ht.cache[gr] = results 2056 + get_all_generics.cache[gr] = result.__generics__ 2057 + get_all_ht_generics.cache[gr] = generic_results 2058 + 2059 + alias_results = (result.__alias__, Result.__alias__) 2060 + both_alias_generics = ( 2061 + result.__alias__.__generics__ + Result.__alias__.__generics__ 2062 + ) 2063 + generic_alias_results = both_alias_generics + alias_results 2064 + for gar in generic_alias_results: 2065 + get_all_ht.cache[gar] = alias_results 2066 + get_all_generics.cache[gar] = both_alias_generics 2067 + get_all_ht_generics.cache[gar] = generic_alias_results 2068 + 2069 + # return Result if camelized_name else result 2070 + return Result if Result.__name__ == defaults["name"] else result 2071 + 2072 + def __instancecheck__(cls, instance: typing.Any) -> bool: 2073 + return isinmroinstance(instance, cls) 2074 + 2075 + @cache 2076 + def __subclasscheck__(cls, C: typing.Any) -> bool: 2077 + return isinmrosubclass(C, cls) 2078 + 2079 + # NOTE: Remember, hashing uses `__eq__` as well, so it can't be cached either. 2080 + def __eq__(cls, other: typing.Any) -> bool: 2081 + if isinstance(other, (Generic, GenericAlias)) or ( 2082 + not isinstance(other, Annotation) 2083 + ): 2084 + return NotImplemented 2085 + # other_camel = getattr(other, "__camel__", None) 2086 + # return ( 2087 + # other is cls 2088 + # or other_camel is cls 2089 + # or other is cls.__camel__ 2090 + # or other_camel is cls.__camel__ 2091 + # # NOTE: Because the results are camelized, 2092 + # # `other` is being checked against the 2093 + # # `cls.__camel__` generics anyway. 2094 + # or other in cls.__generics__ 2095 + # or NotImplemented 2096 + # ) 2097 + return geeq(cls, other) or NotImplemented 2098 + 2099 + # Adapted From: 2100 + # Answer: https://stackoverflow.com/a/7810592 2101 + # User: https://stackoverflow.com/users/68998/newtover 2102 + def __hash__(cls): 2103 + return cls.__hash_value__ 2104 + 2105 + def __call__(cls, *args, **kwargs): 2106 + return call(cls, *args, **kwargs) 2107 + 2108 + # NOTE: Caching this just makes it confusing. 2109 + def __repr__(cls): 2110 + return cls.__repr_message__ 2111 + 2112 + @defcache 2113 + def __getattr__(cls, attr): 2114 + errors = [] 2115 + errored = [] 2116 + for generic in cls.__generics__: 2117 + try: 2118 + return getattr(generic, attr) 2119 + except AttributeError: 2120 + errored.append(generic) 2121 + except Exception as e: 2122 + errors.append(e) 2123 + if len(errors) == 1: 2124 + raise errors[0] 2125 + if len(errors) == 2: 2126 + raise errors[1] from errors[0] 2127 + if len(errors) > 2: 2128 + raise ExceptionGroup("multiple errors occurred:", errors) 2129 + if len(errored) == 0: 2130 + msg = " " 2131 + if len(errored) > 0: 2132 + names = [format_name(e) for e in errored] 2133 + prefix = " (through " 2134 + suffix = ") " 2135 + if len(names) == 1: 2136 + msg = "object " + str(names[0]) 2137 + if len(names) == 2: 2138 + msg = f"objects '{names[0]}' and '{names[1]}'" 2139 + if len(names) > 2: 2140 + msg = "objects '" + "', '".join(names[:-1]) + f"', and '{names[-1]}'" 2141 + msg = prefix + msg + suffix 2142 + raise AttributeError(f"""'{cls}' object{msg}has no attribute '{attr}'""") 2143 + 2144 + def __strategy__(cls, strategies): 2145 + return strategies.from_type(cls.__generic__) 2146 + 2147 + 2148 + class Generic(typing.Generic[typing.TypeVar("T")]): 2149 + __subscriptions__ = -1 2150 + __generalsubclasscheck__ = lambda self, C, check: False 2151 + __class_getitem__ = lambda origin, args: typing.Generic.__class_getitem__(args) 2152 + __final__ = False 2153 + __generic__ = typing.Generic 2154 + __generics__ = (typing.Generic,) 2155 + 2156 + 2157 + GenericMeta.__instance__ = Generic 2158 + 2159 + 2160 + # NOTE: Types like `Literal` won't have a subclass check, 2161 + # so this function will never be invoked in those cases, 2162 + # which means booleans, floats, strings, and integers having 2163 + # the same hash value won't matter. 2164 + @defcache 2165 + def partition_ellipsis(args): 2166 + if ... in args: 2167 + index = args.index(...) 2168 + return args[:index], args[index + 1 :] 2169 + return args, [] 2170 + 2171 + 2172 + class GenericAlias(types.GenericAlias): 2173 + __origin__ = Generic 2174 + __generic__ = types.GenericAlias 2175 + __generics__ = (types.GenericAlias,) 2176 + 2177 + def __instancecheck__(self, instance: typing.Any) -> bool: 2178 + return isinmroinstance(instance, self) 2179 + 2180 + @cache 2181 + def __subclasscheck__(self, C: typing.Any) -> bool: 2182 + return isinmrosubclass(C, self) 2183 + 2184 + @defcache 2185 + def __add__(self, summand): 2186 + origin = typing.get_origin(self) 2187 + original_summand = summand 2188 + summand = generalize(summand) 2189 + sorigin = get_origin_default(summand) 2190 + args = get_args(self) 2191 + sargs = get_args(summand) 2192 + if isinmrosubclass(origin, sorigin): 2193 + try: 2194 + return format_args( 2195 + origin, 2196 + [ 2197 + unionize(types.UnionType, [a, b]) 2198 + for a, b in zip(args, sargs, strict=True) 2199 + ], 2200 + ) 2201 + except ValueError: 2202 + raise TypeError( 2203 + f"unsupported operand argument(s) for +: {self} and {original_summand}" 2204 + ) 2205 + if isinmrosubclass(sorigin, origin): 2206 + try: 2207 + return format_args( 2208 + sorigin, 2209 + [ 2210 + unionize(types.UnionType, [a, b]) 2211 + for a, b in zip(args, sargs, strict=True) 2212 + ], 2213 + ) 2214 + except ValueError: 2215 + raise TypeError( 2216 + f"unsupported operand argument(s) for +: {self} and {original_summand}" 2217 + ) 2218 + raise TypeError( 2219 + f"unsupported operand type(s) for +: {FormatMeta.__format_type__(type(self))} and {FormatMeta.__format_type__(type(original_summand))}" 2220 + ) 2221 + 2222 + @defcache 2223 + def __radd__(self, summand): 2224 + origin = typing.get_origin(self) 2225 + original_summand = summand 2226 + summand = generalize(summand) 2227 + sorigin = get_origin_default(summand) 2228 + args = get_args(self) 2229 + sargs = get_args(summand) 2230 + if isinmrosubclass(origin, sorigin): 2231 + try: 2232 + return format_args( 2233 + origin, 2234 + [ 2235 + unionize(types.UnionType, [a, b]) 2236 + for a, b in zip(sargs, args, strict=True) 2237 + ], 2238 + ) 2239 + except ValueError: 2240 + raise TypeError( 2241 + f"unsupported operand argument(s) for +: {original_summand} and {self}" 2242 + ) 2243 + if isinmrosubclass(sorigin, origin): 2244 + try: 2245 + return format_args( 2246 + sorigin, 2247 + [ 2248 + unionize(types.UnionType, [a, b]) 2249 + for a, b in zip(sargs, args, strict=True) 2250 + ], 2251 + ) 2252 + except ValueError: 2253 + raise TypeError( 2254 + f"unsupported operand argument(s) for +: {original_summand} and {self}" 2255 + ) 2256 + raise TypeError( 2257 + f"unsupported operand type(s) for +: {FormatMeta.__format_type__(type(original_summand))} and {FormatMeta.__format_type__(type(self))}" 2258 + ) 2259 + 2260 + # NOTE: Remember, hashing uses `__eq__` as well, so it can't be cached either. 2261 + def __eq__(self, other): 2262 + if isinstance(other, (Generic, GenericMeta)) or ( 2263 + not isinstance(other, Annotation) 2264 + ): 2265 + return NotImplemented 2266 + return ( 2267 + (typing.get_origin(other) == typing.get_origin(self)) 2268 + and (typing.get_args(other) == typing.get_args(self)) 2269 + ) or NotImplemented 2270 + 2271 + def __hash__(self): 2272 + if hasattr(self, "__hash_value__"): 2273 + return self.__hash_value__ 2274 + raise TypeError(f"unhashable type: {self}") 2275 + 2276 + # NOTE: Caching this just makes it confusing. 2277 + def __repr__(self): 2278 + return self.__repr_message__ 2279 + 2280 + def __call__(self, *args, **kwargs): 2281 + origin = typing.get_origin(self) 2282 + if ( 2283 + isinmrosubclass(origin, Generics.Iterable) 2284 + and args 2285 + and isinstance(first_arg := args[0], Generics.Iterable) 2286 + ): 2287 + self_args = get_args(self) 2288 + if isinmrosubclass(origin, Generics.tuple): 2289 + items = [t(i) for t, i in zip(self_args, first_arg, strict=True)] 2290 + elif isinmrosubclass(origin, Generics.Mapping): 2291 + dict_first_arg = self_args[0] 2292 + dict_second_arg = self_args[1] 2293 + items = { 2294 + dict_first_arg(k): dict_second_arg(v) 2295 + for k, v in (dict(first_arg) | kwargs).items() 2296 + } 2297 + return call(origin, items) 2298 + else: 2299 + items = [self_args[0](item) for item in first_arg] 2300 + args = (items, *args[1:]) 2301 + return call(origin, *args, **kwargs) 2302 + 2303 + @defcache 2304 + def __getattr__(self, attr): 2305 + return getattr(typing.get_origin(self), attr) 2306 + 2307 + @defcache 2308 + def __genericsupertype__(cls): 2309 + return type(cls) 2310 + 2311 + def __strategy__(cls, strategies): 2312 + return strategies.from_type(cls.__generic__) 2313 + 2314 + 2315 + GenericMeta.__alias__ = GenericAlias 2316 + 2317 + Generics["Generic"] = Generic 2318 + GenericAliases["Generic"] = GenericAlias 2319 + GenericMetas["Generic"] = GenericMeta 2320 + 2321 + generic_results = Generic.__generics__ + (Generic,) 2322 + for gr in generic_results + ("generic", "Generic"): 2323 + get_all_ht.cache[gr] = (Generic,) 2324 + get_all_generics.cache[gr] = Generic.__generics__ 2325 + get_all_ht_generics.cache[gr] = generic_results 2326 + for rn in (Generic, "Generic"): 2327 + officiate.cache[rn] = Generic.__generic__ 2328 + 2329 + generic_alias_results = GenericAlias.__generics__ + (GenericAlias,) 2330 + for gr in generic_results + ("genericalias", "GenericAlias"): 2331 + get_all_ht.cache[gr] = (GenericAlias,) 2332 + get_all_generics.cache[gr] = GenericAlias.__generics__ 2333 + get_all_ht_generics.cache[gr] = generic_results 2334 + for rn in (GenericAlias, "GenericAlias"): 2335 + officiate.cache[rn] = GenericAlias.__generic__ 2336 + 2337 + subkwargs = defaultdict(dict) 2338 + subkwargs["tuple"]["subscriptions"] = -1 2339 + subkwargs["UserString"]["subscriptions"] = 0 2340 + 2341 + 2342 + # NOTE: "_deprecatedtypes" (from "_DeprecatedType") and "_typeddictmeta" (from "_TypedDictMeta") 2343 + # cannot be created directly because lowercase and capitalized types will not always be found. 2344 + # TODO: Add support for `typing.get_args` as well; modify the generics getters accordingly. 2345 + def create_types_base( 2346 + cls, *bases, modules=tuple(), recurse=False, subkwargs=None, **kwargs 2347 + ): 2348 + def inner(inner_cls, *inner_bases): 2349 + for subclass in get_singular_subclasses(inner_cls): 2350 + if check_module(subclass, modules) and get_all_ht_generics(subclass): 2351 + create_types_base( 2352 + subclass, 2353 + *inner_bases, 2354 + modules=modules, 2355 + recurse=recurse, 2356 + subkwargs=subkwargs, 2357 + **kwargs, 2358 + ) 2359 + 2360 + if cls in non_generic_classes: 2361 + if recurse and cls not in recursive_classes: 2362 + inner(cls) 2363 + return cls 2364 + name = get_name(cls) 2365 + if not recurse: 2366 + if isinstance(cls, GenericMeta): 2367 + return cls 2368 + if name in Generics: 2369 + return Generics[name] 2370 + if isinstance(cls, Annotation) and not name.startswith("__"): 2371 + bases = flatten.tuple(bases) 2372 + subkwargs = subkwargs or {} 2373 + 2374 + base = None 2375 + if isinstance(cls, GenericMeta): 2376 + base = cls 2377 + if name in Generics: 2378 + base = Generics[name] 2379 + inner_kwargs = kwargs | subkwargs[name] | {"force": True} 2380 + for k, v in subkwargs.items(): 2381 + if isinstance(k, abc.Callable) and k(name): 2382 + inner_kwargs |= v 2383 + if base is None: 2384 + base = Generalize( 2385 + name, 2386 + bases, 2387 + [ 2388 + create_types_base( 2389 + superclass, recurse=False, subkwargs=subkwargs, **kwargs 2390 + ) 2391 + for superclass in trybases(cls) 2392 + if superclass not in (*non_generic_classes, *bases) 2393 + ], 2394 + **inner_kwargs, 2395 + ) 2396 + 2397 + if recurse: 2398 + inner(cls, base) 2399 + return base 2400 + return cls 2401 + 2402 + 2403 + create_types = partial(create_types_base, modules=modules, subkwargs=subkwargs) 2404 + 2405 + 2406 + @Decorator 2407 + def Generalize(cls, *bases, **kwargs): 2408 + # kwargs.pop("default", None) 2409 + kwargs.pop("generic", None) 2410 + if isinstance(cls, str): 2411 + cls = type(cls, tuple(), {}) 2412 + bases = flatten.tuple(bases) 2413 + if bases: 2414 + # metaclass = type(bases[0]) 2415 + metaclass = type( 2416 + f"{cls.__name__}Meta", 2417 + tuple([GenericMeta if base == Generic else type(base) for base in bases]), 2418 + {}, 2419 + ) 2420 + else: 2421 + metaclass = GenericMeta 2422 + bases = (Generic,) 2423 + return metaclass.__new__( 2424 + metaclass, 2425 + cls.__name__, 2426 + bases, 2427 + {k: getattr(cls, k) for k in dir(cls)}, 2428 + generic=True, 2429 + # default=cls, 2430 + **kwargs, 2431 + ) 2432 + 2433 + 2434 + # TODO: Determine what will be private functions, convert them to `defcache`, 2435 + # and keep the user-facing functions as `cache`.
packages/hydrox/hydrox/typing/tests/__init__.py

This is a binary file and will not be displayed.

+176
packages/hydrox/hydrox/typing/tests/test_addition.py
··· 1 + from hydrox.hypothesis import ( 2 + Data, 3 + Args, 4 + AllGenericAlias, 5 + HTGenericAlias, 6 + subgen_strat, 7 + supergen_strat, 8 + ) 9 + import hydrox.typing as ht 10 + import typing 11 + from hypothesis import given, assume, strategies as st 12 + 13 + # TODO: Test the other errors as well. 14 + 15 + 16 + # @mark.skip(reason="Taking too long.") 17 + class TestAddition: 18 + @given( 19 + data=..., 20 + left=..., 21 + left_args=..., 22 + right_args=..., 23 + strat=st.sampled_from((subgen_strat, supergen_strat)), 24 + ) 25 + def test_matching( 26 + self, 27 + data: Data, 28 + left: HTGenericAlias, 29 + left_args: Args, 30 + right_args: Args, 31 + strat, 32 + ): 33 + right = data.draw( 34 + strat(left).filter( 35 + lambda r: ht.get_generic_subscription_size(r) == left.__subscriptions__ 36 + ) 37 + ) 38 + assert ( 39 + ht.format_args(left, left_args) + ht.format_args(right, right_args) 40 + ) == ht.generalize( 41 + ht.format_args( 42 + right if strat is subgen_strat else left, 43 + [typing.Union[la, ra] for la, ra in zip(left_args, right_args)], 44 + ) 45 + ) 46 + 47 + @given( 48 + data=..., 49 + left=..., 50 + left_args=..., 51 + right_args=..., 52 + strat=st.sampled_from((subgen_strat, supergen_strat)), 53 + ) 54 + def test_non_matching_subscription_size( 55 + self, 56 + data: Data, 57 + left: HTGenericAlias, 58 + left_args: Args, 59 + right_args: Args, 60 + strat, 61 + ): 62 + right = data.draw(strat(left)) 63 + right_subscriptions = ht.get_generic_subscription_size(right) 64 + assume(not ({-1, 0} & {left.__subscriptions__, right_subscriptions})) 65 + assume(left.__subscriptions__ != right_subscriptions) 66 + try: 67 + ht.format_args(left, left_args) + ht.format_args(right, right_args) 68 + except TypeError as e: 69 + assert "unsupported operand argument(s) for +" in str(e) 70 + else: 71 + assert False 72 + 73 + @given(left=..., right=..., left_args=..., right_args=...) 74 + def test_non_matching_origin( 75 + self, 76 + left: HTGenericAlias, 77 + right: AllGenericAlias, 78 + left_args: Args, 79 + right_args: Args, 80 + ): 81 + assume(not (ht.isinmrosubclass(left, right) or ht.isinmrosubclass(right, left))) 82 + try: 83 + ht.format_args(left, left_args) + ht.format_args(right, right_args) 84 + except TypeError as e: 85 + assert "unsupported operand type(s) for +" in str(e) 86 + else: 87 + assert False 88 + 89 + 90 + # ht.CamelizedNames[ 91 + # typing.Union[ 92 + # abc.Callable[[abc.Callable, abc.Callable], abc.Callable], 93 + # ht.Callable[[ht.Callable, ht.Callable], ht.Callable], 94 + # ], 95 + # typing.Union[ 96 + # abc.Callable[[abc.Callable, abc.Callable], abc.Callable], 97 + # ht.Callable[[ht.Callable, ht.Callable], ht.Callable], 98 + # ], 99 + # ] == ht.CamelizedNames[ 100 + # abc.Callable[[abc.Callable, abc.Callable], abc.Callable], 101 + # abc.Callable[[abc.Callable, abc.Callable], abc.Callable], 102 + # ] 103 + 104 + 105 + # @mark.skip(reason="Taking too long.") 106 + class TestRaddition: 107 + @given( 108 + data=..., 109 + right=..., 110 + left_args=..., 111 + right_args=..., 112 + strat=st.sampled_from((subgen_strat, supergen_strat)), 113 + ) 114 + def test_matching( 115 + self, 116 + data: Data, 117 + right: HTGenericAlias, 118 + left_args: Args, 119 + right_args: Args, 120 + strat, 121 + ): 122 + left = data.draw( 123 + strat(right).filter( 124 + lambda l: ht.get_generic_subscription_size(l) == right.__subscriptions__ 125 + ) 126 + ) 127 + assert ( 128 + ht.format_args(left, left_args) + ht.format_args(right, right_args) 129 + ) == ht.generalize( 130 + ht.format_args( 131 + left if strat is subgen_strat else right, 132 + [typing.Union[ra, la] for ra, la in zip(right_args, left_args)], 133 + ) 134 + ) 135 + 136 + @given( 137 + data=..., 138 + right=..., 139 + left_args=..., 140 + right_args=..., 141 + strat=st.sampled_from((subgen_strat, supergen_strat)), 142 + ) 143 + def test_non_matching_subscription_size( 144 + self, 145 + data: Data, 146 + right: HTGenericAlias, 147 + left_args: Args, 148 + right_args: Args, 149 + strat, 150 + ): 151 + left = data.draw(strat(right)) 152 + left_subscriptions = ht.get_generic_subscription_size(left) 153 + assume(not ({-1, 0} & {right.__subscriptions__, left_subscriptions})) 154 + assume(right.__subscriptions__ != left_subscriptions) 155 + try: 156 + ht.format_args(left, left_args) + ht.format_args(right, right_args) 157 + except TypeError as e: 158 + assert "unsupported operand argument(s) for +" in str(e) 159 + else: 160 + assert False 161 + 162 + @given(left=..., right=..., left_args=..., right_args=...) 163 + def test_non_matching_origin( 164 + self, 165 + left: AllGenericAlias, 166 + right: HTGenericAlias, 167 + left_args: Args, 168 + right_args: Args, 169 + ): 170 + assume(not (ht.isinmrosubclass(left, right) or ht.isinmrosubclass(right, left))) 171 + try: 172 + ht.format_args(left, left_args) + ht.format_args(right, right_args) 173 + except TypeError as e: 174 + assert "unsupported operand type(s) for +" in str(e) 175 + else: 176 + assert False
+612
packages/hydrox/hydrox/typing/tests/test_ellipsis.py
··· 1 + import hydrox.typing as ht 2 + from hydrox.hypothesis import ( 3 + flat_args_strat, 4 + subargs_strat, 5 + subgen_strat, 6 + ) 7 + from hydrox.helpers import cutreverse 8 + from hypothesis import given, example, strategies as st, assume, note 9 + 10 + 11 + def randgen_strat(arg): 12 + return st.one_of(subgen_strat(arg), subgen_strat(arg, ungen=True)) 13 + 14 + 15 + def get_subargs(args, new_args_length=None): 16 + new_args_length = new_args_length or len(args) 17 + return st.tuples(*[randgen_strat(arg) for arg in args]).filter( 18 + lambda subargs: not all( 19 + map(issubclass, subargs[:new_args_length], args[:new_args_length]) 20 + ) 21 + ) 22 + 23 + 24 + # TODO 25 + class TestEllipsisInstance: 26 + @given(tup=st.just(("",))) 27 + # @given(tup=rand_strat(only_iterables="tuples")) 28 + def test_args_ellipsed(self, tup): 29 + assert isinstance(tup, ht.tuple[*map(type, tup), ...]) 30 + 31 + 32 + class TestEllipses: 33 + @given( 34 + subargs=st.just([bool, int, str, type, float]), args=st.just([int, ..., float]) 35 + ) 36 + def test_true_examples(self, subargs, args): 37 + assert issubclass(ht.tuple[*subargs], ht.tuple[*args]) 38 + 39 + @given(subargs=st.just([...]), args=st.just([str])) 40 + @example(subargs=[bool], args=[str, ...]) 41 + @example(subargs=[str], args=[str, int, ...]) 42 + @example(subargs=[str, ...], args=[str, int, ...]) 43 + @example(subargs=[bool, ...], args=[str, int, ...]) 44 + @example(subargs=[str, ...], args=[int, ...]) 45 + @example(subargs=[str, bool, ...], args=[int, ...]) 46 + @example(subargs=[bool, int, str, type, float], args=[int, ..., int]) 47 + def test_false_examples(self, subargs, args): 48 + assert not issubclass(ht.tuple[*subargs], ht.tuple[*args]) 49 + 50 + 51 + class TestSubEllipses: 52 + def test_no_left_args_right_ellipsed(self): 53 + assert not issubclass(ht.tuple, ht.tuple[...]) 54 + 55 + @given(args=flat_args_strat()) 56 + def test_no_left_args_right_3_args_ellipsed(self, args): 57 + assert not issubclass(ht.tuple, ht.tuple[*args, ...]) 58 + 59 + @given(args=flat_args_strat()) 60 + @example(args=[bool]) 61 + def test_left_3_subargs_right_ellipsed(self, args): 62 + assert issubclass(ht.tuple[*args], ht.tuple[...]) 63 + 64 + @given(args=subargs_strat()) 65 + @example(args=([str], [str])) 66 + def test_left_3_subargs_right_3_args_ellipsed(self, args): 67 + args, subargs = args 68 + assert issubclass(ht.tuple[*subargs], ht.tuple[*args, ...]) 69 + 70 + def test_left_ellipsed_no_right_args(self): 71 + assert issubclass(ht.tuple[...], ht.tuple) 72 + 73 + def test_left_ellipsed_right_ellipsed(self): 74 + assert issubclass(ht.tuple[...], ht.tuple[...]) 75 + 76 + @given(args=flat_args_strat()) 77 + @example(args=[int]) 78 + def test_left_ellipsed_right_3_args(self, args): 79 + assert not issubclass(ht.tuple[...], ht.tuple[*args]) 80 + 81 + @given(args=flat_args_strat()) 82 + @example(args=[int]) 83 + def test_left_ellipsed_right_3_args_ellipsed(self, args): 84 + assert not issubclass(ht.tuple[...], ht.tuple[*args, ...]) 85 + 86 + @given(args=flat_args_strat()) 87 + def test_left_3_subargs_ellipsed_no_right_args(self, args): 88 + assert issubclass(ht.tuple[*args, ...], ht.tuple) 89 + 90 + @given(args=subargs_strat()) 91 + def test_left_3_subargs_ellipsed_right_3_args(self, args): 92 + args, subargs = args 93 + assert not issubclass(ht.tuple[*subargs, ...], ht.tuple[*args]) 94 + 95 + @given(args=flat_args_strat()) 96 + @example(args=[bool]) 97 + def test_left_3_subargs_ellipsed_right_ellipsed(self, args): 98 + assert issubclass(ht.tuple[*args, ...], ht.tuple[...]) 99 + 100 + @given(args=subargs_strat()) 101 + @example(args=([int], [bool])) 102 + @example(args=([str, int], [str, bool])) 103 + @example(args=([str, int], [str, bool, type])) 104 + def test_left_3_subargs_ellipsed_right_3_args_ellipsed(self, args): 105 + args, subargs = args 106 + assert issubclass(ht.tuple[*subargs, ...], ht.tuple[*args, ...]) 107 + 108 + @given(data=st.data(), args=subargs_strat()) 109 + def test_left_n_subargs_ellipsed_right_n_args(self, data, args): 110 + args, subargs = args 111 + max_value = len(args) 112 + assert not issubclass( 113 + ht.tuple[ 114 + *subargs[: data.draw(st.integers(min_value=1, max_value=max_value))], 115 + ..., 116 + ], 117 + ht.tuple[*args[: data.draw(st.integers(min_value=1, max_value=max_value))]], 118 + ) 119 + 120 + @given(data=st.data(), args=subargs_strat()) 121 + def test_left_n_subargs_right_n_args_ellipsed(self, data, args): 122 + args, subargs = args 123 + max_value = len(args) 124 + subargs = subargs[: data.draw(st.integers(min_value=1, max_value=max_value))] 125 + args = args[: data.draw(st.integers(min_value=1, max_value=max_value))] 126 + # if len(args) <= len(subargs): 127 + # assert issubclass(ht.tuple[*subargs], ht.tuple[*args, ...]) 128 + # else: 129 + # assert not issubclass(ht.tuple[*subargs], ht.tuple[*args, ...]) 130 + assert (len(args) > len(subargs)) ^ issubclass( 131 + ht.tuple[*subargs], ht.tuple[*args, ...] 132 + ) 133 + 134 + @given(data=st.data(), args=subargs_strat()) 135 + def test_left_n_subargs_ellipsed_right_n_args_ellipsed(self, data, args): 136 + args, subargs = args 137 + max_value = len(args) 138 + subargs = subargs[: data.draw(st.integers(min_value=1, max_value=max_value))] 139 + args = args[: data.draw(st.integers(min_value=1, max_value=max_value))] 140 + # if len(args) <= len(subargs): 141 + # assert issubclass(ht.tuple[*subargs, ...], ht.tuple[*args, ...]) 142 + # else: 143 + # assert not issubclass(ht.tuple[*subargs, ...], ht.tuple[*args, ...]) 144 + assert (len(args) > len(subargs)) ^ issubclass( 145 + ht.tuple[*subargs, ...], ht.tuple[*args, ...] 146 + ) 147 + 148 + 149 + class TestNonSubEllipses: 150 + @given(data=st.data(), args=flat_args_strat()) 151 + def test_left_3_subargs_right_3_args_ellipsed(self, data, args): 152 + assert not issubclass( 153 + ht.tuple[*data.draw(get_subargs(args))], ht.tuple[*args, ...] 154 + ) 155 + 156 + @given(data=st.data(), args=flat_args_strat()) 157 + def test_left_3_subargs_ellipsed_right_3_args(self, data, args): 158 + assert not issubclass( 159 + ht.tuple[*data.draw(get_subargs(args)), ...], ht.tuple[*args] 160 + ) 161 + 162 + @given(data=st.data(), args=flat_args_strat()) 163 + def test_left_3_subargs_ellipsed_right_3_args_ellipsed(self, data, args): 164 + assert not issubclass( 165 + ht.tuple[*data.draw(get_subargs(args)), ...], ht.tuple[*args, ...] 166 + ) 167 + 168 + @given(data=st.data(), args=flat_args_strat()) 169 + def test_left_n_subargs_ellipsed_right_n_args(self, data, args): 170 + max_value = len(args) 171 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 172 + assert not issubclass( 173 + ht.tuple[ 174 + *data.draw(get_subargs(args, new_args_length))[ 175 + : data.draw(st.integers(min_value=1, max_value=max_value)) 176 + ], 177 + ..., 178 + ], 179 + ht.tuple[*args[:new_args_length]], 180 + ) 181 + 182 + @given(data=st.data(), args=flat_args_strat()) 183 + def test_left_n_subargs_right_n_args_ellipsed(self, data, args): 184 + max_value = len(args) 185 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 186 + assert not issubclass( 187 + ht.tuple[ 188 + *data.draw(get_subargs(args, new_args_length))[ 189 + : data.draw(st.integers(min_value=1, max_value=max_value)) 190 + ] 191 + ], 192 + ht.tuple[*args[:new_args_length], ...], 193 + ) 194 + 195 + @given(data=st.data(), args=flat_args_strat()) 196 + def test_left_n_subargs_ellipsed_right_n_args_ellipsed(self, data, args): 197 + max_value = len(args) 198 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 199 + assert not issubclass( 200 + ht.tuple[ 201 + *data.draw(get_subargs(args, new_args_length))[ 202 + : data.draw(st.integers(min_value=1, max_value=max_value)) 203 + ], 204 + ..., 205 + ], 206 + ht.tuple[*args[:new_args_length], ...], 207 + ) 208 + 209 + 210 + class TestBackSubEllipses: 211 + @given(args=flat_args_strat()) 212 + def test_no_left_args_right_3_args_ellipsed(self, args): 213 + assert not issubclass(ht.tuple, ht.tuple[..., *args[::-1]]) 214 + 215 + @given(args=subargs_strat()) 216 + def test_left_3_subargs_right_3_args_ellipsed(self, args): 217 + args, subargs = args 218 + assert issubclass(ht.tuple[*subargs[::-1]], ht.tuple[..., *args[::-1]]) 219 + 220 + @given(args=flat_args_strat()) 221 + def test_left_ellipsed_right_3_args_ellipsed(self, args): 222 + assert not issubclass(ht.tuple[...], ht.tuple[..., *args[::-1]]) 223 + 224 + @given(args=flat_args_strat()) 225 + def test_left_3_subargs_ellipsed_no_right_args(self, args): 226 + assert issubclass(ht.tuple[..., *args[::-1]], ht.tuple) 227 + 228 + @given(args=subargs_strat()) 229 + def test_left_3_subargs_ellipsed_right_3_args(self, args): 230 + args, subargs = args 231 + assert not issubclass(ht.tuple[..., *subargs[::-1]], ht.tuple[*args[::-1]]) 232 + 233 + @given(args=flat_args_strat()) 234 + def test_left_3_subargs_ellipsed_right_ellipsed(self, args): 235 + assert issubclass(ht.tuple[..., *args[::-1]], ht.tuple[...]) 236 + 237 + @given(args=subargs_strat()) 238 + def test_left_3_subargs_ellipsed_right_3_args_ellipsed(self, args): 239 + args, subargs = args 240 + assert issubclass(ht.tuple[..., *subargs[::-1]], ht.tuple[..., *args[::-1]]) 241 + 242 + @given(data=st.data(), args=subargs_strat()) 243 + def test_left_n_subargs_ellipsed_right_n_args(self, data, args): 244 + args, subargs = args 245 + max_value = len(args) 246 + assert not issubclass( 247 + ht.tuple[ 248 + ..., 249 + *cutreverse( 250 + subargs, data.draw(st.integers(min_value=1, max_value=max_value)) 251 + ), 252 + ], 253 + ht.tuple[ 254 + *cutreverse( 255 + args, data.draw(st.integers(min_value=1, max_value=max_value)) 256 + ) 257 + ], 258 + ) 259 + 260 + @given(data=st.data(), args=subargs_strat()) 261 + def test_left_n_subargs_right_n_args_ellipsed(self, data, args): 262 + args, subargs = args 263 + max_value = len(args) 264 + subargs = cutreverse( 265 + subargs, data.draw(st.integers(min_value=1, max_value=max_value)) 266 + ) 267 + args = cutreverse( 268 + args, data.draw(st.integers(min_value=1, max_value=max_value)) 269 + ) 270 + # if len(args) <= len(subargs): 271 + # assert issubclass(ht.tuple[*subargs], ht.tuple[..., *args]) 272 + # else: 273 + # assert not issubclass(ht.tuple[*subargs], ht.tuple[..., *args]) 274 + assert (len(args) > len(subargs)) ^ issubclass( 275 + ht.tuple[*subargs], ht.tuple[..., *args] 276 + ) 277 + 278 + @given(data=st.data(), args=subargs_strat()) 279 + def test_left_n_subargs_ellipsed_right_n_args_ellipsed(self, data, args): 280 + args, subargs = args 281 + max_value = len(args) 282 + subargs = cutreverse( 283 + subargs, data.draw(st.integers(min_value=1, max_value=max_value)) 284 + ) 285 + args = cutreverse( 286 + args, data.draw(st.integers(min_value=1, max_value=max_value)) 287 + ) 288 + # if len(args) <= len(subargs): 289 + # assert issubclass(ht.tuple[..., *subargs], ht.tuple[..., *args]) 290 + # else: 291 + # assert not issubclass(ht.tuple[..., *subargs], ht.tuple[..., *args]) 292 + assert (len(args) > len(subargs)) ^ issubclass( 293 + ht.tuple[..., *subargs], ht.tuple[..., *args] 294 + ) 295 + 296 + 297 + class TestBackNonSubEllipses: 298 + @given(data=st.data(), args=flat_args_strat()) 299 + def test_left_3_subargs_right_3_args_ellipsed(self, data, args): 300 + assert not issubclass( 301 + ht.tuple[*data.draw(get_subargs(args))[::-1]], ht.tuple[..., *args[::-1]] 302 + ) 303 + 304 + @given(data=st.data(), args=flat_args_strat()) 305 + def test_left_3_subargs_ellipsed_right_3_args(self, data, args): 306 + assert not issubclass( 307 + ht.tuple[..., *data.draw(get_subargs(args))[::-1]], ht.tuple[*args[::-1]] 308 + ) 309 + 310 + @given(data=st.data(), args=flat_args_strat()) 311 + def test_left_3_subargs_ellipsed_right_3_args_ellipsed(self, data, args): 312 + assert not issubclass( 313 + ht.tuple[..., *data.draw(get_subargs(args))[::-1]], 314 + ht.tuple[..., *args[::-1]], 315 + ) 316 + 317 + @given(data=st.data(), args=flat_args_strat()) 318 + def test_left_n_subargs_ellipsed_right_n_args(self, data, args): 319 + max_value = len(args) 320 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 321 + assert not issubclass( 322 + ht.tuple[ 323 + ..., 324 + *cutreverse( 325 + data.draw(get_subargs(args, new_args_length)), 326 + data.draw(st.integers(min_value=1, max_value=max_value)), 327 + ), 328 + ], 329 + ht.tuple[*cutreverse(args, new_args_length)], 330 + ) 331 + 332 + @given(data=st.data(), args=flat_args_strat()) 333 + def test_left_n_subargs_right_n_args_ellipsed(self, data, args): 334 + max_value = len(args) 335 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 336 + assert not issubclass( 337 + ht.tuple[ 338 + *cutreverse( 339 + data.draw(get_subargs(args, new_args_length)), 340 + data.draw(st.integers(min_value=1, max_value=max_value)), 341 + ) 342 + ], 343 + ht.tuple[ 344 + ..., 345 + *cutreverse(args, new_args_length), 346 + ], 347 + ) 348 + 349 + @given(data=st.data(), args=flat_args_strat()) 350 + def test_left_n_subargs_ellipsed_right_n_args_ellipsed(self, data, args): 351 + max_value = len(args) 352 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 353 + assert not issubclass( 354 + ht.tuple[ 355 + ..., 356 + *cutreverse( 357 + data.draw(get_subargs(args, new_args_length)), 358 + data.draw(st.integers(min_value=1, max_value=max_value)), 359 + ), 360 + ], 361 + ht.tuple[ 362 + ..., 363 + *cutreverse(args, new_args_length), 364 + ], 365 + ) 366 + 367 + 368 + class TestBothSubEllipses: 369 + @given(args=flat_args_strat()) 370 + def test_no_left_args_right_3_args_ellipsed(self, args): 371 + assert not issubclass(ht.tuple, ht.tuple[*args, ..., *args[::-1]]) 372 + 373 + @given(args=subargs_strat()) 374 + def test_left_3_subargs_right_3_args_ellipsed(self, args): 375 + args, subargs = args 376 + assert not issubclass(ht.tuple[*subargs], ht.tuple[*args, ..., *args[::-1]]) 377 + 378 + @given(args=flat_args_strat()) 379 + def test_left_ellipsed_right_3_args_ellipsed(self, args): 380 + assert not issubclass(ht.tuple[...], ht.tuple[*args, ..., *args[::-1]]) 381 + 382 + @given(args=flat_args_strat()) 383 + def test_left_3_subargs_ellipsed_no_right_args(self, args): 384 + assert issubclass(ht.tuple[*args, ..., *args[::-1]], ht.tuple) 385 + 386 + @given(args=subargs_strat()) 387 + def test_left_3_subargs_ellipsed_right_3_args(self, args): 388 + args, subargs = args 389 + assert not issubclass(ht.tuple[*subargs, ..., *subargs[::-1]], ht.tuple[*args]) 390 + 391 + @given(args=flat_args_strat()) 392 + def test_left_3_subargs_ellipsed_right_ellipsed(self, args): 393 + assert issubclass(ht.tuple[*args, ..., *args[::-1]], ht.tuple[...]) 394 + 395 + @given(args=subargs_strat()) 396 + def test_left_3_subargs_ellipsed_right_3_args_ellipsed(self, args): 397 + args, subargs = args 398 + assert issubclass( 399 + ht.tuple[*subargs, ..., *subargs[::-1]], 400 + ht.tuple[*args, ..., *args[::-1]], 401 + ) 402 + 403 + @given(data=st.data(), args=subargs_strat()) 404 + def test_left_n_subargs_ellipsed_right_n_args(self, data, args): 405 + args, subargs = args 406 + max_value = len(args) 407 + pre_subargs = subargs[ 408 + : data.draw(st.integers(min_value=1, max_value=max_value)) 409 + ] 410 + post_subargs = cutreverse( 411 + subargs, data.draw(st.integers(min_value=1, max_value=max_value)) 412 + ) 413 + assert not issubclass( 414 + ht.tuple[*pre_subargs, ..., *post_subargs], 415 + ht.tuple[*args[: data.draw(st.integers(min_value=1, max_value=max_value))]], 416 + ) 417 + 418 + # TODO: Vastly simplify this. 419 + @given( 420 + data=st.data(), 421 + pre_args=subargs_strat(), 422 + post_args=subargs_strat(), 423 + ) 424 + def test_left_n_subargs_right_n_args_ellipsed(self, data, pre_args, post_args): 425 + pre_args, pre_subargs = pre_args 426 + post_args, post_subargs = post_args 427 + pre_max_value = len(pre_args) 428 + post_max_value = len(post_args) 429 + subargs = data.draw( 430 + subargs_strat( 431 + exact_aliases=(*pre_args, *post_args[::-1]), 432 + just_sub_aliases=True, 433 + ungen=True, 434 + ) 435 + ) 436 + new_pre_subargs_length = data.draw( 437 + st.integers(min_value=1, max_value=pre_max_value) 438 + ) 439 + pre_subargs = pre_subargs[:new_pre_subargs_length] 440 + new_post_subargs_length = data.draw( 441 + st.integers(min_value=1, max_value=post_max_value) 442 + ) 443 + post_subargs = cutreverse(post_subargs, new_post_subargs_length) 444 + new_pre_args_length = data.draw( 445 + st.integers(min_value=1, max_value=pre_max_value) 446 + ) 447 + pre_args = pre_args[:new_pre_args_length] 448 + new_post_args_length = data.draw( 449 + st.integers(min_value=1, max_value=post_max_value) 450 + ) 451 + post_args = cutreverse(post_args, new_post_args_length) 452 + note( 453 + f"{new_pre_args_length} | {new_pre_subargs_length} | {new_post_args_length} | {new_post_subargs_length}" 454 + ) 455 + # 3..2 456 + # 3..2 457 + if (new_pre_args_length <= new_pre_subargs_length) and ( 458 + new_post_args_length <= new_post_subargs_length 459 + ): 460 + pass 461 + else: 462 + if new_pre_args_length > new_pre_subargs_length: 463 + # 2..2 464 + # 3..3 465 + if new_post_args_length > new_post_subargs_length: 466 + subargs = subargs[ 467 + new_pre_subargs_length : len(subargs) - new_post_subargs_length 468 + ] 469 + # 2..3 470 + # 3..2 471 + else: 472 + subargs = subargs[new_pre_subargs_length:] 473 + else: 474 + # 3..2 475 + # 2..3 476 + if new_post_args_length > new_post_subargs_length: 477 + subargs = subargs[: len(subargs) - new_post_subargs_length] 478 + # 3..3 479 + # 2..2 480 + else: 481 + pass 482 + assert ( 483 + (new_pre_args_length > new_pre_subargs_length) 484 + or (new_post_args_length > new_post_subargs_length) 485 + ) ^ issubclass( 486 + ht.tuple[*pre_subargs, *subargs, *post_subargs], 487 + ht.tuple[*pre_args, ..., *post_args], 488 + ) 489 + 490 + # TODO: Vastly simplify this. 491 + @given(data=st.data(), args=subargs_strat()) 492 + def test_left_n_subargs_ellipsed_right_n_args_ellipsed(self, data, args): 493 + args, subargs = args 494 + max_value = len(args) 495 + pre_subargs = subargs[ 496 + : data.draw(st.integers(min_value=1, max_value=max_value)) 497 + ] 498 + post_subargs = cutreverse( 499 + subargs, data.draw(st.integers(min_value=1, max_value=max_value)) 500 + ) 501 + pre_args = args[: data.draw(st.integers(min_value=1, max_value=max_value))] 502 + post_args = cutreverse( 503 + args, data.draw(st.integers(min_value=1, max_value=max_value)) 504 + ) 505 + # if (len(pre_args) <= len(pre_subargs)) and ( 506 + # len(post_args) <= len(post_subargs) 507 + # ): 508 + # assert issubclass( 509 + # ht.tuple[*pre_subargs, ..., *post_subargs], 510 + # ht.tuple[*pre_args, ..., *post_args], 511 + # ) 512 + # else: 513 + # assert not issubclass( 514 + # ht.tuple[*pre_subargs, ..., *post_subargs], 515 + # ht.tuple[*pre_args, ..., *post_args], 516 + # ) 517 + assert ( 518 + len(pre_args) > len(pre_subargs) or len(post_args) > len(post_subargs) 519 + ) ^ issubclass( 520 + ht.tuple[*pre_subargs, ..., *post_subargs], 521 + ht.tuple[*pre_args, ..., *post_args], 522 + ) 523 + 524 + 525 + class TestBothNonSubEllipses: 526 + @given(data=st.data(), args=flat_args_strat()) 527 + def test_left_3_subargs_right_3_args_ellipsed(self, data, args): 528 + assert not issubclass( 529 + ht.tuple[*data.draw(get_subargs(args))], ht.tuple[*args, ..., *args[::-1]] 530 + ) 531 + 532 + @given(data=st.data(), args=flat_args_strat()) 533 + def test_left_3_subargs_ellipsed_right_3_args(self, data, args): 534 + subargs = data.draw(get_subargs(args)) 535 + assert not issubclass(ht.tuple[*subargs, ..., *subargs[::-1]], ht.tuple[*args]) 536 + 537 + @given(data=st.data(), args=flat_args_strat()) 538 + def test_left_3_subargs_ellipsed_right_3_args_ellipsed(self, data, args): 539 + subargs = data.draw(get_subargs(args)) 540 + assert not issubclass( 541 + ht.tuple[*subargs, ..., *subargs[::-1]], ht.tuple[*args, ..., *args[::-1]] 542 + ) 543 + 544 + @given(data=st.data(), args=flat_args_strat()) 545 + def test_left_n_subargs_ellipsed_right_n_args(self, data, args): 546 + max_value = len(args) 547 + new_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 548 + subargs = data.draw(get_subargs(args, new_args_length)) 549 + pre_subargs = subargs[ 550 + : data.draw(st.integers(min_value=1, max_value=max_value)) 551 + ] 552 + post_subargs = cutreverse( 553 + subargs, data.draw(st.integers(min_value=1, max_value=max_value)) 554 + ) 555 + assert not issubclass( 556 + ht.tuple[*pre_subargs, ..., *post_subargs], 557 + ht.tuple[*args[:new_args_length]], 558 + ) 559 + 560 + @given(data=st.data(), args=flat_args_strat()) 561 + def test_left_n_subargs_right_n_args_ellipsed(self, data, args): 562 + max_value = len(args) 563 + new_pre_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 564 + pre_args = args[:new_pre_args_length] 565 + post_args = cutreverse( 566 + args, data.draw(st.integers(min_value=1, max_value=max_value)) 567 + ) 568 + assert not issubclass( 569 + ht.tuple[ 570 + *data.draw(get_subargs(args, new_pre_args_length))[ 571 + : data.draw(st.integers(min_value=1, max_value=max_value)) 572 + ] 573 + ], 574 + ht.tuple[*pre_args, ..., *post_args], 575 + ) 576 + 577 + @given(data=st.data(), args=flat_args_strat()) 578 + def test_left_n_subargs_ellipsed_right_n_args_ellipsed(self, data, args): 579 + max_value = len(args) 580 + new_pre_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 581 + pre_args = args[:new_pre_args_length] 582 + new_post_args_length = data.draw(st.integers(min_value=1, max_value=max_value)) 583 + post_args = cutreverse(args, new_post_args_length) 584 + pre_subargs = data.draw(get_subargs(args, new_pre_args_length))[ 585 + : data.draw(st.integers(min_value=1, max_value=max_value)) 586 + ] 587 + post_subargs = cutreverse( 588 + data.draw(get_subargs(args, new_post_args_length)), 589 + data.draw(st.integers(min_value=1, max_value=max_value)), 590 + ) 591 + assume( 592 + not ( 593 + all( 594 + map( 595 + issubclass, 596 + pre_subargs[:new_pre_args_length], 597 + pre_args[:new_pre_args_length], 598 + ) 599 + ) 600 + and all( 601 + map( 602 + issubclass, 603 + post_subargs[:new_post_args_length], 604 + post_args[:new_post_args_length], 605 + ) 606 + ) 607 + ) 608 + ) 609 + assert not issubclass( 610 + ht.tuple[*pre_subargs, ..., *post_subargs], 611 + ht.tuple[*pre_args, ..., *post_args], 612 + )
+92
packages/hydrox/hydrox/typing/tests/test_equality.py
··· 1 + from hydrox.hypothesis import Data, Args, AllGeneric, HTGenericAlias, HTGeneric 2 + import hydrox.typing as ht 3 + import typing 4 + from hypothesis import given, assume, strategies as st 5 + 6 + 7 + # NOTE: This tests the equality of both `GenericMeta` and `GenericAlias` objects, 8 + # as `GenericAlias.__eq__` calls `GenericMeta.__eq__` as well. 9 + class TestEquality: 10 + # Matching origins 11 + @given(data=..., generic=...) 12 + def test_matching_origins(self, data: Data, generic: HTGeneric): 13 + class_generics = ht.get_all_ht_generics(generic) 14 + left = data.draw(st.sampled_from(class_generics)) 15 + right = data.draw(st.sampled_from(class_generics)) 16 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 17 + assert left == right 18 + 19 + # Matching origins with matching arguments 20 + @given(data=..., generic=..., args=...) 21 + def test_matching_origins_matching_args( 22 + self, data: Data, generic: HTGeneric, args: Args 23 + ): 24 + class_generics = ht.get_all_ht_generics(generic) 25 + left = data.draw(st.sampled_from(class_generics)) 26 + right = data.draw(st.sampled_from(class_generics)) 27 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 28 + assert ht.format_args(left, args) == ht.format_args(right, args) 29 + 30 + # Matching origins with different arguments 31 + @given(data=..., generic=..., left_args=..., right_args=...) 32 + def test_matching_origins_different_args( 33 + self, data: Data, generic: HTGenericAlias, left_args: Args, right_args: Args 34 + ): 35 + class_generics = ht.get_all_ht_generics(generic) 36 + left = data.draw(st.sampled_from(class_generics)) 37 + right = data.draw(st.sampled_from(class_generics)) 38 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 39 + left = ht.format_args(left, left_args) 40 + right = ht.format_args(right, right_args) 41 + assume(typing.get_args(left) != typing.get_args(right)) 42 + assert left != right 43 + 44 + # Different origins 45 + @given(left=..., right=...) 46 + def test_different_origins(self, left: AllGeneric, right: AllGeneric): 47 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 48 + assume(ht.get_all_generics(left) != ht.get_all_generics(right)) 49 + assert left != right 50 + 51 + # Different origins with matching arguments 52 + @given(left=..., right=..., args=...) 53 + def test_different_origins_matching_args( 54 + self, left: AllGeneric, right: AllGeneric, args: Args 55 + ): 56 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 57 + assume(ht.get_all_generics(left) != ht.get_all_generics(right)) 58 + left = ht.format_args(left, args) 59 + right = ht.format_args(right, args) 60 + assume(typing.get_args(left) == typing.get_args(right)) 61 + assert left != right 62 + 63 + # Different origins with different arguments 64 + @given(left=..., right=..., left_args=..., right_args=...) 65 + def test_different_origins_different_args( 66 + self, 67 + left: AllGeneric, 68 + right: AllGeneric, 69 + left_args: Args, 70 + right_args: Args, 71 + ): 72 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 73 + assume(ht.get_all_generics(left) != ht.get_all_generics(right)) 74 + left = ht.format_args(left, left_args) 75 + right = ht.format_args(right, right_args) 76 + assume(typing.get_args(left) != typing.get_args(right)) 77 + assert left != right 78 + 79 + # Matching or different origins with different arguments 80 + @given(left=..., right=..., left_args=..., right_args=...) 81 + def test_different_args( 82 + self, 83 + left: AllGeneric, 84 + right: AllGeneric, 85 + left_args: Args, 86 + right_args: Args, 87 + ): 88 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 89 + left = ht.format_args(left, left_args) 90 + right = ht.format_args(right, right_args) 91 + assume(typing.get_args(left) != typing.get_args(right)) 92 + assert left != right
+38
packages/hydrox/hydrox/typing/tests/test_finality.py
··· 1 + import types 2 + import operator 3 + from hypothesis import given, strategies as st 4 + from hydrox.hypothesis import generics 5 + 6 + 7 + @given( 8 + data=st.data(), 9 + generic=st.sampled_from(generics["class"]).filter(operator.attrgetter("__final__")), 10 + ) 11 + def test_finality(data, generic): 12 + g = data.draw(st.sampled_from(generic.__generics__)) 13 + try: 14 + types.new_class("Test", (generic,)) 15 + except TypeError as e: 16 + assert str(e) == f"type '{generic}' is not an acceptable base type" 17 + else: 18 + assert False 19 + try: 20 + types.new_class("Test", (g,)) 21 + except TypeError as e: 22 + err_msg = str(e) 23 + 24 + # Adapted From: 25 + # Answer: https://stackoverflow.com/a/68938778 26 + # User: https://stackoverflow.com/users/14030287/ariadne-paradis 27 + assert any( 28 + True 29 + for part in ( 30 + "is not an acceptable base type", 31 + "Cannot subclass", 32 + "takes no arguments", 33 + ) 34 + if part in err_msg 35 + ) 36 + 37 + else: 38 + assert False
+64
packages/hydrox/hydrox/typing/tests/test_generic.py
··· 1 + # TODO: Add more. 2 + 3 + from hydrox.hypothesis import Args, AllGeneric, HTGenericAlias 4 + import hydrox.typing as ht 5 + import hydrox.helpers as hh 6 + import typing 7 + import collections.abc as abc 8 + from hypothesis import given, strategies as st, example 9 + from rich.pretty import pprint 10 + 11 + # @given( 12 + # generic=..., 13 + # maximum=st.integers(min_value=1), 14 + # ) 15 + # def test_recreate(generic: HTGenericAlias, maximum): 16 + # assert generic.__recreate__(maximum).__alias__.max == maximum 17 + 18 + 19 + @given(...) 20 + def test_generalize(generic: AllGeneric, args: Args): 21 + name = ht.get_name(generic) 22 + generic = ht.generalize(ht.format_args(generic, args)) 23 + args = ht.get_args(generic) 24 + if name in ht.Generics: 25 + assert isinstance(ht.get_origin(generic), ht.GenericMeta) 26 + if args: 27 + for arg in hh.flatten(map(ht.get_origin, args), map(ht.get_args, args)): 28 + arg_name = ht.get_name(arg) 29 + if arg_name in ht.Generics: 30 + assert isinstance(arg, ht.GenericMeta) 31 + 32 + 33 + # TODO 34 + # @given( 35 + # generic=st.just(typing.Callable[[abc.Callable], abc.Callable]), 36 + # official=st.just(typing.Callable[[abc.Callable], abc.Callable]), 37 + # ) 38 + # @example( 39 + # generic=ht.Callable[[ht.Callable], ht.Callable], 40 + # official=abc.Callable[[abc.Callable], abc.Callable], 41 + # ) 42 + # def test_officiate(generic, official): 43 + # pprint(ht.officiate.cache) 44 + # assert ht.officiate(generic) == official 45 + 46 + 47 + # TODO 48 + def test_generic(): ... 49 + 50 + 51 + # TODO 52 + def test_alias_generic(): ... 53 + 54 + 55 + @given(...) 56 + def test_generics(generic: HTGenericAlias): 57 + assert generic.__generics__ == ht.get_all_generics(generic) 58 + 59 + 60 + @given(...) 61 + def test_alias_generics(generic: HTGenericAlias, args: Args): 62 + alias = ht.format_args(generic, args) 63 + for a, g in zip(alias.__generics__, generic.__generics__, strict=True): 64 + assert a == ht.format_args(g, args), alias.__generics__
+96
packages/hydrox/hydrox/typing/tests/test_instance.py
··· 1 + import hydrox.typing as ht 2 + from hydrox.hypothesis import Data, HTGeneric 3 + import builtins 4 + from hypothesis import given, strategies as st, example 5 + from parametrized import parametrized 6 + 7 + n = 10 8 + r = range(n) 9 + 10 + 11 + # TODO: Add more. 12 + class TestInstance: 13 + @given(left=st.just(("",)), right=st.just(ht.tuple[str])) 14 + @example(left=("",), right=ht.tuple[...]) 15 + @example(left=("",), right=ht.tuple[str, ...]) 16 + @example(left=[""], right=ht.list[str]) 17 + @example(left=["0", 0], right=ht.list[str | int]) 18 + @example(left={"0": 0}, right=ht.dict[str, int]) 19 + def test_true_examples(self, left, right): 20 + assert isinstance(left, right) 21 + 22 + @given(left=st.just(("",)), right=st.just(ht.tuple[int])) 23 + @example(left=("",), right=ht.tuple[int, ...]) 24 + def test_false_examples(self, left, right): 25 + assert not isinstance(left, right) 26 + 27 + @parametrized 28 + def test_true_str(self, generic=(ht.str, ht.Str, ht.string, ht.String)): 29 + assert isinstance("0", generic) 30 + 31 + @parametrized 32 + def test_false_str(self, generic=(ht.str, ht.Str, ht.string, ht.String)): 33 + assert not isinstance(0, generic) 34 + 35 + @parametrized 36 + def test_true_iterable( 37 + self, generic=(ht.list, ht.List, ht.set, ht.Set, ht.tuple, ht.Tuple) 38 + ): 39 + assert isinstance(getattr(builtins, ht.get_name_lowered(generic))(r), generic) 40 + 41 + @parametrized 42 + def test_false_iterable( 43 + self, 44 + generic=( 45 + ht.list, 46 + ht.List, 47 + ht.set, 48 + ht.Set, 49 + ht.tuple, 50 + ht.Tuple, 51 + ht.dict, 52 + ht.Dict, 53 + ), 54 + ): 55 + assert not isinstance(r, generic) 56 + 57 + @parametrized 58 + def test_true_dict(self, generic=(ht.dict, ht.Dict)): 59 + assert isinstance(dict(zip(r, r)), generic) 60 + 61 + 62 + class TestGenericAliasInstance: 63 + @parametrized 64 + def test_true_iterable(self, generic=(ht.list, ht.List, ht.set, ht.Set)): 65 + assert isinstance( 66 + getattr(builtins, ht.get_name_lowered(generic))(r), generic[int] 67 + ) 68 + 69 + @parametrized 70 + def test_false_iterable(self, generic=(ht.list, ht.List, ht.set, ht.Set)): 71 + assert not isinstance( 72 + getattr(builtins, ht.get_name_lowered(generic))(r), generic[str] 73 + ) 74 + 75 + @parametrized 76 + def test_true_tuple(self, generic=(ht.tuple, ht.Tuple)): 77 + assert isinstance(tuple(r), generic[*[int] * n]) 78 + 79 + @given(data=..., generic=...) 80 + def test_false_tuple(self, data: Data, generic: HTGeneric[tuple]): 81 + args = data.draw( 82 + st.lists(st.sampled_from((str, int)), min_size=n, max_size=n).filter( 83 + lambda l: not all(t is int for t in l) 84 + ) 85 + ) 86 + assert not isinstance(tuple(r), generic[*args]) 87 + 88 + @parametrized 89 + def test_true_dict(self, generic=(ht.dict, ht.Dict)): 90 + assert isinstance(dict(zip(r, r)), generic[int, int]) 91 + 92 + @parametrized.product 93 + def test_false_dict( 94 + self, generic=(ht.dict, ht.Dict), args=((str, str), (int, str), (str, int)) 95 + ): 96 + assert not isinstance(dict(zip(r, r)), generic[*args])
+173
packages/hydrox/hydrox/typing/tests/test_isinmrosubclass_getter.py
··· 1 + # import hydrox.typing as ht 2 + # from hydrox.helpers import superpowerset, flatten 3 + # from hypothesis import given, strategies as st, example 4 + # from random import randint, randrange 5 + # from collections import defaultdict 6 + # from pytest import mark 7 + 8 + # all_attrs = tuple(superpowerset("in", "is", "generic", "ht", "only")) 9 + 10 + # ht_generic_attrs = [] 11 + # no_ht_generic_attrs = [] 12 + # for p in all_attrs: 13 + # if p: 14 + # if "ht" in p or "generic" in p: 15 + # ht_generic_attrs.append(p) 16 + # else: 17 + # no_ht_generic_attrs.append("".join(p)) 18 + 19 + # ends_with_in_is_attrs = [] 20 + # in_is_is_in_attrs = [] 21 + # usable_attrs = [] 22 + # only_attrs = [] 23 + # for p in ht_generic_attrs: 24 + # length = len(p) 25 + # attr = "".join(p) 26 + # if length > 1: 27 + # if p[-1] in ("in", "is"): 28 + # ends_with_in_is_attrs.append(attr) 29 + # continue 30 + # if "inis" in attr or "isin" in attr: 31 + # in_is_is_in_attrs.append(attr) 32 + # continue 33 + # current_key = "global" 34 + # new_attrs = defaultdict(str) 35 + # for a in filter( 36 + # None, flatten(part.partition("is") for part in attr.partition("in")) 37 + # ): 38 + # if a == "in": 39 + # current_key = "in" 40 + # continue 41 + # if a == "is": 42 + # current_key = "is" 43 + # continue 44 + # new_attrs[current_key] = a 45 + # global_attr = new_attrs["global"] 46 + # broken = False 47 + # for i in ("in", "is"): 48 + # i_attr = new_attrs["global"] + new_attrs[i] 49 + # if i_attr and not ("ht" in i_attr or "generic" in i_attr): 50 + # only_attrs.append(attr) 51 + # broken = True 52 + # break 53 + # if broken: 54 + # continue 55 + # usable_attrs.append(p) 56 + 57 + # multiple_in_is_attrs = [] 58 + # for p in usable_attrs: 59 + # length = len(p) 60 + # if length > 1: 61 + # in_is = "in" if randint(0, 1) else "is" 62 + # p = list(p) 63 + # for i in range(length): 64 + # pos = randrange(1, length) 65 + # p.insert(pos, in_is) 66 + # multiple_in_is_attrs.append("".join(p)) 67 + 68 + # is_in_attrs = [] 69 + # no_is_in_attrs = [] 70 + # for p in usable_attrs: 71 + # attr = "".join(p) 72 + # if "in" in p or "is" in p: 73 + # is_in_attrs.append(attr) 74 + # else: 75 + # no_is_in_attrs.append(attr) 76 + 77 + 78 + # class TestIsInMROSubclassGetter: 79 + # @given(attr=st.sampled_from(is_in_attrs)) 80 + # @example(attr="genericinhtisonly") 81 + # def test_is_in(self, attr): 82 + # only = {} 83 + # getters = {} 84 + # only["in"], getters["in"], only["is"], getters["is"] = ( 85 + # ht.isinmrosubclass.getter(attr) 86 + # ) 87 + # current_key = "global" 88 + # new_attrs = defaultdict(str) 89 + # for a in filter( 90 + # None, flatten(part.partition("is") for part in attr.partition("in")) 91 + # ): 92 + # if a == "in": 93 + # current_key = "in" 94 + # if a == "is": 95 + # current_key = "is" 96 + # new_attrs[current_key] = a 97 + # for i in ("in", "is"): 98 + # i_attr = new_attrs["global"] + new_attrs[i] 99 + # if "only" in i_attr: 100 + # assert only[i], i 101 + # if "ht" in i_attr: 102 + # if "generic" in i_attr: 103 + # assert getters[i] == ht.get_all_ht_generics, i 104 + # else: 105 + # assert getters[i] == ht.HTGenerics.__getitem__, i 106 + # elif "generic" in i_attr: 107 + # assert getters[i] == ht.get_all_generics, i 108 + # else: 109 + # assert getters[i] is None 110 + 111 + # @given(attr=st.sampled_from(no_is_in_attrs)) 112 + # def test_no_is_in(self, attr): 113 + # inmro_only, inmro_getter, issubclass_only, issubclass_getter = ( 114 + # ht.isinmrosubclass.getter(attr) 115 + # ) 116 + # if "only" in attr: 117 + # assert inmro_only and issubclass_only 118 + # if "ht" in attr: 119 + # if "generic" in attr: 120 + # assert inmro_getter == issubclass_getter == ht.get_all_ht_generics 121 + # else: 122 + # assert inmro_getter == issubclass_getter == ht.HTGenerics.__getitem__ 123 + # else: 124 + # assert inmro_getter == issubclass_getter == ht.get_all_generics 125 + 126 + # @given(attr=st.sampled_from(in_is_is_in_attrs)) 127 + # def test_in_is_is_in(self, attr): 128 + # try: 129 + # ht.isinmrosubclass.getter(attr) 130 + # except AttributeError as e: 131 + # assert '"in" and "is" cannot be right next to each other' in str(e) 132 + # else: 133 + # assert False 134 + 135 + # @given(attr=st.sampled_from(no_ht_generic_attrs)) 136 + # def test_no_ht_generic(self, attr): 137 + # try: 138 + # ht.isinmrosubclass.getter(attr) 139 + # except AttributeError as e: 140 + # assert 'A combination of "in", "is", "only", "ht", and "generic"' in str(e) 141 + # else: 142 + # assert False 143 + 144 + # @given(attr=st.sampled_from(ends_with_in_is_attrs)) 145 + # def test_starts_ends_with_in_is(self, attr): 146 + # try: 147 + # ht.isinmrosubclass.getter(attr) 148 + # except AttributeError as e: 149 + # assert 'cannot end with "in" or "is"' in str(e) 150 + # else: 151 + # assert False 152 + 153 + # @given(attr=st.sampled_from(multiple_in_is_attrs)) 154 + # def test_multiple_in_is(self, attr): 155 + # try: 156 + # ht.isinmrosubclass.getter(attr) 157 + # except AttributeError as e: 158 + # assert ( 159 + # '"in" and "is" cannot be used more than once in attribute name' 160 + # in str(e) 161 + # ) 162 + # else: 163 + # assert False 164 + 165 + # @given(attr=st.sampled_from(only_attrs)) 166 + # @example(attr="onlyisht") 167 + # def test_only(self, attr): 168 + # try: 169 + # ht.isinmrosubclass.getter(attr) 170 + # except AttributeError as e: 171 + # assert 'A combination of "only", "ht", and "generic"' in str(e) 172 + # else: 173 + # assert False
+61
packages/hydrox/hydrox/typing/tests/test_some.py
··· 1 + # from hydrox.hypothesis import Data, Args, HTGenericAlias 2 + # from hydrox.typing.generic import some 3 + # from hydrox.helpers import Collection 4 + # import hydrox.typing as ht 5 + # import typing 6 + # from hypothesis import given, strategies as st 7 + # from collections import namedtuple 8 + 9 + # Max = namedtuple("Max", "max") 10 + # minimum = 0 11 + 12 + 13 + # # TODO: Simplify. 14 + # class TestSome: 15 + # @given(data=..., generic=..., args=...) 16 + # def test_cls(self, data: Data, generic: HTGenericAlias, args: Args): 17 + # args_length = len(args) 18 + # maximum = data.draw( 19 + # st.integers( 20 + # min_value=minimum, 21 + # max_value=ht.get_generic_subscription_size(generic, rand=True), 22 + # ).filter(lambda i: i < args_length) 23 + # ) 24 + # some_args = some(ht.format_args(generic.__recreate__(maximum), args)) 25 + # some_length = len(some_args) 26 + # assert some_length == maximum 27 + # assert all(a in args for a in some_args) 28 + # assert tuple(some_args) != tuple(args) 29 + 30 + # @given(data=..., generic=..., args=..., maximum=st.integers(min_value=minimum)) 31 + # def test_maximum(self, data: Data, generic: HTGenericAlias, args: Args, maximum): 32 + # args_length = len(args) 33 + # maximum = data.draw( 34 + # st.integers( 35 + # min_value=minimum, 36 + # max_value=ht.get_generic_subscription_size(generic, rand=True), 37 + # ).filter(lambda i: i < args_length) 38 + # ) 39 + # some_args = some(ht.format_args(generic, args), maximum=maximum) 40 + # some_length = len(some_args) 41 + # assert some_length == maximum 42 + # assert all(a in args for a in some_args) 43 + # assert tuple(some_args) != tuple(args) 44 + 45 + # @given(data=..., iterable=...) 46 + # def test_iterable(self, data: Data, iterable: Collection[typing.Any]): 47 + # maximum = data.draw(st.integers(min_value=minimum, max_value=len(iterable) - 1)) 48 + # some_args = some(Max(maximum), iterable) 49 + # some_length = len(some_args) 50 + # assert some_length == maximum 51 + # assert all(a in iterable for a in some_args) 52 + # assert tuple(some_args) != tuple(iterable) 53 + 54 + # @given(data=..., iterable=...) 55 + # def test_maximum_iterable(self, data: Data, iterable: Collection[typing.Any]): 56 + # maximum = data.draw(st.integers(min_value=minimum, max_value=len(iterable) - 1)) 57 + # some_args = some(None, iterable, maximum) 58 + # some_length = len(some_args) 59 + # assert some_length == maximum 60 + # assert all(a in iterable for a in some_args) 61 + # assert tuple(some_args) != tuple(iterable)
+168
packages/hydrox/hydrox/typing/tests/test_subclass.py
··· 1 + from hydrox.hypothesis import ( 2 + Args, 3 + AllGeneric, 4 + AllGenericAlias, 5 + SubArgs, 6 + subgen_strat, 7 + subargs_strat, 8 + Data, 9 + HTGenericAlias, 10 + HTGeneric, 11 + ) 12 + import hydrox.typing as ht 13 + from hypothesis import given, strategies as st, example, assume 14 + from pytest import mark 15 + 16 + 17 + class TestSetSubclass: 18 + @given(left=..., right=...) 19 + def test_set(self, left: AllGeneric[set], right: HTGeneric[set]): 20 + assert issubclass(left, right) 21 + 22 + @given(left=st.sampled_from(ht.get_ht_generics(set)), right=...) 23 + def test_set_MutableSet(self, left, right: HTGeneric[ht.MutableSet]): 24 + ht.debugmrosubclass(left, right) 25 + assert issubclass(left, right) 26 + 27 + @given(left=st.sampled_from(ht.get_ht_generics("Set")), right=...) 28 + def test_Set_MutableSet(self, left, right: HTGeneric[ht.MutableSet]): 29 + assert not issubclass(left, right) 30 + 31 + @given(left=st.sampled_from(ht.get_ht_generics("Set")), right=..., generic=...) 32 + def test_Set_MutableSet_multiple( 33 + self, left, right: HTGeneric[ht.MutableSet], generic: AllGeneric 34 + ): 35 + assume(not ht.trysubclass(left, generic)) 36 + assert not ht.trysubclass(left, (right, generic)) 37 + 38 + @given(left=...) 39 + def test_MutableSet_set(self, left: HTGeneric[ht.MutableSet]): 40 + assert not issubclass(left, ht.set) 41 + 42 + @given(left=..., generic=...) 43 + def test_MutableSet_set_multiple( 44 + self, left: HTGeneric[ht.MutableSet], generic: AllGeneric 45 + ): 46 + assume(not ht.trysubclass(left, generic)) 47 + assert not ht.trysubclass(left, (ht.set, generic)) 48 + 49 + @given(left=...) 50 + def test_MutableSet_Set(self, left: HTGeneric[ht.MutableSet]): 51 + assert issubclass(left, ht.Set) 52 + 53 + @given(left=..., right=...) 54 + def test_MutableSet_MutableSet( 55 + self, left: HTGeneric[ht.MutableSet], right: HTGeneric[ht.MutableSet] 56 + ): 57 + assert issubclass(left, right) 58 + 59 + 60 + class TestSubclass: 61 + @given(left=st.just(ht.str), right=st.just(ht.str)) 62 + @example(left=ht.tuple, right=ht.tuple) 63 + @example(left=ht.dict, right=ht.dict) 64 + @example(left=ht.tuple[bool], right=ht.tuple) 65 + @example(left=ht.tuple[bool], right=ht.tuple[bool]) 66 + @example(left=ht.tuple[bool], right=ht.tuple[int]) 67 + @example(left=ht.dict[str, bool], right=ht.dict) 68 + @example(left=ht.dict[str, bool], right=ht.dict[str, int]) 69 + def test_true_examples(self, left, right): 70 + assert issubclass(left, right) 71 + 72 + @given(left=st.just(ht.tuple), right=st.just(ht.tuple[int])) 73 + @example(left=ht.tuple[str], right=ht.tuple[int]) 74 + @example(left=ht.tuple[str, int], right=ht.tuple[str]) 75 + @example(left=ht.tuple[str], right=ht.tuple[str, int]) 76 + @example(left=ht.tuple[str, int], right=ht.tuple[int, str]) 77 + @example(left=ht.dict, right=ht.dict[str, int]) 78 + def test_false_examples(self, left, right): 79 + assert not issubclass(left, right) 80 + 81 + # Matching origins 82 + @given(data=..., generic=...) 83 + def test_matching_origins(self, data: Data, generic: HTGeneric): 84 + assert issubclass( 85 + data.draw(subgen_strat(generic)), 86 + generic, 87 + ) 88 + 89 + # Matching origins with no right arguments 90 + @given(data=..., generic=..., args=...) 91 + def test_matching_origins_no_right_args( 92 + self, data: Data, generic: HTGenericAlias, args: Args 93 + ): 94 + assert issubclass( 95 + ht.format_args(data.draw(subgen_strat(generic)), args), generic 96 + ) 97 + 98 + # Matching origins with matching arguments 99 + @given(data=..., generic=..., args=...) 100 + def test_matching_origins_matching_args( 101 + self, data: Data, generic: HTGenericAlias, args: SubArgs 102 + ): 103 + args, subargs = args 104 + left = ht.format_args( 105 + data.draw( 106 + subgen_strat(generic).filter( 107 + lambda g: ht.get_generic_subscription_size(g) 108 + == generic.__subscriptions__ 109 + ) 110 + ), 111 + subargs, 112 + ) 113 + right = ht.format_args(generic, args) 114 + assert issubclass(left, right) 115 + 116 + # Different origins 117 + @given(data=..., generic=...) 118 + def test_different_origins(self, data: Data, generic: HTGeneric): 119 + assert not issubclass( 120 + data.draw(subgen_strat(generic, ungen=True)), 121 + generic, 122 + ) 123 + 124 + # Different origins with matching arguments 125 + @given(data=..., generic=..., args=...) 126 + def test_different_origins_matching_args( 127 + self, data: Data, generic: HTGenericAlias, args: SubArgs 128 + ): 129 + args, subargs = args 130 + left = ht.format_args( 131 + data.draw(subgen_strat(generic, ungen=True)), 132 + subargs, 133 + ) 134 + right = ht.format_args(generic, args) 135 + assert not issubclass(left, right) 136 + 137 + # Matching or different origins with different arguments 138 + @given(left=..., right=..., args=subargs_strat(ungen=True)) 139 + def test_different_args( 140 + self, 141 + left: AllGenericAlias, 142 + right: HTGenericAlias, 143 + args, 144 + ): 145 + args, subargs = args 146 + left = ht.format_args(left, subargs) 147 + right = ht.format_args(right, args) 148 + assert not issubclass(left, right) 149 + 150 + # Different origins with no right arguments 151 + @given(data=..., generic=..., args=...) 152 + def test_different_origins_no_right_args( 153 + self, data: Data, generic: HTGenericAlias, args: Args 154 + ): 155 + assert not issubclass( 156 + ht.format_args( 157 + data.draw(subgen_strat(generic, ungen=True)), 158 + args, 159 + ), 160 + generic, 161 + ) 162 + 163 + # Matching or different origins with no left arguments 164 + @given(left=..., right=..., args=...) 165 + def test_no_left_arguments( 166 + self, left: AllGenericAlias, right: HTGenericAlias, args: Args 167 + ): 168 + assert not issubclass(left, ht.format_args(right, args))
+50
packages/hydrox/hydrox/typing/tests/test_subscriptions.py
··· 1 + from hydrox.hypothesis import ( 2 + Args, 3 + HTGeneric, 4 + ) 5 + from hypothesis import given, assume 6 + import hydrox.typing as ht 7 + import collections 8 + from parametrized import parametrized 9 + 10 + 11 + class TestSubscriptionSizes: 12 + @given(generic=...) 13 + def test_generics(self, generic: HTGeneric): 14 + for g in (*ht.get_all_ht_generics(generic), ht.get_name(generic)): 15 + assert ht.get_generic_subscription_size(g) == generic.__subscriptions__ 16 + 17 + @parametrized.zip 18 + def test_specific( 19 + self, 20 + generic=(tuple, str, collections.Counter, collections.UserDict), 21 + subscription=(-1, 0, 1, 2), 22 + ): 23 + name = ht.get_name(generic) 24 + for g in (*ht.get_all_ht_generics(generic), name): 25 + assert ht.get_generic_subscription_size(g) == subscription 26 + 27 + @given(generic=..., args=...) 28 + def test_generic_errors(self, generic: HTGeneric, args: Args): 29 + subscriptions = generic.__subscriptions__ 30 + assume(abs(subscriptions) != 1) 31 + match subscriptions: 32 + case 0 | 2: 33 + args = [args[0]] 34 + case _: 35 + args = args[:2] 36 + args_length = len(args) 37 + try: 38 + generic[*args] 39 + except TypeError as e: 40 + err_msg = str(e) 41 + name = ht.format_name(generic) 42 + if subscriptions: 43 + assert ( 44 + err_msg 45 + == f"Too {'many' if args_length > subscriptions else 'few'} arguments for {name}; actual {args_length}, expected {subscriptions}" 46 + ) 47 + else: 48 + assert err_msg == f"type '{name}' is not subscriptable" 49 + else: 50 + assert False
+7
packages/hydrox/hydrox/typing/tests/test_tests.py
··· 1 + import os 2 + 3 + 4 + class TestTests: 5 + def test_tests(self): 6 + # raise Exception(os.environ["PYTEST_CURRENT_TEST"]) 7 + ...
+177
packages/hydrox/hydrox/typing/tests/test_type.py
··· 1 + import hydrox.typing as ht 2 + from functools import partial 3 + from hypothesis import given, strategies as st, example 4 + from hydrox.hypothesis import rand_strat 5 + from hydrox.helpers import coflag 6 + from hydrox.hypothesis import AllGeneric 7 + from parametrized import parametrized 8 + from pytest import fixture 9 + from random import randrange 10 + from string import ascii_lowercase as lowercase 11 + from types import GeneratorType 12 + from types import new_class 13 + from typing import Any 14 + 15 + getters = (ht.get_type, ht.supertype, ht.type, ht.Type) 16 + ints = range(10) 17 + rand_strat_part = partial(rand_strat, "functions", levels=0) 18 + union = ht.union 19 + 20 + 21 + class TestTypeGetter: 22 + @fixture 23 + def f1(self, scope="class"): 24 + def func(): 25 + yield from ints 26 + 27 + return func 28 + 29 + @fixture 30 + def f2(self, scope="class"): 31 + def func(): 32 + for i in ints: 33 + yield i 34 + 35 + return func 36 + 37 + @given(iterable=rand_strat_part(only_iterables="tuples", min_size=1)) 38 + @example(iterable=(0,)) 39 + @example(iterable=("",)) 40 + @example(iterable=("", 0)) 41 + @parametrized 42 + def test_tuple(self, iterable, getter=getters): 43 + types = [type(o) for o in iterable] 44 + print(getter, iterable, types) 45 + assert getter(iterable) == ht.tuple[*types] == ht.Tuple[*types] 46 + 47 + @given( 48 + data=st.data(), 49 + getter=st.sampled_from(getters), 50 + generic=st.sampled_from((ht.list, ht.List, ht.set, ht.Set)), 51 + ) 52 + def test_iterable(self, data, getter, generic): 53 + iterable = data.draw( 54 + rand_strat_part( 55 + only_iterables=ht.get_name_lowered(generic) + "s", min_size=1 56 + ) 57 + ) 58 + types = [type(o) for o in iterable] 59 + assert ( 60 + getter(iterable) 61 + == generic[union[*types]] 62 + == generic.__camel__[union.__camel__[*types]] 63 + ) 64 + 65 + @given(iterable=st.just([0])) 66 + @example(iterable=[""]) 67 + @example(iterable=["", 0]) 68 + @parametrized.product 69 + def test_iterable_examples(self, iterable, getter=getters): 70 + generic = ht.generalize(type(iterable)) 71 + types = [type(o) for o in iterable] 72 + assert ( 73 + getter(iterable) 74 + == generic[union[*types]] 75 + == generic.__camel__[union.__camel__[*types]] 76 + ) 77 + 78 + @given( 79 + getter=st.sampled_from(getters), 80 + iterable=rand_strat_part(only_iterables="dictionaries", min_size=1), 81 + ) 82 + def test_dictionary(self, getter, iterable): 83 + keys = [type(o) for o in iterable.keys()] 84 + values = [type(o) for o in iterable.values()] 85 + assert ( 86 + getter(iterable) 87 + == ht.dict[union[*keys], union[*values]] 88 + == ht.Dict[union.__camel__[*keys], union.__camel__[*values]] 89 + ) 90 + 91 + @given(getter=st.sampled_from(getters)) 92 + @parametrized.zip 93 + def test_iterator( 94 + self, getter, i=((i for i in ints), map(str, ints)), t=(GeneratorType, map) 95 + ): 96 + assert getter(i) == t 97 + 98 + @given(getter=st.sampled_from(getters)) 99 + def test_iterator_functions(self, getter, f1, f2): 100 + assert getter(f1()) == getter(f2()) == GeneratorType 101 + 102 + 103 + class TestCallableTypeGetter: 104 + @given( 105 + getter=st.sampled_from(getters), 106 + annotations=st.lists( 107 + st.from_type(AllGeneric), min_size=0, max_size=len(lowercase) 108 + ), 109 + ) 110 + def test_callable(self, getter, annotations): 111 + def f(): ... 112 + 113 + return_type = annotations[-1] if annotations else None 114 + arg_types = annotations[:-1] 115 + length = len(arg_types) 116 + co_varnames = tuple(lowercase[:length]) 117 + f_annotations = { 118 + c: t for c, t in zip(co_varnames, arg_types, strict=True) if randrange(0, 1) 119 + } 120 + 121 + # Adapted From: 122 + # Answer 1: https://stackoverflow.com/a/11292547 123 + # User 1: https://stackoverflow.com/users/534790/ahsan 124 + # Answer 2: https://stackoverflow.com/a/77155447 125 + # User 2: https://stackoverflow.com/users/6890912/blhsing 126 + # Answer 3: https://stackoverflow.com/a/73607744 127 + # User 3: https://stackoverflow.com/users/3936044/mandera 128 + # Answer 4: https://stackoverflow.com/a/17625756 129 + # User 4: https://stackoverflow.com/users/908494/abarnert 130 + # And: https://chatgpt.com/c/7a03b03f-8150-43cb-9ae0-e6fd111a05b6 131 + f.__code__ = f.__code__.replace( 132 + co_argcount=length, 133 + co_flags=coflag(), 134 + co_varnames=co_varnames, 135 + co_nlocals=length, 136 + ) 137 + f.__annotations__ = f_annotations | {"return": return_type} 138 + 139 + assert ( 140 + getter(f) 141 + == ht.Callable[ 142 + [ 143 + t if c in f_annotations else Any 144 + for c, t in zip(co_varnames, arg_types, strict=True) 145 + ], 146 + return_type, 147 + ] 148 + ) 149 + 150 + # @given(getter=st.sampled_from(getters)) 151 + # @parametrized.zip 152 + # def test_specific_callables(self, callable=(lambda: None,), annotation=(,)): 153 + # assert 154 + 155 + # TODO: This tests `types.FunctionType` and what else? 156 + def test_callable_call(self): ... 157 + 158 + 159 + excluded = ( 160 + "__init_subclass__", 161 + "__subclasshook__", 162 + "__module__", 163 + "__dict__", 164 + "__weakref__", 165 + ) 166 + 167 + 168 + class TestTypeCreator: 169 + @given(creator=st.sampled_from(getters[1:])) 170 + def test_creation(self, creator): 171 + T1 = creator("Test", tuple(), {}) 172 + dir1 = {k: getattr(T1, k, None) for k in dir(T1) if k not in excluded} 173 + T2 = type("Test", tuple(), {}) 174 + dir2 = {k: getattr(T2, k, None) for k in dir(T2) if k not in excluded} 175 + T3 = new_class("Test", tuple(), {}) 176 + dir3 = {k: getattr(T3, k, None) for k in dir(T3) if k not in excluded} 177 + assert dir1 == dir2 == dir3
+107
packages/hydrox/hydrox/typing/tests/types/test_callable.py
··· 1 + from hypothesis import given 2 + import hydrox.typing as ht 3 + from hydrox.hypothesis import HTGeneric 4 + import typing 5 + from parametrized import parametrized 6 + from pytest import fixture 7 + 8 + 9 + @fixture 10 + def f(): 11 + def f(a: str, b: bool) -> int: 12 + return int(b) 13 + 14 + return f 15 + 16 + 17 + class TestCallableSubscriptionSize: 18 + @given(generic=...) 19 + def test_list(self, generic: HTGeneric[typing.Callable]): 20 + assert generic[[int, int], int] 21 + 22 + @given(generic=..., ps=...) 23 + def test_paramspec( 24 + self, generic: HTGeneric[typing.Callable], ps: HTGeneric[typing.ParamSpec] 25 + ): 26 + assert generic[ps, int] 27 + 28 + @given(generic=..., concat=...) 29 + def test_concatenate( 30 + self, 31 + generic: HTGeneric[typing.Callable], 32 + concat: HTGeneric[ht.Concatenate], 33 + ): 34 + assert generic[concat[int, ...], int] == generic[concat[int, ...], int] 35 + 36 + @given(generic=...) 37 + def test_flat_2_list(self, generic: HTGeneric[typing.Callable]): 38 + assert generic[int, int] == generic[[int], int] 39 + 40 + @given(generic=...) 41 + def test_ellipsed_flat_2_list(self, generic: HTGeneric[typing.Callable]): 42 + assert generic[..., int] == generic[..., int] 43 + 44 + @given(generic=...) 45 + def test_used_as(self, generic: HTGeneric[typing.Callable]): 46 + try: 47 + generic[int, int, int] 48 + except TypeError as e: 49 + name = ht.format_name(generic) 50 + assert str(e) == f"{name} must be used as {name}[[arg, ...], result]." 51 + else: 52 + assert False 53 + 54 + @given(generic=...) 55 + def test_expected(self, generic: HTGeneric[typing.Callable]): 56 + try: 57 + generic[0, int] 58 + except TypeError as e: 59 + assert ( 60 + str(e) 61 + == "Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got 0" 62 + ) 63 + else: 64 + assert False 65 + 66 + 67 + class TestCallableInstance: 68 + @parametrized 69 + def test_is_None( 70 + self, 71 + generic=( 72 + ht.Callable, 73 + ht.Callable[..., typing.Any], 74 + ht.Callable[[typing.Any, typing.Any], typing.Any], 75 + ), 76 + ): 77 + assert isinstance(lambda a, b: None, generic) 78 + 79 + @parametrized 80 + def test_is_str_bool_int( 81 + self, 82 + f, 83 + generic=(ht.Callable, ht.Callable[..., int], ht.Callable[[str, bool], int]), 84 + ): 85 + assert isinstance(f, generic) 86 + 87 + @parametrized 88 + def test_not_None( 89 + self, 90 + generic=( 91 + ht.Callable[..., int], 92 + ht.Callable[[str, bool], int], 93 + ), 94 + ): 95 + assert not isinstance(lambda a, b: None, generic) 96 + 97 + @parametrized 98 + def test_not_str_int_bool( 99 + self, 100 + f, 101 + generic=( 102 + ht.Callable[..., bool], 103 + ht.Callable[[str, int], bool], 104 + ht.Callable[[str], bool], 105 + ), 106 + ): 107 + assert not isinstance(f, generic)
+24
packages/hydrox/hydrox/typing/tests/types/test_check.py
··· 1 + import hydrox.typing as ht 2 + from hydrox.hypothesis import HTGeneric 3 + from hypothesis import given, strategies as st 4 + 5 + checks = (ht.check, ht.Check) 6 + funcs = (bool, lambda x: x) 7 + 8 + 9 + class TestCheck: 10 + @given(i=st.integers(min_value=1), generic=...) 11 + def test_true_instance(self, i, generic: HTGeneric[ht.Check]): 12 + assert isinstance(i, generic[lambda j: j > 0]) 13 + 14 + @given(i=st.integers(max_value=0), generic=...) 15 + def test_false_instance(self, i, generic: HTGeneric[ht.Check]): 16 + assert not isinstance(i, generic[lambda j: j > 0]) 17 + 18 + @given(i=st.integers().filter(bool), generic=..., func=st.sampled_from(funcs)) 19 + def test_non_zero_instance(self, i, generic: HTGeneric[ht.Check], func): 20 + assert isinstance(i, generic[func]) 21 + 22 + @given(generic=..., func=st.sampled_from(funcs)) 23 + def test_zero_instance(self, generic: HTGeneric[ht.Check], func): 24 + assert not isinstance(0, generic[func])
+127
packages/hydrox/hydrox/typing/tests/types/test_coll.py
··· 1 + from hydrox.hypothesis import ( 2 + HTGeneric, 3 + Args, 4 + HTGenericAlias, 5 + AllGeneric, 6 + rand_strat, 7 + ) 8 + import hydrox.typing as ht 9 + from hydrox.helpers import Collection 10 + from hypothesis import given, assume 11 + 12 + 13 + class TestColl: 14 + @given(generic=..., iterable=...) 15 + def test_true_instance(self, generic: HTGeneric[ht.Coll], iterable: Collection): 16 + assert isinstance(iterable, generic) 17 + 18 + @given(generic=...) 19 + def test_true_range_instance(self, generic: HTGeneric[ht.Coll]): 20 + assert isinstance(range(10), generic) 21 + 22 + @given( 23 + generic=..., 24 + iterable=rand_strat( 25 + "dictionaries", "tuples", only_in_iterables=int, iterable=True, min_size=1 26 + ), 27 + ) 28 + def test_true_int_alias_instance(self, generic: HTGeneric[ht.Coll], iterable): 29 + assert isinstance(iterable, generic[int]) 30 + 31 + @given( 32 + generic=..., 33 + iterable=rand_strat( 34 + "dictionaries", "tuples", only_in_iterables=str, iterable=True, min_size=1 35 + ), 36 + ) 37 + def test_true_str_alias_instance(self, generic: HTGeneric[ht.Coll], iterable): 38 + assert isinstance(iterable, generic[str]) 39 + 40 + @given( 41 + generic=..., 42 + iterable=rand_strat( 43 + only_iterables="dictionaries", 44 + only_in_iterables=int, 45 + iterable=True, 46 + min_size=1, 47 + ), 48 + ) 49 + def test_true_dict_int_alias_instance(self, generic: HTGeneric[ht.Coll], iterable): 50 + assert isinstance(iterable, generic[int, int]) 51 + 52 + @given( 53 + generic=..., 54 + iterable=rand_strat( 55 + only_iterables="dictionaries", 56 + only_in_iterables=str, 57 + iterable=True, 58 + min_size=1, 59 + ), 60 + ) 61 + def test_true_dict_str_alias_instance(self, generic: HTGeneric[ht.Coll], iterable): 62 + assert isinstance(iterable, generic[str, str]) 63 + 64 + @given( 65 + generic=..., 66 + iterable=rand_strat( 67 + only_iterables="tuples", 68 + only_in_iterables=int, 69 + iterable=True, 70 + min_size=1, 71 + ), 72 + ) 73 + def test_true_dict_int_alias_instance(self, generic: HTGeneric[ht.Coll], iterable): 74 + assert isinstance(iterable, generic[*[int] * len(iterable)]) 75 + 76 + @given( 77 + generic=..., 78 + iterable=rand_strat( 79 + only_iterables="tuples", 80 + only_in_iterables=str, 81 + iterable=True, 82 + min_size=1, 83 + ), 84 + ) 85 + def test_true_dict_str_alias_instance(self, generic: HTGeneric[ht.Coll], iterable): 86 + assert isinstance(iterable, generic[*[str] * len(iterable)]) 87 + 88 + @given(generic=..., string=...) 89 + def test_false_instance(self, generic: HTGeneric[ht.Coll], string: str): 90 + assert not isinstance(string, generic) 91 + 92 + @given(generic=..., iterable=...) 93 + def test_false_str_alias_instance( 94 + self, generic: HTGeneric[ht.Coll], iterable: Collection[int] 95 + ): 96 + assert not isinstance(iterable, generic[str]) 97 + 98 + @given(generic=..., iterable=...) 99 + def test_false_int_alias_instance( 100 + self, generic: HTGeneric[ht.Coll], iterable: Collection[str] 101 + ): 102 + assert not isinstance(iterable, generic[int]) 103 + 104 + @given(generic=..., t=..., args=...) 105 + def test_generic_aliases( 106 + self, generic: HTGeneric[ht.Coll], t: HTGenericAlias, args: Args 107 + ): 108 + assert not isinstance(ht.format_args(t, args), generic) 109 + 110 + @given(left=..., right=...) 111 + def test_true_subclass( 112 + self, left: HTGeneric[ht.Coll], right: HTGeneric[ht.Iterable] 113 + ): 114 + assert issubclass(left, right) 115 + 116 + @given(left=..., right=...) 117 + def test_false_subclass(self, left: HTGeneric[ht.Coll], right: AllGeneric): 118 + assume(right not in ht.get_all_ht("Iterable")) 119 + assert not issubclass(left, right) 120 + 121 + @given(left=..., right=...) 122 + def test_true_equality(self, left: HTGeneric[ht.Coll], right: HTGeneric[ht.Coll]): 123 + assert left == right 124 + 125 + @given(left=..., right=...) 126 + def test_false_equality(self, left: HTGeneric[ht.Coll], right: AllGeneric): 127 + assert not left == right
+55
packages/hydrox/hydrox/typing/tests/types/test_concatenate.py
··· 1 + import hydrox.typing as ht 2 + from hypothesis import given, strategies as st, example 3 + from hydrox.hypothesis import HTGeneric 4 + 5 + 6 + # TODO: Simplify with `given`, and use the lowercased version as well. 7 + class TestConcatenateSubclass: 8 + @given(left=st.just(ht.Concatenate[...]), right=st.just(ht.Concatenate[...])) 9 + @example(left=ht.Concatenate[...], right=ht.Concatenate) 10 + @example(left=ht.Concatenate[ht.ParamSpec("P")], right=ht.Concatenate) 11 + @example( 12 + left=ht.Concatenate[ht.ParamSpec("P")], right=ht.Concatenate[ht.ParamSpec("P")] 13 + ) 14 + @example(left=ht.Concatenate[bool, ...], right=ht.Concatenate) 15 + @example(left=ht.Concatenate[bool, ht.ParamSpec("P")], right=ht.Concatenate) 16 + @example(left=ht.Concatenate[bool, ...], right=ht.Concatenate[int, ...]) 17 + @example( 18 + left=ht.Concatenate[bool, ht.ParamSpec("P")], right=ht.Concatenate[int, ...] 19 + ) 20 + @example( 21 + left=ht.Concatenate[bool, ht.ParamSpec("P")], 22 + right=ht.Concatenate[int, ht.ParamSpec("P")], 23 + ) 24 + @example(left=ht.Concatenate[ht.ParamSpec("P"), ...], right=ht.Concatenate) 25 + @example(left=ht.Concatenate[..., ht.ParamSpec("P")], right=ht.Concatenate) 26 + def test_true_subclass(self, left, right): 27 + assert issubclass(left, right) 28 + 29 + @given(left=st.just(ht.Concatenate), right=st.just(ht.Concatenate[...])) 30 + @example(left=ht.Concatenate, right=ht.Concatenate[ht.ParamSpec("P")]) 31 + @example(left=ht.Concatenate, right=ht.Concatenate[str, ...]) 32 + @example(left=ht.Concatenate, right=ht.Concatenate[str, ht.ParamSpec("P")]) 33 + def test_false_subclass(self, left, right): 34 + assert not issubclass(left, right) 35 + 36 + 37 + class TestConcatenateSubscriptions: 38 + @given(generic=...) 39 + def test_last_parameter_fail(self, generic: HTGeneric[ht.Concatenate]): 40 + try: 41 + generic[int, int] 42 + except TypeError as e: 43 + assert ( 44 + str(e) 45 + == "The last parameter to Concatenate should be a ParamSpec variable or ellipsis." 46 + ) 47 + else: 48 + assert False 49 + 50 + @given( 51 + generic=..., 52 + param=(st.sampled_from((ht.ParamSpec("P"), ht.paramspec("p"), ...))), 53 + ) 54 + def test_last_parameter_succeed(self, generic: HTGeneric[ht.Concatenate], param): 55 + assert generic[int, param]
+110
packages/hydrox/hydrox/typing/tests/types/test_is_literal.py
··· 1 + import hydrox.typing as ht 2 + from hydrox.hypothesis import HTGeneric 3 + from hydrox.hypothesis import rand_strat 4 + from hypothesis import given, strategies as st, assume 5 + 6 + unhashable_literals = (ht.unhashableliteral, ht.UnhashableLiteral) 7 + unhashable_iss = (ht.unhashableis, ht.UnhashableIs) 8 + 9 + 10 + class TestLiteral: 11 + @given( 12 + data=st.data(), 13 + objs=rand_strat( 14 + only_iterables="tuples", 15 + hashable_only_in_iterable=True, 16 + levels=0, 17 + min_size=1, 18 + ), 19 + generic=..., 20 + ) 21 + def test_true_instance(self, data, objs, generic: HTGeneric[ht.Literal]): 22 + assert isinstance(data.draw(st.sampled_from(objs)), generic[*objs]) 23 + 24 + @given( 25 + objs=rand_strat( 26 + only_iterables="tuples", 27 + hashable_only_in_iterable=True, 28 + levels=0, 29 + min_size=1, 30 + ), 31 + generic=..., 32 + ) 33 + def test_false_instance(self, objs, generic: HTGeneric[ht.Literal]): 34 + assert not isinstance(object(), generic[*objs]) 35 + 36 + @given(t=..., generic=...) 37 + def test_true_equality(self, t: HTGeneric, generic: HTGeneric[ht.Literal]): 38 + assert generic[t] == generic[t.__camel__] 39 + 40 + @given(t1=..., t2=..., generic=...) 41 + def test_false_equality( 42 + self, t1: HTGeneric, t2: HTGeneric, generic: HTGeneric[ht.Literal] 43 + ): 44 + assume(t1 != t2) 45 + assert generic[t1] != generic[t2] 46 + 47 + 48 + class TestIs: 49 + @given(t=..., generic=...) 50 + def test_true_instance(self, t: HTGeneric, generic: HTGeneric[ht.Is]): 51 + assert isinstance(t, generic[t]) 52 + 53 + @given(t=..., generic=...) 54 + def test_false_instance(self, t: HTGeneric, generic: HTGeneric[ht.Is]): 55 + assert not isinstance(t, generic[*t.__generics__, t.__camel__]) 56 + 57 + @given(t=..., generic=...) 58 + def test_true_equality(self, t: HTGeneric, generic: HTGeneric[ht.Is]): 59 + assert generic[t] == generic[t] 60 + 61 + @given(t=..., generic=...) 62 + def test_false_equality(self, t: HTGeneric, generic: HTGeneric[ht.Is]): 63 + assert generic[t] != generic[t.__camel__] 64 + 65 + 66 + # TODO: Create classes that can't be hashed for use with these. 67 + class TestUnhashableLiteral: 68 + @given( 69 + data=st.data(), 70 + objs=rand_strat(only_iterables="tuples", levels=0, min_size=1), 71 + generic=..., 72 + ) 73 + def test_true_instance(self, data, objs, generic: HTGeneric[ht.UnhashableLiteral]): 74 + assert isinstance(data.draw(st.sampled_from(objs)), generic[*objs]) 75 + 76 + @given(objs=rand_strat(only_iterables="tuples", levels=0, min_size=1), generic=...) 77 + def test_false_instance(self, objs, generic: HTGeneric[ht.UnhashableLiteral]): 78 + assert not isinstance(object(), generic[*objs]) 79 + 80 + @given(t=..., generic=...) 81 + def test_true_equality( 82 + self, t: HTGeneric, generic: HTGeneric[ht.UnhashableLiteral] 83 + ): 84 + assert generic[t] == generic[t.__camel__] 85 + 86 + @given(t1=..., t2=..., generic=...) 87 + def test_false_equality( 88 + self, t1: HTGeneric, t2: HTGeneric, generic: HTGeneric[ht.UnhashableLiteral] 89 + ): 90 + assume(t1 != t2) 91 + assert generic[t1] != generic[t2] 92 + 93 + 94 + # TODO: Create classes that can't be hashed for use with these. 95 + class TestUnhashableIs: 96 + @given(t=..., generic=...) 97 + def test_true_instance(self, t: HTGeneric, generic: HTGeneric[ht.UnhashableIs]): 98 + assert isinstance(t, generic[t]) 99 + 100 + @given(t=..., generic=...) 101 + def test_false_instance(self, t: HTGeneric, generic: HTGeneric[ht.UnhashableIs]): 102 + assert not isinstance(t, generic[*t.__generics__]) 103 + 104 + @given(t=..., generic=...) 105 + def test_true_equality(self, t: HTGeneric, generic: HTGeneric[ht.UnhashableIs]): 106 + assert generic[t] == generic[t] 107 + 108 + @given(t=..., generic=...) 109 + def test_false_equality(self, t: HTGeneric, generic: HTGeneric[ht.UnhashableIs]): 110 + assert generic[t] != generic[t.__camel__]
+22
packages/hydrox/hydrox/typing/tests/types/test_newtype.py
··· 1 + import hydrox.typing as ht 2 + from hypothesis import given, strategies as st, example 3 + 4 + 5 + # TODO: Simplify with `given`, and use the lowercased version as well. 6 + class TestNewType: 7 + # @given(left=st.just(ht.NewType("T", bool)), right=st.just(ht.NewType)) 8 + # @example(left=ht.NewType("T", bool), right=ht.NewType("T", int)) 9 + @given(left=st.just(ht.NewType("T", bool)), right=st.just(ht.NewType("T", int))) 10 + def test_true_subclass(self, left, right): 11 + assert issubclass(left, right) 12 + 13 + @given(left=st.just(ht.NewType), right=st.just(ht.NewType("T", int))) 14 + @example(left=ht.NewType("T", str), right=ht.NewType("T", int)) 15 + def test_false_subclass(self, left, right): 16 + assert not issubclass(left, right) 17 + 18 + # def test_true_instance(self, left, right): 19 + # ... 20 + 21 + # def test_false_instance(self, left, right): 22 + # ...
+32
packages/hydrox/hydrox/typing/tests/types/test_not.py
··· 1 + import hydrox.typing as ht 2 + from hydrox.hypothesis import ( 3 + Data, 4 + AllGeneric, 5 + HTGeneric, 6 + subgen_strat, 7 + ) 8 + from hydrox.hypothesis import rand_strat 9 + from hypothesis import given, assume 10 + 11 + 12 + # TODO 13 + class TestNot: 14 + @given(data=..., generic=..., t=...) 15 + def test_true_subclass(self, data: Data, generic: HTGeneric[ht.Not], t: AllGeneric): 16 + assert ht.isinmrosubclass(data.draw(subgen_strat(t, ungen=True)), generic[t]) 17 + 18 + @given(data=..., generic=..., t=...) 19 + def test_false_subclass( 20 + self, data: Data, generic: HTGeneric[ht.Not], t: AllGeneric 21 + ): 22 + subcls = data.draw(subgen_strat(t)) 23 + assert not ht.isinmrosubclass(subcls, generic[t]) 24 + 25 + @given(a=rand_strat(), b=rand_strat(), generic=...) 26 + def test_true_instance(self, a, b, generic: HTGeneric[ht.Not]): 27 + assume(not isinstance(a, ht.get_type(b))) 28 + assert isinstance(a, generic[ht.get_type(b)]) 29 + 30 + @given(data=rand_strat(), generic=...) 31 + def test_false_instance(self, data, generic: HTGeneric[ht.Not]): 32 + assert not isinstance(data, generic[ht.get_type(data)])
packages/hydrox/hydrox/typing/tests/types/test_optional.py

This is a binary file and will not be displayed.

+29
packages/hydrox/hydrox/typing/tests/types/test_paramspec.py
··· 1 + import hydrox.typing as ht 2 + from hypothesis import given, strategies as st, example 3 + 4 + 5 + # TODO: Simplify with `given`, and use the lowercased version as well. 6 + class TestParamSpec: 7 + @given(left=st.just(ht.ParamSpec("P")), right=st.just(ht.ParamSpec("P"))) 8 + # @example(left=ht.ParamSpec("P"), right=ht.ParamSpec) 9 + @example( 10 + left=ht.ParamSpec("P", covariant=True), right=ht.ParamSpec("P", covariant=True) 11 + ) 12 + @example( 13 + left=ht.ParamSpec("P", contravariant=True), 14 + right=ht.ParamSpec("P", contravariant=True), 15 + ) 16 + def test_true_subclass(self, left, right): 17 + assert issubclass(left, right) 18 + 19 + @given(left=st.just(ht.ParamSpec("P")), right=st.just(ht.ParamSpec("T"))) 20 + @example(left=ht.ParamSpec, right=ht.ParamSpec("P")) 21 + @example( 22 + left=ht.ParamSpec("P", covariant=True), right=ht.ParamSpec("P", covariant=False) 23 + ) 24 + @example( 25 + left=ht.ParamSpec("P", contravariant=True), 26 + right=ht.ParamSpec("P", contravariant=False), 27 + ) 28 + def test_false_subclass(self, left, right): 29 + assert not issubclass(left, right)
+58
packages/hydrox/hydrox/typing/tests/types/test_type.py
··· 1 + from hydrox.hypothesis import Data, subgen_strat, HTGeneric 2 + import hydrox.typing as ht 3 + from hypothesis import given, strategies as st 4 + 5 + 6 + class TestType: 7 + @given(data=..., generic=..., t=...) 8 + def test_true_instance(self, data: Data, generic: HTGeneric[type], t: HTGeneric): 9 + assert isinstance(data.draw(subgen_strat(t)), generic[t]) 10 + 11 + @given(data=..., generic=..., t=...) 12 + def test_false_instance(self, data: Data, generic: HTGeneric[type], t: HTGeneric): 13 + assert not isinstance(data.draw(subgen_strat(t, ungen=True)), generic[t]) 14 + 15 + 16 + class TestExactType: 17 + @given(data=..., generic=..., t=...) 18 + def test_true_instance( 19 + self, data: Data, generic: HTGeneric[ht.ExactType], t: HTGeneric 20 + ): 21 + assert isinstance( 22 + data.draw(st.sampled_from(ht.get_all_ht_generics(t))), generic[t] 23 + ) 24 + 25 + @given(data=..., generic=..., t=...) 26 + def test_false_instance( 27 + self, data: Data, generic: HTGeneric[ht.ExactType], t: HTGeneric 28 + ): 29 + assert not isinstance( 30 + data.draw( 31 + st.one_of( 32 + subgen_strat(t).filter(lambda s: t != s), 33 + subgen_strat(t, ungen=True), 34 + ) 35 + ), 36 + generic[t], 37 + ) 38 + 39 + 40 + class TestIsType: 41 + @given(generic=..., t=...) 42 + def test_true_instance(self, generic: HTGeneric[ht.IsType], t: HTGeneric): 43 + assert isinstance(t, generic[t]) 44 + 45 + @given(data=..., generic=..., t=...) 46 + def test_false_instance( 47 + self, data: Data, generic: HTGeneric[ht.IsType], t: HTGeneric 48 + ): 49 + assert not isinstance( 50 + data.draw( 51 + st.one_of( 52 + st.sampled_from((t.__camel__, *t.__generics__)), 53 + subgen_strat(t).filter(lambda s: t != s), 54 + subgen_strat(t, ungen=True), 55 + ) 56 + ), 57 + generic[t], 58 + )
+90
packages/hydrox/hydrox/typing/tests/types/test_types.py
··· 1 + import hydrox.typing as ht 2 + import builtins 3 + import collections.abc as abc 4 + import typing 5 + from parametrized import parametrized 6 + 7 + n = 10 8 + r = range(n) 9 + s = [str(i) for i in r] 10 + 11 + __generics__ = { 12 + "callable": (abc.Callable, typing.Callable), 13 + "iterable": (abc.Iterable, typing.Iterable), 14 + "list": (builtins.list, typing.List), 15 + "set": (builtins.set, abc.Set, typing.Set), 16 + "tuple": (builtins.tuple, typing.Tuple), 17 + "dict": (builtins.dict, typing.Dict), 18 + } 19 + 20 + 21 + # def test_forwardref(): 22 + # fr = ht.ForwardRef("FR") 23 + 24 + # class FR: ... 25 + 26 + # assert isinstance(FR(), fr) 27 + 28 + # class GR(FR): ... 29 + 30 + # assert ht.isinmrosubclass(GR, fr) 31 + 32 + 33 + @parametrized 34 + def test_type_generics( 35 + generic=( 36 + ht.callable, 37 + ht.Callable, 38 + ht.iterable, 39 + ht.Iterable, 40 + ht.list, 41 + ht.List, 42 + ht.set, 43 + ht.Set, 44 + ht.tuple, 45 + ht.Tuple, 46 + ht.dict, 47 + ht.Dict, 48 + ), 49 + ): 50 + assert generic.__generics__ == __generics__[ht.get_name_lowered(generic)] 51 + 52 + 53 + # TODO: Add more. 54 + class TestTypes: 55 + @parametrized 56 + def test_str(self, generic=(ht.str, ht.Str, ht.string, ht.String)): 57 + assert generic(0) == "0" 58 + 59 + @parametrized 60 + def test_iterables( 61 + self, generic=(ht.list, ht.List, ht.set, ht.Set, ht.tuple, ht.Tuple) 62 + ): 63 + assert generic(r) == getattr(builtins, ht.get_name_lowered(generic))(r) 64 + 65 + @parametrized 66 + def test_dict(self, generic=(ht.dict, ht.Dict)): 67 + assert generic(zip(r, r)) == getattr(builtins, ht.get_name_lowered(generic))( 68 + zip(r, r) 69 + ) 70 + 71 + 72 + class TestGenericAliasTypes: 73 + @parametrized 74 + def test_iterables(self, generic=(ht.list, ht.List, ht.set, ht.Set)): 75 + assert generic[str](r) == getattr(builtins, ht.get_name_lowered(generic))( 76 + map(str, r) 77 + ) 78 + 79 + @parametrized 80 + def test_tuple(self, generic=(ht.tuple, ht.Tuple)): 81 + n = 10 82 + assert generic[*[str] * n](range(n)) == getattr( 83 + builtins, ht.get_name_lowered(generic) 84 + )(s) 85 + 86 + @parametrized 87 + def test_dict(self, generic=(ht.dict, ht.Dict)): 88 + assert generic[str, str](zip(r, r)) == getattr( 89 + builtins, ht.get_name_lowered(generic) 90 + )(zip(s, s))
+128
packages/hydrox/hydrox/typing/tests/types/test_typevar.py
··· 1 + import hydrox.typing as ht, typing 2 + from hydrox.hypothesis import AllGeneric, Data, HTGeneric, Args, SubArgs, subgen_strat 3 + from hypothesis import assume, given, strategies as st, example 4 + 5 + 6 + # TODO: Simplify with `given`. 7 + # TODO: Test errors. 8 + class TestTypeVar: 9 + @given( 10 + data=..., 11 + left=..., 12 + right=..., 13 + left_name=st.characters(), 14 + right_name=st.characters(), 15 + bound=st.one_of(st.none(), st.from_type(AllGeneric)), 16 + covariant=..., 17 + contravariant=..., 18 + constraints=st.one_of(st.just((tuple(), tuple())), st.from_type(SubArgs)), 19 + ) 20 + def test_true_subclass( 21 + self, 22 + data: Data, 23 + left: AllGeneric[typing.TypeVar], 24 + right: HTGeneric[typing.TypeVar], 25 + left_name, 26 + right_name, 27 + bound, 28 + covariant: bool, 29 + contravariant: bool, 30 + constraints, 31 + ): 32 + assume(not (covariant and contravariant)) 33 + right_constraints, left_constraints = constraints 34 + assume(not (bound and right_constraints)) 35 + assert issubclass(left, right) and issubclass( 36 + left( 37 + left_name, 38 + *left_constraints, 39 + bound=data.draw(subgen_strat(bound)) if bound else bound, 40 + covariant=covariant, 41 + contravariant=contravariant, 42 + ), 43 + right( 44 + right_name, 45 + *right_constraints, 46 + bound=bound, 47 + covariant=covariant, 48 + contravariant=contravariant, 49 + ), 50 + ) 51 + 52 + @given( 53 + left=..., 54 + right=..., 55 + left_name=st.characters(), 56 + right_name=st.characters(), 57 + left_bound=st.one_of(st.none(), st.from_type(AllGeneric)), 58 + right_bound=st.one_of(st.none(), st.from_type(AllGeneric)), 59 + left_covariant=..., 60 + right_covariant=..., 61 + left_contravariant=..., 62 + right_contravariant=..., 63 + left_constraints=st.one_of(st.just(tuple()), st.from_type(Args)), 64 + right_constraints=st.one_of(st.just(tuple()), st.from_type(Args)), 65 + ) 66 + def test_false_subclass( 67 + self, 68 + left: AllGeneric[typing.TypeVar], 69 + right: HTGeneric[typing.TypeVar], 70 + left_name, 71 + right_name, 72 + left_bound, 73 + right_bound, 74 + left_covariant: bool, 75 + right_covariant: bool, 76 + left_contravariant: bool, 77 + right_contravariant: bool, 78 + left_constraints, 79 + right_constraints, 80 + ): 81 + assume( 82 + not ( 83 + (left_covariant and left_contravariant) 84 + or (right_covariant and right_contravariant) 85 + or (left_bound and left_constraints) 86 + or (right_bound and right_constraints) 87 + or ht.isinmrosubclass(left_bound, right_bound) 88 + or ( 89 + left_constraints 90 + and right_constraints 91 + and all( 92 + ht.isinmrosubclass(l, r) 93 + for l, r in zip( 94 + left_constraints, right_constraints, strict=True 95 + ) 96 + ) 97 + ) 98 + ) 99 + ) 100 + assert not issubclass( 101 + left( 102 + left_name, 103 + *left_constraints, 104 + bound=left_bound, 105 + covariant=left_covariant, 106 + contravariant=left_contravariant, 107 + ), 108 + right( 109 + right_name, 110 + *right_constraints, 111 + bound=right_bound, 112 + covariant=right_covariant, 113 + contravariant=right_contravariant, 114 + ), 115 + ) 116 + 117 + @given(left=st.just(0), right=st.just(ht.TypeVar("T"))) 118 + @example(left=0, right=ht.TypeVar("T", bound=int)) 119 + @example(left=True, right=ht.TypeVar("T", bound=int)) 120 + @example(left=0, right=ht.TypeVar("T", str, int)) 121 + @example(left="", right=ht.TypeVar("T", str, int)) 122 + def test_true_instance(self, left, right): 123 + assert isinstance(left, right) 124 + 125 + @given(left=st.just(""), right=st.just(ht.TypeVar("T", bound=int))) 126 + @example(left=True, right=ht.TypeVar("T", str, int)) 127 + def test_false_instance(self, left, right): 128 + assert not isinstance(left, right)
+88
packages/hydrox/hydrox/typing/tests/types/test_union.py
··· 1 + from hydrox.hypothesis import AllGeneric, HTGeneric 2 + import hydrox.typing as ht 3 + from hypothesis import given, assume, strategies as st 4 + from hydrox.hypothesis import rand_strat 5 + 6 + generic_types = list(ht.Generics.values()) 7 + for module in ht.modules.values(): 8 + for attr in dir(module): 9 + obj = getattr(module, attr) 10 + if isinstance(obj, ht.Annotation): 11 + generic_types.append(obj) 12 + 13 + 14 + @given(...) 15 + def test_union_subscription_size(generic: AllGeneric[ht.Union]): 16 + assert ht.get_generic_subscription_size(generic) == -1 17 + 18 + 19 + class TestUnionEquality: 20 + @given(left=..., subleft=..., right=...) 21 + def test_flattening( 22 + self, 23 + left: AllGeneric[ht.Union], 24 + subleft: AllGeneric[ht.Union], 25 + right: AllGeneric[ht.Union], 26 + ): 27 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 28 + assert ht.unionize( 29 + left, (ht.unionize(subleft, (str, int)), bool) 30 + ) == ht.unionize(right, (str, int, bool)) 31 + 32 + @given(left=...) 33 + def test_single_argument(self, left: HTGeneric[ht.Union]): 34 + assert left[str] == str 35 + 36 + @given(left=..., generic=...) 37 + def test_redundant_generics(self, left: HTGeneric[ht.Union], generic: AllGeneric): 38 + assert left[*ht.get_all_ht_generics(generic)] == generic 39 + 40 + @given(left=..., right=...) 41 + def test_redundant_arguments( 42 + self, left: AllGeneric[ht.Union], right: AllGeneric[ht.Union] 43 + ): 44 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 45 + assert ht.unionize(left, (str, int, str)) == ht.unionize(right, (str, int)) 46 + 47 + @given(left=..., right=...) 48 + def test_argument_order( 49 + self, left: AllGeneric[ht.Union], right: AllGeneric[ht.Union] 50 + ): 51 + assume(isinstance(left, ht.GenericMeta) or isinstance(right, ht.GenericMeta)) 52 + assert ht.unionize(left, (str, int)) == ht.unionize(right, (int, str)) 53 + 54 + 55 + class TestUnionSubclassArgs: 56 + @given(left=..., right=...) 57 + def test_partial(self, left: AllGeneric[ht.Union], right: HTGeneric[ht.Union]): 58 + assert issubclass(ht.unionize(left, (str, int)), right[str, bool]) 59 + 60 + @given(left=..., right=...) 61 + def test_all(self, left: AllGeneric[ht.Union], right: HTGeneric[ht.Union]): 62 + assert issubclass(ht.unionize(left, (str, bool)), right[str, int]) 63 + 64 + @given(generic=...) 65 + def test_true(self, generic: HTGeneric[ht.Union]): 66 + assert issubclass(str, generic[str, bool]) 67 + 68 + @given(generic=...) 69 + def test_false(self, generic: HTGeneric[ht.Union]): 70 + assert not issubclass(int, generic[str, bool]) 71 + 72 + 73 + class TestUnionSubclass: 74 + @given(data=st.sampled_from(generic_types), generic=...) 75 + def test_true(self, data, generic: HTGeneric[ht.Union]): 76 + assert issubclass(data, generic) 77 + 78 + @given(data=rand_strat("none"), generic=...) 79 + def test_false(self, data, generic: HTGeneric[ht.Union]): 80 + assert not issubclass(data, generic) 81 + 82 + 83 + @given( 84 + data=rand_strat(strats=st.sampled_from(generic_types)), 85 + generic=..., 86 + ) 87 + def test_union_instance(data, generic: HTGeneric[ht.Union]): 88 + assert isinstance(data, generic)
+703
packages/hydrox/hydrox/typing/types.py
··· 1 + import builtins 2 + import collections.abc as abc 3 + import types 4 + import typing 5 + 6 + from ..helpers import BString, Collection, flatten, infm1, aleph 7 + from ..typing.generic import Annotation, Generalize, GenericMetas, Generics 8 + from ..typing.generic import ( 9 + annotation_is_not_generic, 10 + anything, 11 + argsinmrosubclass, 12 + callable_first_arg_no_list, 13 + callable_first_arg, 14 + any_score, 15 + format_name, 16 + geeq, 17 + generalize, 18 + get_all_generics, 19 + get_args, 20 + get_name, 21 + get_type, 22 + scoreinstance, 23 + isinmrosubclass, 24 + maxscore, 25 + officiate, 26 + reducehash, 27 + scoreargs, 28 + scoresubclass, 29 + sumscore, 30 + supertype, 31 + ) 32 + from ..variables import conversion_table 33 + from contextlib import suppress 34 + from more_itertools import unique_everseen, partition 35 + 36 + 37 + @Generalize 38 + class Any: 39 + def __metasubclasscheck__(cls, C: typing.Any): 40 + return isinstance(C, Annotation) 41 + 42 + def __metainstancecheck__(cls, C: typing.Any): 43 + return True 44 + 45 + 46 + # TODO: Does this work with generics? 47 + @Generalize 48 + class Type: 49 + def __metacall__(self, *args, **kwargs): 50 + return supertype(*args, **kwargs) 51 + 52 + def __aliassubclassscore__(self, C: typing.Any): 53 + g = typing.get_args(self)[0] 54 + return scoresubclass(C, getattr(g, "__instance__", get_type(g))) 55 + 56 + def __aliasinstancescore__(self, instance: typing.Any): 57 + return scoresubclass(instance, typing.get_args(self)[0]) 58 + 59 + def __metasubclasses__(cls): 60 + return builtins.type.__subclasses__(cls) 61 + 62 + 63 + @Generalize 64 + class ExactType: 65 + def __aliasinstancecheck__(self, instance: typing.Any): 66 + return instance == typing.get_args(self)[0] 67 + 68 + def __aliassubclassscore__(self, C: typing.Any): 69 + g = typing.get_args(self)[0] 70 + if C == getattr(g, "__instance__", get_type(g)): 71 + return 1 72 + 73 + def __aliasinstancescore__(self, instance: typing.Any): 74 + if isinstance(instance, self): 75 + return 1 76 + 77 + 78 + @Generalize 79 + class IsType: 80 + def __aliasinstancecheck__(self, instance: typing.Any): 81 + return instance is typing.get_args(self)[0] 82 + 83 + # TODO: Test this. 84 + def __aliassubclassscore__(self, C: typing.Any): 85 + g = typing.get_args(self)[0] 86 + if C is getattr(g, "__instance__", get_type(g)): 87 + return 2 88 + 89 + def __aliasinstancescore__(self, instance: typing.Any): 90 + if isinstance(instance, self): 91 + return 2 92 + 93 + 94 + # TODO: Should these return 1, 0, or lower, as the match could be as vague as *args and **kwargs? 95 + # It would have the same score as any other generic without arguments. 96 + # Additionally, should it take into account how many levels down a subclass is, 97 + # such as `None` for an exact match, but then `-inf`, `-infm1`, `-infm2`, etc.? 98 + # Or would those be too low? 99 + @Generalize(subscriptions=-1) 100 + class Not: 101 + def __aliassubclassscore__(self, C: typing.Any): 102 + if not isinmrosubclass(C, self.__args__): 103 + return 1 104 + 105 + def __aliasinstancescore__(self, instance: typing.Any): 106 + if not isinstance(instance, self.__args__): 107 + return 1 108 + 109 + 110 + # TODO: Create an `IsNot` and `NotEq` generic as well. 111 + 112 + 113 + @Generalize 114 + class Callable: 115 + def __metacall__(self, *args, **kwargs): 116 + return types.FunctionType(*args, **kwargs) 117 + 118 + def __aliasargsprocessor__(origin, args): 119 + args_is_iterable = isinstance(args, Collection) 120 + length_is_2 = len(args) == 2 121 + if args_is_iterable and length_is_2: 122 + first_arg = args[0] 123 + if callable_first_arg(first_arg): 124 + return args 125 + if isinstance(first_arg, Annotation): 126 + return [first_arg], args[1] 127 + raise TypeError( 128 + f"Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got {first_arg}" 129 + ) 130 + name = format_name(origin) 131 + raise TypeError(f"{name} must be used as {name}[[arg, ...], result].") 132 + 133 + # TODO: This does not account for situations like `Callable[[str, ...], ...]`, 134 + # which can take `*args` and `**kwargs` as well. 135 + # Learn more about this here: 136 + # https://docs.python.org/3/library/typing.html#annotating-callable-objects 137 + def __aliassubclassscore__(self, C: Annotation) -> typing.Optional[int]: 138 + if C_args := typing.get_args(C): 139 + if typing.get_origin(C) == typing.get_origin(self): 140 + total = 1 141 + self_args = typing.get_args(self) 142 + last_score = scoresubclass(C_args[-1], self_args[-1]) 143 + if last_score is not None: 144 + total += last_score 145 + ca = C_args[0] 146 + sa = self_args[0] 147 + if isinstance(ca, list): 148 + if isinstance(sa, list): 149 + return sumscore( 150 + total, scoreargs(tuple(ca), tuple(sa), True) 151 + ) 152 + return sumscore( 153 + total, any_score if sa is ... else scoresubclass(ca, sa) 154 + ) 155 + 156 + def __aliasrepr__(self): 157 + args = get_args(self) 158 + joint_args = ", ".join( 159 + [ 160 + format_name(arg) 161 + for arg in ( 162 + args 163 + if callable_first_arg_no_list(args[0]) 164 + else (list(args[:-1]), args[-1]) 165 + ) 166 + ] 167 + ) 168 + return f"{typing.get_origin(self)}[{joint_args}]" 169 + 170 + 171 + # TODO: How do I pass in a literal tuple or empty iterables? 172 + @Generalize(subscriptions=-1) 173 + class Literal: 174 + def __aliasargsfullprocessor__(origin, args): 175 + new_args = set() 176 + for arg in set(args) if args and isinstance(args, tuple) else {args}: 177 + if typing.get_origin(arg) in get_all_generics("Literal"): 178 + new_args.update(typing.get_args(arg)) 179 + else: 180 + new_args.add(arg) 181 + return new_args 182 + 183 + def __aliasofficialprocessor__(args): 184 + return args 185 + 186 + # TODO: Copy or extract the `Union` __aliaseq__ method here as well. 187 + def __aliaseq__(self, other): 188 + if typing.get_origin(other) != typing.get_origin(self): 189 + return False 190 + other_args = typing.get_args(other) 191 + self_args = typing.get_args(self) 192 + return ( 193 + len(other_args) == len(self_args) 194 + and all(arg in other_args for arg in self_args) 195 + ) or NotImplemented 196 + 197 + def __aliasinstancecheck__(self, instance: typing.Any) -> bool: 198 + return instance in typing.get_args(self) 199 + 200 + def __aliasinstancescore__(self, instance: typing.Any) -> typing.Optional[int]: 201 + if isinstance(instance, self): 202 + return float("inf") 203 + 204 + 205 + @Generalize(subscriptions=-1) 206 + class Is: 207 + def __aliasargsfullprocessor__(origin, args): 208 + new_args = set() 209 + for arg in set(args) if args and isinstance(args, tuple) else {args}: 210 + if typing.get_origin(arg) == Generics.Is: 211 + new_args.update(typing.get_args(arg)) 212 + else: 213 + new_args.add(arg) 214 + return new_args 215 + 216 + def __aliasofficialprocessor__(args): 217 + return args 218 + 219 + # TODO: Copy or extract the `Union` __aliaseq__ method here as well. 220 + def __aliaseq__(self, other): 221 + if typing.get_origin(other) != typing.get_origin(self): 222 + return False 223 + other_args = typing.get_args(other) 224 + self_args = typing.get_args(self) 225 + return ( 226 + len(other_args) == len(self_args) 227 + # Adapted From: 228 + # Answer: https://stackoverflow.com/a/68938778 229 + # User: https://stackoverflow.com/users/14030287/ariadne-paradis 230 + and all(any(True for oa in other_args if sa is oa) for sa in self_args) 231 + ) or NotImplemented 232 + 233 + # Adapted From: 234 + # Answer: https://stackoverflow.com/a/68938778 235 + # User: https://stackoverflow.com/users/14030287/ariadne-paradis 236 + def __aliasinstancecheck__(self, instance: typing.Any) -> bool: 237 + return any(True for arg in typing.get_args(self) if instance is arg) 238 + 239 + def __aliasinstancescore__(self, instance: typing.Any) -> typing.Optional[int]: 240 + if isinstance(instance, self): 241 + return aleph() 242 + 243 + 244 + # TODO: Should I `setattr` the methods from `Literal` like in `Union` and `UnionType`? 245 + # TODO: Copy or extract the `Union` __aliaseq__ method here as well. 246 + class UnhashableLiteral(Literal, generic=True, force=True): 247 + def __aliasargsfullprocessor__(origin, args): 248 + new_args = [] 249 + for arg in unique_everseen( 250 + args if args and isinstance(args, tuple) else (args,) 251 + ): 252 + if typing.get_origin(arg) == Generics.UnhashableLiteral: 253 + new_args.extend(typing.get_args(arg)) 254 + else: 255 + new_args.append(arg) 256 + return new_args 257 + 258 + def __aliasofficialprocessor__(args): 259 + return args 260 + 261 + def __aliasinstancescore__(self, instance: typing.Any) -> typing.Optional[int]: 262 + if isinstance(instance, self): 263 + return float("inf") 264 + 265 + 266 + # Adapted From: 267 + # Answer: https://stackoverflow.com/a/68938778 268 + # User: https://stackoverflow.com/users/14030287/ariadne-paradis 269 + def unhashable_is_check(args, i, a): 270 + return any(True for j, b in enumerate(args[i:], i) if i != j and a is b) 271 + 272 + 273 + # NOTE: ... Eh. I'm keeping this... 274 + # TODO: Should I `setattr` the methods from `Is` like in `Union` and `UnionType`? 275 + # TODO: How do I flatten other `UnhashableIs` here? 276 + # TODO: Copy or extract the `Union` __aliaseq__ method here as well. 277 + class UnhashableIs(Is, generic=True): 278 + def __aliasargsfullprocessor__(origin, args): 279 + args = args if args and isinstance(args, tuple) else (args,) 280 + new_args = [] 281 + for i, a in enumerate(args): 282 + if typing.get_origin(a) == Generics.UnhashableIs: 283 + new_args.extend(filter(unhashable_is_check, typing.get_args(a))) 284 + if not unhashable_is_check(args, i, a): 285 + new_args.append(a) 286 + return new_args 287 + 288 + def __aliasofficialprocessor__(args): 289 + return args 290 + 291 + def __aliasinstancescore__(self, instance: typing.Any) -> typing.Optional[int]: 292 + if isinstance(instance, self): 293 + return aleph() 294 + 295 + 296 + sentinel = object() 297 + 298 + 299 + Generics["String"] = Generics.Str 300 + Generics["string"] = Generics.str 301 + 302 + 303 + class Union: 304 + def __aliasprocessor__(origin, args, alias): 305 + new_args = [] 306 + for arg in flatten(partition(annotation_is_not_generic, args)): 307 + if anything(arg): 308 + return Generics.Any 309 + if typing.get_origin(arg) in get_all_generics("union"): 310 + new_args.extend(a for a in typing.get_args(arg) if a not in new_args) 311 + elif arg not in new_args: 312 + new_args.append(arg) 313 + if len(new_args) == 1: 314 + return new_args[0] 315 + return alias(origin, tuple(new_args)) 316 + 317 + # TODO: Can I convert this back to using `__aliasgeneralsubclasscheck__` with `operator.neq`? 318 + def __aliaseq__(self, other): 319 + origin = typing.get_origin(self) 320 + if typing.get_origin(other) == origin: 321 + args = [] 322 + for arg in typing.get_args(self): 323 + if geeq(typing.get_origin(arg), origin): 324 + args.extend(a for a in typing.get_args(arg) if a not in args) 325 + else: 326 + args.append(arg) 327 + other_args = [] 328 + for arg in typing.get_args(other): 329 + if geeq(typing.get_origin(arg), origin): 330 + other_args.extend( 331 + a for a in typing.get_args(arg) if a not in other_args 332 + ) 333 + else: 334 + other_args.append(arg) 335 + return all(False for arg in args if arg not in other_args) and all( 336 + False for arg in other_args if arg not in args 337 + ) 338 + return NotImplemented 339 + 340 + def __aliassubclassscore__(self, C: Annotation) -> typing.Optional[int]: 341 + self_args = typing.get_args(self) 342 + C_args = typing.get_args(C) 343 + if typing.get_origin(C) == typing.get_origin(self): 344 + return maxscore(scoresubclass(ca, sa) for ca in C_args for sa in self_args) 345 + return maxscore(scoresubclass(C, sa) for sa in self_args) 346 + 347 + def __metasubclassscore__(cls, C: typing.Any): 348 + if isinstance(C, Annotation): 349 + return 1 350 + 351 + def __metainstancescore__(cls, C: typing.Any): 352 + return any_score 353 + 354 + 355 + class UnionType: ... 356 + 357 + 358 + for attr in dir(Union): 359 + if attr.startswith("__meta") or attr.startswith("__alias"): 360 + setattr(UnionType, attr, getattr(Union, attr)) 361 + 362 + Generalize(Union, subscriptions=-1) 363 + Generalize(UnionType, subscriptions=-1) 364 + 365 + 366 + @Generalize 367 + class Optional: 368 + def __aliasprocessor__(origin, args, alias): 369 + return Generics.Union[*args, Generics.NoneType] 370 + 371 + 372 + class Coll(Generics.Iterable, generic=True, subscriptions=-1): 373 + def __metasubclassscore__(self, C: typing.Any) -> bool: 374 + if not (geeq(C, abc.Iterable) or isinmrosubclass(C, BString)): 375 + return scoresubclass(C, Generics.Iterable) 376 + 377 + def __metainstancescore__(cls, instance: typing.Any) -> bool: 378 + if not typing.get_origin(instance): 379 + return scoresubclass(get_type(instance), cls) 380 + 381 + 382 + conversion_table[Coll] = frozenset 383 + 384 + 385 + # TODO: Maybe have extra args be args to ignore, i.e. return `False` for? 386 + @Generalize 387 + class Check: 388 + def __aliasargsmapper__(x): 389 + return x 390 + 391 + def __aliasinstancecheck__(self, instance: typing.Any) -> bool: 392 + return typing.get_args(self)[0](instance) 393 + 394 + def __aliasgeneralsubclasscheck__(self, C: typing.Any, check) -> bool: 395 + if check(typing.get_origin(C), typing.get_origin(self)): 396 + return check(typing.get_args(C)[0], get_type(typing.get_args(self)[0])) 397 + return False 398 + 399 + def __aliassubclassscore__(self, C: typing.Any) -> typing.Optional[int]: 400 + origin_score = scoresubclass(typing.get_origin(C), typing.get_origin(self)) 401 + if origin_score is not None: 402 + check_score = scoresubclass( 403 + typing.get_args(C)[0], get_type(typing.get_args(self)[0]) 404 + ) 405 + if check_score is not None: 406 + return origin_score + check_score 407 + 408 + def __aliasinstancescore__(self, instance: typing.Any) -> typing.Optional[int]: 409 + if isinstance(instance, self): 410 + return infm1 411 + 412 + 413 + @Generalize 414 + class TypeVar: 415 + def __init__( 416 + self, 417 + name, 418 + *constraints, 419 + bound=None, 420 + covariant=False, 421 + contravariant=False, 422 + # infer_variance=False, 423 + # default=typing.NoDefault, 424 + ): 425 + if covariant and contravariant: 426 + raise ValueError("Bivariant types are not supported.") 427 + if len(constraints) == 1: 428 + raise TypeError("A single constraint is not allowed") 429 + if constraints and bound is not None: 430 + raise TypeError("Constraints cannot be combined with bound=...") 431 + self.__name__ = name 432 + self.__qualname__ = name 433 + self.__constraints__ = tuple( 434 + [generalize(constraint) for constraint in constraints] 435 + ) 436 + self.__bound__ = generalize(bound) 437 + self.__covariant__ = covariant 438 + self.__contravariant__ = contravariant 439 + # self.__infer_variance__ = infer_variance 440 + # self.__default__ = default 441 + self.__generic__ = generic = typing.TypeVar( 442 + name, 443 + *[officiate(constraint) for constraint in constraints], 444 + bound=officiate(bound), 445 + covariant=covariant, 446 + contravariant=contravariant, 447 + # infer_variance=infer_variance, 448 + # default=officiate(default), 449 + ) 450 + self.__generics__ = (generic,) 451 + self.__hash_value__ = reducehash( 452 + self.__class__, 453 + self.__name__, 454 + self.__constraints__, 455 + self.__covariant__, 456 + self.__contravariant__, 457 + # self.__infer_variance__, self.__default__ 458 + ) 459 + 460 + def __repr__(self): 461 + if self.__covariant__: 462 + prefix = "+" 463 + elif self.__contravariant__: 464 + prefix = "-" 465 + else: 466 + prefix = "~" 467 + # if self.__infer_variance__: 468 + # return f"{bprefix}, infer_variance=True)" 469 + prefix += f"{self.__name__}" 470 + # if self.__default__ is not typing.NoDefault: 471 + # prefix += f", default={self.__default__}," 472 + if self.__bound__: 473 + return prefix + f"(bound={get_name(self.__bound__)})" 474 + if self.__constraints__: 475 + return ( 476 + prefix 477 + + f"({', '.join([get_name(constraint) for constraint in self.__constraints__])})" 478 + ) 479 + return prefix 480 + 481 + def __subclassscore__(self, C: typing.Any) -> typing.Optional[int]: 482 + origin_score = scoreinstance(C, Generics.TypeVar) 483 + if origin_score is not None: 484 + bound_score = scoresubclass(C.__bound__, self.__bound__) 485 + if bound_score is not None and ( 486 + len(C.__constraints__) == len(self.__constraints__) 487 + ): 488 + # TODO: Should this account for `is` vs `==`? 489 + for c in C.__constraints__: 490 + if c not in self.__constraints__: 491 + return None 492 + for s in self.__constraints__: 493 + if s not in C.__constraints__: 494 + return None 495 + return origin_score + bound_score + len(self.__constraints__) 496 + 497 + def __instancescore__(self, instance: typing.Any) -> typing.Optional[int]: 498 + t = get_type(instance) 499 + if self.__bound__: 500 + return scoresubclass(t, self.__bound__) 501 + if self.__constraints__: 502 + for s in self.__constraints__: 503 + if t == s: 504 + return 1 505 + return None 506 + return any_score 507 + 508 + def __subclasscheck__(self, C: typing.Any) -> bool: 509 + check: abc.Callable[[typing.Any], bool] = isinmrosubclass 510 + if ( 511 + isinstance(C, Generics.TypeVar) 512 + and (C.__covariant__ == self.__covariant__) 513 + and (C.__contravariant__ == self.__contravariant__) 514 + # and (C.__infer_variance__ == self.__infer_variance__) 515 + # and (C.__default__ == self.__default__) 516 + and check(C.__bound__, self.__bound__) 517 + ): 518 + return argsinmrosubclass(C.__constraints__, self.__constraints__, True) 519 + return False 520 + 521 + def __instancecheck__(self, instance: typing.Any) -> bool: 522 + check: abc.Callable[[typing.Any], bool] = isinmrosubclass 523 + t = get_type(instance) 524 + if self.__bound__: 525 + return check(t, self.__bound__) 526 + if self.__constraints__: 527 + return t in self.__constraints__ 528 + return True 529 + 530 + def __eq__(self, other): 531 + if isinstance(other, Generics.TypeVar): 532 + attrs = [ 533 + "name", 534 + "constraints", 535 + "bound", 536 + "covariant", 537 + "contravariant", 538 + # "infer_variance", "default" 539 + ] 540 + return all( 541 + False 542 + for attr in attrs 543 + if getattr(self, f"__{attr}__") != getattr(other, f"__{attr}__") 544 + ) 545 + return NotImplemented 546 + 547 + def __hash__(self): 548 + return self.__hash_value__ 549 + 550 + 551 + @Generalize 552 + class NewType: 553 + def __init__(self, name, tp): 554 + self.__name__ = name 555 + self.__qualname__ = name 556 + self.__supertype__ = generalize(tp) 557 + self.__generic__ = generic = typing.NewType(name, officiate(tp)) 558 + self.__generics__ = (generic,) 559 + self.__hash_value__ = reducehash( 560 + self.__class__, self.__name__, self.__supertype__ 561 + ) 562 + 563 + def __call__(self, arg): 564 + return arg 565 + 566 + def __subclassscore__(self, C: typing.Any) -> typing.Optional[int]: 567 + origin_score = scoreinstance(C, Generics.NewType) 568 + if origin_score is not None: 569 + supertype_score = scoresubclass(C.__supertype__, self.__supertype__) 570 + if supertype_score is not None: 571 + return origin_score + supertype_score 572 + 573 + def __instancescore__(self, instance: typing.Any) -> typing.Optional[int]: 574 + return scoresubclass(get_type(instance), self.__supertype__) 575 + 576 + def __subclasscheck__(self, C: typing.Any) -> bool: 577 + check: abc.Callable[[typing.Any], bool] = isinmrosubclass 578 + return isinstance(C, Generics.NewType) and check( 579 + C.__supertype__, self.__supertype__ 580 + ) 581 + 582 + # TODO: Learn more about `typing.NewType` and test: 583 + # https://docs.python.org/3/library/typing.html#typing.NewType 584 + def __instancecheck__(self, instance: typing.Any) -> bool: 585 + check: abc.Callable[[typing.Any], bool] = isinmrosubclass 586 + return check(get_type(instance), self.__supertype__) 587 + 588 + def __eq__(self, other): 589 + return ( 590 + isinstance(other, Generics.NewType) 591 + and (self.__name__ == other.__name__) 592 + and (self.__supertype__ == other.__supertype__) 593 + ) 594 + 595 + def __hash__(self): 596 + return self.__hash_value__ 597 + 598 + 599 + # TODO: Implement using: 600 + # https://docs.python.org/3/library/typing.html#typing.ParamSpec 601 + @Generalize 602 + class ParamSpec: 603 + def __init__( 604 + self, 605 + name, 606 + bound=None, 607 + covariant=False, 608 + contravariant=False, 609 + # default=typing.NoDefault, 610 + ): 611 + self.__name__ = name 612 + self.__qualname__ = name 613 + self.__bound__ = generalize(bound) 614 + self.__covariant__ = covariant 615 + self.__contravariant__ = contravariant 616 + # self.__default__ = default 617 + self.__generic__ = generic = typing.ParamSpec( 618 + name, 619 + bound=officiate(bound), 620 + covariant=covariant, 621 + contravariant=contravariant, 622 + # default=officiate(default), 623 + ) 624 + self.__generics__ = (generic,) 625 + self.__hash_value__ = reducehash( 626 + self.__class__, 627 + self.__name__, 628 + self.__bound__, 629 + self.__covariant__, 630 + self.__contravariant__, 631 + # self.__default__, 632 + ) 633 + 634 + def __repr__(self): 635 + prefix = f'{Generics.ParamSpec}("{self.__name__}"' 636 + # if self.__default__ is not typing.NoDefault: 637 + # prefix += f", default={self.__default__}," 638 + if self.__bound__: 639 + bprefix = f"{prefix}, bound={get_name(self.__bound__)}" 640 + if self.__covariant__: 641 + return f"{bprefix}, covariant=True)" 642 + if self.__contravariant__: 643 + return f"{bprefix}, contravariant=True)" 644 + return f"{bprefix})" 645 + return prefix + ")" 646 + 647 + def __subclassscore__(self, C: typing.Any) -> typing.Optional[int]: 648 + origin_score = scoreinstance(C, Generics.ParamSpec) 649 + if origin_score is not None: 650 + bound_score = scoresubclass(C.__bound__, self.__bound__) 651 + if bound_score is not None: 652 + return origin_score + bound_score 653 + 654 + def __instancescore__(self, instance: typing.Any) -> typing.Optional[int]: 655 + return scoresubclass(get_type(instance), self.__bound__) 656 + 657 + def __subclasscheck__(self, C: typing.Any): 658 + return ( 659 + isinstance(C, Generics.ParamSpec) 660 + and isinmrosubclass(C.__bound__, self.__bound__) 661 + and self.__name__ == C.__name__ 662 + and self.__covariant__ == C.__covariant__ 663 + and self.__contravariant__ == C.__contravariant__ 664 + # and (C.__default__ == self.__default__) 665 + ) 666 + 667 + # TODO: Learn more about `typing.ParamSpec` and test: 668 + # https://docs.python.org/3/library/typing.html#typing.ParamSpec 669 + def __instancecheck__(self, instance: typing.Any): 670 + # return self.__bound__ and isinmrosubclass(get_type(instance), self.__bound__) 671 + return isinmrosubclass(get_type(instance), self.__bound__) 672 + 673 + def __eq__(self, other): 674 + return isinstance(other, Generics.ParamSpec) and all( 675 + getattr(self, f"__{attr}__") == getattr(other, f"__{attr}__") 676 + for attr in ( 677 + "name", 678 + "bound", 679 + "covariant", 680 + "contravariant", 681 + # "default" 682 + ) 683 + ) 684 + 685 + def __hash__(self): 686 + return self.__hash_value__ 687 + 688 + 689 + def not_ellipsis_paramspec(arg): 690 + return not (arg is ... or isinstance(arg, Generics.ParamSpec)) 691 + 692 + 693 + @Generalize(subscriptions=-1) 694 + class Concatenate: 695 + def __aliasargsprocessor__(origin, args): 696 + args_is_tuple = isinstance(args, tuple) 697 + if (not args_is_tuple and not_ellipsis_paramspec(args)) or ( 698 + args_is_tuple and not_ellipsis_paramspec(args[-1]) 699 + ): 700 + raise TypeError( 701 + "The last parameter to Concatenate should be a ParamSpec variable or ellipsis." 702 + ) 703 + return args
+39
packages/hydrox/hydrox/variables.py
··· 1 + from collections import defaultdict 2 + from os import environ 3 + 4 + unique_attributes = ("__dict__", "__weakref__") 5 + 6 + 7 + def none(): ... 8 + 9 + 10 + conversion_table = defaultdict(none) 11 + 12 + # Adapted From: 13 + # Answer: https://stackoverflow.com/a/68938778 14 + # User: https://stackoverflow.com/users/14030287/ariadne-paradis 15 + building = any( 16 + True 17 + for var in ( 18 + "NIX_BINTOOLS", 19 + "NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu", 20 + "NIX_BUILD_CORES", 21 + "NIX_BUILD_TOP", 22 + "NIX_CC", 23 + "NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu", 24 + "NIX_CFLAGS_COMPILE", 25 + "NIX_ENFORCE_NO_NATIVE", 26 + "NIX_ENFORCE_PURITY", 27 + "NIX_HARDENING_ENABLE", 28 + "NIX_INDENT_MAKE", 29 + "NIX_LDFLAGS", 30 + "NIX_LOG_FD", 31 + "NIX_SSL_CERT_FILE", 32 + "NIX_STORE", 33 + ) 34 + if var in environ 35 + ) or any( 36 + True 37 + for var in ("TMP", "TMPDIR", "TEMP", "TEMPDIR") 38 + if environ.get(var, "/tmp") == "/build" 39 + )
+26
packages/hydrox/pyproject.toml
··· 1 + [project] 2 + name = "hydrox" 3 + version = "0.1.0" 4 + description = "A syvl.org typing system!" 5 + readme = "README.md" 6 + authors = [ 7 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 8 + ] 9 + maintainers = [ 10 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 11 + ] 12 + requires-python = ">=3.14" 13 + license = "MIT" 14 + urls."Source code" = "https://tangled.sh/@syvl.org/hydrox" 15 + dependencies = [ 16 + "autoslot>=2024.12.1", 17 + "more-itertools>=10.7.0", 18 + "rich==13.8.1", 19 + ] 20 + 21 + [build-system] 22 + requires = ["hatchling"] 23 + build-backend = "hatchling.build" 24 + 25 + [tool.hatch.build.targets.wheel] 26 + packages = [ "hydrox" ]
+35
packages/hydrox/test1.py
··· 1 + import collections.abc as abc, typing 2 + from warnings import warn 3 + from functools import cache as defcache 4 + from hydrox.helpers import cache, reversecut 5 + from hydrox.typing import ( 6 + _scoresubclass, 7 + generalize, 8 + generic_flatten, 9 + GenericMeta, 10 + get_origin, 11 + has_custom_init, 12 + left_right_collection_check, 13 + partition_ellipsis, 14 + score, 15 + scoreargs, 16 + scoremro, 17 + tryinmro, 18 + tryinmrosubclass, 19 + trysubclass, 20 + TypeWarning, 21 + ) 22 + from contextlib import contextmanager 23 + from rich.rule import Rule 24 + 25 + # NOTE: Can't cache this; booleans, floats, strings, and integers sometimes have the same hash values. 26 + def scoreinstance(left, right): 27 + if anything(right): 28 + return any_score 29 + if hasattr(right, "__instancescore__"): 30 + # return right.__instancescore__(left) 31 + if isinstance(right, GenericMeta): 32 + if has_custom_init(right): 33 + return type(right).__instancescore__(right, left) 34 + return right.__instancescore__(left) 35 + return scoresubclass(get_type(left), right)
+43
packages/hydrox/test2.py
··· 1 + import hydrox.helpers as hh 2 + 3 + # assert + 4 + # assert - 5 + # assert * 6 + # assert / 7 + 8 + # 0 0 9 + # 0 1 10 + # 1 0 11 + # 1 1 12 + # 0 Inf0 13 + # 0 Inf1 14 + # 1 Inf0 15 + # 1 Inf1 16 + # 0 Aleph0 17 + # 0 Aleph1 18 + # 1 Aleph0 19 + # 1 Aleph1 20 + # Inf0 0 21 + # Inf0 1 22 + # Inf1 0 23 + # Inf1 1 24 + # Inf0 Inf0 25 + # Inf0 Inf1 26 + # Inf1 Inf0 27 + # Inf1 Inf1 28 + # Inf0 Aleph0 29 + # Inf0 Aleph1 30 + # Inf1 Aleph0 31 + # Inf1 Aleph1 32 + # Aleph0 0 33 + # Aleph0 1 34 + # Aleph1 0 35 + # Aleph1 1 36 + # Aleph0 Inf0 37 + # Aleph0 Inf1 38 + # Aleph1 Inf0 39 + # Aleph1 Inf1 40 + # Aleph0 Aleph0 41 + # Aleph0 Aleph1 42 + # Aleph1 Aleph0 43 + # Aleph1 Aleph1
+334
packages/hydrox/uv.lock
··· 1 + version = 1 2 + revision = 2 3 + requires-python = ">=3.14" 4 + 5 + [[package]] 6 + name = "attrs" 7 + version = "25.3.0" 8 + source = { registry = "https://pypi.org/simple" } 9 + sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } 10 + wheels = [ 11 + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, 12 + ] 13 + 14 + [[package]] 15 + name = "autoslot" 16 + version = "2024.12.1" 17 + source = { registry = "https://pypi.org/simple" } 18 + sdist = { url = "https://files.pythonhosted.org/packages/d4/2b/d62d12200bac293891a5dd9f3cae605f04aad0c440a5fecced80e9ce99cb/autoslot-2024.12.1.tar.gz", hash = "sha256:e52e3ce5b98d5fb2d7dbd5c7773627bee552a9cfb49a53b2bddb689adf2a3a5a", size = 11010, upload-time = "2024-12-06T14:42:53.317Z" } 19 + wheels = [ 20 + { url = "https://files.pythonhosted.org/packages/05/1a/39b527bdd3220d490f63960efd361afa611d3ca314cfbf389cc2c7dd7df7/autoslot-2024.12.1-py2.py3-none-any.whl", hash = "sha256:b50098372bf99caf1f135e4f1bbd9f20d56cb23a046e8a3bf402a20399526bfe", size = 7858, upload-time = "2024-12-06T14:42:51.046Z" }, 21 + ] 22 + 23 + [[package]] 24 + name = "colorama" 25 + version = "0.4.6" 26 + source = { registry = "https://pypi.org/simple" } 27 + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 28 + wheels = [ 29 + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 30 + ] 31 + 32 + [[package]] 33 + name = "execnet" 34 + version = "2.1.1" 35 + source = { registry = "https://pypi.org/simple" } 36 + sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } 37 + wheels = [ 38 + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, 39 + ] 40 + 41 + [[package]] 42 + name = "hydrox" 43 + version = "0.1.0" 44 + source = { virtual = "." } 45 + dependencies = [ 46 + { name = "autoslot" }, 47 + { name = "more-itertools" }, 48 + { name = "rich" }, 49 + ] 50 + 51 + [package.dev-dependencies] 52 + dev = [ 53 + { name = "hypothesis" }, 54 + { name = "pytest" }, 55 + { name = "pytest-custom-exit-code" }, 56 + { name = "pytest-order" }, 57 + { name = "pytest-parametrized" }, 58 + { name = "pytest-randomly" }, 59 + { name = "pytest-repeat" }, 60 + { name = "pytest-sugar" }, 61 + { name = "pytest-xdist" }, 62 + { name = "tzdata" }, 63 + ] 64 + lint = [ 65 + { name = "ruff" }, 66 + ] 67 + 68 + [package.metadata] 69 + requires-dist = [ 70 + { name = "autoslot", specifier = ">=2024.12.1" }, 71 + { name = "more-itertools", specifier = ">=10.7.0" }, 72 + { name = "rich", specifier = ">=14.0.0" }, 73 + ] 74 + 75 + [package.metadata.requires-dev] 76 + dev = [ 77 + { name = "hypothesis", specifier = ">=6.131.15" }, 78 + { name = "pytest", specifier = ">=8.3.5" }, 79 + { name = "pytest-custom-exit-code", specifier = ">=0.3.0" }, 80 + { name = "pytest-order", specifier = ">=1.3.0" }, 81 + { name = "pytest-parametrized", specifier = ">=1.7" }, 82 + { name = "pytest-randomly", specifier = ">=3.16.0" }, 83 + { name = "pytest-repeat", specifier = ">=0.9.4" }, 84 + { name = "pytest-sugar", specifier = ">=1.0.0" }, 85 + { name = "pytest-xdist", specifier = ">=3.6.1" }, 86 + { name = "tzdata", specifier = ">=2025.2" }, 87 + ] 88 + lint = [{ name = "ruff", specifier = ">=0.11.9" }] 89 + 90 + [[package]] 91 + name = "hypothesis" 92 + version = "6.131.15" 93 + source = { registry = "https://pypi.org/simple" } 94 + dependencies = [ 95 + { name = "attrs" }, 96 + { name = "sortedcontainers" }, 97 + ] 98 + sdist = { url = "https://files.pythonhosted.org/packages/f1/6f/1e291f80627f3e043b19a86f9f6b172b910e3575577917d3122a6558410d/hypothesis-6.131.15.tar.gz", hash = "sha256:11849998ae5eecc8c586c6c98e47677fcc02d97475065f62768cfffbcc15ef7a", size = 436596, upload-time = "2025-05-07T23:04:25.127Z" } 99 + wheels = [ 100 + { url = "https://files.pythonhosted.org/packages/b6/c7/78597bcec48e1585ea9029deb2bf2341516e90dd615a3db498413d68a4cc/hypothesis-6.131.15-py3-none-any.whl", hash = "sha256:e02e67e9f3cfd4cd4a67ccc03bf7431beccc1a084c5e90029799ddd36ce006d7", size = 501128, upload-time = "2025-05-07T23:04:22.045Z" }, 101 + ] 102 + 103 + [[package]] 104 + name = "iniconfig" 105 + version = "2.1.0" 106 + source = { registry = "https://pypi.org/simple" } 107 + sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } 108 + wheels = [ 109 + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, 110 + ] 111 + 112 + [[package]] 113 + name = "markdown-it-py" 114 + version = "3.0.0" 115 + source = { registry = "https://pypi.org/simple" } 116 + dependencies = [ 117 + { name = "mdurl" }, 118 + ] 119 + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 120 + wheels = [ 121 + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 122 + ] 123 + 124 + [[package]] 125 + name = "mdurl" 126 + version = "0.1.2" 127 + source = { registry = "https://pypi.org/simple" } 128 + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 129 + wheels = [ 130 + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 131 + ] 132 + 133 + [[package]] 134 + name = "more-itertools" 135 + version = "10.7.0" 136 + source = { registry = "https://pypi.org/simple" } 137 + sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } 138 + wheels = [ 139 + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, 140 + ] 141 + 142 + [[package]] 143 + name = "packaging" 144 + version = "25.0" 145 + source = { registry = "https://pypi.org/simple" } 146 + sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 147 + wheels = [ 148 + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 149 + ] 150 + 151 + [[package]] 152 + name = "pluggy" 153 + version = "1.5.0" 154 + source = { registry = "https://pypi.org/simple" } 155 + sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } 156 + wheels = [ 157 + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, 158 + ] 159 + 160 + [[package]] 161 + name = "pygments" 162 + version = "2.19.1" 163 + source = { registry = "https://pypi.org/simple" } 164 + sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 165 + wheels = [ 166 + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 167 + ] 168 + 169 + [[package]] 170 + name = "pytest" 171 + version = "8.3.5" 172 + source = { registry = "https://pypi.org/simple" } 173 + dependencies = [ 174 + { name = "colorama", marker = "sys_platform == 'win32'" }, 175 + { name = "iniconfig" }, 176 + { name = "packaging" }, 177 + { name = "pluggy" }, 178 + ] 179 + sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } 180 + wheels = [ 181 + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, 182 + ] 183 + 184 + [[package]] 185 + name = "pytest-custom-exit-code" 186 + version = "0.3.0" 187 + source = { registry = "https://pypi.org/simple" } 188 + dependencies = [ 189 + { name = "pytest" }, 190 + ] 191 + sdist = { url = "https://files.pythonhosted.org/packages/92/9d/e1eb0af5e96a5c34f59b9aa69dfb680764420fe60f2ec28cfbc5339f99f8/pytest-custom_exit_code-0.3.0.tar.gz", hash = "sha256:51ffff0ee2c1ddcc1242e2ddb2a5fd02482717e33a2326ef330e3aa430244635", size = 3633, upload-time = "2019-08-07T09:45:15.781Z" } 192 + wheels = [ 193 + { url = "https://files.pythonhosted.org/packages/35/a0/effb6cbbccfd1c106c572d3d619b3418d71093afb4cd4f91f51e6a1799d2/pytest_custom_exit_code-0.3.0-py3-none-any.whl", hash = "sha256:6e0ce6e57ce3a583cb7e5023f7d1021e19dfec22be41d9ad345bae2fc61caf3b", size = 4055, upload-time = "2019-08-07T09:45:13.767Z" }, 194 + ] 195 + 196 + [[package]] 197 + name = "pytest-order" 198 + version = "1.3.0" 199 + source = { registry = "https://pypi.org/simple" } 200 + dependencies = [ 201 + { name = "pytest" }, 202 + ] 203 + sdist = { url = "https://files.pythonhosted.org/packages/1d/66/02ae17461b14a52ce5a29ae2900156b9110d1de34721ccc16ccd79419876/pytest_order-1.3.0.tar.gz", hash = "sha256:51608fec3d3ee9c0adaea94daa124a5c4c1d2bb99b00269f098f414307f23dde", size = 47544, upload-time = "2024-08-22T12:29:54.512Z" } 204 + wheels = [ 205 + { url = "https://files.pythonhosted.org/packages/1b/73/59b038d1aafca89f8e9936eaa8ffa6bb6138d00459d13a32ce070be4f280/pytest_order-1.3.0-py3-none-any.whl", hash = "sha256:2cd562a21380345dd8d5774aa5fd38b7849b6ee7397ca5f6999bbe6e89f07f6e", size = 14609, upload-time = "2024-08-22T12:29:53.156Z" }, 206 + ] 207 + 208 + [[package]] 209 + name = "pytest-parametrized" 210 + version = "1.7" 211 + source = { registry = "https://pypi.org/simple" } 212 + dependencies = [ 213 + { name = "pytest" }, 214 + ] 215 + sdist = { url = "https://files.pythonhosted.org/packages/44/ae/8ed9bd7dee749610f4e1c52f1bc28f1dfc873f41fc54fcaf509e07829237/pytest_parametrized-1.7.tar.gz", hash = "sha256:3ec50eb164dae2ad3a0595ec197ff8bc1ce741342991f0016ba7daa6a861361b", size = 3712, upload-time = "2024-12-21T17:34:52.606Z" } 216 + wheels = [ 217 + { url = "https://files.pythonhosted.org/packages/92/66/51fdf9cb516783cfa9363b587fd9a99f86726bd5606baa6c9adf0fa4b0d2/pytest_parametrized-1.7-py3-none-any.whl", hash = "sha256:a7da6948c0ccb6798924f600b01c4c19064d46fd2bd49b5406c2d9aa7303d05e", size = 3822, upload-time = "2024-12-21T17:34:49.951Z" }, 218 + ] 219 + 220 + [[package]] 221 + name = "pytest-randomly" 222 + version = "3.16.0" 223 + source = { registry = "https://pypi.org/simple" } 224 + dependencies = [ 225 + { name = "pytest" }, 226 + ] 227 + sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367, upload-time = "2024-10-25T15:45:34.274Z" } 228 + wheels = [ 229 + { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, 230 + ] 231 + 232 + [[package]] 233 + name = "pytest-repeat" 234 + version = "0.9.4" 235 + source = { registry = "https://pypi.org/simple" } 236 + dependencies = [ 237 + { name = "pytest" }, 238 + ] 239 + sdist = { url = "https://files.pythonhosted.org/packages/80/d4/69e9dbb9b8266df0b157c72be32083403c412990af15c7c15f7a3fd1b142/pytest_repeat-0.9.4.tar.gz", hash = "sha256:d92ac14dfaa6ffcfe6917e5d16f0c9bc82380c135b03c2a5f412d2637f224485", size = 6488, upload-time = "2025-04-07T14:59:53.077Z" } 240 + wheels = [ 241 + { url = "https://files.pythonhosted.org/packages/73/d4/8b706b81b07b43081bd68a2c0359fe895b74bf664b20aca8005d2bb3be71/pytest_repeat-0.9.4-py3-none-any.whl", hash = "sha256:c1738b4e412a6f3b3b9e0b8b29fcd7a423e50f87381ad9307ef6f5a8601139f3", size = 4180, upload-time = "2025-04-07T14:59:51.492Z" }, 242 + ] 243 + 244 + [[package]] 245 + name = "pytest-sugar" 246 + version = "1.0.0" 247 + source = { registry = "https://pypi.org/simple" } 248 + dependencies = [ 249 + { name = "packaging" }, 250 + { name = "pytest" }, 251 + { name = "termcolor" }, 252 + ] 253 + sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992, upload-time = "2024-02-01T18:30:36.735Z" } 254 + wheels = [ 255 + { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171, upload-time = "2024-02-01T18:30:29.395Z" }, 256 + ] 257 + 258 + [[package]] 259 + name = "pytest-xdist" 260 + version = "3.6.1" 261 + source = { registry = "https://pypi.org/simple" } 262 + dependencies = [ 263 + { name = "execnet" }, 264 + { name = "pytest" }, 265 + ] 266 + sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } 267 + wheels = [ 268 + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, 269 + ] 270 + 271 + [[package]] 272 + name = "rich" 273 + version = "14.0.0" 274 + source = { registry = "https://pypi.org/simple" } 275 + dependencies = [ 276 + { name = "markdown-it-py" }, 277 + { name = "pygments" }, 278 + ] 279 + sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } 280 + wheels = [ 281 + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, 282 + ] 283 + 284 + [[package]] 285 + name = "ruff" 286 + version = "0.11.9" 287 + source = { registry = "https://pypi.org/simple" } 288 + sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } 289 + wheels = [ 290 + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, 291 + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, 292 + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, 293 + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, 294 + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, 295 + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, 296 + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, 297 + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, 298 + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, 299 + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, 300 + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, 301 + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, 302 + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, 303 + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, 304 + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, 305 + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, 306 + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, 307 + ] 308 + 309 + [[package]] 310 + name = "sortedcontainers" 311 + version = "2.4.0" 312 + source = { registry = "https://pypi.org/simple" } 313 + sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } 314 + wheels = [ 315 + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, 316 + ] 317 + 318 + [[package]] 319 + name = "termcolor" 320 + version = "3.1.0" 321 + source = { registry = "https://pypi.org/simple" } 322 + sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } 323 + wheels = [ 324 + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, 325 + ] 326 + 327 + [[package]] 328 + name = "tzdata" 329 + version = "2025.2" 330 + source = { registry = "https://pypi.org/simple" } 331 + sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } 332 + wheels = [ 333 + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, 334 + ]
+1
packages/jugulis/.python-version
··· 1 + 3.14
packages/jugulis/README.md

This is a binary file and will not be displayed.

+6
packages/jugulis/jugulis/__init__.py
··· 1 + from .deino import Deino 2 + from .hydreigon import Hydreigon, AmbiguityError 3 + from .functions import * 4 + from rich.traceback import install 5 + 6 + install(show_locals=True)
+110
packages/jugulis/jugulis/deino.py
··· 1 + from .functions import scorefunction, processsignature 2 + import hydrox.typing as ht 3 + from hydrox.helpers import as_decorator, query, flatten, cache_get 4 + from inspect import getfullargspec, Signature 5 + from functools import partial 6 + 7 + 8 + def format_callable(arg): 9 + return Deino(arg) if isinstance(arg, ht.FunctionTypes) else arg 10 + 11 + 12 + def format_args_kwargs(args, kwargs): 13 + return [format_callable(arg) for arg in args], { 14 + format_callable(k): format_callable(v) for k, v in kwargs.items() 15 + } 16 + 17 + 18 + def format_message(message, args, kwargs, capitalize=False): 19 + args, kwargs = format_args_kwargs(args, kwargs) 20 + if args: 21 + message += f" {'A' if capitalize else 'a'}rguments {args}" 22 + if kwargs: 23 + message += f" and {'K' if capitalize else 'k'}eyword {'A' if capitalize else 'a'}rguments {kwargs}" 24 + elif kwargs: 25 + message += f" {'K' if capitalize else 'k'}eyword {'A' if capitalize else 'a'}rguments {kwargs}" 26 + return message 27 + 28 + 29 + class Deino: 30 + def __init__(self, *args, evolved=False, processors=tuple(), **cache_kwargs): 31 + self.__cache_kwargs__ = {"hashed": True, "cls": False} | cache_kwargs 32 + self.__evolved__ = evolved 33 + self.__processors__ = flatten.list(processors) 34 + if as_decorator(query=query): 35 + self.__initialized__ = False 36 + self.__cache__ = args[0] if args else None 37 + else: 38 + self.__initialized__ = True 39 + # TODO: If `func` is an instance of `Deino`, combine the `kwargs`. Can I combine the caches as well? 40 + func = args[0] 41 + self.__cache__ = args[1] if len(args) > 1 else None 42 + self.__process__(func) 43 + for processor in self.__processors__[::-1]: 44 + func = processor(func) 45 + self.__func__ = func 46 + 47 + def __process__(self, func): 48 + func.__signature__ = ht.signature(func) 49 + func.__spec__ = getfullargspec(func) 50 + for attr in dir(func): 51 + if attr not in ("__dict__", "__class__"): 52 + setattr(self, attr, getattr(func, attr)) 53 + self.__repr_message__ = ht.format_name(func) 54 + 55 + def __repr__(self): 56 + return self.__repr_message__ 57 + 58 + def __score__(self, *args, **kwargs): 59 + return scorefunction(self.__signature__, self.__spec__, args, kwargs) 60 + 61 + def __check__(self, sig: Signature, args, kwargs, func): 62 + return scorefunction( 63 + sig, 64 + self.__spec__, 65 + args, 66 + kwargs, 67 + func, 68 + True, 69 + ) 70 + 71 + def __process_result__(self, args, kwargs): 72 + func = lambda: None 73 + func.__name__ = self.__name__ 74 + 75 + # TODO: When calling from `Hydreigon`, the scoring processes the signature, 76 + # then running the function processes it again. 77 + # How this be deduplicated? 78 + sig: Signature 79 + sig = func.__signature__ = processsignature(self.__signature__, args, kwargs) 80 + 81 + if not self.__evolved__: 82 + self.__check__(sig, args, kwargs, func) 83 + 84 + result = self.__func__(*args, **kwargs) 85 + if isinstance(result, return_type := sig.return_annotation): 86 + return result 87 + result_format = f"'{result}'" if isinstance(result, str) else result 88 + raise TypeError( 89 + f"{ht.format_name(func)} returned value {result_format} of type {ht.format_name(ht.get_type(result))}, " 90 + f"but was expecting an instance of {ht.format_name(return_type)}" 91 + ) 92 + 93 + def __call__(self, *args, **kwargs): 94 + if self.__initialized__: 95 + return cache_get( 96 + partial(self.__process_result__, args, kwargs), 97 + self.__cache__, 98 + self.__cache_kwargs__["hashed"], 99 + self.__cache_kwargs__["cls"], 100 + args, 101 + kwargs, 102 + ) 103 + self.__initialized__ = True 104 + # TODO: If `func` is an instance of `Deino`, combine the `kwargs`. Can I combine the caches as well? 105 + func = args[0] 106 + self.__process__(func) 107 + for processor in self.__processors__[::-1]: 108 + func = processor(func) 109 + self.__func__ = func 110 + return self
+296
packages/jugulis/jugulis/functions.py
··· 1 + import collections.abc as abc, hydrox.typing as ht 2 + from .variables import sentinel 3 + from contextlib import suppress 4 + from functools import cache as defcache, partial 5 + from hydrox.typing import format_args, generalize, get_args, get_origin, scoreinstance 6 + from hydrox.helpers import zipsuppress, CacheError, _make_key 7 + from inspect import FullArgSpec, Parameter, Signature 8 + from itertools import zip_longest 9 + from types import MappingProxyType 10 + from typing import Any, Optional, TypeVar 11 + from rich import print 12 + from rich.rule import Rule 13 + from rich.pretty import pprint 14 + 15 + 16 + @defcache 17 + def normalize_typevars( 18 + annotation: list | ht.Annotation, 19 + typevar: TypeVar, 20 + replacement: ht.Annotation, 21 + ): 22 + if annotation == typevar: 23 + return generalize(replacement) 24 + inner = partial(normalize_typevars, typevar=typevar, replacement=replacement) 25 + if isinstance(annotation, list): 26 + return [inner(a) for a in annotation] 27 + if args := get_args(annotation): 28 + return format_args( 29 + generalize(get_origin(annotation)), [inner(arg) for arg in args] 30 + ) 31 + return generalize(annotation) 32 + 33 + 34 + @defcache 35 + def normalize_typevar_signature( 36 + sig: Signature, typevar: TypeVar, replacement: ht.Annotation 37 + ): 38 + inner = partial(normalize_typevars, typevar=typevar, replacement=replacement) 39 + return sig.replace( 40 + return_annotation=inner(sig.return_annotation), 41 + parameters=( 42 + param.replace(annotation=inner(param.annotation)) 43 + for param in sig.parameters.values() 44 + ), 45 + ) 46 + 47 + 48 + # NOTE: This cannot be cached, as `obj` may be an integers, boolean, string, or float. 49 + def normalize_instance_signature(sig: Signature, annotation: ht.Annotation, obj: Any): 50 + replacements = {} 51 + 52 + @defcache 53 + def inner(left, right): 54 + if isinstance(left, ht.TypeVar) and left not in replacements: 55 + if bound := left.__bound__: 56 + if ht.isinmrosubclass(right, bound): 57 + replacements[left] = right 58 + elif constraints := left.__constraints__: 59 + for constraint in constraints: 60 + if right == constraint: 61 + replacements[left] = right 62 + break 63 + else: 64 + replacements[left] = right 65 + else: 66 + with zipsuppress(): 67 + for la, ra in zip(ht.get_args(left), ht.get_args(right), strict=True): 68 + inner(la, ra) 69 + 70 + inner(annotation, ht.get_type(obj)) 71 + for k, v in replacements.items(): 72 + sig = normalize_typevar_signature(sig, k, v) 73 + return sig 74 + 75 + 76 + def processsignature(sig: Signature, args, kwargs): 77 + for index, (name, param) in enumerate(sig.parameters.items()): 78 + normalize = partial(normalize_instance_signature, sig, param.annotation) 79 + if param.kind is Parameter.POSITIONAL_ONLY: 80 + if index < len(args): 81 + sig = normalize(args[index]) 82 + elif param.kind is Parameter.POSITIONAL_OR_KEYWORD: 83 + if index < len(args): 84 + sig = normalize(args[index]) 85 + elif name in kwargs: 86 + sig = normalize(kwargs[name]) 87 + elif param.kind is Parameter.KEYWORD_ONLY: 88 + if name in kwargs: 89 + sig = normalize(kwargs[name]) 90 + return sig 91 + 92 + 93 + # def scorefunction(sig: Signature, spec: FullArgSpec, args, kwargs): 94 + # sig: Signature = processsignature(sig, args, kwargs) 95 + # score = -1 if specvarargs or specvarkw else 0 96 + # positional = [] 97 + # keywords = [] 98 + # for index, (name, param) in enumerate(sig.parameters.items()): 99 + # scorer = partial(scoreinstance, right=param.annotation) 100 + # if param.kind is Parameter.POSITIONAL_ONLY: 101 + # if index < len(args): 102 + # positional.append(name) 103 + # score += scorer(args[index]) 104 + # else: 105 + # score -= 1 106 + # elif param.kind is Parameter.POSITIONAL_OR_KEYWORD: 107 + # if index < len(args): 108 + # positional.append(name) 109 + # score += scorer(args[index]) 110 + # elif name in kwargs: 111 + # keywords.append(name) 112 + # score += scorer(kwargs[name]) 113 + # else: 114 + # score -= 1 115 + # elif param.kind is Parameter.KEYWORD_ONLY: 116 + # if name in kwargs: 117 + # keywords.append(name) 118 + # score += scorer(kwargs[name]) 119 + # else: 120 + # score -= 1 121 + # if len(args) > len(positional) and not specvarargs: 122 + # score -= len(args) - len(positional) 123 + # if not specvarkw: 124 + # for name in kwargs: 125 + # if name in keywords: 126 + # if name in positional: 127 + # score -= 2 128 + # else: 129 + # score -= 1 130 + # return score 131 + 132 + 133 + # TODO: How does deal with positional and keyword only arguments, 134 + # as well as default arguments? 135 + def scorefunction( 136 + sig: Signature, spec: FullArgSpec, args, kwargs, self=None, processed=False 137 + ): 138 + specargs = spec.args 139 + speckwargs = spec.kwonlyargs 140 + specargs_length = len(specargs) 141 + args_length = len(args) 142 + 143 + self_name = ht.format_name(self) 144 + if self is None: 145 + should_raise = False 146 + else: 147 + should_raise = True 148 + 149 + def format_error(name, arg, annotation: ht.Annotation): 150 + arg_format = f"'{arg}'" if isinstance(arg, str) else arg 151 + return ( 152 + f"{self_name} {'positional' if name in specargs else 'keyword'} " 153 + f"argument '{name}' got value {arg_format} of type {ht.format_name(ht.get_type(arg))}, " 154 + f"but was expecting an instance of {ht.format_name(annotation)}" 155 + ) 156 + 157 + specvarkw = spec.varkw 158 + specvarargs = spec.varargs 159 + total: int = 0 160 + if specvarkw: 161 + total -= 2 162 + if specvarargs: 163 + total -= 2 164 + 165 + sig: Signature = sig if processed else processsignature(sig, args, kwargs) 166 + 167 + params: MappingProxyType[str, Parameter] = sig.parameters 168 + total -= sum(param.default is not Parameter.empty for param in params.values()) 169 + param_keys = params.keys() 170 + if kwargs: 171 + if not params: 172 + if should_raise: 173 + raise TypeError( 174 + f"{self_name} got an unexpected keyword argument '{next(iter(kwargs))}'" 175 + ) 176 + return None 177 + 178 + kwarg_keys = kwargs.keys() 179 + 180 + if difference := (kwarg_keys - param_keys): 181 + if not specvarkw: 182 + if should_raise: 183 + for k in kwarg_keys: 184 + if k in difference: 185 + raise TypeError( 186 + f"{self_name} got an unexpected keyword argument '{k}'" 187 + ) 188 + return None 189 + total -= len(difference) 190 + 191 + if intersection := (kwarg_keys & param_keys): 192 + for k in intersection: 193 + if k not in (specvarkw, specvarargs): 194 + v = kwargs[k] 195 + param = params[k] 196 + if param.kind is Parameter.POSITIONAL_ONLY: 197 + if should_raise: 198 + # po_kwargs = ", ".join( 199 + # n 200 + # for n, p in params.items() 201 + # if n in kwargs and p.kind is Parameter.POSITIONAL_ONLY 202 + # ) 203 + po_kwargs = ", ".join( 204 + n 205 + for n in intersection 206 + if params[n].kind is Parameter.POSITIONAL_ONLY 207 + ) 208 + raise TypeError( 209 + f"{self_name} got some positional-only arguments passed as keyword arguments: '{po_kwargs}'" 210 + ) 211 + return None 212 + annotation = param.annotation 213 + kwarg_score: Optional[int] = scoreinstance(v, annotation) 214 + if kwarg_score is None: 215 + if should_raise: 216 + raise TypeError(format_error(k, v, annotation)) 217 + return None 218 + total += kwarg_score 219 + 220 + message = f"{self_name} takes {specargs_length} positional arguments but {args_length} {'were' if args_length == 1 else 'was'} given" 221 + if args and not params: 222 + if should_raise: 223 + raise TypeError(message) 224 + return None 225 + 226 + def format_missing_error(index, keyword=False): 227 + if should_raise: 228 + if keyword: 229 + missing_args = [a for a in speckwargs[index:] if a not in kwargs] 230 + else: 231 + missing_args = [] 232 + for a in specargs[index:]: 233 + kind = params[a].kind 234 + if (kind is Parameter.POSITIONAL_ONLY) or ( 235 + (kind is Parameter.POSITIONAL_OR_KEYWORD) and (a not in kwargs) 236 + ): 237 + missing_args.append(a) 238 + missing_length = len(missing_args) 239 + more_than_1_missing = missing_length > 1 240 + if more_than_1_missing: 241 + missing_message = "'" + "', '".join(missing_args[:-1]) + "'" 242 + if missing_length > 2: 243 + missing_message += "," 244 + missing_message += f" and '{missing_args[-1]}'" 245 + else: 246 + missing_message = f"'{missing_args[0]}'" 247 + raise TypeError( 248 + f"{self_name} missing {missing_length} required {'keyword-only' if keyword else 'positional'} argument{'s' if more_than_1_missing else ''}: {missing_message}" 249 + ) 250 + return None 251 + 252 + for index, (param, arg) in enumerate( 253 + zip_longest(params.values(), args, fillvalue=sentinel) 254 + ): 255 + if param is sentinel: 256 + if arg is sentinel: 257 + break 258 + if not specvarargs: 259 + if should_raise: 260 + raise TypeError(message) 261 + return None 262 + total -= 1 263 + else: 264 + name = param.name 265 + kind = param.kind 266 + if arg is sentinel: 267 + if param.default is Parameter.empty: 268 + if kind is Parameter.POSITIONAL_ONLY: 269 + return format_missing_error(index) 270 + if name not in kwargs: 271 + if kind is Parameter.POSITIONAL_OR_KEYWORD: 272 + return format_missing_error(index) 273 + if kind is Parameter.KEYWORD_ONLY: 274 + return format_missing_error(speckwargs.index(name), True) 275 + elif kind in (Parameter.KEYWORD_ONLY, Parameter.VAR_KEYWORD): 276 + if should_raise: 277 + raise TypeError(message) 278 + return None 279 + elif kind is Parameter.VAR_POSITIONAL: 280 + total -= 1 281 + elif kind is Parameter.POSITIONAL_OR_KEYWORD and name in kwargs: 282 + if should_raise: 283 + raise TypeError( 284 + f"{self_name} got multiple values for argument '{name}'" 285 + ) 286 + return None 287 + else: 288 + annotation = param.annotation 289 + arg_score: Optional[int] = scoreinstance(arg, annotation) 290 + if arg_score is None: 291 + if should_raise: 292 + raise TypeError(format_error(name, arg, annotation)) 293 + return None 294 + total += arg_score 295 + 296 + return total
+144
packages/jugulis/jugulis/hydreigon.py
··· 1 + from autoslot import SlotsMeta, SlotsPlusDictMeta 2 + from hydrox.helpers import as_decorator, query, cache_get 3 + from .deino import Deino, format_message 4 + from collections import defaultdict, namedtuple 5 + from functools import partial 6 + from rich.table import Table 7 + from rich.console import Console 8 + from inspect import isclass 9 + 10 + console = Console() 11 + 12 + 13 + class AmbiguityError(Exception): ... 14 + 15 + 16 + class Hydreigon(list): 17 + def __init__( 18 + self, *args, cache=None, superhashed=True, supercls=False, **cache_kwargs 19 + ): 20 + super().__init__() 21 + cache_kwargs.pop("evolved", None) 22 + self.__cache_kwargs__ = cache_kwargs 23 + self.__cache__ = cache 24 + self.__superhashed__ = superhashed 25 + self.__supercls__ = supercls 26 + if as_decorator(query=query): 27 + self.__initialized__ = False 28 + self.__supercache__ = args[0] if args else None 29 + else: 30 + self.__initialized__ = True 31 + if args: 32 + self.__supercache__ = args[1] if len(args) > 1 else None 33 + self.__append_func__(args[0]) 34 + else: 35 + self.__supercache__ = None 36 + 37 + def __append_func__(self, func): 38 + if isinstance(func, Deino): 39 + func.__cache__ = self.__cache__ | func.__cache__ 40 + func.__cache_kwargs__ = self.__cache_kwargs__ | func.__cache_kwargs__ 41 + self.append(func) 42 + else: 43 + self.append( 44 + Deino(func, self.__cache__, evolved=True, **self.__cache_kwargs__) 45 + ) 46 + 47 + def __score__(self, *args, **kwargs): 48 + return {func: func.__score__(*args, **kwargs) for func in self} 49 + 50 + def __tabulate__(self, *args, **kwargs): 51 + table = Table( 52 + "Function", 53 + "Score", 54 + title=format_message("Function Scores for", args, kwargs, capitalize=True), 55 + ) 56 + scores = self.__score__(*args, **kwargs) 57 + for func, score in scores.items(): 58 + table.add_row(str(func), str(score)) 59 + console.print(table) 60 + return scores 61 + 62 + def __funcall__(self, args, kwargs): 63 + candidates = defaultdict(list) 64 + for func in self: 65 + function_score = func.__score__(*args, **kwargs) 66 + if function_score is not None: 67 + candidates[function_score].append(func) 68 + if not candidates: 69 + message = ( 70 + format_message("no callables match", args, kwargs) 71 + + " from the following candidates:" 72 + ) 73 + for choice in self: 74 + message += f"\n\t\t- {choice}" 75 + raise AmbiguityError(message) 76 + best_choice = candidates[max(candidates)] 77 + if len(best_choice) > 1: 78 + message = format_message("multiple callables match", args, kwargs) + ":" 79 + for choice in best_choice: 80 + message += f"\n\t\t- {choice}" 81 + raise AmbiguityError(message) 82 + return best_choice[0](*args, **kwargs) 83 + 84 + def roar(self, *args, **kwargs): 85 + kwargs.pop("evolved", None) 86 + if as_decorator(query=query): 87 + 88 + def wrapper(func): 89 + self.append(Deino(func, *args, evolved=True, **kwargs)) 90 + return self 91 + 92 + return wrapper 93 + 94 + self.append(Deino(*args, evolved=True, **kwargs)) 95 + return self 96 + 97 + def __call__(self, *args, **kwargs): 98 + if self.__initialized__: 99 + return cache_get( 100 + partial(self.__funcall__, args, kwargs), 101 + self.__supercache__, 102 + self.__superhashed__, 103 + self.__supercls__, 104 + args, 105 + kwargs, 106 + ) 107 + self.__initialized__ = True 108 + self.__append_func__(args[0]) 109 + return self 110 + 111 + 112 + def nydra(func): 113 + func.__hydread__ = (tuple(), {"disabled": True}) 114 + return func 115 + 116 + 117 + def hydread(*args, **kwargs): 118 + def wrapper(func): 119 + func.__hydread__ = (args, kwargs) 120 + return func 121 + 122 + return wrapper 123 + 124 + 125 + class prepare(dict): 126 + def __init__(self, name, bases): 127 + pass 128 + 129 + def __setitem__(self, key, value): 130 + if callable(value): 131 + args, kwargs = getattr(value, "__hydread__", (tuple(), {})) 132 + if not kwargs.get("disabled", False): 133 + value = getattr(self.get(key), "roar", Hydreigon)( 134 + value, *args, **kwargs 135 + ) 136 + super().__setitem__(key, value) 137 + 138 + 139 + class MetaHydra(SlotsMeta): 140 + __prepare__ = prepare 141 + 142 + 143 + class MetaHydrict(SlotsPlusDictMeta): 144 + __prepare__ = prepare
packages/jugulis/jugulis/tests/__init__.py

This is a binary file and will not be displayed.

+81
packages/jugulis/jugulis/tests/test_deino.py
··· 1 + from jugulis import Deino 2 + from pytest import fixture 3 + 4 + 5 + class TestDeino: 6 + @fixture 7 + def f1(self): 8 + @Deino 9 + def f1(a: int, /) -> int: 10 + return 0 11 + 12 + return f1 13 + 14 + @fixture 15 + def f2(self): 16 + @Deino 17 + def f2(a: int, /, b: int) -> int: 18 + return 1 19 + 20 + return f2 21 + 22 + @fixture 23 + def f3(self): 24 + @Deino 25 + def f3(a: int, /, b: int, *c) -> int: 26 + return 2 27 + 28 + return f3 29 + 30 + @fixture 31 + def f4(self): 32 + @Deino 33 + def f4(a: int, /, b: int, *c, d: int) -> int: 34 + return 3 35 + 36 + return f4 37 + 38 + @fixture 39 + def f5(self): 40 + @Deino 41 + def f5(a: int, /, b: int, *c, d: int, **e) -> int: 42 + return 4 43 + 44 + return f5 45 + 46 + def test_positional_only(self, f1): 47 + assert f1(0) == 0 48 + 49 + def test_positional_or_keyword(self, f2): 50 + assert f2(0, 0) == f2(0, b=0) == 1 51 + 52 + def test_varargs(self, f3): 53 + assert f3(0, 0, 0) == 2 54 + 55 + def test_keyword_only(self, f4): 56 + assert f4(0, 0, 0, d=0) == 3 57 + 58 + def test_same_varkw(self, f5): 59 + assert f5(0, 0, 0, d=0, e=0) == 4 60 + 61 + def test_different_varkw(self, f5): 62 + assert f5(0, 0, 0, d=0, z=0) == 4 63 + 64 + @fixture 65 + def e(self): 66 + @Deino 67 + def e(a: int, /) -> int: 68 + return str(0) 69 + 70 + return e 71 + 72 + def test_wrong_return_type(self, e): 73 + try: 74 + error = e(0) 75 + except TypeError as te: 76 + assert ( 77 + str(te) 78 + == "e(a: ht.int, /) -> ht.int returned value '0' of type ht.str, but was expecting an instance of ht.int" 79 + ), str(te) 80 + else: 81 + assert False, error
+107
packages/jugulis/jugulis/tests/test_hydreigon.py
··· 1 + from jugulis import Hydreigon, AmbiguityError 2 + from pytest import fixture 3 + 4 + 5 + class TestHydreigon: 6 + @fixture 7 + def f(self): 8 + @Hydreigon 9 + def f(a: int, /) -> int: 10 + return 0 11 + 12 + @f.roar 13 + def f(a: int, /, b: int) -> int: 14 + return 1 15 + 16 + @f.roar 17 + def f(a: int, /, b: int, *c) -> int: 18 + return 2 19 + 20 + @f.roar 21 + def f(a: int, /, b: int, *c, d: int) -> int: 22 + return 3 23 + 24 + @f.roar 25 + def f(a: int, /, b: int, *c, d: int, **e) -> int: 26 + return 4 27 + 28 + return f 29 + 30 + def test_positional_only(self, f): 31 + assert f(0) == 0 32 + 33 + def test_positional_or_keyword(self, f): 34 + assert f(0, 0) == f(0, b=0) == 1 35 + 36 + def test_varargs(self, f): 37 + assert f(0, 0, 0) == 2 38 + 39 + def test_keyword_only(self, f): 40 + assert f(0, 0, 0, d=0) == 3 41 + 42 + def test_same_varkw(self, f): 43 + assert f(0, 0, 0, d=0, e=0) == 4 44 + 45 + def test_different_varkw(self, f): 46 + assert f(0, 0, 0, d=0, z=0) == 4 47 + 48 + def test_multiple_callables(self, f): 49 + @f.roar 50 + def f(b: int, /) -> int: ... 51 + 52 + try: 53 + result = f(1) 54 + except AmbiguityError as e: 55 + assert str(e) == ( 56 + "multiple callables match arguments [1]:" 57 + "\n\t\t- f(a: ht.int, /) -> ht.int" 58 + "\n\t\t- f(b: ht.int, /) -> ht.int" 59 + ), str(e) 60 + else: 61 + assert False, result 62 + 63 + def test_no_callables(self, f): 64 + f.pop() 65 + 66 + try: 67 + result = f(e=1) 68 + except AmbiguityError as e: 69 + assert str(e) == ( 70 + "no callables match keyword arguments {'e': 1} from the following candidates:" 71 + "\n\t\t- f(a: ht.int, /) -> ht.int" 72 + "\n\t\t- f(a: ht.int, /, b: ht.int) -> ht.int" 73 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c) -> ht.int" 74 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c, d: ht.int) -> ht.int" 75 + ), str(e) 76 + else: 77 + assert False, result 78 + 79 + def test_no_callable_arg_string(self, f): 80 + try: 81 + result = f("") 82 + except AmbiguityError as e: 83 + assert str(e) == ( 84 + "no callables match arguments [''] from the following candidates:" 85 + "\n\t\t- f(a: ht.int, /) -> ht.int" 86 + "\n\t\t- f(a: ht.int, /, b: ht.int) -> ht.int" 87 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c) -> ht.int" 88 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c, d: ht.int) -> ht.int" 89 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int" 90 + ), str(e) 91 + else: 92 + assert False, result 93 + 94 + def test_no_callable_kwarg_string(self, f): 95 + try: 96 + result = f(d="") 97 + except AmbiguityError as e: 98 + assert str(e) == ( 99 + "no callables match keyword arguments {'d': ''} from the following candidates:" 100 + "\n\t\t- f(a: ht.int, /) -> ht.int" 101 + "\n\t\t- f(a: ht.int, /, b: ht.int) -> ht.int" 102 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c) -> ht.int" 103 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c, d: ht.int) -> ht.int" 104 + "\n\t\t- f(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int" 105 + ), str(e) 106 + else: 107 + assert False, result
+248
packages/jugulis/jugulis/tests/test_normalize_typevars.py
··· 1 + # TODO: Create `bound` examples. 2 + 3 + import hydrox.typing as ht, collections.abc as abc, typing 4 + from hydrox.hypothesis import ( 5 + Data, 6 + bound_type_var_strat, 7 + constrained_type_var_strat, 8 + subgen_strat, 9 + typevar_func_strat, 10 + typevar_instance_strat, 11 + ) 12 + from hydrox.helpers import flatten 13 + from jugulis import ( 14 + normalize_typevars, 15 + normalize_typevar_signature, 16 + normalize_instance_signature, 17 + Deino, 18 + ) 19 + from hypothesis import given, example 20 + from inspect import Signature, signature 21 + 22 + B = typing.TypeVar("B", str, int) 23 + dbgen = dict[B, B] 24 + lbgen = list[B] 25 + sbgen = set[B] 26 + cbgen = abc.Callable[[B, lbgen, sbgen], dbgen] 27 + tbgen = tuple[str, int, B, lbgen, sbgen, dbgen, cbgen] 28 + 29 + C = typing.TypeVar("C", str, int) 30 + dcgen = dict[C, C] 31 + lcgen = list[C] 32 + scgen = set[C] 33 + ccgen = abc.Callable[[C, lcgen, scgen], dcgen] 34 + tcgen = tuple[str, int, C, lcgen, scgen, dcgen, ccgen] 35 + 36 + 37 + class TestNormalizeTypeVars: 38 + @given(data=..., generic=bound_type_var_strat()) 39 + # @example(generic=(cbgen, B, str)) 40 + # @example(generic=(tbgen, B, str)) 41 + # @example(generic=(lbgen, B, str)) 42 + # @example(generic=(sbgen, B, str)) 43 + # @example(generic=(dbgen, B, str)) 44 + def test_bound(self, data: Data, generic): 45 + generic, T, bound = generic 46 + checked = False 47 + 48 + def inner(a_args, b_args): 49 + nonlocal checked 50 + try: 51 + for a, b in zip(a_args, b_args, strict=True): 52 + if a == T: 53 + assert ht.isinmrosubclass(b, bound) 54 + checked = True 55 + else: 56 + inner(ht.get_args(a), ht.get_args(b)) 57 + except ValueError: 58 + assert False 59 + 60 + normal = normalize_typevars(generic, T, data.draw(subgen_strat(bound))) 61 + if generic == T: 62 + assert ht.isinmrosubclass(normal, bound) 63 + checked = True 64 + else: 65 + inner(ht.get_args(generic), ht.get_args(normal)) 66 + 67 + if not checked: 68 + assert False, f"{generic} | {normal}" 69 + 70 + @given(generic=constrained_type_var_strat()) 71 + @example(generic=(ccgen, C, str)) 72 + @example(generic=(tcgen, C, str)) 73 + @example(generic=(lcgen, C, str)) 74 + @example(generic=(scgen, C, str)) 75 + @example(generic=(dcgen, C, str)) 76 + def test_constrained(self, generic): 77 + generic, T, constraint = generic 78 + checked = False 79 + 80 + def inner(a_args, b_args): 81 + nonlocal checked 82 + try: 83 + for a, b in zip(a_args, b_args, strict=True): 84 + if a == T: 85 + assert b == constraint 86 + checked = True 87 + else: 88 + inner(ht.get_args(a), ht.get_args(b)) 89 + except ValueError: 90 + assert False 91 + 92 + normal = normalize_typevars(generic, T, constraint) 93 + if generic == T: 94 + assert normal == constraint 95 + checked = True 96 + else: 97 + inner(ht.get_args(generic), ht.get_args(normal)) 98 + 99 + if not checked: 100 + assert False, f"{generic} | {normal}" 101 + 102 + 103 + def f(t: str, lg: list[str], sg: set[str]) -> dict[str, str]: ... 104 + def fb( 105 + s: str, 106 + i: int, 107 + t: B, 108 + lg: lbgen, 109 + sg: sbgen, 110 + cg: cbgen, 111 + dg: dbgen, 112 + ) -> tcgen: ... 113 + def fc(s: str, i: int, t: C, lg: lcgen, sg: scgen, cg: ccgen, dg: dcgen) -> tcgen: ... 114 + 115 + 116 + class TestNormalizeTypeVarSignatures: 117 + @given(data=..., func=typevar_func_strat(True)) 118 + # @example(func=(fb, B, str)) 119 + def test_bound(self, data: Data, func): 120 + func, T, bound = func 121 + checked = False 122 + 123 + def inner(a_args, b_args): 124 + nonlocal checked 125 + try: 126 + for a, b in zip(a_args, b_args, strict=True): 127 + if a == T: 128 + assert ht.isinmrosubclass(b, bound), func 129 + checked = True 130 + else: 131 + inner(ht.get_args(a), ht.get_args(b)) 132 + except ValueError: 133 + assert False 134 + 135 + sig: Signature = normalize_typevar_signature( 136 + signature(func), T, data.draw(subgen_strat(bound)) 137 + ) 138 + func = Deino(func) 139 + inner( 140 + func.__annotations__.values(), 141 + flatten( 142 + (param.annotation for param in sig.parameters.values()), 143 + sig.return_annotation, 144 + ), 145 + ) 146 + 147 + if not checked: 148 + assert False, f"{func} | {sig}" 149 + 150 + @given(func=typevar_func_strat()) 151 + @example(func=(fc, C, str)) 152 + def test_constrained(self, func): 153 + func, T, constraint = func 154 + checked = False 155 + 156 + def inner(a_args, b_args): 157 + nonlocal checked 158 + try: 159 + for a, b in zip(a_args, b_args, strict=True): 160 + if a == T: 161 + assert b == constraint, func 162 + checked = True 163 + else: 164 + inner(ht.get_args(a), ht.get_args(b)) 165 + except ValueError: 166 + assert False 167 + 168 + sig: Signature = normalize_typevar_signature(signature(func), T, constraint) 169 + func = Deino(func) 170 + inner( 171 + func.__annotations__.values(), 172 + flatten( 173 + (param.annotation for param in sig.parameters.values()), 174 + sig.return_annotation, 175 + ), 176 + ) 177 + 178 + if not checked: 179 + assert False, f"{func} | {sig}" 180 + 181 + 182 + class TestNormalizeTypeVarInstances: 183 + @given(data=..., func=typevar_instance_strat(True)) 184 + # @example(func=(fb, B, str)) 185 + def test_bound(self, data: Data, func): 186 + func, T, bound, t, obj = func 187 + checked = False 188 + 189 + def inner(a_args, b_args): 190 + nonlocal checked 191 + try: 192 + for a, b in zip(a_args, b_args, strict=True): 193 + if a == T: 194 + assert ht.isinmrosubclass(b, bound), func 195 + checked = True 196 + else: 197 + inner(ht.get_args(a), ht.get_args(b)) 198 + except ValueError: 199 + assert False 200 + 201 + sig: Signature = normalize_instance_signature(signature(func), t, obj) 202 + func = Deino(func) 203 + inner( 204 + func.__annotations__.values(), 205 + flatten( 206 + (param.annotation for param in sig.parameters.values()), 207 + sig.return_annotation, 208 + ), 209 + ) 210 + 211 + if not checked: 212 + assert False, f"{func} | {sig}" 213 + 214 + @given(func=typevar_instance_strat()) 215 + @example(func=(fc, C, str, C, "")) 216 + @example(func=(fc, C, str, lcgen, [""])) 217 + @example(func=(fc, C, str, dcgen, {"": ""})) 218 + @example(func=(fc, C, str, scgen, {""})) 219 + @example(func=(fc, C, str, ccgen, f)) 220 + @example(func=(fc, C, str, tcgen, ("", 0, "", [""], {""}, {"": ""}, f))) 221 + def test_constrained(self, func): 222 + func, T, constraint, t, obj = func 223 + checked = False 224 + 225 + def inner(a_args, b_args): 226 + nonlocal checked 227 + try: 228 + for a, b in zip(a_args, b_args, strict=True): 229 + if a == T: 230 + assert b == constraint, func 231 + checked = True 232 + else: 233 + inner(ht.get_args(a), ht.get_args(b)) 234 + except ValueError: 235 + assert False 236 + 237 + sig: Signature = normalize_instance_signature(signature(func), t, obj) 238 + func = Deino(func) 239 + inner( 240 + func.__annotations__.values(), 241 + flatten( 242 + (param.annotation for param in sig.parameters.values()), 243 + sig.return_annotation, 244 + ), 245 + ) 246 + 247 + if not checked: 248 + assert False, f"{func} | {sig}"
+281
packages/jugulis/jugulis/tests/test_scoring.py
··· 1 + import hydrox.typing as ht, typing, collections.abc 2 + from hydrox.hypothesis import ( 3 + AllGeneric, 4 + HTGeneric, 5 + Data, 6 + subgen_strat, 7 + subarg_strat, 8 + arg_strat, 9 + ) 10 + from hypothesis import assume, strategies as st, given, example 11 + from jugulis import scorefunction 12 + from inspect import Signature, FullArgSpec, getfullargspec 13 + from parametrized import parametrized 14 + from pytest import fixture 15 + 16 + 17 + class A: 18 + pass 19 + 20 + 21 + class B(A): 22 + pass 23 + 24 + 25 + # TODO: What happens in multiple inheritance situations? 26 + class C(B, A): 27 + pass 28 + 29 + 30 + class TestScoreSubclass: 31 + @given(left=..., right=..., score=st.just(-3)) 32 + @example(left=A, right=C, score=None) 33 + @example(left=B, right=C, score=None) 34 + @example(left=C, right=C, score=1) 35 + @example(left=C, right=B, score=0) 36 + @example(left=C, right=A, score=-1) 37 + @example( 38 + left=collections.abc.Iterable[int], right=typing.Iterable[int], score=1 + 1 39 + ) 40 + @example( 41 + left=collections.abc.Iterable[bool], right=typing.Iterable[int], score=1 + 0 42 + ) 43 + @example(left=collections.abc.Iterable[str], right=typing.Iterable[int], score=None) 44 + def test_examples( 45 + self, left: AllGeneric[list], right: AllGeneric[typing.Iterable], score 46 + ): 47 + assert ht.scoresubclass(left, right) == score 48 + 49 + @given(data=..., generic=...) 50 + def test_true(self, data: Data, generic: HTGeneric): 51 + subcls = data.draw(subgen_strat(generic)) 52 + mro = ht.trymro_generic(subcls) 53 + assert ht.scoresubclass(subcls, generic) == ( 54 + (1 - mro.index(generic)) if generic in mro else -(len(mro) + 1) 55 + ), mro 56 + 57 + @given(data=..., generic=...) 58 + def test_false(self, data: Data, generic: AllGeneric): 59 + assert ( 60 + ht.scoresubclass(data.draw(subgen_strat(generic, ungen=True)), generic) 61 + is None 62 + ) 63 + 64 + # @given(generic=subarg_strat()) 65 + # def test_true_alias(self, generic): 66 + # arg, subarg = generic 67 + # assert ht.scoresubclass(subarg, arg) == ... 68 + 69 + @given(generic=subarg_strat(ungen=True)) 70 + def test_false_alias(self, generic): 71 + assert ht.scoresubclass(*generic[::-1]) is None 72 + 73 + 74 + a = A() 75 + b = B() 76 + c = C() 77 + 78 + 79 + class TestScoreInstance: 80 + @given( 81 + left=st.just(tuple()), 82 + right=st.just(collections.abc.Iterable), 83 + score=st.just(-3), 84 + ) 85 + @example(left=a, right=C, score=None) 86 + @example(left=b, right=C, score=None) 87 + @example(left=c, right=C, score=1) 88 + @example(left=c, right=B, score=0) 89 + @example(left=c, right=A, score=-1) 90 + def test_examples(self, left, right, score): 91 + assert ht.scoreinstance(left, right) == score 92 + 93 + # @given() 94 + # def test_true(self): 95 + # ... 96 + 97 + # @given() 98 + # def test_false(self): 99 + # ... 100 + 101 + # @given() 102 + # def test_true_alias(self): 103 + # ... 104 + 105 + # @given() 106 + # def test_false_alias(self): 107 + # ... 108 + 109 + 110 + def f1(i: int): ... 111 + 112 + 113 + def f2(i=0): ... 114 + 115 + 116 + class TestScoreFunction: 117 + # NOTE: This is `-2` because `**kwargs` reduces the score by one, 118 + # then `i` not being in the keyword arguments reduces it by one more. 119 + @given( 120 + func=st.just(lambda **kwargs: None), 121 + args=st.just(tuple()), 122 + kwargs=st.just({"i": 0}), 123 + score=st.just(-3), 124 + ) 125 + @example(func=lambda **kwargs: None, args=tuple(), kwargs={}, score=-2) 126 + @example(func=lambda **kwargs: None, args=(0,), kwargs={}, score=None) 127 + @example(func=lambda *args: None, args=tuple(), kwargs={"i": 0}, score=None) 128 + @example(func=lambda *args: None, args=(0,), kwargs={}, score=-3) 129 + @example(func=lambda *args: None, args=tuple(), kwargs={}, score=-2) 130 + def test_examples(self, func, args, kwargs, score): 131 + assert ( 132 + scorefunction(ht.signature(func), getfullargspec(func), args, kwargs) 133 + == score 134 + ) 135 + 136 + @given(obj=...) 137 + def test_required(self, obj: str | int | bool): 138 + t = type(obj) 139 + sig: Signature = ht.signature(f1) 140 + spec: FullArgSpec = getfullargspec(f1) 141 + assert ( 142 + scorefunction(sig, spec, (obj,), {}) 143 + == scorefunction(sig, spec, tuple(), {"i": obj}) 144 + == (None if t is str else int(t is int)) 145 + ) 146 + 147 + @given(obj=...) 148 + def test_default(self, obj: str | int | bool): 149 + t = type(obj) 150 + sig: Signature = ht.signature(f2) 151 + spec: FullArgSpec = getfullargspec(f2) 152 + assert ( 153 + scorefunction(sig, spec, (obj,), {}) 154 + == scorefunction(sig, spec, tuple(), {"i": obj}) 155 + == (None if t is str else (int(t is int) - 1)) 156 + ) 157 + 158 + # TODO: Test using `TypeVars` as well. 159 + 160 + @fixture 161 + def empty(self, scope="class"): 162 + def empty() -> ...: ... 163 + 164 + return empty, ht.signature(empty), getfullargspec(empty) 165 + 166 + def test_no_args_function_1(self, empty): 167 + try: 168 + result = scorefunction(*empty[1:], (0,), {}, empty[0]) 169 + except TypeError as te: 170 + ( 171 + str(te) 172 + == "empty() -> ... takes 0 positional arguments but 1 was given", 173 + str(te), 174 + ) 175 + else: 176 + assert False, result 177 + 178 + def test_no_args_function_2(self, empty): 179 + try: 180 + result = scorefunction(*empty[1:], (0, 0), {}, empty[0]) 181 + except TypeError as te: 182 + ( 183 + str(te) 184 + == "empty() -> ... takes 0 positional arguments but 2 were given", 185 + str(te), 186 + ) 187 + else: 188 + assert False, result 189 + 190 + @given( 191 + kwargs=st.dictionaries( 192 + st.characters(), 193 + st.integers(), 194 + min_size=1, 195 + max_size=ht.max_subscription_size, 196 + ) 197 + ) 198 + def test_no_kwargs_function(self, kwargs, empty): 199 + try: 200 + result = scorefunction(*empty[1:], tuple(), kwargs, empty[0]) 201 + except TypeError as te: 202 + ( 203 + str(te) 204 + == f"empty() -> ... got an unexpected keyword argument '{next(iter(kwargs))}'", 205 + str(te), 206 + ) 207 + else: 208 + assert False, result 209 + 210 + @fixture 211 + def e(self, scope="class"): 212 + def e(a: int, /, b: int, *c, d: int, **e) -> int: ... 213 + 214 + return e, ht.signature(e), getfullargspec(e) 215 + 216 + def test_no_args(self, e): 217 + try: 218 + result = scorefunction(*e[1:], tuple(), {}, e[0]) 219 + except TypeError as te: 220 + assert ( 221 + str(te) 222 + == "e(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int missing 2 required positional arguments: 'a' and 'b'" 223 + ), str(te) 224 + else: 225 + assert False, result 226 + 227 + def test_positional_as_keyword(self, e): 228 + try: 229 + result = scorefunction(*e[1:], tuple(), {"a": 0}, e[0]) 230 + except TypeError as te: 231 + assert ( 232 + str(te) 233 + == "e(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int got some positional-only arguments passed as keyword arguments: 'a'" 234 + ), str(te) 235 + else: 236 + assert False, result 237 + 238 + def test_missing_positional(self, e): 239 + try: 240 + result = scorefunction(*e[1:], tuple(), {"b": 0}, e[0]) 241 + except TypeError as te: 242 + assert ( 243 + str(te) 244 + == "e(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int missing 1 required positional argument: 'a'" 245 + ), str(te) 246 + else: 247 + assert False, result 248 + 249 + @given(c=st.characters().filter(lambda c: c not in ("a", "b"))) 250 + def test_missing_positionals(self, c, e): 251 + try: 252 + result = scorefunction(*e[1:], tuple(), {c: 0}, e[0]) 253 + except TypeError as te: 254 + assert ( 255 + str(te) 256 + == "e(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int missing 2 required positional arguments: 'a' and 'b'" 257 + ), str(te) 258 + else: 259 + assert False, result 260 + 261 + def test_positional_as_keyword(self, e): 262 + try: 263 + result = scorefunction(*e[1:], (0, 0), {"z": 0}, e[0]) 264 + except TypeError as te: 265 + assert ( 266 + str(te) 267 + == "e(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int missing 1 required keyword-only argument: 'd'" 268 + ), str(te) 269 + else: 270 + assert False, result 271 + 272 + def test_wrong_argument_type(self, e): 273 + try: 274 + error = scorefunction(*e[1:], ("0",), {}, e[0]) 275 + except TypeError as te: 276 + assert ( 277 + str(te) 278 + == "e(a: ht.int, /, b: ht.int, *c, d: ht.int, **e) -> ht.int positional argument 'a' got value '0' of type ht.str, but was expecting an instance of ht.int" 279 + ), str(te) 280 + else: 281 + assert False, error
+1
packages/jugulis/jugulis/variables.py
··· 1 + sentinel = object()
+25
packages/jugulis/pyproject.toml
··· 1 + [project] 2 + name = "jugulis" 3 + version = "0.1.0" 4 + description = "A syvl.org multiple dispatch system!" 5 + readme = "README.md" 6 + authors = [ 7 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 8 + ] 9 + maintainers = [ 10 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 11 + ] 12 + requires-python = ">=3.14" 13 + license = "MIT" 14 + urls."Source code" = "https://tangled.sh/@syvl.org/jugulis" 15 + dependencies = [ "hydrox" ] 16 + 17 + [build-system] 18 + requires = ["hatchling"] 19 + build-backend = "hatchling.build" 20 + 21 + [tool.hatch.build.targets.wheel] 22 + packages = [ "jugulis" ] 23 + 24 + [tool.uv.sources] 25 + hydrox = { workspace = true }
+23
packages/jugulis/test1.py
··· 1 + from jugulis import Hydreigon 2 + 3 + @Hydreigon 4 + def f(a: int, /) -> int: 5 + return 0 6 + 7 + @f.roar 8 + def f(a: int, /, b: int) -> int: 9 + return 1 10 + 11 + @f.roar 12 + def f(a: int, /, b: int, *c) -> int: 13 + return 2 14 + 15 + @f.roar 16 + def f(a: int, /, b: int, *c, d: int) -> int: 17 + return 3 18 + 19 + @f.roar 20 + def f(a: int, /, b: int, *c, d: int, **e) -> int: 21 + return 4 22 + 23 + print(f(""))
+360
packages/jugulis/uv.lock
··· 1 + version = 1 2 + revision = 2 3 + requires-python = ">=3.14" 4 + 5 + [[package]] 6 + name = "attrs" 7 + version = "25.3.0" 8 + source = { registry = "https://pypi.org/simple" } 9 + sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } 10 + wheels = [ 11 + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, 12 + ] 13 + 14 + [[package]] 15 + name = "autoslot" 16 + version = "2024.12.1" 17 + source = { registry = "https://pypi.org/simple" } 18 + sdist = { url = "https://files.pythonhosted.org/packages/d4/2b/d62d12200bac293891a5dd9f3cae605f04aad0c440a5fecced80e9ce99cb/autoslot-2024.12.1.tar.gz", hash = "sha256:e52e3ce5b98d5fb2d7dbd5c7773627bee552a9cfb49a53b2bddb689adf2a3a5a", size = 11010, upload-time = "2024-12-06T14:42:53.317Z" } 19 + wheels = [ 20 + { url = "https://files.pythonhosted.org/packages/05/1a/39b527bdd3220d490f63960efd361afa611d3ca314cfbf389cc2c7dd7df7/autoslot-2024.12.1-py2.py3-none-any.whl", hash = "sha256:b50098372bf99caf1f135e4f1bbd9f20d56cb23a046e8a3bf402a20399526bfe", size = 7858, upload-time = "2024-12-06T14:42:51.046Z" }, 21 + ] 22 + 23 + [[package]] 24 + name = "colorama" 25 + version = "0.4.6" 26 + source = { registry = "https://pypi.org/simple" } 27 + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 28 + wheels = [ 29 + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 30 + ] 31 + 32 + [[package]] 33 + name = "execnet" 34 + version = "2.1.1" 35 + source = { registry = "https://pypi.org/simple" } 36 + sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } 37 + wheels = [ 38 + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, 39 + ] 40 + 41 + [[package]] 42 + name = "hydrox" 43 + version = "0.1.0" 44 + source = { editable = "../hydrox" } 45 + dependencies = [ 46 + { name = "autoslot" }, 47 + { name = "more-itertools" }, 48 + { name = "rich" }, 49 + ] 50 + 51 + [package.metadata] 52 + requires-dist = [ 53 + { name = "autoslot", specifier = ">=2024.12.1" }, 54 + { name = "more-itertools", specifier = ">=10.7.0" }, 55 + { name = "rich", specifier = ">=14.0.0" }, 56 + ] 57 + 58 + [package.metadata.requires-dev] 59 + dev = [ 60 + { name = "hypothesis", specifier = ">=6.131.15" }, 61 + { name = "pytest", specifier = ">=8.3.5" }, 62 + { name = "pytest-custom-exit-code", specifier = ">=0.3.0" }, 63 + { name = "pytest-order", specifier = ">=1.3.0" }, 64 + { name = "pytest-parametrized", specifier = ">=1.7" }, 65 + { name = "pytest-randomly", specifier = ">=3.16.0" }, 66 + { name = "pytest-repeat", specifier = ">=0.9.4" }, 67 + { name = "pytest-sugar", specifier = ">=1.0.0" }, 68 + { name = "pytest-xdist", specifier = ">=3.6.1" }, 69 + { name = "tzdata", specifier = ">=2025.2" }, 70 + ] 71 + lint = [{ name = "ruff", specifier = ">=0.11.9" }] 72 + 73 + [[package]] 74 + name = "hypothesis" 75 + version = "6.131.16" 76 + source = { registry = "https://pypi.org/simple" } 77 + dependencies = [ 78 + { name = "attrs" }, 79 + { name = "sortedcontainers" }, 80 + ] 81 + sdist = { url = "https://files.pythonhosted.org/packages/3c/05/fcda52dd0817080d9a953ef926ca1ba63ce660c437a5f36cdc13c6f74ef3/hypothesis-6.131.16.tar.gz", hash = "sha256:4953b8dbb79ae3399c4243b1799b3286c7809d08457269274f77581aef8b7048", size = 436840, upload-time = "2025-05-13T08:20:18.087Z" } 82 + wheels = [ 83 + { url = "https://files.pythonhosted.org/packages/dc/f1/a51627f39787b21ee1392fce4685a9480037b548009761e833b89fe3c6db/hypothesis-6.131.16-py3-none-any.whl", hash = "sha256:574e0ebc28e469a891c2c4fcc092ed7d5da81255b84505b599e6768b39d7ca61", size = 501365, upload-time = "2025-05-13T08:20:13.741Z" }, 84 + ] 85 + 86 + [[package]] 87 + name = "iniconfig" 88 + version = "2.1.0" 89 + source = { registry = "https://pypi.org/simple" } 90 + sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } 91 + wheels = [ 92 + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, 93 + ] 94 + 95 + [[package]] 96 + name = "jugulis" 97 + version = "0.1.0" 98 + source = { editable = "." } 99 + dependencies = [ 100 + { name = "hydrox" }, 101 + ] 102 + 103 + [package.dev-dependencies] 104 + dev = [ 105 + { name = "hypothesis" }, 106 + { name = "pytest" }, 107 + { name = "pytest-custom-exit-code" }, 108 + { name = "pytest-order" }, 109 + { name = "pytest-parametrized" }, 110 + { name = "pytest-randomly" }, 111 + { name = "pytest-repeat" }, 112 + { name = "pytest-sugar" }, 113 + { name = "pytest-xdist" }, 114 + { name = "tzdata" }, 115 + ] 116 + lint = [ 117 + { name = "ruff" }, 118 + ] 119 + 120 + [package.metadata] 121 + requires-dist = [{ name = "hydrox", editable = "../hydrox" }] 122 + 123 + [package.metadata.requires-dev] 124 + dev = [ 125 + { name = "hypothesis", specifier = ">=6.131.15" }, 126 + { name = "pytest", specifier = ">=8.3.5" }, 127 + { name = "pytest-custom-exit-code", specifier = ">=0.3.0" }, 128 + { name = "pytest-order", specifier = ">=1.3.0" }, 129 + { name = "pytest-parametrized", specifier = ">=1.7" }, 130 + { name = "pytest-randomly", specifier = ">=3.16.0" }, 131 + { name = "pytest-repeat", specifier = ">=0.9.4" }, 132 + { name = "pytest-sugar", specifier = ">=1.0.0" }, 133 + { name = "pytest-xdist", specifier = ">=3.6.1" }, 134 + { name = "tzdata", specifier = ">=2025.2" }, 135 + ] 136 + lint = [{ name = "ruff", specifier = ">=0.11.9" }] 137 + 138 + [[package]] 139 + name = "markdown-it-py" 140 + version = "3.0.0" 141 + source = { registry = "https://pypi.org/simple" } 142 + dependencies = [ 143 + { name = "mdurl" }, 144 + ] 145 + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 146 + wheels = [ 147 + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 148 + ] 149 + 150 + [[package]] 151 + name = "mdurl" 152 + version = "0.1.2" 153 + source = { registry = "https://pypi.org/simple" } 154 + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 155 + wheels = [ 156 + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 157 + ] 158 + 159 + [[package]] 160 + name = "more-itertools" 161 + version = "10.7.0" 162 + source = { registry = "https://pypi.org/simple" } 163 + sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } 164 + wheels = [ 165 + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, 166 + ] 167 + 168 + [[package]] 169 + name = "packaging" 170 + version = "25.0" 171 + source = { registry = "https://pypi.org/simple" } 172 + sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 173 + wheels = [ 174 + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 175 + ] 176 + 177 + [[package]] 178 + name = "pluggy" 179 + version = "1.5.0" 180 + source = { registry = "https://pypi.org/simple" } 181 + sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } 182 + wheels = [ 183 + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, 184 + ] 185 + 186 + [[package]] 187 + name = "pygments" 188 + version = "2.19.1" 189 + source = { registry = "https://pypi.org/simple" } 190 + sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 191 + wheels = [ 192 + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 193 + ] 194 + 195 + [[package]] 196 + name = "pytest" 197 + version = "8.3.5" 198 + source = { registry = "https://pypi.org/simple" } 199 + dependencies = [ 200 + { name = "colorama", marker = "sys_platform == 'win32'" }, 201 + { name = "iniconfig" }, 202 + { name = "packaging" }, 203 + { name = "pluggy" }, 204 + ] 205 + sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } 206 + wheels = [ 207 + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, 208 + ] 209 + 210 + [[package]] 211 + name = "pytest-custom-exit-code" 212 + version = "0.3.0" 213 + source = { registry = "https://pypi.org/simple" } 214 + dependencies = [ 215 + { name = "pytest" }, 216 + ] 217 + sdist = { url = "https://files.pythonhosted.org/packages/92/9d/e1eb0af5e96a5c34f59b9aa69dfb680764420fe60f2ec28cfbc5339f99f8/pytest-custom_exit_code-0.3.0.tar.gz", hash = "sha256:51ffff0ee2c1ddcc1242e2ddb2a5fd02482717e33a2326ef330e3aa430244635", size = 3633, upload-time = "2019-08-07T09:45:15.781Z" } 218 + wheels = [ 219 + { url = "https://files.pythonhosted.org/packages/35/a0/effb6cbbccfd1c106c572d3d619b3418d71093afb4cd4f91f51e6a1799d2/pytest_custom_exit_code-0.3.0-py3-none-any.whl", hash = "sha256:6e0ce6e57ce3a583cb7e5023f7d1021e19dfec22be41d9ad345bae2fc61caf3b", size = 4055, upload-time = "2019-08-07T09:45:13.767Z" }, 220 + ] 221 + 222 + [[package]] 223 + name = "pytest-order" 224 + version = "1.3.0" 225 + source = { registry = "https://pypi.org/simple" } 226 + dependencies = [ 227 + { name = "pytest" }, 228 + ] 229 + sdist = { url = "https://files.pythonhosted.org/packages/1d/66/02ae17461b14a52ce5a29ae2900156b9110d1de34721ccc16ccd79419876/pytest_order-1.3.0.tar.gz", hash = "sha256:51608fec3d3ee9c0adaea94daa124a5c4c1d2bb99b00269f098f414307f23dde", size = 47544, upload-time = "2024-08-22T12:29:54.512Z" } 230 + wheels = [ 231 + { url = "https://files.pythonhosted.org/packages/1b/73/59b038d1aafca89f8e9936eaa8ffa6bb6138d00459d13a32ce070be4f280/pytest_order-1.3.0-py3-none-any.whl", hash = "sha256:2cd562a21380345dd8d5774aa5fd38b7849b6ee7397ca5f6999bbe6e89f07f6e", size = 14609, upload-time = "2024-08-22T12:29:53.156Z" }, 232 + ] 233 + 234 + [[package]] 235 + name = "pytest-parametrized" 236 + version = "1.7" 237 + source = { registry = "https://pypi.org/simple" } 238 + dependencies = [ 239 + { name = "pytest" }, 240 + ] 241 + sdist = { url = "https://files.pythonhosted.org/packages/44/ae/8ed9bd7dee749610f4e1c52f1bc28f1dfc873f41fc54fcaf509e07829237/pytest_parametrized-1.7.tar.gz", hash = "sha256:3ec50eb164dae2ad3a0595ec197ff8bc1ce741342991f0016ba7daa6a861361b", size = 3712, upload-time = "2024-12-21T17:34:52.606Z" } 242 + wheels = [ 243 + { url = "https://files.pythonhosted.org/packages/92/66/51fdf9cb516783cfa9363b587fd9a99f86726bd5606baa6c9adf0fa4b0d2/pytest_parametrized-1.7-py3-none-any.whl", hash = "sha256:a7da6948c0ccb6798924f600b01c4c19064d46fd2bd49b5406c2d9aa7303d05e", size = 3822, upload-time = "2024-12-21T17:34:49.951Z" }, 244 + ] 245 + 246 + [[package]] 247 + name = "pytest-randomly" 248 + version = "3.16.0" 249 + source = { registry = "https://pypi.org/simple" } 250 + dependencies = [ 251 + { name = "pytest" }, 252 + ] 253 + sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367, upload-time = "2024-10-25T15:45:34.274Z" } 254 + wheels = [ 255 + { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, 256 + ] 257 + 258 + [[package]] 259 + name = "pytest-repeat" 260 + version = "0.9.4" 261 + source = { registry = "https://pypi.org/simple" } 262 + dependencies = [ 263 + { name = "pytest" }, 264 + ] 265 + sdist = { url = "https://files.pythonhosted.org/packages/80/d4/69e9dbb9b8266df0b157c72be32083403c412990af15c7c15f7a3fd1b142/pytest_repeat-0.9.4.tar.gz", hash = "sha256:d92ac14dfaa6ffcfe6917e5d16f0c9bc82380c135b03c2a5f412d2637f224485", size = 6488, upload-time = "2025-04-07T14:59:53.077Z" } 266 + wheels = [ 267 + { url = "https://files.pythonhosted.org/packages/73/d4/8b706b81b07b43081bd68a2c0359fe895b74bf664b20aca8005d2bb3be71/pytest_repeat-0.9.4-py3-none-any.whl", hash = "sha256:c1738b4e412a6f3b3b9e0b8b29fcd7a423e50f87381ad9307ef6f5a8601139f3", size = 4180, upload-time = "2025-04-07T14:59:51.492Z" }, 268 + ] 269 + 270 + [[package]] 271 + name = "pytest-sugar" 272 + version = "1.0.0" 273 + source = { registry = "https://pypi.org/simple" } 274 + dependencies = [ 275 + { name = "packaging" }, 276 + { name = "pytest" }, 277 + { name = "termcolor" }, 278 + ] 279 + sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992, upload-time = "2024-02-01T18:30:36.735Z" } 280 + wheels = [ 281 + { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171, upload-time = "2024-02-01T18:30:29.395Z" }, 282 + ] 283 + 284 + [[package]] 285 + name = "pytest-xdist" 286 + version = "3.6.1" 287 + source = { registry = "https://pypi.org/simple" } 288 + dependencies = [ 289 + { name = "execnet" }, 290 + { name = "pytest" }, 291 + ] 292 + sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } 293 + wheels = [ 294 + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, 295 + ] 296 + 297 + [[package]] 298 + name = "rich" 299 + version = "14.0.0" 300 + source = { registry = "https://pypi.org/simple" } 301 + dependencies = [ 302 + { name = "markdown-it-py" }, 303 + { name = "pygments" }, 304 + ] 305 + sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } 306 + wheels = [ 307 + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, 308 + ] 309 + 310 + [[package]] 311 + name = "ruff" 312 + version = "0.11.9" 313 + source = { registry = "https://pypi.org/simple" } 314 + sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } 315 + wheels = [ 316 + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, 317 + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, 318 + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, 319 + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, 320 + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, 321 + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, 322 + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, 323 + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, 324 + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, 325 + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, 326 + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, 327 + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, 328 + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, 329 + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, 330 + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, 331 + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, 332 + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, 333 + ] 334 + 335 + [[package]] 336 + name = "sortedcontainers" 337 + version = "2.4.0" 338 + source = { registry = "https://pypi.org/simple" } 339 + sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } 340 + wheels = [ 341 + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, 342 + ] 343 + 344 + [[package]] 345 + name = "termcolor" 346 + version = "3.1.0" 347 + source = { registry = "https://pypi.org/simple" } 348 + sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } 349 + wheels = [ 350 + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, 351 + ] 352 + 353 + [[package]] 354 + name = "tzdata" 355 + version = "2025.2" 356 + source = { registry = "https://pypi.org/simple" } 357 + sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } 358 + wheels = [ 359 + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, 360 + ]
+50
pyproject.toml
··· 1 + [project] 2 + name = "oreo" 3 + version = "3.0.0" 4 + description = "A syvl.org helper system!" 5 + readme = "README.md" 6 + authors = [ 7 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 8 + ] 9 + maintainers = [ 10 + { name = "Jeet Ray", email = "jeet.ray@syvl.org" } 11 + ] 12 + requires-python = ">=3.14" 13 + license = "MIT" 14 + scripts.oreo = "oreo.oreo:oreo" 15 + urls."Source code" = "https://tangled.sh/@syvl.org/oreo" 16 + dependencies = ["bundle"] 17 + 18 + [build-system] 19 + requires = ["hatchling"] 20 + build-backend = "hatchling.build" 21 + 22 + [tool.hatch.build.targets.wheel] 23 + packages = [ "oreo" ] 24 + 25 + [tool.uv.sources] 26 + bundle = { workspace = true } 27 + 28 + [tool.uv.workspace] 29 + members = ["packages/*"] 30 + 31 + [tool.pytest.ini_options] 32 + xfail_strict = true 33 + addopts = "--dist loadgroup -n auto --order-group-scope class -rs --strict-markers --suppress-no-test-exit-code -vvv -x" 34 + 35 + [dependency-groups] 36 + dev = [ 37 + "hypothesis>=6.131.15", 38 + "pytest>=8.3.5", 39 + "pytest-custom-exit-code>=0.3.0", 40 + "pytest-order>=1.3.0", 41 + "pytest-parametrized>=1.7", 42 + "pytest-randomly>=3.16.0", 43 + "pytest-repeat>=0.9.4", 44 + "pytest-sugar>=1.0.0", 45 + "pytest-xdist>=3.6.1", 46 + "tzdata>=2025.2", 47 + ] 48 + lint = [ 49 + "ruff>=0.11.9", 50 + ]
+375
uv.lock
··· 1 + version = 1 2 + revision = 2 3 + requires-python = ">=3.14" 4 + 5 + [manifest] 6 + members = [ 7 + "bundle", 8 + "hydrox", 9 + "jugulis", 10 + "oreo", 11 + ] 12 + 13 + [[package]] 14 + name = "attrs" 15 + version = "25.3.0" 16 + source = { registry = "https://pypi.org/simple" } 17 + sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } 18 + wheels = [ 19 + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, 20 + ] 21 + 22 + [[package]] 23 + name = "autoslot" 24 + version = "2024.12.1" 25 + source = { registry = "https://pypi.org/simple" } 26 + sdist = { url = "https://files.pythonhosted.org/packages/d4/2b/d62d12200bac293891a5dd9f3cae605f04aad0c440a5fecced80e9ce99cb/autoslot-2024.12.1.tar.gz", hash = "sha256:e52e3ce5b98d5fb2d7dbd5c7773627bee552a9cfb49a53b2bddb689adf2a3a5a", size = 11010, upload-time = "2024-12-06T14:42:53.317Z" } 27 + wheels = [ 28 + { url = "https://files.pythonhosted.org/packages/05/1a/39b527bdd3220d490f63960efd361afa611d3ca314cfbf389cc2c7dd7df7/autoslot-2024.12.1-py2.py3-none-any.whl", hash = "sha256:b50098372bf99caf1f135e4f1bbd9f20d56cb23a046e8a3bf402a20399526bfe", size = 7858, upload-time = "2024-12-06T14:42:51.046Z" }, 29 + ] 30 + 31 + [[package]] 32 + name = "bundle" 33 + version = "0.1.0" 34 + source = { editable = "packages/bundle" } 35 + dependencies = [ 36 + { name = "jugulis" }, 37 + ] 38 + 39 + [package.metadata] 40 + requires-dist = [{ name = "jugulis", editable = "packages/jugulis" }] 41 + 42 + [[package]] 43 + name = "colorama" 44 + version = "0.4.6" 45 + source = { registry = "https://pypi.org/simple" } 46 + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 47 + wheels = [ 48 + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 49 + ] 50 + 51 + [[package]] 52 + name = "execnet" 53 + version = "2.1.1" 54 + source = { registry = "https://pypi.org/simple" } 55 + sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } 56 + wheels = [ 57 + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, 58 + ] 59 + 60 + [[package]] 61 + name = "hydrox" 62 + version = "0.1.0" 63 + source = { editable = "packages/hydrox" } 64 + dependencies = [ 65 + { name = "autoslot" }, 66 + { name = "more-itertools" }, 67 + { name = "rich" }, 68 + ] 69 + 70 + [package.metadata] 71 + requires-dist = [ 72 + { name = "autoslot", specifier = ">=2024.12.1" }, 73 + { name = "more-itertools", specifier = ">=10.7.0" }, 74 + { name = "rich", specifier = "==13.8.1" }, 75 + ] 76 + 77 + [[package]] 78 + name = "hypothesis" 79 + version = "6.131.16" 80 + source = { registry = "https://pypi.org/simple" } 81 + dependencies = [ 82 + { name = "attrs" }, 83 + { name = "sortedcontainers" }, 84 + ] 85 + sdist = { url = "https://files.pythonhosted.org/packages/3c/05/fcda52dd0817080d9a953ef926ca1ba63ce660c437a5f36cdc13c6f74ef3/hypothesis-6.131.16.tar.gz", hash = "sha256:4953b8dbb79ae3399c4243b1799b3286c7809d08457269274f77581aef8b7048", size = 436840, upload-time = "2025-05-13T08:20:18.087Z" } 86 + wheels = [ 87 + { url = "https://files.pythonhosted.org/packages/dc/f1/a51627f39787b21ee1392fce4685a9480037b548009761e833b89fe3c6db/hypothesis-6.131.16-py3-none-any.whl", hash = "sha256:574e0ebc28e469a891c2c4fcc092ed7d5da81255b84505b599e6768b39d7ca61", size = 501365, upload-time = "2025-05-13T08:20:13.741Z" }, 88 + ] 89 + 90 + [[package]] 91 + name = "iniconfig" 92 + version = "2.1.0" 93 + source = { registry = "https://pypi.org/simple" } 94 + sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } 95 + wheels = [ 96 + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, 97 + ] 98 + 99 + [[package]] 100 + name = "jugulis" 101 + version = "0.1.0" 102 + source = { editable = "packages/jugulis" } 103 + dependencies = [ 104 + { name = "hydrox" }, 105 + ] 106 + 107 + [package.metadata] 108 + requires-dist = [{ name = "hydrox", editable = "packages/hydrox" }] 109 + 110 + [[package]] 111 + name = "markdown-it-py" 112 + version = "3.0.0" 113 + source = { registry = "https://pypi.org/simple" } 114 + dependencies = [ 115 + { name = "mdurl" }, 116 + ] 117 + sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 118 + wheels = [ 119 + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 120 + ] 121 + 122 + [[package]] 123 + name = "mdurl" 124 + version = "0.1.2" 125 + source = { registry = "https://pypi.org/simple" } 126 + sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 127 + wheels = [ 128 + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 129 + ] 130 + 131 + [[package]] 132 + name = "more-itertools" 133 + version = "10.7.0" 134 + source = { registry = "https://pypi.org/simple" } 135 + sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } 136 + wheels = [ 137 + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, 138 + ] 139 + 140 + [[package]] 141 + name = "oreo" 142 + version = "3.0.0" 143 + source = { editable = "." } 144 + dependencies = [ 145 + { name = "bundle" }, 146 + ] 147 + 148 + [package.dev-dependencies] 149 + dev = [ 150 + { name = "hypothesis" }, 151 + { name = "pytest" }, 152 + { name = "pytest-custom-exit-code" }, 153 + { name = "pytest-order" }, 154 + { name = "pytest-parametrized" }, 155 + { name = "pytest-randomly" }, 156 + { name = "pytest-repeat" }, 157 + { name = "pytest-sugar" }, 158 + { name = "pytest-xdist" }, 159 + { name = "tzdata" }, 160 + ] 161 + lint = [ 162 + { name = "ruff" }, 163 + ] 164 + 165 + [package.metadata] 166 + requires-dist = [{ name = "bundle", editable = "packages/bundle" }] 167 + 168 + [package.metadata.requires-dev] 169 + dev = [ 170 + { name = "hypothesis", specifier = ">=6.131.15" }, 171 + { name = "pytest", specifier = ">=8.3.5" }, 172 + { name = "pytest-custom-exit-code", specifier = ">=0.3.0" }, 173 + { name = "pytest-order", specifier = ">=1.3.0" }, 174 + { name = "pytest-parametrized", specifier = ">=1.7" }, 175 + { name = "pytest-randomly", specifier = ">=3.16.0" }, 176 + { name = "pytest-repeat", specifier = ">=0.9.4" }, 177 + { name = "pytest-sugar", specifier = ">=1.0.0" }, 178 + { name = "pytest-xdist", specifier = ">=3.6.1" }, 179 + { name = "tzdata", specifier = ">=2025.2" }, 180 + ] 181 + lint = [{ name = "ruff", specifier = ">=0.11.9" }] 182 + 183 + [[package]] 184 + name = "packaging" 185 + version = "25.0" 186 + source = { registry = "https://pypi.org/simple" } 187 + sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 188 + wheels = [ 189 + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 190 + ] 191 + 192 + [[package]] 193 + name = "pluggy" 194 + version = "1.5.0" 195 + source = { registry = "https://pypi.org/simple" } 196 + sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } 197 + wheels = [ 198 + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, 199 + ] 200 + 201 + [[package]] 202 + name = "pygments" 203 + version = "2.19.1" 204 + source = { registry = "https://pypi.org/simple" } 205 + sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 206 + wheels = [ 207 + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 208 + ] 209 + 210 + [[package]] 211 + name = "pytest" 212 + version = "8.3.5" 213 + source = { registry = "https://pypi.org/simple" } 214 + dependencies = [ 215 + { name = "colorama", marker = "sys_platform == 'win32'" }, 216 + { name = "iniconfig" }, 217 + { name = "packaging" }, 218 + { name = "pluggy" }, 219 + ] 220 + sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } 221 + wheels = [ 222 + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, 223 + ] 224 + 225 + [[package]] 226 + name = "pytest-custom-exit-code" 227 + version = "0.3.0" 228 + source = { registry = "https://pypi.org/simple" } 229 + dependencies = [ 230 + { name = "pytest" }, 231 + ] 232 + sdist = { url = "https://files.pythonhosted.org/packages/92/9d/e1eb0af5e96a5c34f59b9aa69dfb680764420fe60f2ec28cfbc5339f99f8/pytest-custom_exit_code-0.3.0.tar.gz", hash = "sha256:51ffff0ee2c1ddcc1242e2ddb2a5fd02482717e33a2326ef330e3aa430244635", size = 3633, upload-time = "2019-08-07T09:45:15.781Z" } 233 + wheels = [ 234 + { url = "https://files.pythonhosted.org/packages/35/a0/effb6cbbccfd1c106c572d3d619b3418d71093afb4cd4f91f51e6a1799d2/pytest_custom_exit_code-0.3.0-py3-none-any.whl", hash = "sha256:6e0ce6e57ce3a583cb7e5023f7d1021e19dfec22be41d9ad345bae2fc61caf3b", size = 4055, upload-time = "2019-08-07T09:45:13.767Z" }, 235 + ] 236 + 237 + [[package]] 238 + name = "pytest-order" 239 + version = "1.3.0" 240 + source = { registry = "https://pypi.org/simple" } 241 + dependencies = [ 242 + { name = "pytest" }, 243 + ] 244 + sdist = { url = "https://files.pythonhosted.org/packages/1d/66/02ae17461b14a52ce5a29ae2900156b9110d1de34721ccc16ccd79419876/pytest_order-1.3.0.tar.gz", hash = "sha256:51608fec3d3ee9c0adaea94daa124a5c4c1d2bb99b00269f098f414307f23dde", size = 47544, upload-time = "2024-08-22T12:29:54.512Z" } 245 + wheels = [ 246 + { url = "https://files.pythonhosted.org/packages/1b/73/59b038d1aafca89f8e9936eaa8ffa6bb6138d00459d13a32ce070be4f280/pytest_order-1.3.0-py3-none-any.whl", hash = "sha256:2cd562a21380345dd8d5774aa5fd38b7849b6ee7397ca5f6999bbe6e89f07f6e", size = 14609, upload-time = "2024-08-22T12:29:53.156Z" }, 247 + ] 248 + 249 + [[package]] 250 + name = "pytest-parametrized" 251 + version = "1.7" 252 + source = { registry = "https://pypi.org/simple" } 253 + dependencies = [ 254 + { name = "pytest" }, 255 + ] 256 + sdist = { url = "https://files.pythonhosted.org/packages/44/ae/8ed9bd7dee749610f4e1c52f1bc28f1dfc873f41fc54fcaf509e07829237/pytest_parametrized-1.7.tar.gz", hash = "sha256:3ec50eb164dae2ad3a0595ec197ff8bc1ce741342991f0016ba7daa6a861361b", size = 3712, upload-time = "2024-12-21T17:34:52.606Z" } 257 + wheels = [ 258 + { url = "https://files.pythonhosted.org/packages/92/66/51fdf9cb516783cfa9363b587fd9a99f86726bd5606baa6c9adf0fa4b0d2/pytest_parametrized-1.7-py3-none-any.whl", hash = "sha256:a7da6948c0ccb6798924f600b01c4c19064d46fd2bd49b5406c2d9aa7303d05e", size = 3822, upload-time = "2024-12-21T17:34:49.951Z" }, 259 + ] 260 + 261 + [[package]] 262 + name = "pytest-randomly" 263 + version = "3.16.0" 264 + source = { registry = "https://pypi.org/simple" } 265 + dependencies = [ 266 + { name = "pytest" }, 267 + ] 268 + sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367, upload-time = "2024-10-25T15:45:34.274Z" } 269 + wheels = [ 270 + { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, 271 + ] 272 + 273 + [[package]] 274 + name = "pytest-repeat" 275 + version = "0.9.4" 276 + source = { registry = "https://pypi.org/simple" } 277 + dependencies = [ 278 + { name = "pytest" }, 279 + ] 280 + sdist = { url = "https://files.pythonhosted.org/packages/80/d4/69e9dbb9b8266df0b157c72be32083403c412990af15c7c15f7a3fd1b142/pytest_repeat-0.9.4.tar.gz", hash = "sha256:d92ac14dfaa6ffcfe6917e5d16f0c9bc82380c135b03c2a5f412d2637f224485", size = 6488, upload-time = "2025-04-07T14:59:53.077Z" } 281 + wheels = [ 282 + { url = "https://files.pythonhosted.org/packages/73/d4/8b706b81b07b43081bd68a2c0359fe895b74bf664b20aca8005d2bb3be71/pytest_repeat-0.9.4-py3-none-any.whl", hash = "sha256:c1738b4e412a6f3b3b9e0b8b29fcd7a423e50f87381ad9307ef6f5a8601139f3", size = 4180, upload-time = "2025-04-07T14:59:51.492Z" }, 283 + ] 284 + 285 + [[package]] 286 + name = "pytest-sugar" 287 + version = "1.0.0" 288 + source = { registry = "https://pypi.org/simple" } 289 + dependencies = [ 290 + { name = "packaging" }, 291 + { name = "pytest" }, 292 + { name = "termcolor" }, 293 + ] 294 + sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992, upload-time = "2024-02-01T18:30:36.735Z" } 295 + wheels = [ 296 + { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171, upload-time = "2024-02-01T18:30:29.395Z" }, 297 + ] 298 + 299 + [[package]] 300 + name = "pytest-xdist" 301 + version = "3.6.1" 302 + source = { registry = "https://pypi.org/simple" } 303 + dependencies = [ 304 + { name = "execnet" }, 305 + { name = "pytest" }, 306 + ] 307 + sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } 308 + wheels = [ 309 + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, 310 + ] 311 + 312 + [[package]] 313 + name = "rich" 314 + version = "13.8.1" 315 + source = { registry = "https://pypi.org/simple" } 316 + dependencies = [ 317 + { name = "markdown-it-py" }, 318 + { name = "pygments" }, 319 + ] 320 + sdist = { url = "https://files.pythonhosted.org/packages/92/76/40f084cb7db51c9d1fa29a7120717892aeda9a7711f6225692c957a93535/rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a", size = 222080, upload-time = "2024-09-10T12:52:44.779Z" } 321 + wheels = [ 322 + { url = "https://files.pythonhosted.org/packages/b0/11/dadb85e2bd6b1f1ae56669c3e1f0410797f9605d752d68fb47b77f525b31/rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06", size = 241608, upload-time = "2024-09-10T12:52:42.714Z" }, 323 + ] 324 + 325 + [[package]] 326 + name = "ruff" 327 + version = "0.11.9" 328 + source = { registry = "https://pypi.org/simple" } 329 + sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } 330 + wheels = [ 331 + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, 332 + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, 333 + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, 334 + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, 335 + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, 336 + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, 337 + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, 338 + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, 339 + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, 340 + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, 341 + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, 342 + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, 343 + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, 344 + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, 345 + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, 346 + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, 347 + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, 348 + ] 349 + 350 + [[package]] 351 + name = "sortedcontainers" 352 + version = "2.4.0" 353 + source = { registry = "https://pypi.org/simple" } 354 + sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } 355 + wheels = [ 356 + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, 357 + ] 358 + 359 + [[package]] 360 + name = "termcolor" 361 + version = "3.1.0" 362 + source = { registry = "https://pypi.org/simple" } 363 + sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } 364 + wheels = [ 365 + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, 366 + ] 367 + 368 + [[package]] 369 + name = "tzdata" 370 + version = "2025.2" 371 + source = { registry = "https://pypi.org/simple" } 372 + sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } 373 + wheels = [ 374 + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, 375 + ]