Skip to content

refactor(mypy): add stricter rules to specific modules [part I/VI] #969

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

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
TransactionStorage,
)
from hathor.util import Random, get_environment_info, not_none
from hathor.verification.verification_service import VerificationService, VertexVerifiers
from hathor.verification.verification_service import VerificationService
from hathor.verification.vertex_verifiers import VertexVerifiers
from hathor.wallet import BaseWallet, Wallet

logger = get_logger()
Expand Down
3 changes: 2 additions & 1 deletion hathor/cli/mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ def execute(args: Namespace) -> None:

from hathor.conf.get_settings import get_global_settings
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.verification.verification_service import VerificationService, VertexVerifiers
from hathor.verification.verification_service import VerificationService
from hathor.verification.vertex_verifiers import VertexVerifiers
settings = get_global_settings()
daa = DifficultyAdjustmentAlgorithm(settings=settings)
verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa, feature_service=Mock())
Expand Down
4 changes: 2 additions & 2 deletions hathor/consensus/block_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

from itertools import chain
from typing import TYPE_CHECKING, Iterable, Optional, cast
from typing import TYPE_CHECKING, Any, Iterable, Optional, cast

from structlog import get_logger

Expand All @@ -39,7 +39,7 @@ def __init__(self, context: 'ConsensusAlgorithmContext') -> None:
self.context = context

@classproperty
def log(cls):
def log(cls) -> Any:
""" This is a workaround because of a bug on structlog (or abc).

See: https://github.com/hynek/structlog/issues/229
Expand Down
4 changes: 2 additions & 2 deletions hathor/consensus/transaction_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING, Iterable, cast
from typing import TYPE_CHECKING, Any, Iterable, cast

from structlog import get_logger

Expand All @@ -38,7 +38,7 @@ def __init__(self, context: 'ConsensusAlgorithmContext') -> None:
self.context = context

@classproperty
def log(cls):
def log(cls) -> Any:
""" This is a workaround because of a bug on structlog (or abc).

See: https://github.com/hynek/structlog/issues/229
Expand Down
6 changes: 3 additions & 3 deletions hathor/event/model/base_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional
from typing import Any, Optional

from pydantic import NonNegativeInt, validator

from hathor.event.model.event_data import EventData
from hathor.event.model.event_data import BaseEventData, EventData
from hathor.event.model.event_type import EventType
from hathor.pubsub import EventArguments
from hathor.utils.pydantic import BaseModel
Expand Down Expand Up @@ -58,7 +58,7 @@ def from_event_arguments(
)

@validator('data')
def data_type_must_match_event_type(cls, v, values):
def data_type_must_match_event_type(cls, v: BaseEventData, values: dict[str, Any]) -> BaseEventData:
event_type = EventType(values['type'])
expected_data_type = event_type.data_type()

Expand Down
3 changes: 2 additions & 1 deletion hathor/event/resources/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import Optional

from pydantic import Field, NonNegativeInt
from twisted.web.http import Request

from hathor.api_util import Resource, set_cors
from hathor.cli.openapi_files.register import register_resource
Expand All @@ -35,7 +36,7 @@ def __init__(self, event_manager: Optional[EventManager]):
super().__init__()
self.event_manager = event_manager

def render_GET(self, request):
def render_GET(self, request: Request) -> bytes:
request.setHeader(b'content-type', b'application/json; charset=utf-8')
set_cors(request, 'GET')

Expand Down
22 changes: 8 additions & 14 deletions hathor/event/websocket/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING, Callable, Optional
from typing import TYPE_CHECKING, Optional

from autobahn.exception import Disconnected
from autobahn.twisted.websocket import WebSocketServerProtocol
from autobahn.websocket import ConnectionRequest
from pydantic import ValidationError
from structlog import get_logger
from typing_extensions import assert_never

from hathor.event.websocket.request import AckRequest, Request, RequestWrapper, StartStreamRequest, StopStreamRequest
from hathor.event.websocket.response import EventResponse, InvalidRequestResponse, InvalidRequestType, Response
Expand Down Expand Up @@ -50,7 +51,7 @@ class EventWebsocketProtocol(WebSocketServerProtocol):
# Whether the stream is enabled or not.
_stream_is_active: bool = False

def __init__(self):
def __init__(self) -> None:
super().__init__()
self.log = logger.new()

Expand Down Expand Up @@ -102,18 +103,11 @@ def onMessage(self, payload: bytes, isBinary: bool) -> None:

def _handle_request(self, request: Request) -> None:
"""Handles a request message according to its type."""
# This could be a pattern match in Python 3.10
request_type = type(request)
handlers: dict[type, Callable] = {
StartStreamRequest: self._handle_start_stream_request,
AckRequest: self._handle_ack_request,
StopStreamRequest: lambda _: self._handle_stop_stream_request()
}
handle_fn = handlers.get(request_type)

assert handle_fn is not None, f'cannot handle request of unknown type "{request_type}"'

handle_fn(request)
match request:
case StartStreamRequest(): self._handle_start_stream_request(request)
case AckRequest(): self._handle_ack_request(request)
case StopStreamRequest(): self._handle_stop_stream_request()
case _: assert_never(request)

