Skip to content

Commit

Permalink
Use future annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
jwodder committed Oct 16, 2022
1 parent 8473d64 commit 2e69565
Show file tree
Hide file tree
Showing 12 changed files with 56 additions and 46 deletions.
5 changes: 3 additions & 2 deletions src/outgoing/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations
from email import message_from_binary_file, policy
from email.message import EmailMessage
import logging
from typing import Any, IO, List, Optional
from typing import Any, IO, Optional
import click
from click_loglevel import LogLevel
from dotenv import find_dotenv, load_dotenv
Expand Down Expand Up @@ -65,7 +66,7 @@
@click.pass_context
def main(
ctx: click.Context,
message: List[IO[bytes]],
message: list[IO[bytes]],
config: Optional[str],
section: Any,
log_level: int,
Expand Down
19 changes: 10 additions & 9 deletions src/outgoing/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations
from collections.abc import Mapping
import pathlib
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
import pydantic
from . import core
from .errors import InvalidPasswordError
Expand All @@ -26,22 +27,22 @@ class Path(pathlib.Path):
"""

@classmethod
def __get_validators__(cls) -> "CallableGenerator":
def __get_validators__(cls) -> CallableGenerator:
yield path_resolve

class FilePath(pydantic.FilePath):
"""Like `Path`, but the path must exist and be a file"""

@classmethod
def __get_validators__(cls) -> "CallableGenerator":
def __get_validators__(cls) -> CallableGenerator:
yield path_resolve
yield from super().__get_validators__()

class DirectoryPath(pydantic.DirectoryPath):
"""Like `Path`, but the path must exist and be a directory"""

@classmethod
def __get_validators__(cls) -> "CallableGenerator":
def __get_validators__(cls) -> CallableGenerator:
yield path_resolve
yield from super().__get_validators__()

Expand Down Expand Up @@ -94,7 +95,7 @@ class MyPassword(outgoing.Password):
host = "service"
@staticmethod
def username(values: Dict[str, Any]) -> str:
def username(values: dict[str, Any]) -> str:
return "__token__"
and then use it in your model like so:
Expand Down Expand Up @@ -152,12 +153,12 @@ def __init_subclass__(cls) -> None:
raise RuntimeError("Password.username must be a str, callable, or None")

@classmethod
def __get_validators__(cls) -> "CallableGenerator":
def __get_validators__(cls) -> CallableGenerator:
yield cls._resolve
yield from super().__get_validators__()

@classmethod
def _resolve(cls, v: Any, values: Dict[str, Any]) -> str:
def _resolve(cls, v: Any, values: dict[str, Any]) -> str:
if not isinstance(v, (str, Mapping)):
raise ValueError(
"Password must be either a string or an object with exactly one field"
Expand Down Expand Up @@ -199,7 +200,7 @@ def _resolve(cls, v: Any, values: Dict[str, Any]) -> str:
raise ValueError(e.details)


def path_resolve(v: AnyPath, values: Dict[str, Any]) -> pathlib.Path:
def path_resolve(v: AnyPath, values: dict[str, Any]) -> pathlib.Path:
return resolve_path(v, values.get("configpath"))


Expand Down Expand Up @@ -242,7 +243,7 @@ class NetrcConfig(pydantic.BaseModel):
password: Optional[StandardPassword]

@pydantic.root_validator(skip_on_failure=True)
def _validate(cls, values: Dict[str, Any]) -> Dict[str, Any]: # noqa: B902, U100
def _validate(cls, values: dict[str, Any]) -> dict[str, Any]: # noqa: B902, U100
if values["password"] is not None:
if values["netrc"]:
raise ValueError("netrc cannot be set when a password is present")
Expand Down
19 changes: 10 additions & 9 deletions src/outgoing/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Mapping as MappingABC
from __future__ import annotations
from collections.abc import Mapping
from email.message import EmailMessage
import inspect
import json
Expand All @@ -7,7 +8,7 @@
from pathlib import Path
import sys
from types import TracebackType
from typing import Any, Mapping, Optional, Tuple, Type, TypeVar, Union, cast
from typing import Any, Optional, TypeVar, cast
from platformdirs import user_config_path
import tomli
from . import errors
Expand Down Expand Up @@ -50,14 +51,14 @@ def __enter__(self: S) -> S:

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_type: Optional[type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> Optional[bool]:
...

def send(self, msg: EmailMessage) -> Any:
""" Send ``msg`` or raise an exception if that's not possible """
"""Send ``msg`` or raise an exception if that's not possible"""
...


Expand Down Expand Up @@ -108,7 +109,7 @@ def from_config_file(
except FileNotFoundError:
data = None
if data is not None and section is not None:
if not isinstance(data, MappingABC):
if not isinstance(data, Mapping):
raise errors.InvalidConfigError(
"Top-level structure must be a dict/object",
configpath=configpath,
Expand All @@ -123,7 +124,7 @@ def from_config_file(
raise e
else:
raise errors.MissingConfigError([configpath])
if not isinstance(data, MappingABC):
if not isinstance(data, Mapping):
raise errors.InvalidConfigError(
"Section must be a dict/object",
configpath=configpath,
Expand Down Expand Up @@ -176,10 +177,10 @@ def from_dict(


def resolve_password(
password: Union[str, Mapping[str, Any]],
password: str | Mapping[str, Any],
host: Optional[str] = None,
username: Optional[str] = None,
configpath: Union[str, Path, None] = None,
configpath: str | Path | None = None,
) -> str:
"""
Resolve a configuration password value. If ``password`` is a string, it is
Expand Down Expand Up @@ -238,7 +239,7 @@ def resolve_password(

def lookup_netrc(
host: str, username: Optional[str] = None, path: Optional[AnyPath] = None
) -> Tuple[str, str]:
) -> tuple[str, str]:
"""
Look up the entry for ``host`` in the netrc file at ``path`` (default:
:file:`~/.netrc`) and return a pair of the username & password. If
Expand Down
10 changes: 6 additions & 4 deletions src/outgoing/errors.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from __future__ import annotations
from collections.abc import Sequence
import os
from typing import List, Optional, Sequence
from typing import Optional
from .util import AnyPath


class Error(Exception):
""" The superclass for all exceptions raised by ``outgoing`` """
"""The superclass for all exceptions raised by ``outgoing``"""

pass


class InvalidConfigError(Error):
""" Raised on encountering an invalid configuration structure """
"""Raised on encountering an invalid configuration structure"""

def __init__(self, details: str, configpath: Optional[AnyPath] = None):
#: A message about the error
Expand Down Expand Up @@ -47,7 +49,7 @@ class MissingConfigError(Error):

def __init__(self, configpaths: Sequence[AnyPath]):
#: Paths to the configfiles searched for configuration
self.configpaths: List[AnyPath] = list(configpaths)
self.configpaths: list[AnyPath] = list(configpaths)

def __str__(self) -> str:
return "outgoing configuration not found in files: " + ", ".join(
Expand Down
3 changes: 2 additions & 1 deletion src/outgoing/senders/mailboxes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
from abc import abstractmethod
from email.message import EmailMessage
import logging
Expand Down Expand Up @@ -88,7 +89,7 @@ class MHSender(MailboxSender):
def _makebox(self) -> mailbox.MH:
box = mailbox.MH(self.path)
if self.folder is not None:
folders: List[str]
folders: list[str]
if isinstance(self.folder, str):
folders = [self.folder]
else:
Expand Down
5 changes: 3 additions & 2 deletions src/outgoing/senders/smtp.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations
from email.message import EmailMessage
import logging
import smtplib
import sys
from typing import Any, Dict, Optional
from typing import Any, Optional
from pydantic import Field, PrivateAttr, validator
from ..config import NetrcConfig
from ..util import OpenClosable
Expand All @@ -24,7 +25,7 @@ class SMTPSender(NetrcConfig, OpenClosable):

@validator("port", always=True)
def _set_default_port(
cls, v: Any, values: Dict[str, Any] # noqa: B902, U100
cls, v: Any, values: dict[str, Any] # noqa: B902, U100
) -> Any:
if v == 0:
ssl = values.get("ssl")
Expand Down
5 changes: 3 additions & 2 deletions src/outgoing/util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations
from abc import ABC, abstractmethod
import os
from pathlib import Path
from types import TracebackType
from typing import Optional, Type, TypeVar, Union
from typing import Optional, TypeVar, Union
from pydantic import BaseModel, PrivateAttr

AnyPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
Expand Down Expand Up @@ -40,7 +41,7 @@ def __enter__(self: OC) -> OC:

def __exit__(
self,
_exc_type: Optional[Type[BaseException]],
_exc_type: Optional[type[BaseException]],
_exc_val: Optional[BaseException],
_exc_tb: Optional[TracebackType],
) -> None:
Expand Down
5 changes: 3 additions & 2 deletions test/test_config/test_netrc_config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from pathlib import Path
from typing import Optional, Union
from typing import Optional
from unittest.mock import sentinel
from pydantic import SecretStr, ValidationError
import pytest
Expand Down Expand Up @@ -147,7 +148,7 @@ def test_netrc_config_password_no_username(mocker: MockerFixture) -> None:
def test_netrc_config_password_netrc(
mocker: MockerFixture,
monkeypatch: pytest.MonkeyPatch,
netrc: Union[bool, str],
netrc: bool | str,
tmp_path: Path,
) -> None:
m = mocker.patch(
Expand Down
15 changes: 8 additions & 7 deletions test/test_config/test_password.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, Optional
from typing import Any, Optional
from pydantic import BaseModel, SecretStr, ValidationError
import pytest
from pytest_mock import MockerFixture
Expand Down Expand Up @@ -90,11 +91,11 @@ def test_standard_password_invalid_username(mocker: MockerFixture) -> None:

class Password02(Password):
@classmethod
def host(cls, _values: Dict[str, Any]) -> str:
def host(cls, _values: dict[str, Any]) -> str:
return "api.example.com"

@classmethod
def username(cls, _values: Dict[str, Any]) -> str:
def username(cls, _values: dict[str, Any]) -> str:
return "mylogin"


Expand Down Expand Up @@ -127,11 +128,11 @@ def test_password_constant_fields(mocker: MockerFixture) -> None:

class Password03(Password):
@classmethod
def host(cls, values: Dict[str, Any]) -> str:
def host(cls, values: dict[str, Any]) -> str:
return f"http:{values['host']}"

@classmethod
def username(cls, values: Dict[str, Any]) -> str:
def username(cls, values: dict[str, Any]) -> str:
return f"{values['username']}@{values['host']}"


Expand Down Expand Up @@ -203,7 +204,7 @@ def test_password_bad_username() -> None:

class HostErrorPassword(Password):
@classmethod
def host(cls, _values: Dict[str, Any]) -> None:
def host(cls, _values: dict[str, Any]) -> None:
raise RuntimeError("Invalid host method")


Expand All @@ -222,7 +223,7 @@ def test_host_error_password(mocker: MockerFixture) -> None:

class UsernameErrorPassword(Password):
@classmethod
def username(cls, _values: Dict[str, Any]) -> None:
def username(cls, _values: dict[str, Any]) -> None:
raise RuntimeError("Invalid username method")


Expand Down
8 changes: 4 additions & 4 deletions test/test_senders/test_command.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations
from email.message import EmailMessage
import logging
from pathlib import Path
import subprocess
from typing import List, Union
import pytest
from pytest_mock import MockerFixture
from outgoing import Sender, from_dict
Expand All @@ -22,7 +22,7 @@ def test_command_construct_default(tmp_path: Path) -> None:
@pytest.mark.parametrize(
"command", ["~/my/command --option", ["~/my/command", "--option"]]
)
def test_command_construct(command: Union[str, List[str]], tmp_path: Path) -> None:
def test_command_construct(command: str | list[str], tmp_path: Path) -> None:
sender = from_dict(
{"method": "command", "command": command}, configpath=tmp_path / "foo.toml"
)
Expand All @@ -42,7 +42,7 @@ def test_command_construct(command: Union[str, List[str]], tmp_path: Path) -> No
)
def test_command_send(
caplog: pytest.LogCaptureFixture,
command: Union[str, List[str]],
command: str | list[str],
shell: bool,
mocker: MockerFixture,
test_email1: EmailMessage,
Expand Down Expand Up @@ -81,7 +81,7 @@ def test_command_send(
],
)
def test_command_send_no_context(
command: Union[str, List[str]],
command: str | list[str],
shell: bool,
mocker: MockerFixture,
test_email1: EmailMessage,
Expand Down
4 changes: 2 additions & 2 deletions test/test_senders/test_mh.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations
from email.message import EmailMessage
import logging
from mailbox import MH
from operator import itemgetter
from pathlib import Path
from typing import List, Union
from mailbits import email2dict
import pytest
from outgoing import Sender, from_dict
Expand All @@ -12,7 +12,7 @@

@pytest.mark.parametrize("folder", [None, "work", ["important", "work"]])
def test_mh_construct(
folder: Union[str, List[str], None], monkeypatch: pytest.MonkeyPatch, tmp_path: Path
folder: str | list[str] | None, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.chdir(tmp_path)
sender = from_dict(
Expand Down

0 comments on commit 2e69565

Please sign in to comment.