Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeout to publisher_coverage.py #508

Merged
merged 8 commits into from
May 16, 2024

Conversation

MaxDall
Copy link
Collaborator

@MaxDall MaxDall commented May 6, 2024

This adds a new timeout wrapper to publisher coverage enabling Fundus to use Sitemaps for coverage as well.

@MaxDall MaxDall requested a review from dobbersc May 6, 2024 15:31
scripts/utility.py Outdated Show resolved Hide resolved
scripts/utility.py Outdated Show resolved Hide resolved
MaxDall added 2 commits May 16, 2024 13:54
Catch errors in coverage only if no complete articles were received
Comment on lines 20 to 30
@overload
def timeout(func: Callable[P, T], time: int, silent: bool = False) -> Callable[P, T]:
...


@overload
def timeout(func: Callable[P, T], time: int, silent: bool = True) -> Callable[P, Optional[T]]:
...


def timeout(func: Callable[P, T], time: int, silent: bool = False) -> Callable[P, Optional[T]]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the overload type hints work as intended. Example:

def test(a: int) -> int:
    return a + 1
    
reveal_type(timeout(test, time=20))  # Revealed type is "def (a: builtins.int) -> builtins.int"
reveal_type(timeout(test, time=20, silent=True))  # Revealed type is "def (a: builtins.int) -> builtins.int"
reveal_type(timeout(test, time=20, silent=False))  # Revealed type is "def (a: builtins.int) -> builtins.int"

I would expect the second variant to yield Optional[int] as return type.

Suggested change
@overload
def timeout(func: Callable[P, T], time: int, silent: bool = False) -> Callable[P, T]:
...
@overload
def timeout(func: Callable[P, T], time: int, silent: bool = True) -> Callable[P, Optional[T]]:
...
def timeout(func: Callable[P, T], time: int, silent: bool = False) -> Callable[P, Optional[T]]:
@overload
def timeout(func: Callable[P, T], time: int, silent: Literal[False] = ...) -> Callable[P, T]:
...
@overload
def timeout(func: Callable[P, T], time: int, silent: Literal[True]) -> Callable[P, Optional[T]]:
...
def timeout(func: Callable[P, T], time: int, silent: bool = False) -> Callable[P, Optional[T]]:

The overloaded types of silent directly match the literal type of the parameter while the ellipsis signifies the optional presence of the parameter. (This is also explained here in the mypy documentation.)

Now we get:

main.py:52: note: Revealed type is "def (a: builtins.int) -> builtins.int"
main.py:53: note: Revealed type is "def (a: builtins.int) -> Union[builtins.int, None]"
main.py:54: note: Revealed type is "def (a: builtins.int) -> builtins.int"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why the Literal? Shouldn't silent: bool = True/False be correct? According to the documentation? What am I missing here because it obviously isn't 😅

Copy link
Collaborator

@dobbersc dobbersc May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, the mypy documentation does not touch on this (or I missed it). As I understand it, mypy only checks the type itself and the absence or presence:

The default values of a function’s arguments don’t affect its signature – only the absence or presence of a default value does.

So to tell mypy that we return Callable[P, T] for slient=False we need to restrict the type itself to only False via Literal[False], as bool would still allow for both values True and False. When using silent: bool = True/False mypy would not accept the variant where the caller does not provide a silent parameter, and the default is used. This case is covered by the ellipsis, which is then annotated with Literal[True] since the default value is True.

Example with ellipsis:

# Applies when "flag" is omitted or set to True.
@overload
def func(a: int, flag: Literal[True] = ...) -> int: ...


# Applies when "flag" is set to False.
@overload
def func(a: int, flag: Literal[False]) -> str: ...


def func(a: int, flag: bool = True) -> Union[int, str]:
    return a if flag else str(a)

Example without the ellipsis that is equivalent to the previous example:

# Applies when "flag" is omitted.
@overload
def func(a: int) -> int: ...

# Applies when "flag" is set to True.
@overload
def func(a: int, flag: Literal[True]) -> int: ...

# Applies when "flag" set to False.
@overload
def func(a: int, flag: Literal[False]) -> str: ...

def func(a: int, flag: bool = True) -> Union[int, str]:
    return a if flag else str(a)

But is is also just speculation. There is also an issue about this overloads behavior here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the detailed explanation 👍

@MaxDall MaxDall merged commit 2ed6d31 into master May 16, 2024
4 checks passed
@MaxDall MaxDall deleted the add-timeout-to-publisher-coverage branch May 16, 2024 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants