# pragma: exclude from coverage
from types import NoneType, UnionType
from typing import (
Annotated,
Any,
Iterable,
Literal,
Optional,
Type,
Union,
get_args,
get_origin,
)
[docs]
def is_nullable(annotation) -> bool:
if annotation is NoneType:
return True
if get_origin(annotation) in (Union, UnionType):
return NoneType in get_args(annotation)
return False
[docs]
def get_allowed_values(annotation) -> list:
if get_origin(annotation) is Literal:
return list(get_args(annotation))
return []
[docs]
def convert_description_to_line_string(desc: str) -> str:
"""
Converts the description of config param to a single line string
suitable for sphinx node parsing
Parameters
----------
desc
Returns
-------
Description converted to a single line, with any indents removed
"""
return " ".join(line.strip() for line in desc.splitlines() if line.strip())
[docs]
def convert_allowed_values_to_string(values: Optional[Iterable[Any]]) -> str:
"""
Convert "allowed values" parameter of Config Param to a string.
Parameters
----------
values
Returns
-------
String representation of allowed values
"""
if not values: # () or [] or None
return ""
s = str(values)
# Remove outer brackets like () or []
# Always assumes that some outer brackets exist
# In future, might need robust checks for edge cases
return s[1:-1]
[docs]
def get_resolved_path(tp: Type) -> str:
"""
Gets the fully qualified name of a type
For builtins like int, str;
the module name i.e. 'builtins' is not part of the output.
Examples
--------
>>> get_resolved_path(int)
"int"
>>> get_resolved_path(Any)
"typing.Any"
"""
module = tp.__module__
if module == "builtins":
return tp.__qualname__
return module + "." + tp.__qualname__
[docs]
def convert_type_to_reST_string(tp: Type) -> str:
"""
Convert a type to string compatible for intersphinx mapping
The input can be:
1. single type
2. an Annotated type — only the first arg (the actual type) is used
3. a union type (Union or UnionType)
4. a generic collection type (list, dict, set, etc.) — both the
collection and its element types are linked
For cases 3 and 4, the types are converted to sphinx compatible
string and joined using a comma.
Parameters
----------
tp
type or tuple of type to convert to string
Returns
-------
String representation of type / tuple of type
Examples
--------
>>> convert_type_to_reST_string(int)
":py:class:`int`"
>>> convert_type_to_reST_string(float, Any)
":py:class:`float`, :py:class:`typing.Any`"
>>> convert_type_to_reST_string(float | int)
":py:class:`float` | :py:class:`int`"
>>> convert_type_to_reST_string(list[int])
":py:class:`~list`\\[:py:class:`~int`\\]"
>>> convert_type_to_reST_string(Annotated[int, \"some metadata\"])
":py:class:`~int`"
"""
origin = get_origin(tp)
if origin is Annotated:
return convert_type_to_reST_string(
tp.__origin__ # pylint: disable=no-member
)
if origin is Literal:
return ""
if origin in (Union, UnionType):
return " | ".join(convert_type_to_reST_string(a) for a in get_args(tp))
# Handle generic collection types
if origin is not None:
origin_str = f":py:class:`~{get_resolved_path(origin)}`"
args = get_args(tp)
if args:
args_str = ", ".join(convert_type_to_reST_string(a) for a in args)
return f"{origin_str}\\[{args_str}\\]"
return origin_str
return f":py:class:`~{get_resolved_path(tp)}`"