Repository: pystiche/pystiche

Issue:
Typing for vgg_multi_layer_encoder is broken
#392 changed the generation of `vgg_(11|13|16|19)(_bn)?_multi_layer_encoder` from static to dynamic. While this reduces repetition, it also breaks the type checks with `mypy`:

```python
from pystiche.enc import vgg11_multi_layer_encoder
```

```
error: Module 'pystiche.enc' has no attribute 'vgg11_multi_layer_encoder'; maybe "alexnet_multi_layer_encoder"?  [attr-defined]
```

Since `mypy` only reads but not imports `pystiche.enc.models.vgg` it has no knowledge that the functions will exist after import.


Discussion:
Apparently, as of Python `3.7` `mypy` supports a module level `__getattr__` that handles undefined attributes. Since we for now still support Python `3.6`, this is not a viable solution for everyone. 
Unfortunately a module level `__getattr__` will also not be of much help. It has to be defined where the `import` is happening. Since the multi-layer encoders are commonly imported from `pystiche.enc` we would have to include the following in `pystiche.enc.__init__`

```python
def __getattr__(name: str) -> Callable[..., VGGMultiLayerEncoder]:
   pass
```

For python `3.7` and above it would solve the problematic, but opens up another set of problems. With a module level `__getattr__` `mypy` accepts any unknown attribute for the given return type. Thus,

```python
from pystiche.enc import foo

reveal_type(foo)
```

```
note: Revealed type is 'def (*Any, **Any) -> pystiche.enc.models.vgg.VGGMultiLayerEncoder'
```

Since this is even more undesirable, I think we should revert the dynamic creation of the functions.

Resolving patch:
diff --git a/pystiche/enc/models/vgg.py b/pystiche/enc/models/vgg.py
--- a/pystiche/enc/models/vgg.py
+++ b/pystiche/enc/models/vgg.py
@@ -1,7 +1,5 @@
-import functools
 import re
 import warnings
-from copy import copy
 from typing import Any, Callable, Dict, List, Optional, Tuple, cast
 
 import torchvision
@@ -224,20 +222,39 @@ def _make_vgg_multi_layer_encoder_docstring(arch: str) -> str:
     return "\n".join((description, "", args))
 
 
-def _update_loader_magic(loader: Callable, name: str, doc: str) -> None:
-    loader.__module__ = VGGMultiLayerEncoder.__module__
-    loader.__name__ = loader.__qualname__ = name
+def vgg11_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg11", **kwargs)
 
-    annotations = copy(VGGMultiLayerEncoder.__init__.__annotations__)
-    del annotations["arch"]
-    annotations["return"] = VGGMultiLayerEncoder
-    loader.__annotations__ = annotations
-    loader.__doc__ = doc
+
+def vgg11_bn_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg11_bn", **kwargs)
+
+
+def vgg13_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg13", **kwargs)
+
+
+def vgg13_bn_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg13_bn", **kwargs)
+
+
+def vgg16_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg16", **kwargs)
+
+
+def vgg16_bn_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg16_bn", **kwargs)
+
+
+def vgg19_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg19", **kwargs)
+
+
+def vgg19_bn_multi_layer_encoder(**kwargs: Any) -> VGGMultiLayerEncoder:
+    return VGGMultiLayerEncoder("vgg19_bn", **kwargs)
 
 
 for arch in ARCHS:
     name = f"{arch}_multi_layer_encoder"
     doc = _make_vgg_multi_layer_encoder_docstring(arch)
-    loader = functools.partial(VGGMultiLayerEncoder, arch)
-    _update_loader_magic(loader, name, doc)
-    locals()[name] = loader
+    globals()[name].__doc__ = doc


Test patch:
diff --git a/tests/integration/enc/models/test_vgg.py b/tests/integration/enc/models/test_vgg.py
--- a/tests/integration/enc/models/test_vgg.py
+++ b/tests/integration/enc/models/test_vgg.py
@@ -56,17 +56,6 @@ def test_VGGMultiLayerEncoder_unknown_arch():
         enc.VGGMultiLayerEncoder(arch, pretrained=False, internal_preprocessing=False)
 
 
-def test_vgg_multi_layer_encoder_annotations(subtests, vgg_multi_layer_encoder_loaders):
-    # This has to be checked here, since mypy can only check statically defined
-    # annotations
-    for loader in vgg_multi_layer_encoder_loaders:
-        with subtests.test(loader.__name__):
-            annotations = loader.__annotations__
-
-            assert "arch" not in annotations
-            assert annotations["return"] is enc.VGGMultiLayerEncoder
-
-
 @pytest.mark.slow
 def test_vgg_multi_layer_encoder_smoke(
     subtests, vgg_archs, vgg_multi_layer_encoder_loaders,