I’ll have to agree with you on that. As soon as you enclose more than one import spread across multiple statements it becomes difficult or even impossible to ensure you are modelling the correct behavior. I would prefer something similar in structure to sys.version_info
/ sys.platform
checks.
There’s a proposal from five years ago that may have easier to justify semantics, although it never really went anywhere due to the lack of motivating use-cases.
But we could provide a simple function in typing_extensions
with the same semantics that will later get added to sys
or typing
or wherever feels most appropriate, to implement a static import with a fallback other than Any
. This could just be a simple wrapper for __import__
at runtime.
Alternatively it could be a static check, that doesn’t perform the import, it just checks whether it would raise an ImportError
. This would be the most flexible and most closely mirror the version/platform switches, since this allows you to execute arbitrarily complex code in the two cases. Although there would probably be some special cases where this would break unless the function performed the actual import and just skipped the step where it put it into the local namespace.
Chameleon for example provides two translate
functions in their i18n
module, but one of them only exists if you happen to have zope.i18n
installed. Any
does not seem like a good fallback in this case, in fact any fallback would be wrong, the symbol should just be missing. Although to be fair this function has since been deprecated, due to conflicting expectations of zope’s API. This is just an example I have seen more recently. I’ve definitely come across more examples that do similar things, but the most common case is probably that one of the modules will fail to import completely at runtime, because it is only meant to be used once an optional dependency has been installed, which is a little less bad than the other cases, since it is more likely to be caught during development, this also can’t be fully modelled with any of the proposed solutions.
But to get back to the crux of the matter: This is about providing the most accurate types in the presence of optional dependencies without polluting people’s environments with those same dependencies or create a bunch of noise if they don’t have follow_imports=silent
enabled. You will have to do weird stuff like type: ignore[import-not-found,unused-ignore]
to shut up the type checker in both cases[1].
If we can find another way to improve the situation here, I would be happy enough. Maybe something as simple as a type: optional-import
comment to clue the type checker into shutting up if the module is missing completely[2]. We could even consider changing the fallback to Never
in this specific case, since we have explicitly marked the import as optional, so the potential for a false positive is much lower.