Conditional imports in stub files

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.


  1. and I’m not even sure that works, I’ve tried to come up with a combination of ignores in the past that works with typeshed, but I haven’t found one that actually works for both mypy and pyright ↩︎

  2. it should still complain if the module is present but missing type information ↩︎

1 Like