Skip to content

Commit c726f49

Browse files
committed
3.8.3rc0
1 parent 493cf1f commit c726f49

10 files changed

+393
-97
lines changed

CHANGELOG.rst

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Fixed
3939
-----
4040
* Fixed incorrect handling of timeout when checking if new version has been released.
4141

42+
Internals
43+
---------
44+
* DictType hints for configuration.
4245

4346
Version 3.8.2
4447
====================

RELEASE.rst

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
Fixed
22
-----
33
* Fixed incorrect handling of timeout when checking if new version has been released.
4+
5+
Internals
6+
---------
7+
* DictType hints for configuration.

mypy.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# File info at https://mypy.readthedocs.io/en/stable/config_file.html
55

66
# Specifies the Python version used to parse and check the target program.
7-
python_version = 3.7
7+
# python_version = 3.7
88

99
# Shows error codes in error messages.
1010
show_error_codes = True

tox.ini

+17-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# entry point for build, test and release activities.
77

88
[tox]
9-
minversion = 3.24.1
9+
minversion = 3.24.3
1010
envlist =
1111
pre-commit,
1212
py{310,39,38,37},
@@ -19,26 +19,26 @@ skip_missing_interpreters = true
1919
commands = coverage run --parallel-mode -m pytest
2020
deps =
2121
-rtests/requirements_pytest.txt
22-
passenv =
23-
REDIS_URI
24-
TELEGRAM_TOKEN
2522
setenv =
2623
PYTHONPATH = {toxinidir}
2724
PYTHONUTF8 = 1
25+
passenv =
26+
REDIS_URI
27+
TELEGRAM_TOKEN
2828
# parallel_show_output = true
29+
download = True
2930
depends =
3031
# py{310,39,38,37}: pre-commit, new-install
3132
post: py{310,39,38,37}
3233

3334
[testenv:pre-commit]
3435
# Settings defined in the top-level testenv section are automatically inherited if not overwritten
35-
basepython = python3.7
36-
deps =
37-
-rtests/requirements_pre-commit.txt
38-
-rdocs/requirements.txt
3936
commands =
4037
pre-commit autoupdate
4138
pre-commit run -a
39+
deps =
40+
-rtests/requirements_pre-commit.txt
41+
-rdocs/requirements.txt
4242

4343
[testenv:post]
4444
# Post-test cleanup
@@ -54,28 +54,31 @@ skip_install = true
5454

5555
[testenv:docs]
5656
# Test docs
57+
commands =
58+
sphinx-build -W -j auto docs docs/_build
59+
# TODO The below works in Windows only
60+
cmd /c if %errorlevel% equ 0 start "" "file://{toxinidir}/docs/_build/index.html"
5761
deps =
5862
-rrequirements.txt
5963
-rdocs/requirements.txt
6064
allowlist_externals =
6165
cmd
6266
sphinx-build
63-
commands =
64-
sphinx-build -W -j auto docs docs/_build
65-
# TODO The below works in Windows only
66-
cmd /c if %errorlevel% equ 0 start "" "file://{toxinidir}/docs/_build/index.html"
6767

6868
[testenv:new-install]
6969
# Settings defined in the top-level testenv section are automatically inherited if not overwritten
7070
# new-install tests a clean new installation using wheel, ensuring e.g. that all packages are installed as well
71-
basepython = python3.7
71+
isolated_build = true
72+
# tox fails with Python 3.7 (does not run pip correctly)
73+
basepython = python3.8
7274
commands =
7375
pip install --upgrade pip setuptools wheel
7476
python setup.py bdist_wheel
7577
pip install --upgrade --find-links={toxinidir}/dist webchanges
7678
webchanges -v --clean-cache
7779
python -c "from pathlib import Path; dir = Path.home().joinpath('Documents').joinpath('webchanges'); [f.unlink() for f in dir.iterdir()]; dir.rmdir()"
7880
setenv = USERPROFILE = {env:TEMP}
81+
# empty list of deps to override top-level
82+
deps =
7983
download = true
8084
skip_install = true
81-
isolated_build = true

webchanges/command.py

+24-19
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import traceback
1010
from concurrent.futures import ThreadPoolExecutor
1111
from pathlib import Path
12-
from typing import Optional, Union
12+
from typing import Optional, TYPE_CHECKING, Union
1313

1414
import requests
1515

@@ -24,6 +24,10 @@
2424

2525
logger = logging.getLogger(__name__)
2626

27+
if TYPE_CHECKING:
28+
from .reporters import ConfigReportersList
29+
from .storage import ConfigReportEmail, ConfigReportEmailSmtp, ConfigReportTelegram, ConfigReportXmpp
30+
2731

