def test_resolve_annotation_with_cache_310(annotation, resolved): cache = {} assert resolved == utils.resolve_annotation(annotation, globals(), locals(), cache) assert typing.get_origin(resolved) is typing.Union assert len(cache) == 1 cached_item = cache[annotation] latest = utils.resolve_annotation(annotation, globals(), locals(), cache) assert latest is cached_item assert typing.get_origin(latest) is typing.get_origin(resolved)
def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[str, Any]) -> Dict[str, Flag]: annotations = namespace.get('__annotations__', {}) case_insensitive = namespace['__commands_flag_case_insensitive__'] flags: Dict[str, Flag] = {} cache: Dict[str, Any] = {} names: Set[str] = set() for name, annotation in annotations.items(): flag = namespace.pop(name, MISSING) if isinstance(flag, Flag): flag.annotation = annotation else: flag = Flag(name=name, annotation=annotation, default=flag) flag.attribute = name if flag.name is MISSING: flag.name = name annotation = flag.annotation = resolve_annotation(flag.annotation, globals, locals, cache) if flag.default is MISSING and hasattr(annotation, '__commands_is_flag__') and annotation._can_be_constructible(): flag.default = annotation._construct_default if flag.aliases is MISSING: flag.aliases = [] # Add sensible defaults based off of the type annotation # <type> -> (max_args=1) # List[str] -> (max_args=-1) # Tuple[int, ...] -> (max_args=1) # Dict[K, V] -> (max_args=-1, override=True) # Union[str, int] -> (max_args=1) # Optional[str] -> (default=None, max_args=1) try: origin = annotation.__origin__ except AttributeError: # A regular type hint if flag.max_args is MISSING: flag.max_args = 1 else: if origin is Union: # typing.Union if flag.max_args is MISSING: flag.max_args = 1 if annotation.__args__[-1] is type(None) and flag.default is MISSING: # typing.Optional flag.default = None elif origin is tuple: # typing.Tuple # tuple parsing is e.g. `flag: peter 20` # for Tuple[str, int] would give you flag: ('peter', 20) if flag.max_args is MISSING: flag.max_args = 1 elif origin is list: # typing.List if flag.max_args is MISSING: flag.max_args = -1 elif origin is dict: # typing.Dict[K, V] # Equivalent to: # typing.List[typing.Tuple[K, V]] flag.cast_to_dict = True if flag.max_args is MISSING: flag.max_args = -1 if flag.override is MISSING: flag.override = True elif origin is Literal: if flag.max_args is MISSING: flag.max_args = 1 else: raise TypeError(f'Unsupported typing annotation {annotation!r} for {flag.name!r} flag') if flag.override is MISSING: flag.override = False # Validate flag names are unique name = flag.name.casefold() if case_insensitive else flag.name if name in names: raise TypeError(f'{flag.name!r} flag conflicts with previous flag or alias.') else: names.add(name) for alias in flag.aliases: # Validate alias is unique alias = alias.casefold() if case_insensitive else alias if alias in names: raise TypeError(f'{flag.name!r} flag alias {alias!r} conflicts with previous flag or alias.') else: names.add(alias) flags[flag.name] = flag return flags
def test_resolve_annotation_310(annotation, resolved): assert resolved == utils.resolve_annotation(annotation, globals(), locals(), None)
def test_resolve_annotation_optional_normalisation(): value = utils.resolve_annotation('typing.Union[None, int]', globals(), locals(), None) assert value.__args__ == (int, type(None))