Problems with type hint anotation

Decided to try and add type-hint annotation to my project but seems I have quite a few unresolved errors reported by PyCharm, presumably I have quite a few bits wrong. Sorry it is a bit long but code below is just an extraction of those lines which have problems and a few other possibly relevant lines:

1. from wsgiref import simple_server
2. 
3. interactive_mode = True
4. if interactive_mode:
5.     from collections.abc import Callable
6.     from logging import Logger
7. 
8. class WsgiController:
9.     logger: Logger
10.     start_response: Callable[[str, list[tuple[str, str]]], None]
11.     oPage: type[Process]
12.     def __init__(self, environ: dict[str, str], start_response: Callable[[str, list[tuple[str, str]]], None]):
13.         self.environ = environ
14.         self.start_response = start_response
15.         self.oPage = Process(environ, start_response)
16. 
17. httpd = simple_server.make_server("", 8000, WsgiController)
18. 
19. class Request:
20.     environ: dict[str, str]
21.     cookies: dict[str, str]
22. 
23.     def __init__(self, environ: dict[str, str]) -> None:
24.         self.environ = environ
25. 
26.     def get(self, key: str) -> str | None:
27.         return self.environ.get(key)
28. 
29.     def parse_cookies(self) -> None:
30.         _ = self.get("HTTP_COOKIE")
31. 
32.     def set_cookie(self, name: str, value: str, attributes: None | dict[str, str]=None) -> None:
33.         if attributes is None: attributes = {}
34.         _ = f"{name}={value};"
35.         for _, _ in attributes.items():
36.             pass
37. 
38. class Response:
39.     start_response: Callable[[str, list[tuple[str, str]]], None]
40.     headers: list[tuple[str, str]]
41. 
42.     def __init__(self, start_response) -> None:
43.         self.start_response = start_response
44.         self.headers = [("", "")]
45. 
46.     def start_headers(self, status: str, headers: list[tuple[str, str]]) -> None:
47.         self.start_response(status, headers + self.headers)
48. 
49.     def serve_pre_compiled(self, pyc_file: str) -> str:
50.             local_dict = {"oRequest": Process.oRequest, "oResponse": self}
51.             _ = pyc_file
52.             return local_dict["__HTML__"]
53. 
54.     def serve_new_source(self, source_file: str, module_name: str, pyc_file: str) -> str:
55.         import importlib.util
56.         import importlib.machinery
57.         spec = importlib.util.spec_from_file_location(module_name, source_file)
58.         importlib.util.module_from_spec(spec)
59.         module = importlib.util.module_from_spec(spec)
60.         spec.loader.exec_module(module)
61.         _ = self
62.         _ = pyc_file
63.         return _
64. 
65. class Process:
66.     oRequest: type[Request]
67.     oResponse: type[Response]
68.     logger: type[logger]
69. 
70.     def __init__(self, environ: dict[str, str], start_response: Callable[[str, list[tuple[str, str]]], None]) -> None:
71.         Process.oRequest = Request(environ)
72.         Process.oResponse = Response(start_response)
73.         _ = self.oRequest.get("PATH_INFO")[1:]
74.         self.oResponse.start_headers("404 Not Found", [("Content-Type", "text/html; charset=utf-8")])
75.         self.oRequest.parse_cookies()
76.         _ = self.oResponse.serve_pre_compiled("pyc_file")
77.         _ = self.oResponse.serve_new_source("source_file", "module_name", "pyc_file")
78.         _ = "- " + self.oResponse.start_response.__self__.status
79.         _ = self.oRequest.get("PATH_INFO")[1:]
80.         self.oResponse.start_headers('200 OK', [('Content-type', "mime_type" + '; charset=utf-8'), ("Content-Length", str(len("ret_val")))])
81. 

Errors reported by PyCharm:

15	Expected type 'type[Process]', got 'Process' instead
17	Expected type '(dict[str, Any], StartResponse) -> Iterable[bytes]', got 'type[WsgiController]' instead
53	Expected type 'str', got 'type[Request] | Self@Response' instead
59	Expected type 'ModuleSpec', got 'ModuleSpec | None' instead
60	Expected type 'ModuleSpec', got 'ModuleSpec | None' instead
72	Expected type 'type[Request]', got 'Request' instead
73	Expected type 'type[Response]', got 'Response' instead
74	Parameter 'key' unfilled
75	Parameter 'headers' unfilled
76	Parameter 'self' unfilled
77	Parameter 'pyc_file' unfilled
78	Parameter 'pyc_file' unfilled
79	Cannot find reference '__self__' in '(str, list) -> None'
80	Parameter 'key' unfilled
81	Parameter 'headers' unfilled
35	Member 'None' of 'dict[str, str] | None' does not have attribute 'items'
60	Member 'None' of 'ModuleSpec | None' does not have attribute 'loader'
60	Member 'None' of 'Loader | None' does not have attribute 'exec_module'
73	Member 'None' of 'str | None' does not have attribute '__getitem__'
79	Member 'None' of 'str | None' does not have attribute '__getitem__'

You’re using type[<class>] wrong. That syntax is to specifically tell the type checker that you want a type object, not an instance

class my_dict(dict): ...

# Type
a: type[dict] = my_dict

# Instance
b: dict = my_dict()

Your Process and WsgiController classes both make this mistake. If you fix that it should clear all related type errors (there are more, but that is a big one)

Made a few changes based on your suggestion but still stuck with the following. Sorry but don’t seem to be able to edit the original post for some reason so repeating (updated) code:

1 from wsgiref.types import StartResponse
2 
3 interactive_mode = True
4 if interactive_mode:
5     from collections.abc import Callable
6     import logging
7     from logging import Logger
8     from wsgiref import simple_server
9 
10 class WsgiController:
11     logger: Logger
12     # Expected type '(str, list[tuple[str, str]]) -> None', got 'StartResponse' instead
13     start_response: StartResponse
14     oPage: Process
15 
16     def __init__(self, environ: dict[str, str], start_response: StartResponse) -> None:
17         self.environ = environ
18         self.start_response = start_response
19         self.oPage = Process(environ, start_response)
20 
21 httpd = simple_server.make_server("", 8000, WsgiController)
22 
23 class Request:
24     environ: dict[str, str]
25     cookies: dict[str, str]
26 
27     def __init__(self, environ: dict[str, str]) -> None:
28         self.environ = environ
29 
30     def get(self, key: str) -> str | None:
31         return self.environ.get(key)
32 
33     def parse_cookies(self) -> None:
34         _ = self.get("HTTP_COOKIE")
35 
36     def set_cookie(self, name: str, value: str, attributes: None | dict[str, str] = None) -> None:
37         if attributes is None: attributes = {}
38         _ = f"{name}={value};"
39         _ = self
40         for _, _ in attributes.items():
41             pass
42 
43 class Response:
44     start_response: Callable[[str, list[tuple[str, str]]], None]
45     headers: list[tuple[str, str]]
46 
47     def __init__(self, start_response) -> None:
48         self.start_response = start_response
49         self.headers = [("", "")]
50 
51     def start_headers(self, status: str, headers: list[tuple[str, str]]) -> None:
52         self.start_response(status, headers + self.headers)
53 
54     def serve_pre_compiled(self, pyc_file: str) -> str:
55         local_dict = {"oRequest": Process.oRequest, "oResponse": self}
56         _ = pyc_file
57         return local_dict["__HTML__"]
58 
59     def serve_new_source(self, source_file: str, module_name: str, pyc_file: str) -> str:
60         import importlib.util
61         import importlib.machinery
62         spec = importlib.util.spec_from_file_location(module_name, source_file)
63         importlib.util.module_from_spec(spec)
64         module = importlib.util.module_from_spec(spec)
65         spec.loader.exec_module(module)
66         _ = self
67         _ = pyc_file
68         return _
69 
70 class Process:
71     oRequest: Request
72     oResponse: Response
73     logger: logging.Logger
74     def __init__(self, environ: dict[str, str], start_response: Callable[[str, list[tuple[str, str]]], None]) -> None:
75         Process.oRequest = Request(environ)
76         Process.oResponse = Response(start_response)
77         self.oResponse.start_headers("404 Not Found", [("Content-Type", "text/html; charset=utf-8")])
78         self.oRequest.parse_cookies()
79         _ = self.oResponse.serve_pre_compiled("pyc_file")
80         _ = self.oResponse.serve_new_source("source_file", "module_name", "pyc_file")
81         _ = "- " + self.oResponse.start_response.__self__.status
82         source_file = self.oRequest.get("PATH_INFO")
83         _ = None if source_file is None else source_file.removeprefix("/")
84         self.oResponse.start_headers('200 OK', [('Content-type', "mime_type" + '; charset=utf-8'),
85                                                 ("Content-Length", str(len("ret_val")))])
86 