2832
class UrlwatchCommand:
2933
def __init__(self, urlwatcher: Urlwatch) -> None:
@@ -89,7 +93,8 @@ def edit_hooks(self) -> int:
8993
print(f'Saved edits in {self.urlwatch_config.hooks}')
9094
return 0
9195

92-
def show_features(self) -> int:
96+
@staticmethod
97+
def show_features() -> int:
9398
print(f'Please see full documentation at {__docs_url__}')
9499
print()
95100
print('Supported jobs:\n')
@@ -310,9 +315,9 @@ def edit_config(self) -> int:
310315
return result
311316

312317
def check_telegram_chats(self) -> None:
313-
config = self.urlwatcher.config_storage.config['report'].get('telegram')
318+
config: ConfigReportTelegram = self.urlwatcher.config_storage.config['report']['telegram']
314319

315-
bot_token = config.get('bot_token')
320+
bot_token = config['bot_token']
316321
if not bot_token:
317322
print('You need to set up your bot token first (see documentation)')
318323
self._exit(1)
@@ -354,8 +359,8 @@ def check_test_reporter(self) -> None:
354359
print(f'\nSupported reporters:\n{ReporterBase.reporter_documentation()}\n')
355360
self._exit(1)
356361

357-
cfg = self.urlwatcher.config_storage.config['report'].get(name, {'enabled': False})
358-
if not cfg.get('enabled', False):
362+
cfg: ConfigReportersList = self.urlwatcher.config_storage.config['report'][name] # type: ignore[misc]
363+
if not cfg['enabled']:
359364
print(f'Reporter is not enabled/configured: {name}')
360365
print(f'Use {__project_name__} --edit-config to configure reporters')
361366
self._exit(1)
@@ -427,38 +432,38 @@ def set_error(job_state: 'JobState', message: str) -> JobState:
427432
self._exit(0)
428433

429434
def check_smtp_login(self) -> None:
430-
config = self.urlwatcher.config_storage.config['report']['email']
431-
smtp_config = config['smtp']
435+
config: ConfigReportEmail = self.urlwatcher.config_storage.config['report']['email']
436+
smtp_config: ConfigReportEmailSmtp = config['smtp']
432437

433438
success = True
434439

435-
if not config.get('enabled'):
440+
if not config['enabled']:
436441
print('Please enable e-mail reporting in the config first.')
437442
success = False
438443

439-
if config.get('method') != 'smtp':
444+
if config['method'] != 'smtp':
440445
print('Please set the method to SMTP for the e-mail reporter.')
441446
success = False
442447

443-
smtp_auth = smtp_config.get('auth')
448+
smtp_auth = smtp_config['auth']
444449
if not smtp_auth:
445450
print('Authentication must be enabled for SMTP.')
446451
success = False
447452

448-
smtp_hostname = smtp_config.get('host')
453+
smtp_hostname = smtp_config['host']
449454
if not smtp_hostname:
450455
print('Please configure the SMTP hostname in the config first.')
451456
success = False
452457

453-
smtp_username = smtp_config.get('user') or config['from']
458+
smtp_username = smtp_config['user'] or config['from']
454459
if not smtp_username:
455460
print('Please configure the SMTP user in the config first.')
456461
success = False
457462

458463
if not success:
459464
self._exit(1)
460465

461-
insecure_password = smtp_config.get('insecure_password')
466+
insecure_password = smtp_config['insecure_password']
462467
if insecure_password:
463468
print('The SMTP password is set in the config file (key "insecure_password")')
464469
elif smtp_have_password(smtp_hostname, smtp_username):
@@ -468,8 +473,8 @@ def check_smtp_login(self) -> None:
468473
else:
469474
smtp_set_password(smtp_hostname, smtp_username)
470475

471-
smtp_port = smtp_config.get('port')
472-
smtp_tls = smtp_config.get('starttls')
476+
smtp_port = smtp_config['port']
477+
smtp_tls = smtp_config['starttls']
473478

474479
mailer = SMTPMailer(smtp_username, smtp_hostname, smtp_port, smtp_tls, smtp_auth, insecure_password)
475480
print('Trying to log into the SMTP server...')
@@ -479,20 +484,20 @@ def check_smtp_login(self) -> None:
479484
self._exit(0)
480485

481486
def check_xmpp_login(self) -> None:
482-
xmpp_config = self.urlwatcher.config_storage.config['report']['xmpp']
487+
xmpp_config: ConfigReportXmpp = self.urlwatcher.config_storage.config['report']['xmpp']
483488

484489
success = True
485490

486491
if not xmpp_config['enabled']:
487492
print('Please enable XMPP reporting in the config first.')
488493
success = False
489494

490-
xmpp_sender = xmpp_config.get('sender')
495+
xmpp_sender = xmpp_config['sender']
491496
if not xmpp_sender:
492497
print('Please configure the XMPP sender in the config first.')
493498
success = False
494499

