# --------------------------------------------------------------------------------------
# Copyright (c) 2021-2023, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import sys
from itertools import chain
from typing import (
TYPE_CHECKING,
Any,
List,
Literal,
Sequence,
Tuple,
TypedDict,
TypeVar,
Union,
get_args,
get_origin,
)
# In Python 3.9+, List is a _SpecialGenericAlias and does not inherit from
# _GenericAlias which is the type of List[int] for example
if sys.version_info >= (3, 9):
from types import GenericAlias
GENERICS = (type(List), type(List[int]), GenericAlias)
else:
GENERICS = (type(List), type(List[int]))
if sys.version_info >= (3, 10):
from types import UnionType
UNION = (UnionType,)
else:
UNION = ()
# Type checker consider that typing.List and typing.List[int] are types even though
# there are not at runtime.
if sys.version_info >= (3, 10):
from types import GenericAlias, UnionType
TypeLike = Union[type, TypeVar, UnionType, GenericAlias]
elif sys.version_info >= (3, 9):
from types import GenericAlias
TypeLike = Union[type, TypeVar, GenericAlias]
else: # 3.8
TypeLike = Union[type, TypeVar]
if TYPE_CHECKING:
from .atom import Atom
class _ChangeDict(TypedDict):
type: Union[
Literal["create"],
Literal["update"],
Literal["delete"],
Literal["event"],
Literal["property"],
Literal["container"],
]
name: str
object: "Atom"
value: Any
[docs]
class ChangeDict(_ChangeDict, total=False):
oldvalue: Any
# ContainerList specific entries, present only when type == "container"
operation: Union[
Literal["reverse"],
Literal["__delitem__"],
Literal["__iadd__"],
Literal["__imul__"],
Literal["__setitem__"],
Literal["append"],
Literal["extend"],
Literal["insert"],
Literal["pop"],
Literal["remove"],
Literal["sort"],
]
# The following are present based on the operation value
olditem: Any # operation in ("__setitem__",)
newitem: Any # operation in ("__setitem__",)
item: Any # operation in ("append", "insert", "pop", "remove", "__delitem__")
index: int # operation in ("insert", "pop")
items: Sequence[Any] # operation in ("extend", "__iadd__")
count: int # operation in ("__imul__")
key: Any # operation in ("sort")
reverse: bool # operation in ("sort")
def _extract_types(kind: TypeLike) -> Tuple[type, ...]:
"""Extract a tuple of types from a type-like object"""
if isinstance(kind, str):
raise TypeError(
f"Str-based annotations ({kind!r}) are not supported in atom Members."
)
ret: List[Any]
if isinstance(kind, GENERICS):
args = get_args(kind)
origin = get_origin(kind)
if origin is Union:
ret = list(chain.from_iterable(extract_types(a) for a in args))
else:
ret = [origin]
elif UNION and isinstance(kind, UNION):
ret = list(chain.from_iterable(extract_types(a) for a in get_args(kind)))
else:
ret = [kind]
extracted: List[type] = []
for t in ret:
if isinstance(t, TypeVar):
b = t.__bound__
if b is not None:
if isinstance(b, str):
raise ValueError(
"Forward reference in type var bounds are not supported."
)
extracted.extend(_extract_types(b))
elif t.__constraints__:
raise ValueError("Constraints in type var are not supported.")
else:
extracted.append(object)
if t.__contravariant__:
raise ValueError("TypeVar used in Atom object cannot be contravariant")
elif t is Any:
extracted.append(object)
else:
assert isinstance(t, type)
extracted.append(t)
return tuple(extracted)
NONE_TYPE = type(None)
[docs]
def is_optional(kinds: Tuple[type, ...]) -> Tuple[bool, Tuple[type, ...]]:
"""Determine if a tuple of types contains NoneType."""
if NONE_TYPE in kinds:
return True, tuple(k for k in kinds if k is not NONE_TYPE)
else:
return False, kinds