Consider we have Template method pattern inheritance scenario. I am using an example with requests library.
import abc
import json
import requests
from typing import Dict, Any
class Base(abc.ABC)
@abc.abstractmethod
def call(self, *args, **kwargs) -> requests.Response:
raise NotImplementedError
def safe_call(self, *args, **kwargs) -> Dict[str, Any]:
try:
response = self.call(*args, **kwargs)
response.raise_for_status()
return response.json()
except json.JSONDecodeError as je: ...
except requests.exceptions.ConnectionError as ce: ...
except requests.exceptions.Timeout as te: ...
except requests.exceptions.HTTPError as he: ...
except Exception as e: ...
return {"success": False}
class Derived(Base):
def call(self, url: str, json: Dict[str, Any], timeout: int, retries: int) -> requests.Response:
# actual logic for making the http call
response = requests.post(url, json=json, timeout=timeout, ...)
return response
which will be used like
executor = Derived()
response = executor.safe_call(url='https://example.com', json={"key": "value"}, ...)
Is there a way to attach the signature of Derived.call to Derived.safe_call so modern IDE auto-complete and type checkers work?
Because this awfully looks like a decorator pattern, I tried using functools.update_wrapper in __init__ but that fails with
AttributeError: 'method' object has no attribute '__module__'
I do have a few options in mind, but I am unable to find any recommendations on this problem
- I can go the metaclass route to change up signature and docstring attributes before the class is completely constructed. However, this might not work well with IDEs.
- I can define a stub file
.pyiand maintain it along with the main code.
class Derived(Base):
def safe_call(self, url: str, json: Dict[str, Any], timeout: int, retries: int) -> Dict[str, Any]: ...
Change the design to something else completely
(Edit: Added post first answer) (Ab)use
@typing.overload
class Derived(Base):
@typing.overload
def safe_call(self, url: str, json: Dict[str, Any], timeout: int, retries: int) -> Dict[str, Any]:
...
def call(self, url: str, json: Dict[str, Any], timeout: int, retries: int) -> requests.Response:
# actual logic for making the http call
response = requests.post(url, json=json, timeout=timeout, ...)
return response