495-
if not xmpp_config.get('recipient'):
500+
if not xmpp_config['recipient']:
496501
print('Please configure the XMPP recipient in the config first.')
497502
success = False
498503

webchanges/handler.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from datetime import datetime
1414
from pathlib import Path
1515
from types import TracebackType
16-
from typing import Any, ContextManager, Dict, Iterable, List, Optional, Type, TYPE_CHECKING, Union
16+
from typing import ContextManager, Iterable, List, Optional, Type, TYPE_CHECKING, Union
1717

1818
from .filters import FilterBase
1919
from .jobs import NotModifiedError
@@ -22,13 +22,13 @@
2222
try:
2323
from zoneinfo import ZoneInfo
2424
except ImportError:
25-
from backports import zoneinfo as ZoneInfo # type: ignore[no-redef]
25+
from backports.zoneinfo import ZoneInfo # type: ignore[no-redef]
2626

2727
# https://stackoverflow.com/questions/39740632
2828
if TYPE_CHECKING:
2929
from .jobs import JobBase
3030
from .main import Urlwatch
31-
from .storage import CacheStorage
31+
from .storage import CacheStorage, Config
3232

3333
logger = logging.getLogger(__name__)
3434

@@ -196,7 +196,7 @@ def _generate_diff(self, tz: Optional[str] = None) -> str:
196196
if tz:
197197
tz_info = ZoneInfo(tz)
198198
else:
199-
tz_info = None
199+
tz_info = None # type: ignore[assignment]
200200
timestamp_old = (
201201
datetime.fromtimestamp(self.old_timestamp).astimezone(tz=tz_info).strftime('%a, %d %b %Y %H:%M:%S %z')
202202
)
@@ -295,7 +295,7 @@ def __init__(self, urlwatch_config: Urlwatch) -> None:
295295
"""
296296
self.start = time.perf_counter()
297297

298-
self.config: Dict[str, Any] = urlwatch_config.config_storage.config
298+
self.config: Config = urlwatch_config.config_storage.config
299299
self.job_states: List[JobState] = []
300300

301301
def _result(self, verb: str, job_state: JobState) -> None:
@@ -358,7 +358,7 @@ def get_filtered_job_states(self, job_states: List[JobState]) -> Iterable[JobSta
358358
for job_state in job_states:
359359
if (
360360
not any(
361-
job_state.verb == verb and not self.config['display'][verb]
361+
job_state.verb == verb and not self.config['display'][verb] # type: ignore[misc]
362362
for verb in ('unchanged', 'new', 'error')
363363
)
364364
and job_state.verb != 'changed,no_report'

webchanges/jobs.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
# https://stackoverflow.com/questions/39740632
3333
if TYPE_CHECKING:
3434
from .handler import JobState
35+
from .storage import Config
3536

3637
# required to suppress warnings with 'ssl_no_verify: true'
3738
requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # type: ignore[no-untyped-call]
@@ -353,7 +354,7 @@ def _set_defaults(self, defaults: Optional[Dict[str, Any]]) -> None:
353354
if hasattr(self, key) and subkey not in getattr(self, key):
354355
getattr(self, key)[subkey] = subvalue
355356

356-
def with_defaults(self, config: Dict[str, Dict[str, Any]]) -> 'JobBase':
357+
def with_defaults(self, config: Config) -> 'JobBase':
357358
"""Obtain a Job object that also contains defaults from the configuration.
358359
359360
:param config: The configuration as a dict.
@@ -362,7 +363,7 @@ def with_defaults(self, config: Dict[str, Dict[str, Any]]) -> 'JobBase':
362363
job_with_defaults = copy.deepcopy(self)
363364
cfg = config.get('job_defaults')
364365
if isinstance(cfg, dict):
365-
job_with_defaults._set_defaults(cfg.get(self.__kind__))
366+
job_with_defaults._set_defaults(cfg.get(self.__kind__)) # type: ignore[arg-type]
366367
job_with_defaults._set_defaults(cfg.get('all'))
367368
return job_with_defaults
368369

webchanges/mailer.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import subprocess
77
from email import policy
88
from email.message import EmailMessage
9+
from pathlib import Path
910
from typing import Optional, Union
1011

1112
try:
@@ -109,7 +110,7 @@ def send(self, msg: Optional[EmailMessage]) -> None:
109110
class SendmailMailer(Mailer):
110111
"""The Mailer class to use sendmail executable."""
111112

112-
def __init__(self, sendmail_path: str) -> None:
113+
def __init__(self, sendmail_path: Union[str, Path]) -> None:
113114
self.sendmail_path = sendmail_path
114115

115116
def send(self, msg: Union[EmailMessage]) -> None:

0 commit comments

Comments
 (0)