Personally, I use type aliases in cases where I need to describe very dynamic types that make intuitive sense but are too wordy to pattern-match visually. So you might decompose that example like:
FileSourceType = Union[basestring, file]
FileSpecType = Union[
# (filename, file_source)
Tuple[basestring, Optional[FileSourceType]],
# (filename, file_source, content_type)
Tuple[basestring, Optional[FileSourceType], Optional[basestring]],
# (filename, file_source, content_type, custom_headers)
Tuple[basestring, Optional[FileSourceType], Optional[basestring], Optional[Headers]]
]
...
files: Optional[
Union[
Mapping[basestring, FileSpecType],
Iterable[Tuple[basestring, FileSpecType]
]
]
You theoretically lose some "glance value" because now you have to look in two places for the type...but in practice, I think you can figure it out a lot easier than the original example. Obviously you don't want to do this in simple cases, but it can make pathological cases like the above a lot easier to chew on.