def _handle_start_stream_request(self, request: StartStreamRequest) -> None:
"""
Expand Down
4 changes: 2 additions & 2 deletions hathor/event/websocket/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Annotated, Literal, Optional, Union
from typing import Annotated, Literal, Optional

from pydantic import Field, NonNegativeInt

Expand Down Expand Up @@ -54,7 +54,7 @@ class StopStreamRequest(BaseModel):
type: Literal['STOP_STREAM']


Request = Annotated[Union[StartStreamRequest, AckRequest, StopStreamRequest], Field(discriminator='type')]
Request = Annotated[StartStreamRequest | AckRequest | StopStreamRequest, Field(discriminator='type')]


class RequestWrapper(BaseModel):
Expand Down
8 changes: 5 additions & 3 deletions hathor/sysctl/sysctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Callable, Iterator, NamedTuple, Optional
from typing import Any, Callable, Iterator, NamedTuple, Optional, ParamSpec, TypeVar

from pydantic import validate_arguments
from structlog import get_logger
Expand All @@ -21,16 +21,18 @@

Getter = Callable[[], Any]
Setter = Callable[..., None]
P = ParamSpec('P')
T = TypeVar('T')

logger = get_logger()


def signal_handler_safe(f):
def signal_handler_safe(f: Callable[P, T]) -> Callable[P, T]:
"""Decorator to mark methods as signal handler safe.

It should only be used if that method can be executed during a signal handling.
Notice that a signal handling can pause the code execution at any point and the execution will resume after."""
f._signal_handler_safe = True
f._signal_handler_safe = True # type: ignore[attr-defined]
return f


Expand Down
16 changes: 9 additions & 7 deletions hathor/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TypeAlias

# XXX There is a lot of refactor to be done before we can use `NewType`.
# So, let's skip using NewType until everything is refactored.

VertexId = bytes # NewType('TxId', bytes)
Address = bytes # NewType('Address', bytes)
AddressB58 = str
TxOutputScript = bytes # NewType('TxOutputScript', bytes)
Timestamp = int # NewType('Timestamp', int)
TokenUid = VertexId # NewType('TokenUid', VertexId)
Amount = int # NewType('Amount', int)
VertexId: TypeAlias = bytes # NewType('TxId', bytes)
Address: TypeAlias = bytes # NewType('Address', bytes)
AddressB58: TypeAlias = str
TxOutputScript: TypeAlias = bytes # NewType('TxOutputScript', bytes)
Timestamp: TypeAlias = int # NewType('Timestamp', int)
TokenUid: TypeAlias = VertexId # NewType('TokenUid', VertexId)
Amount: TypeAlias = int # NewType('Amount', int)
68 changes: 34 additions & 34 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 25 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ hathor-cli = 'hathor.cli.main:main'
[tool.poetry.dev-dependencies]
flake8 = "~6.1.0"
isort = {version = "~5.12.0", extras = ["colors"]}
mypy = {version = "^1.5.1", markers = "implementation_name == 'cpython'"}
mypy-zope = {version = "^1.0.1", markers = "implementation_name == 'cpython'"}
mypy = {version = "^1.9.0", markers = "implementation_name == 'cpython'"}
mypy-zope = {version = "^1.0.4", markers = "implementation_name == 'cpython'"}
pytest = "~7.4.3"
pytest-cov = "~4.1.0"
flaky = "~3.7.0"
Expand Down Expand Up @@ -97,6 +97,8 @@ multi_line_output = 3
pretty = true
disallow_incomplete_defs = true
no_implicit_optional = true
extra_checks = true
disallow_untyped_decorators = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
Expand Down Expand Up @@ -131,6 +133,27 @@ module = [
]
ignore_missing_imports = true

# This override enables stricter rules for some specific modules.
# Currently, we have only two options from strict-mode that are disabled, but we have to opt-in instead of opt-out
# because setting strict=true doesn't work for module-level settings.
# Reference: https://mypy.readthedocs.io/en/stable/existing_code.html#introduce-stricter-options
[[tool.mypy.overrides]]
module = [
"hathor.consensus.*",
"hathor.feature_activation.*",
"hathor.event.*",
"hathor.verification.*"
]
strict_equality = true
strict_concatenate = true
check_untyped_defs = true
disallow_any_generics = true
disallow_untyped_defs = true
no_implicit_reexport = true
warn_return_any = true
# disallow_subclassing_any = true
# disallow_untyped_calls = true

[tool.pydantic-mypy]
init_typed = true
init_forbid_extra = true
Expand Down
2 changes: 1 addition & 1 deletion tests/sysctl/test_sysctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class SysctlTest(unittest.TestCase):
# We need this patch because pydantic.validate_arguments fails when it gets a mock function.
@patch('hathor.sysctl.sysctl.validate_arguments', new=lambda x: x)
@patch('hathor.sysctl.sysctl.validate_arguments', new=lambda x: x) # type: ignore
def setUp(self) -> None:
super().setUp()

Expand Down