Type annotations
Added in version 1.5.0.
Deprecated since version 2.0.0: mypy plugin is deprecated, see Typed registry approach instead .
omniconf version 1.5.0 added type annotations, and also an experimental mypy plugin for dynamic type annotations
of the return values of config() by parsing the type and type_hint parameters of setting().
Without enabling the plugin, the return type of configuration values is Any | None .
Prerequisites
The mypy plugin has a few prerequisites at this point:
A mapping is made between
setting()key and its type. This probably only works properly when the key is defined as a literal.The plugin only works when using the
config()andsetting()functions. Settings added directly onSettingRegistryand read directly fromConfigRegistrywill be not found or properly annotated.There is no support for multiple registries, only the default registries are supported.
Despite these limits, if your current worklow only consists on using setting(), omniconf_load()
and config(), you’re probably fine.
Enabling the plugin
Simply add the plugin to your mypy configuration, for instance using pyproject.toml:
[tool.mypy]
...
plugins = [
"omniconf.mypy.plugin"
]
- omniconf.mypy.plugin.plugin(version: str) type[OmniconfTypePlugin][source]
Deprecated since version 2.0.0: mypy plugin is deprecated, see
TypedSettingRegistryfor an alternative.
- class omniconf.mypy.plugin.OmniconfTypePlugin(options)[source]
Deprecated since version 2.0.0: mypy plugin is deprecated, see
TypedSettingRegistryfor an alternative.
Annotating settings
First, an example script.
from omniconf import setting, config, omniconf_load
from omniconf.types import separator_sequence
class Foo:
def __init__(self, value: str) -> None:
self.value = value
setting("app.optionalstr")
setting("app.requiredstr", required=True)
setting("app.optionalint", type=int)
setting("app.strdefault", type=int, default="nonint")
setting("app.requiredbool", type=bool, required=True)
setting("app.simpledict", type=dict, required=True)
setting("app.annotateddict", type=dict, required=True, type_hint=dict[str, Foo])
setting("app.override", type=int, default="nonint", type_hint=int)
setting("app.object", type=Foo, required=True)
setting("app.sequence", type=separator_sequence(","), required=True)
omniconf_load()
For the simple types, this will behave as expected. Note that the mypy plugin will always annotate using Unions, to handle the required property. The type of default is also taken into account.
config("app.optionalstr") # str | None
config("app.requiredstr") # str
config("app.optionalint") # int | None
config("app.strdefault") # int | Literal['nonint']
config("app.requiredbool") # bool
config("app.object") # test.Foo
config("app.sequence") # Sequence[str]
The dict case is a bit more tricky, because by default this will result in a dict[_KT, _VT]. This will cause trouble because this signature disallows indexing and causes mypy to show this message:
error: Invalid index type "str" for "dict[_KT, _VT]"; expected type "_KT" [index]
value["a"]
^~~
To work around this, you can set the exact type using type_hint, which will be used as-is instead of the inferred type from type. You can use this in any case where the inferred type is giving you trouble.
setting("app.simpledict", type=dict, required=True)
setting("app.annotateddict", type=dict, required=True, type_hint=dict[str, Foo])
...
config("simpledict") # dict[_KT`1, _VT`2]
config("annotateddict") # dict[str, Foo]
Do note that type_hint gives you all the tools to shoot yourself in the foot, when you override the type to something that does not match what will be output.
setting("app.override", type=int, default="nonint", type_hint=int)
...
# returned value will be a str when nothing is set, because of the default.
config("app.override") # int
Debugging mypy plugin
If you’re running into weird behaviour and want to see exactly what types the mypy plugin infers based on your
settings, set OMNICONF_DEBUG=1 in your environment and call mypy with –no-incremental to skip cache:
$ OMNICONF_DEBUG=1 mypy --no-incremental test.py
Registering app.optionalstr as None | builtins.str
Registering app.requiredstr as builtins.str
Registering app.optionalint as None | builtins.int
Registering app.requiredbool as builtins.bool
Registering app.simpledict as dict[_KT`1, _VT`2]
Registering app.annotateddict as dict[str, test.Foo]
Registering app.object as test.Foo
Registering app.sequence as Sequence[builtins.str]
Registering app.strdefault as int | Literal['nonint']
Registering app.override as int
Success: no issues found in 1 source file