Errors:

19	Expected type '(str, list[tuple[str, str]]) -> None', got 'StartResponse' instead
21	Expected type '(dict[str, Any], StartResponse) -> Iterable[bytes]', got 'type[WsgiController]' instead
57	Expected type 'str', got 'type[Request] | Self@Response' instead
63	Expected type 'ModuleSpec', got 'ModuleSpec | None' instead
64	Expected type 'ModuleSpec', got 'ModuleSpec | None' instead
81	Cannot find reference '__self__' in '(str, list) -> None'
40	Member 'None' of 'dict[str, str] | None' does not have attribute 'items'
65	Member 'None' of 'ModuleSpec | None' does not have attribute 'loader'
65	Member 'None' of 'Loader | None' does not have attribute 'exec_module'

FYI. when you’re asking for programming help, it’s usually best to leave out the line numbers. It’s easier for us to recreate them by pasting into a code editor than remove them before running mypy :slight_smile:

Showing a reproducer as simple as possible is always appreciated.

Code without line numbers:

from wsgiref.types import StartResponse
import importlib.util
import importlib.machinery

interactive_mode = True
if interactive_mode:
    from collections.abc import Callable
    import logging
    from wsgiref import simple_server

class WsgiController:

    def __init__(self, environ: dict[str, str], start_response: StartResponse) -> None:
        self.oPage = Process(environ, start_response)

httpd = simple_server.make_server("", 8000, WsgiController)

class Request:

    def __init__(self, environ: dict[str, str]) -> None:
        self.environ = environ

    def get(self, key: str) -> str | None:
        return self.environ.get(key)

    def parse_cookies(self) -> None:
        _ = self.get("HTTP_COOKIE")

    def set_cookie(self, attributes: None | dict[str, str] = None) -> None:
        _ = self
        if attributes is None: attributes = {}
        for _, _ in attributes.items():
            pass

class Response:
    start_response: Callable[[str, list[tuple[str, str]]], None]

    def __init__(self, start_response) -> None:
        self.start_response = start_response
        self.headers = [("", "")]

    def serve_pre_compiled(self) -> str:
        local_dict = {"oRequest": Process.oRequest, "oResponse": self}
        return local_dict["__HTML__"]

    def serve_new_source(self, source_file: str, module_name: str, pyc_file: str) -> str:
        spec = importlib.util.spec_from_file_location(module_name, source_file)
        importlib.util.module_from_spec(spec)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        _ = self
        _ = pyc_file
        return _

class Process:
    oRequest: Request
    oResponse: Response
    logger: logging.Logger
    def __init__(self, _, start_response: Callable[[str, list[tuple[str, str]]], None]) -> None:
        _ = self.oResponse.start_response.__self__.status

That’s what I have tried to do, example is cut down from around three hundred lines of code.

When dealing with typing errors like this, they tend to mean one of two things: either your type annotations are incorrect, and need to updated to reflect the code; or your code is incorrect, and needs to be fixed in a way that will no longer provide the type errors.

Your example here has both kinds of issues. For instance, your Process.__init__() takes a Callable[etc] argument instead of a StartResponse, and the two types are not compatible. Is it intentional that you’re using two different types there? If not, you probably want to annotate the argument in Process.__init__() as a StartResponse.

On the other hand, serve_pre_compiled() appears to actually be bugged. As far as I can tell, as written, it will always raise a KeyError because the dict has no key __HTML__. The type error you’re getting there about the return type is a good clue that you’re doing something wrong.

Sorry serve_pre_compiled() should have had the line:

exec("file", {}, local_dict)

in the middle of it and the file which is run by exec() adds the__html__ variable into local_dict{}

That was down to me trying to minimise to code example.

Edit: again can’t change original posted code to include this for some reason.