def get_dims(type_: Type[DataArrayLike[D, T]]) -> Tuple[str, ...]: """Extract dimensions (dims) from DataArrayLike[D, T].""" if get_origin(type_) is Annotated: type_ = get_args(type_)[0] dims_ = get_args(get_args(type_)[0])[0] if get_origin(dims_) is tuple: dims_ = get_args(dims_) else: dims_ = (dims_, ) dims: List[str] = [] for dim_ in dims_: if dim_ == () or dim_ is NoneType: continue if isinstance(dim_, ForwardRef): dims.append(dim_.__forward_arg__) continue if get_origin(dim_) is Literal: dims.append(str(get_args(dim_)[0])) continue raise TypeError("Could not extract dimension.") return tuple(dims)
def is_subtype(a: BoundType, b: BoundType) -> bool: # noqa: C901 """ Return True if ``a`` is a subtype of ``b``. Supports single-level typing.Unions and intersections represented as tuples respectively without nesting. The cases that this function deals with can be divided into the following cases, where T is neither a union or intersection: 1. Union, Union: Success if all types in a have a subclass in b. 2. T, Union: Success if a is a subclass of one or more types in b. 3. Union, T: Always fails (except when a single-type union, see 9). 4. T, Intersection: Success if a is a subclass of all types in b. 5. Intersection, T: Success if one type in a is a subclass of b. 6. Intersection, Union: Success if one type in a is a subclass of one type in b. 7. Union, Intersection: Always fails (except when a is a single-type union see 4). 8. Intersection, Intersection: Success if all items in b have a subclass in a. 9. T, T: Success if a is a subclass of b. """ if _is_union(a) and _is_union(b): for a_part in get_args(a): for b_part in get_args(b): if issubclass(a_part, b_part): break else: return False return True elif _is_intersection(a) and _is_union(b): assert isinstance(a, tuple) for a_part, b_part in product(a, get_args(b)): if issubclass(a_part, b_part): return True return False elif _is_intersection(a) and _is_intersection(b): assert isinstance(a, tuple) assert isinstance(b, tuple) for b_part in b: for a_part in a: if issubclass(a_part, b_part): break else: return False return True elif _is_union(a): return False elif _is_union(b): assert isinstance(a, type) return any(issubclass(a, b_part) for b_part in get_args(b)) elif _is_intersection(b): assert isinstance(a, type) assert isinstance(b, tuple) return all(issubclass(a, b_part) for b_part in b) elif _is_intersection(a): assert isinstance(a, tuple) return any(issubclass(a_part, b) for a_part in a) assert isinstance(a, type) return issubclass(a, b)
async def _convert( self, ctx: "Context", converter: Union[converters.Converter, type, Callable[[str], Any]], param: inspect.Parameter, argument: str, ) -> Any: if isinstance(converter, converters.Converter): try: converter = converter() if callable(converter) else converter return await converter.convert(ctx, argument) except Exception as exc: try: name = converter.__name__ except AttributeError: name = converter.__class__.__name__ raise BadArgument( f"{argument} failed to convert to {name}") from exc else: if converter is bool: return to_bool(argument) origin = get_origin(converter) if origin is not None: for arg in get_args(converter): converter = self._get_converter(arg) try: return await self._convert(ctx, converter, argument) except BadArgument: if origin is Union: continue raise else: if origin is Union and type(None) in get_args( converter): # typing.Optional try: return self._get_default( ctx, param) # get the default if possible except MissingRequiredArgument: return None # fall back to None raise BadArgument( f"Failed to parse {argument} to any type") try: return converter(argument) except Exception as exc: try: name = converter.__name__ except AttributeError: name = converter.__class__.__name__ raise BadArgument( f"{argument!r} failed to convert to {name}") from exc
def get_runtime_pattern_arg_types(): runtime_generic_types = [] for type_ in get_args(T_PatternArg): if type_ is Union: # T_SegmentClass runtime_generic_types.extend(get_args(type_)) else: # str, bool, int, float runtime_generic_types.append(type_) # debug(runtime_generic_types) runtime_class_types = tuple( map(lambda generic: get_args(generic)[0], runtime_generic_types)) # debug(runtime_class_types) return runtime_class_types
def add_future_data(gui, future: Future, return_type, _from_tuple=True): """Process a Future object from a magicgui widget. This function will be called when a magicgui-decorated function has a return annotation of one of the `napari.types.<layer_name>Data` ... and will add the data in ``result`` to the current viewer as the corresponding layer type. Parameters ---------- gui : FunctionGui The instantiated magicgui widget. May or may not be docked in a dock widget. future : Future An instance of `concurrent.futures.Future` (or any third-party) object with the same interface, that provides `add_done_callback` and `result` methods. When the future is `done()`, the `result()` will be added to the viewer. return_type : type The return annotation that was used in the decorated function. _from_tuple : bool, optional (only for internal use). True if the future returns `LayerDataTuple`, False if it returns one of the `LayerData` types. """ from ..utils._injection._processors import _add_future_data if viewer := find_viewer_ancestor(gui): _add_future_data( future, return_type=get_args(return_type)[0], _from_tuple=_from_tuple, viewer=viewer, source={'widget': gui}, )
def log_level(self, level: LogLevel) -> None: valid_levels = get_args(LogLevel) if not (level is None or level in valid_levels): raise ValueError( f"Unknown log level '{level}', valid levels are: {valid_levels}" ) self._log_level = level
def args_to_matchers( function: Callable[..., Any], ) -> Dict[str, libcst_typing.Matcher]: """Extract node matchers from the function arguments. Untyped arguments will get a `DoNotCare` matcher, while arguments typed and annoated with a `BaseMatcherNode` will return that matcher. """ matchers: Dict[str, libcst_typing.Matcher] = {} # Create default matchers for all arguments args = function.__code__.co_varnames[:function.__code__.co_argcount] if args: for name in args: if name == "self": continue matchers[name] = libcst.matchers.DoNotCare() # Check if any of the arguments was annotated with a Matcher for name, type_declaration in typing_extensions.get_type_hints( function, include_extras=True).items(): if typing_extensions.get_origin(type_declaration) is Annotated: # Check if there is a matcher arg = typing_extensions.get_args(type_declaration)[1] if isinstance(arg, libcst.matchers.BaseMatcherNode): matchers[name] = arg return matchers
def is_private(type_: object) -> bool: if get_origin(type_) is Annotated: return any( isinstance(argument, StrawberryPrivate) for argument in get_args(type_) ) return False
def _register_types_with_magicgui(): """Register ``napari.types`` objects with magicgui.""" import sys from concurrent.futures import Future from magicgui import register_type from .utils import _magicgui as _mgui for _type in (LayerDataTuple, List[LayerDataTuple]): register_type( _type, return_callback=_mgui.add_layer_data_tuples_to_viewer, ) if sys.version_info >= (3, 9): future_type = Future[_type] # type: ignore register_type(future_type, return_callback=_mgui.add_future_data) for data_type in get_args(_LayerData): register_type( data_type, choices=_mgui.get_layers_data, return_callback=_mgui.add_layer_data_to_viewer, ) if sys.version_info >= (3, 9): register_type( Future[data_type], # type: ignore choices=_mgui.get_layers_data, return_callback=partial(_mgui.add_future_data, _from_tuple=False), )
async def _parse_var_keyword_argument(self, ctx: Context, param: inspect.Parameter, kwargs: dict[str, Any]) -> None: kv_pairs = [arg.split("=") for arg in ctx.lex] if not kv_pairs: raise MissingRequiredArgument(param) # defaults don't work here key_type, value_type = ( (str, str) if param.annotation in (param.empty, dict) else get_args(param.annotation) ) # default to dict[str, str] key_converter = self._get_converter(key_type) value_converter = self._get_converter(value_type) try: for key_arg, value_arg in kv_pairs: if key_arg in kwargs: raise DuplicateKeywordArgument(key_arg) kwargs.update({ await self._convert(ctx, key_converter, param, key_arg.strip()): await self._convert(ctx, value_converter, param, value_arg.strip()) }) except ValueError: raise UnmatchedKeyValuePair( "Unmatched key-value pair passed") from None
def parse_inner_type_of_optional(optional_type: Any) -> Type: args = get_args(optional_type) if not (len(args) == 2 and any(arg is _NoneType for arg in args)): raise TypeError(f"Unsupported type: {optional_type}.") return next(arg for arg in args if arg is not _NoneType)
def get_inner(hint: Any, *indexes: int) -> Any: """Return an inner type hint by indexes.""" if not indexes: return hint index, indexes = indexes[0], indexes[1:] return get_inner(get_args(hint)[index], *indexes)
def convert_to_union(self, ctx: typing.Optional[click.Context], param: typing.Optional[click.Parameter], value: typing.Any) -> Literal: lt = self._literal_type for i in range(len(self._literal_type.union_type.variants)): variant = self._literal_type.union_type.variants[i] python_type = get_args(self._python_type)[i] converter = FlyteLiteralConverter( ctx, self._flyte_ctx, variant, python_type, self._create_upload_fn, ) try: # Here we use click converter to convert the input in command line to native python type, # and then use flyte converter to convert it to literal. python_val = converter._click_type.convert(value, param, ctx) literal = converter.convert_to_literal(ctx, param, python_val) self._python_type = python_type return literal except (Exception or AttributeError) as e: logging.debug( f"Failed to convert python type {python_type} to literal type {variant}", e) raise ValueError( f"Failed to convert python type {self._python_type} to literal type {lt}" )
def get_dtype(type_: Type[DataArrayLike[D, T]]) -> Union[type, str, None]: """Extract a data type (dtype) from DataArrayLike[D, T].""" if get_origin(type_) is Annotated: type_ = get_args(type_)[0] dtype_ = get_args(get_args(type_)[0])[1] if dtype_ is Any: return None if isinstance(dtype_, ForwardRef): return dtype_.__forward_arg__ if get_origin(dtype_) is Literal: return get_args(dtype_)[0] return cast(type, dtype_)
def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]: """Convert a field into a Docutils optparse options dict.""" if at.type is int: return {"metavar": "<int>", "validator": _validate_int}, f"(default: {default})" if at.type is bool: return { "metavar": "<boolean>", "validator": frontend.validate_boolean, }, f"(default: {default})" if at.type is str: return { "metavar": "<str>", }, f"(default: '{default}')" if get_origin(at.type) is Literal and all( isinstance(a, str) for a in get_args(at.type) ): args = get_args(at.type) return { "metavar": f"<{'|'.join(repr(a) for a in args)}>", "type": "choice", "choices": args, }, f"(default: {default!r})" if at.type in (Iterable[str], Sequence[str]): return { "metavar": "<comma-delimited>", "validator": frontend.validate_comma_separated_list, }, f"(default: '{','.join(default)}')" if at.type == Tuple[str, str]: return { "metavar": "<str,str>", "validator": _create_validate_tuple(2), }, f"(default: '{','.join(default)}')" if at.type == Union[int, type(None)]: return { "metavar": "<null|int>", "validator": _validate_int, }, f"(default: {default})" if at.type == Union[Iterable[str], type(None)]: default_str = ",".join(default) if default else "" return { "metavar": "<null|comma-delimited>", "validator": frontend.validate_comma_separated_list, }, f"(default: {default_str!r})" raise AssertionError( f"Configuration option {at.name} not set up for use in docutils.conf." )
def provider(func: C) -> C: """Decorator that declares `func` as a provider of its return type. Note, If func returns `Optional[Type]`, it will be registered as a provider for Type. Examples -------- >>> @provider >>> def provides_int() -> int: ... return 42 """ return_hint = get_type_hints(func).get('return') if get_origin(return_hint) == Union: if ((args := get_args(return_hint)) and len(args) == 2 and type(None) in args): return_hint = next(a for a in args if a is not type(None)) # noqa
def extract_config(t: Type[TensorFlow2ONNX]) -> Tuple[Type[TensorFlow2ONNX], TensorFlow2ONNXConfig]: config = None if get_origin(t) is Annotated: base_type, config = get_args(t) if isinstance(config, TensorFlow2ONNXConfig): return base_type, config else: raise TypeTransformerFailedError(f"{t}'s config isn't of type TensorFlow2ONNX") return t, config
def enumeration(*values: Any, case_sensitive: bool = True, quote: bool = False) -> Enumeration: ''' Create an |Enumeration| object from a sequence of values. Call ``enumeration`` with a sequence of (unique) strings to create an Enumeration object: .. code-block:: python #: Specify the horizontal alignment for rendering text TextAlign = enumeration("left", "right", "center") Args: values (str) : string enumeration values, passed as positional arguments The order of arguments is the order of the enumeration, and the first element will be considered the default value when used to create |Enum| properties. Keyword Args: case_sensitive (bool, optional) : Whether validation should consider case or not (default: True) quote (bool, optional): Whether values should be quoted in the string representations (default: False) Raises: ValueError if values empty, if any value is not a string or not unique Returns: Enumeration ''' if len(values) == 1 and hasattr(values[0], "__args__"): values = get_args(values[0]) if not (values and all(isinstance(value, str) and value for value in values)): raise ValueError( f"expected a non-empty sequence of strings, got {nice_join(values)}" ) if len(values) != len(set(values)): raise ValueError( f"enumeration items must be unique, got {nice_join(values)}") attrs: Dict[str, Any] = {value: value for value in values} attrs.update({ "_values": list(values), "_default": values[0], "_case_sensitive": case_sensitive, "_quote": quote, }) return type("Enumeration", (Enumeration, ), attrs)()
def _decode_value(self, msg: RosMessage, buffer: bytes, offset, typ): o = get_origin(typ) val: Any = None if o is None and issubclass(typ, RosMessage): val = typ() offset = self.decode(val, buffer, offset) return val, offset base, size, signed = get_args(typ) if base == str: l = int.from_bytes(buffer[offset : offset + 4], "little", signed=False) val = buffer[offset + 4 : offset + 4 + l].decode("utf-8") offset += 4 + l elif base == int: val = int.from_bytes(buffer[offset : offset + size], "little", signed=signed) offset += size elif base == float: val, *_ = struct.unpack("<f" if size == 4 else "<d", buffer[offset : offset + size]) offset += size elif base == bytes: if size == 0: size = int.from_bytes(buffer[offset : offset + 4], "little", signed=False) offset += 4 val = buffer[offset : offset + size] offset += size elif base == Time or base == Duration: secs = int.from_bytes(buffer[offset : offset + 4], "little", signed=False) nsecs = int.from_bytes(buffer[offset + 4 : offset + 8], "little", signed=False) val = base(secs, nsecs) offset += 8 elif get_origin(base) == list: t, *_ = get_args(base) if size == 0: size = int.from_bytes(buffer[offset : offset + 4], "little", signed=False) offset += 4 val = [] for _ in range(size): v, offset = self._decode_value(msg, buffer, offset, t) val.append(v) return val, offset
async def _convert( self, ctx: Context, converter: converters.Converters, param: inspect.Parameter, argument: str, ) -> Any: if isinstance(converter, converters.ConverterBase): if isinstance(converter, type): # needs to be instantiated converter = converter() try: return await converter.convert(ctx, argument) except Exception as exc: try: name = converter.__name__ except AttributeError: name = converter.__class__.__name__ raise BadArgument( f"{argument!r} failed to convert to {name}") from exc origin = get_origin(converter) if origin is not None: args = get_args(converter) for arg in args: converter = self._get_converter(arg) try: ret = await self._convert(ctx, converter, param, argument) except BadArgument: if origin is not Union: raise else: if origin is not Literal: return ret if arg == ret: return ret if origin is Union and args[-1] is type(None): # typing.Optional try: return self._get_default( ctx, param) # get the default if possible except MissingRequiredArgument: return None # fall back to None if origin is Literal: raise BadArgument( f"Expected one of {', '.join(args)} not {argument!r}") raise BadArgument(f"Failed to parse {argument!r} to any type") try: return converter(argument) except Exception as exc: try: name = converter.__name__ except AttributeError: name = converter.__class__.__name__ raise BadArgument( f"{argument!r} failed to convert to {name}") from exc
def small_validate(value, allowed_type: None | Any = None, name: str | None = None) -> None: """Type validation. It also works for Union and validate Literal values. Instead of typeguard validation, it define just subset of types, but is simplier and needs no extra import, therefore can be faster. Args: value (Any): Value that will be validated. allowed_type (Any, optional): For example int, str or list. It can be also Union or Literal. If Literal, validated value has to be one of Literal values. If None, it's skipped. Defaults to None. name (str | None, optional): If error raised, name will be printed. Defaults to None. Raises: TypeError: Type does not fit. Examples: >>> from typing_extensions import Literal ... >>> small_validate(1, int) >>> small_validate(None, Union[list, None]) >>> small_validate("two", Literal["one", "two"]) >>> small_validate("three", Literal["one", "two"]) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ValidationError: ... """ if allowed_type: # If Union if get_origin(allowed_type) == Union: if type(value) in get_args(allowed_type): return else: raise ValidationError( mylogging.format_str( f"Allowed type for variable '{name}' are {allowed_type}, but you try to set an {type(value)}" ) ) # If Literal - parse options elif get_origin(allowed_type) == Literal: options = getattr(allowed_type, "__args__") if value in options: return else: raise ValidationError( f"New value < {value} > for variable < {name} > is not in allowed options {options}." ) else: if isinstance(value, allowed_type): # type: ignore return else: raise ValidationError( f"Allowed allowed_type for variable < {name} > is {allowed_type}, but you try to set an {type(value)}" )
def get(self, annotation): """Get object from container""" type_args = get_args(annotation) if type_args: annotation = (annotation, type_args) for dependency in self._resolver.get_resolved_dependencies(annotation): return dependency raise DependencyNotFound("Dependency not found")
def get_all(self, annotation): """Get all object from container""" type_args = get_args(annotation) if type_args: annotation = (annotation, type_args) dependencies = [] for dependency in self._resolver.get_resolved_dependencies(annotation): dependencies.append(dependency) return dependencies
def _eval_type(type: Any, globals: Dict[str, Any]) -> Any: """Evaluate all forward reverences in the given type.""" if isinstance(type, str): type = ForwardRef(type) if isinstance(type, ForwardRef): return type._evaluate(globals, {}) if isinstance(type, _GenericAlias): args = tuple(_eval_type(arg, globals) for arg in get_args(type)) return get_origin(type)[args] return type
def _is_valid_typeddict_item( td: type[TypedDict], key: str, value: Any # type: ignore [valid-type] ) -> bool: """Check if `key` and `value` form a valid item for the TypedDict `td`.""" annotations = get_type_hints(td) if key not in annotations: return False if get_origin(annotations[key]) is Literal: return value in get_args(annotations[key]) return isinstance(value, annotations[key])
def annotation(self): """Return type annotation for the parameter represented by the widget. ForwardRefs will be resolve when setting the annotation. If the widget is nullable (had a type annototation of Optional[Type]), annotation will return the first argument in the Optional clause. """ annotation = Widget.annotation.fget(self) # type: ignore if self._nullable and get_origin(annotation) is Union: return get_args(annotation)[0] return annotation
def flatten_greedy(item: T | Greedy[Any]) -> Generator[T, None, None]: if get_origin(item) in (Greedy, Union): for arg in get_args(item): if arg in INVALID_GREEDY_TYPES: raise TypeError(f"Greedy[{arg.__name__}] is invalid") if get_origin(arg) in (Greedy, Union): yield from flatten_greedy(arg) else: yield arg else: yield item
def is_str_literal(hint: Any) -> bool: """Check if a type hint is Literal[str].""" args: Any = get_args(hint) origin = get_origin(hint) if origin is not Literal: return False if not len(args) == 1: return False return isinstance(args[0], str)
def extract_cols_and_format( t: typing.Any, ) -> typing.Tuple[Type[T], Optional[typing.OrderedDict[str, Type]], Optional[str], Optional[pa.lib.Schema]]: """ Helper function, just used to iterate through Annotations and extract out the following information: - base type, if not Annotated, it will just be the type that was passed in. - column information, as a collections.OrderedDict, - the storage format, as a ``StructuredDatasetFormat`` (str), - pa.lib.Schema If more than one of any type of thing is found, an error will be raised. If no instances of a given type are found, then None will be returned. If we add more things, we should put all the returned items in a dataclass instead of just a tuple. :param t: The incoming type which may or may not be Annotated :return: Tuple representing the original type, optional OrderedDict of columns, optional str for the format, optional pyarrow Schema """ fmt = None ordered_dict_cols = None pa_schema = None if get_origin(t) is Annotated: base_type, *annotate_args = get_args(t) for aa in annotate_args: if isinstance(aa, StructuredDatasetFormat): if fmt is not None: raise ValueError( f"A format was already specified {fmt}, cannot use {aa}" ) fmt = aa elif isinstance(aa, collections.OrderedDict): if ordered_dict_cols is not None: raise ValueError( f"Column information was already found {ordered_dict_cols}, cannot use {aa}" ) ordered_dict_cols = aa elif isinstance(aa, pyarrow.Schema): if pa_schema is not None: raise ValueError( f"Arrow schema was already found {pa_schema}, cannot use {aa}" ) pa_schema = aa return base_type, ordered_dict_cols, fmt, pa_schema # We return None as the format instead of parquet or something because the transformer engine may find # a better default for the given dataframe type. return t, ordered_dict_cols, fmt, pa_schema
def add_future_data(gui, future, return_type, _from_tuple=True): """Process a Future object from a magicgui widget. This function will be called when a magicgui-decorated function has a return annotation of one of the `napari.types.<layer_name>Data` ... and will add the data in ``result`` to the current viewer as the corresponding layer type. Parameters ---------- gui : FunctionGui The instantiated magicgui widget. May or may not be docked in a dock widget. future : Future An instance of `concurrent.futures.Future` (or any third-party) object with the same interface, that provides `add_done_callback` and `result` methods. When the future is `done()`, the `result()` will be added to the viewer. return_type : type The return annotation that was used in the decorated function. _from_tuple : bool, optional (only for internal use). True if the future returns `LayerDataTuple`, False if it returns one of the `LayerData` types. """ from .._qt.utils import Sentry # get the actual return type from the Future type annotation _return_type = get_args(return_type)[0] if _from_tuple: # when the future is done, add layer data to viewer, dispatching # to the appropriate method based on the Future data type. def _on_future_ready(): add_layer_data_tuples_to_viewer(gui, future.result(), return_type) _FUTURES.discard(future) else: def _on_future_ready(): add_layer_data_to_viewer(gui, future.result(), _return_type) _FUTURES.discard(future) # some future types (such as a dask Future) will call the callback in # another thread, which wont always work here. So we create a very small # QObject that can signal back to the main thread to call `_on_done`. sentry = Sentry() sentry.alerted.connect(_on_future_ready) future.add_done_callback(sentry.alert) _FUTURES.add(future)