diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a011f1907..b2a0c29e3c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout diff --git a/Jenkinsfile b/Jenkinsfile index 3f73ee4538..bc6acb1217 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ def pys = [ [name: 'Python 3.13', docker: '3.13-bookworm', tox:'py313', main: true], [name: 'Python 3.12', docker: '3.12-bookworm', tox:'py312', main: false], - [name: 'Python 3.8', docker: '3.8-bookworm', tox:'py38', main: false], + [name: 'Python 3.9', docker: '3.9-bookworm', tox:'py39', main: false], ] properties([ @@ -76,12 +76,6 @@ parallel modern: { windowsBuild('3.13', 'dosage.exe') } }, - legacy: { - stage('Legacy Windows binary') { - // Still compatible with Windows 7 - windowsBuild('3.8', 'dosage-legacy.exe') - } - }, report: { stage('Allure report') { processAllure() diff --git a/README.md b/README.md index 3d6959eda9..9c1ac8382a 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ are old enough to view them. ### Dependencies Since dosage is written in [Python](http://www.python.org/), a Python -installation is required: Dosage needs at least Python 3.8. Dosage requires +installation is required: Dosage needs at least Python 3.9. Dosage requires some Python modules from PyPI, so installation with `pip` is recommended. ### Optional dependencies diff --git a/dosagelib/cmd.py b/dosagelib/cmd.py index d599e019b0..17fd845ccd 100644 --- a/dosagelib/cmd.py +++ b/dosagelib/cmd.py @@ -156,7 +156,7 @@ def display_version(verbose: bool) -> None: attrs = {'version': version, 'app': AppName, 'url': url, 'currentversion': __version__} print(text % attrs) - except (IOError, KeyError) as err: + except (OSError, KeyError) as err: print(f'An error occured while checking for an update of {AppName}: {err!r}') diff --git a/dosagelib/comic.py b/dosagelib/comic.py index 3b2d7d0b35..cfdacc93bb 100644 --- a/dosagelib/comic.py +++ b/dosagelib/comic.py @@ -9,8 +9,8 @@ import glob import logging import os +from collections.abc import Iterator from datetime import datetime -from typing import Iterator from rich import filesize @@ -82,7 +82,7 @@ def connect(self, lastchange=None): subtype = None if maintype != 'image' and content_type not in ( 'application/octet-stream', 'application/x-shockwave-flash'): - raise IOError('content type %r is not an image at %s' % ( + raise OSError('content type {!r} is not an image at {}'.format( content_type, self.url)) # Always use mime type for file extension if it is sane. if maintype == 'image': diff --git a/dosagelib/director.py b/dosagelib/director.py index 6fc69d29da..bcc6078ad4 100644 --- a/dosagelib/director.py +++ b/dosagelib/director.py @@ -8,8 +8,8 @@ import os import re import threading +from collections.abc import Collection from queue import Empty, Queue -from typing import Collection, Dict from urllib.parse import urlparse from . import events @@ -45,7 +45,7 @@ def clear(self): # ensure threads download only from one host at a time -host_locks: Dict[str, threading.Lock] = {} +host_locks: dict[str, threading.Lock] = {} def get_hostname(url): @@ -67,7 +67,7 @@ class ComicGetter(threading.Thread): def __init__(self, options, jobs) -> None: """Store options.""" - super(ComicGetter, self).__init__() + super().__init__() self.options = options self.jobs = jobs self.origname = self.name diff --git a/dosagelib/http.py b/dosagelib/http.py index ad67703bdf..cb0d08b4dc 100644 --- a/dosagelib/http.py +++ b/dosagelib/http.py @@ -92,7 +92,7 @@ def check_robotstxt(url: str, session: Session) -> None: roboturl = _get_roboturl(url) rp = _get_robotstxt_parser(roboturl, session) if not rp.can_fetch(configuration.App, str(url)): - raise IOError("%s is disallowed by %s" % (url, roboturl)) + raise OSError(f"{url} is disallowed by {roboturl}") def _get_roboturl(url: str) -> str: @@ -101,7 +101,7 @@ def _get_roboturl(url: str) -> str: return parse.urlunsplit((pu.scheme, pu.netloc, "/robots.txt", None, None)) -@functools.lru_cache() +@functools.lru_cache def _get_robotstxt_parser(url, session: Session) -> robotparser.RobotFileParser: """Get a RobotFileParser for the given robots.txt URL.""" rp = robotparser.RobotFileParser() diff --git a/dosagelib/plugins/a.py b/dosagelib/plugins/a.py index c4724c1ea4..72c899b083 100644 --- a/dosagelib/plugins/a.py +++ b/dosagelib/plugins/a.py @@ -77,7 +77,7 @@ def getPrevUrl(self, url, data): # Fix broken navigation links if url == self.stripUrl % 'lg06': return self.stripUrl % 'lg05' - return super(AdventuresOfFifne, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) class AfterStrife(WordPressNavi): diff --git a/dosagelib/plugins/b.py b/dosagelib/plugins/b.py index 7f26b4e2a7..c36b35ddab 100644 --- a/dosagelib/plugins/b.py +++ b/dosagelib/plugins/b.py @@ -184,7 +184,7 @@ def namer(self, imageUrl, pageUrl): if filename == 'image.jpg': [year, month] = imageUrl.rsplit('/', 3)[-3:-1] pageNr = int(pageUrl.rsplit('/', 1)[-2].rsplit('-', 1)[-1]) - filename = '{0}-{1}-Vol2-pg{2}.jpg'.format(year, month, pageNr) + filename = f'{year}-{month}-Vol2-pg{pageNr}.jpg' elif filename == '27637.jpg': filename = 'BB_Vol2_Cover.jpg' return filename diff --git a/dosagelib/plugins/c.py b/dosagelib/plugins/c.py index 5d830ed35a..91078a7391 100644 --- a/dosagelib/plugins/c.py +++ b/dosagelib/plugins/c.py @@ -4,7 +4,6 @@ # SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher # SPDX-FileCopyrightText: © 2019 Daniel Ring from re import compile, escape -from typing import List from .. import util from ..helpers import bounceStarter, indirectStarter, joinPathPartsNamer @@ -137,7 +136,7 @@ class CatenaManor(ParserScraper): imageSearch = '//img[d:class("comicthumbnail")]' multipleImagesPerStrip = True endOfLife = True - strips: List[str] = [] + strips: list[str] = [] def starter(self): # Retrieve archive links and select valid range diff --git a/dosagelib/plugins/comicfury.py b/dosagelib/plugins/comicfury.py index 823ee58467..86a67927cf 100644 --- a/dosagelib/plugins/comicfury.py +++ b/dosagelib/plugins/comicfury.py @@ -25,7 +25,7 @@ class ComicFury(ParserScraper): XPATH_LINK % ('comicnavlink', 'Previous'), XPATH_IMG % ('Previous'), # Art, ConsolersDLC, etc. - u'//nav//a[contains(text(), "\u2039")]', + '//nav//a[contains(text(), "\u2039")]', # LatchkeyKingdom '//a[d:class("navi") and img[contains(@src, "Previous")]]', # KATRAN @@ -43,7 +43,7 @@ class ComicFury(ParserScraper): XPATH_LINK % ('comicnavlink', 'Next'), XPATH_IMG % ('Next'), # Art, ConsolersDLC, etc. - u'//nav//a[contains(text(), "\u203A")]', + '//nav//a[contains(text(), "\u203A")]', # LatchkeyKingdom '//a[d:class("navi") and img[contains(@src, "Next")]]', # RedSpot, KATRAN diff --git a/dosagelib/plugins/common.py b/dosagelib/plugins/common.py index c14a5b7fb2..fa8543bcb6 100644 --- a/dosagelib/plugins/common.py +++ b/dosagelib/plugins/common.py @@ -1,9 +1,11 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2022 Tobias Gruetzmacher -# Copyright (C) 2019-2020 Daniel Ring -from typing import Sequence, Union +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Daniel Ring +from __future__ import annotations + +from collections.abc import Sequence from ..scraper import ParserScraper @@ -22,7 +24,7 @@ class ComicControlScraper(ParserScraper): - imageSearch: Union[Sequence[str], str] = '//img[@id="cc-comic"]' + imageSearch: Sequence[str] | str = '//img[@id="cc-comic"]' prevSearch = '//a[@rel="prev"]' nextSearch = '//a[@rel="next"]' latestSearch = '//a[@rel="last"]' diff --git a/dosagelib/plugins/creators.py b/dosagelib/plugins/creators.py index 250b390b8c..627b1cd1ec 100644 --- a/dosagelib/plugins/creators.py +++ b/dosagelib/plugins/creators.py @@ -1,9 +1,9 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2022 Tobias Gruetzmacher -from ..scraper import ParserScraper +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher from ..helpers import indirectStarter +from ..scraper import ParserScraper class Creators(ParserScraper): @@ -13,7 +13,7 @@ class Creators(ParserScraper): starter = indirectStarter def __init__(self, name, path, lang=None): - super(Creators, self).__init__('Creators/' + name) + super().__init__('Creators/' + name) self.url = 'https://www.creators.com/features/' + path if lang: self.lang = lang diff --git a/dosagelib/plugins/d.py b/dosagelib/plugins/d.py index f0f55e38c6..7050b72f58 100644 --- a/dosagelib/plugins/d.py +++ b/dosagelib/plugins/d.py @@ -150,7 +150,7 @@ class DerTodUndDasMaedchen(_ParserScraper): stripUrl = url + '?bild=%s.jpg' firstStripUrl = stripUrl % '00_01_01' imageSearch = '//img[contains(@src, "images/tod/teil2")]' - prevSearch = u'//a[text()="zur\u00FCck"]' + prevSearch = '//a[text()="zur\u00FCck"]' help = 'Index format: nn_nn_nn' lang = 'de' diff --git a/dosagelib/plugins/dmfa.py b/dosagelib/plugins/dmfa.py index e8732fc314..8d9fb979e4 100644 --- a/dosagelib/plugins/dmfa.py +++ b/dosagelib/plugins/dmfa.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2019-2022 Tobias Gruetzmacher -# Copyright (C) 2019-2020 Daniel Ring +# SPDX-FileCopyrightText: © 2019 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Daniel Ring from ..helpers import bounceStarter from ..scraper import ParserScraper @@ -15,15 +15,15 @@ class DMFA(ParserScraper): def __init__(self, name, first, last=None): if name == 'DMFA': - super(DMFA, self).__init__(name) + super().__init__(name) else: - super(DMFA, self).__init__('DMFA/' + name) + super().__init__('DMFA/' + name) self.firstStripUrl = self.stripUrl % first if last: self.url = self.stripUrl % last - self.starter = super(DMFA, self).starter + self.starter = super().starter self.endOfLife = True @classmethod diff --git a/dosagelib/plugins/keenspot.py b/dosagelib/plugins/keenspot.py index ea00485c91..0e3fdc613b 100644 --- a/dosagelib/plugins/keenspot.py +++ b/dosagelib/plugins/keenspot.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2022 Tobias Gruetzmacher -# Copyright (C) 2019-2020 Daniel Ring +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Daniel Ring from ..scraper import ParserScraper @@ -37,7 +37,7 @@ class KeenSpot(ParserScraper): help = 'Index format: yyyymmdd' def __init__(self, name, sub, last=None, adult=False, path='d/%s.html'): - super(KeenSpot, self).__init__('KeenSpot/' + name) + super().__init__('KeenSpot/' + name) self.url = 'http://%s.keenspot.com/' % sub self.stripUrl = self.url + path diff --git a/dosagelib/plugins/kemonocafe.py b/dosagelib/plugins/kemonocafe.py index 22692d85e0..66796b0017 100644 --- a/dosagelib/plugins/kemonocafe.py +++ b/dosagelib/plugins/kemonocafe.py @@ -9,7 +9,7 @@ class KemonoCafe(ParserScraper): prevSearch = '//a[contains(@class, "comic-nav-previous")]' def __init__(self, name, sub, first, last=None, adult=False): - super(KemonoCafe, self).__init__('KemonoCafe/' + name) + super().__init__('KemonoCafe/' + name) self.url = 'https://%s.kemono.cafe/' % sub self.stripUrl = self.url + 'comic/%s/' diff --git a/dosagelib/plugins/krisstraub.py b/dosagelib/plugins/krisstraub.py index 49efaf36e4..c165f9bb90 100644 --- a/dosagelib/plugins/krisstraub.py +++ b/dosagelib/plugins/krisstraub.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2021 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2021 Tobias Gruetzmacher from .common import WordPressScraper @@ -10,7 +10,7 @@ class KrisStraub(WordPressScraper): def __init__(self, name, firstDate): super().__init__(name) - self.url = 'https://{}.krisstraub.com/'.format(name.lower()) + self.url = f'https://{name.lower()}.krisstraub.com/' self.stripUrl = self.url + '%s.shtml' self.firstStripUrl = self.stripUrl % firstDate diff --git a/dosagelib/plugins/l.py b/dosagelib/plugins/l.py index 88c4b5d035..8660db70ca 100644 --- a/dosagelib/plugins/l.py +++ b/dosagelib/plugins/l.py @@ -113,14 +113,14 @@ def getPrevUrl(self, url, data): page = url.rstrip('/').rsplit('/', 1)[-1] if page in self.nav: return self.stripUrl % self.nav[page] - return super(LifeAsRendered, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) def fetchText(self, url, data, textSearch, optional): # Save final summary text if url == self.firstStripUrl: url = self.stripUrl % 'the-end' data = self.getPage(url) - return super(LifeAsRendered, self).fetchText(url, data, textSearch, optional) + return super().fetchText(url, data, textSearch, optional) return None @@ -165,7 +165,7 @@ def getPrevUrl(self, url, data): page = url.rsplit('=', 1)[1] if page in self.nav: return self.stripUrl % self.nav[page] - return super(LittleTales, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) class LoadingArtist(_ParserScraper): diff --git a/dosagelib/plugins/n.py b/dosagelib/plugins/n.py index 7356fc7711..a8d7cbe370 100644 --- a/dosagelib/plugins/n.py +++ b/dosagelib/plugins/n.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2021 Tobias Gruetzmacher -# Copyright (C) 2019-2020 Daniel Ring +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Daniel Ring from re import compile, escape from ..helpers import bounceStarter, indirectStarter, joinPathPartsNamer @@ -77,7 +77,7 @@ def getPrevUrl(self, url, data): # Add navigation link between comic and graphic novel if url == self.stripUrl % 'nh2/20070201': return self.stripUrl % 'nh1/20061208' - return super(Newshounds, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) class NewWorld(WordPressScraper): diff --git a/dosagelib/plugins/namirdeiter.py b/dosagelib/plugins/namirdeiter.py index f40ff203d8..d8e4158c2f 100644 --- a/dosagelib/plugins/namirdeiter.py +++ b/dosagelib/plugins/namirdeiter.py @@ -12,9 +12,9 @@ class NamirDeiter(ParserScraper): def __init__(self, name, baseUrl, first=None, last=None): if name == 'NamirDeiter': - super(NamirDeiter, self).__init__(name) + super().__init__(name) else: - super(NamirDeiter, self).__init__('NamirDeiter/' + name) + super().__init__('NamirDeiter/' + name) self.url = 'https://' + baseUrl + '/' self.stripUrl = self.url + 'comics/index.php?date=%s' diff --git a/dosagelib/plugins/old.py b/dosagelib/plugins/old.py index 15b120f782..4ffa4bf7a5 100644 --- a/dosagelib/plugins/old.py +++ b/dosagelib/plugins/old.py @@ -22,7 +22,7 @@ class Removed(Scraper): } def __init__(self, name, reason='del'): - super(Removed, self).__init__(name) + super().__init__(name) self.reason = reason def getDisabledReasons(self): @@ -1697,7 +1697,7 @@ def counter(cls): return cls.count def __init__(self, name, newname): - super(Renamed, self).__init__(name) + super().__init__(name) self.newname = newname self.i = self.counter() diff --git a/dosagelib/plugins/p.py b/dosagelib/plugins/p.py index e88566ccca..7f045ef345 100644 --- a/dosagelib/plugins/p.py +++ b/dosagelib/plugins/p.py @@ -158,7 +158,7 @@ class PHDComics(ParserScraper): # Ugly hack :( def _parse_page(self, data): data = self.BROKEN_COMMENT_END.sub('-->', data) - return super(PHDComics, self)._parse_page(data) + return super()._parse_page(data) def shouldSkipUrl(self, url, data): """Skip pages without images.""" diff --git a/dosagelib/plugins/petitesymphony.py b/dosagelib/plugins/petitesymphony.py index d5a0052f3a..48a866bf7f 100644 --- a/dosagelib/plugins/petitesymphony.py +++ b/dosagelib/plugins/petitesymphony.py @@ -3,7 +3,7 @@ # Copyright (C) 2012-2014 Bastian Kleineidam # Copyright (C) 2015-2020 Tobias Gruetzmacher # Copyright (C) 2019-2020 Daniel Ring -from .common import WordPressScraper, WordPressNavi +from .common import WordPressNavi, WordPressScraper class PetiteSymphony(WordPressNavi): @@ -11,7 +11,7 @@ class PetiteSymphony(WordPressNavi): help = 'Index format: named number' def __init__(self, name): - super(PetiteSymphony, self).__init__('PetiteSymphony/' + + super().__init__('PetiteSymphony/' + name.capitalize()) self.url = 'http://%s.petitesymphony.com/' % name self.stripUrl = self.url + 'comic/%s' @@ -26,7 +26,7 @@ def getmodules(cls): class ComicsBreak(WordPressScraper): def __init__(self, name, archive=None, adult=False): - super(ComicsBreak, self).__init__('ComicsBreak/' + name) + super().__init__('ComicsBreak/' + name) self.url = 'http://%s.comicsbreak.com/' % name.lower() if archive: self.url = 'https://web.archive.org/web/{}/{}'.format( @@ -45,7 +45,7 @@ def namer(self, imageUrl, pageUrl): filename = filename.replace('p57', 'p56') return filename else: - return super(ComicsBreak, self).namer(imageUrl, pageUrl) + return super().namer(imageUrl, pageUrl) @classmethod def getmodules(cls): diff --git a/dosagelib/plugins/projectfuture.py b/dosagelib/plugins/projectfuture.py index eb385adc6f..c5303be891 100644 --- a/dosagelib/plugins/projectfuture.py +++ b/dosagelib/plugins/projectfuture.py @@ -10,9 +10,9 @@ class ProjectFuture(ParserScraper): def __init__(self, name, comic, first, last=None): if name == 'ProjectFuture': - super(ProjectFuture, self).__init__(name) + super().__init__(name) else: - super(ProjectFuture, self).__init__('ProjectFuture/' + name) + super().__init__('ProjectFuture/' + name) self.url = 'http://www.projectfuturecomic.com/' + comic + '.php' self.stripUrl = self.url + '?strip=%s' diff --git a/dosagelib/plugins/t.py b/dosagelib/plugins/t.py index a068e22784..81ba17f05d 100644 --- a/dosagelib/plugins/t.py +++ b/dosagelib/plugins/t.py @@ -227,7 +227,7 @@ class TheWhiteboard(_ParserScraper): def _parse_page(self, data): # Ugly hack to fix broken HTML data = self.BROKEN_PAGE_MIDDLE.sub('<', data) - return super(TheWhiteboard, self)._parse_page(data) + return super()._parse_page(data) def imageUrlModifier(self, url, data): return self.url + url diff --git a/dosagelib/plugins/w.py b/dosagelib/plugins/w.py index daa0b6f6d0..7110576064 100644 --- a/dosagelib/plugins/w.py +++ b/dosagelib/plugins/w.py @@ -77,7 +77,7 @@ def getPrevUrl(self, url, data): return self.stripUrl % (self.chapters[1], 'latest') if url == self.stripUrl % (self.chapters[1], '0'): return self.stripUrl % (self.chapters[0], 'latest') - return super(WereIWolf, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) def getIndexStripUrl(self, index): # Get comic strip URL from index diff --git a/dosagelib/plugins/webcomicfactory.py b/dosagelib/plugins/webcomicfactory.py index 24da4039e5..5b7a434fd2 100644 --- a/dosagelib/plugins/webcomicfactory.py +++ b/dosagelib/plugins/webcomicfactory.py @@ -1,17 +1,17 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2020 Tobias Gruetzmacher -# Copyright (C) 2019-2020 Daniel Ring -from .common import WordPressScraper +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Daniel Ring from ..helpers import indirectStarter +from .common import WordPressScraper class WebcomicFactory(WordPressScraper): starter = indirectStarter def __init__(self, name, url): - super(WebcomicFactory, self).__init__(name) + super().__init__(name) self.url = url self.firstStripUrl = url diff --git a/dosagelib/plugins/wlpcomics.py b/dosagelib/plugins/wlpcomics.py index 9652232516..a4da2e8e76 100644 --- a/dosagelib/plugins/wlpcomics.py +++ b/dosagelib/plugins/wlpcomics.py @@ -1,12 +1,12 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2022 Tobias Gruetzmacher -# Copyright (C) 2019-2020 Daniel Ring +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Daniel Ring import re -from ..scraper import ParserScraper from ..helpers import bounceStarter +from ..scraper import ParserScraper class WLPComics(ParserScraper): @@ -71,7 +71,7 @@ def getPrevUrl(self, url, data): # Fix loop in site navigation if url == self.stripUrl % '194': return self.stripUrl % '193' - return super(PeterIsTheWolfAdult, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) class PeterIsTheWolfGeneral(WLPComics): @@ -91,7 +91,7 @@ def getPrevUrl(self, url, data): return self.stripUrl % '228' if url == self.stripUrl % '153': return self.stripUrl % '152' - return super(PeterIsTheWolfGeneral, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) class Stellar(WLPComics): diff --git a/dosagelib/plugins/wrongside.py b/dosagelib/plugins/wrongside.py index ce75d38bfb..b9340ef299 100644 --- a/dosagelib/plugins/wrongside.py +++ b/dosagelib/plugins/wrongside.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: © 2019 Tobias Gruetzmacher # SPDX-FileCopyrightText: © 2019 Daniel Ring -from ..scraper import ParserScraper from ..helpers import indirectStarter +from ..scraper import ParserScraper class Wrongside(ParserScraper): @@ -25,7 +25,7 @@ def starter(self): def getPrevUrl(self, url, data): if self.match(data, self.prevSearch) == [] and len(self.archive) > 0: return self.archive.pop() - return super(Wrongside, self).getPrevUrl(url, data) + return super().getPrevUrl(url, data) def namer(self, imageUrl, pageUrl): page = self.getPage(pageUrl) diff --git a/dosagelib/plugins/wumo.py b/dosagelib/plugins/wumo.py index 832994a06b..0c47fbbf84 100644 --- a/dosagelib/plugins/wumo.py +++ b/dosagelib/plugins/wumo.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2022 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher from ..scraper import ParserScraper @@ -10,7 +10,7 @@ class KindOfNormal(ParserScraper): prevSearch = '//a[@class="prev"]' def __init__(self, name, url): - super(KindOfNormal, self).__init__(name) + super().__init__(name) self.url = 'http://wumo.com/' + url @classmethod diff --git a/dosagelib/scraper.py b/dosagelib/scraper.py index 5fd98071be..348113e7a9 100644 --- a/dosagelib/scraper.py +++ b/dosagelib/scraper.py @@ -10,7 +10,7 @@ import pathlib import re import warnings -from typing import Collection, Dict, List, Optional, Pattern, Sequence, Union +from collections.abc import Collection, Sequence from urllib.parse import urljoin import lxml @@ -55,14 +55,14 @@ class Scraper: implementation.''' # The URL for the comic strip - url: Optional[str] = None + url: str | None = None # A string that is interpolated with the strip index to yield the URL for a # particular strip. - stripUrl: Optional[str] = None + stripUrl: str | None = None # Stop search for previous URLs at this URL - firstStripUrl: Optional[str] = None + firstStripUrl: str | None = None # if more than one image per URL is expected multipleImagesPerStrip: bool = False @@ -78,15 +78,15 @@ class Scraper: # an expression that will locate the URL for the previous strip in a page # this can also be a list or tuple - prevSearch: Optional[Union[Sequence[Union[str, Pattern]], str, Pattern]] = None + prevSearch: Sequence[str | re.Pattern] | str | re.Pattern | None = None # an expression that will locate the strip image URLs strip in a page # this can also be a list or tuple - imageSearch: Optional[Union[Sequence[Union[str, Pattern]], str, Pattern]] = None + imageSearch: Sequence[str | re.Pattern] | str | re.Pattern | None = None # an expression to store a text together with the image # sometimes comic strips have additional text info for each comic - textSearch: Optional[Union[Sequence[Union[str, Pattern]], str, Pattern]] = None + textSearch: Sequence[str | re.Pattern] | str | re.Pattern | None = None # Is the additional text required or optional? When it is required (the # default), you see an error message whenever a comic page is encountered @@ -184,8 +184,7 @@ def getStrips(self, maxstrips=None): msg += u" (including adult content)" logger.info(msg) for url in urls: - for strip in self.getStripsFor(url, maxstrips): - yield strip + yield from self.getStripsFor(url, maxstrips) def getStripsFor(self, url, maxstrips): """Get comic strips for an URL. If maxstrips is a positive number, stop after @@ -546,7 +545,7 @@ class Cache: slow. """ def __init__(self) -> None: - self.data: List[Scraper] = [] + self.data: list[Scraper] = [] self.userdirs: set[pathlib.Path] = set() def find(self, comic: str) -> Scraper: @@ -627,7 +626,7 @@ def all(self, include_removed=False) -> list[Scraper]: def validate(self) -> None: """Check for duplicate scraper names.""" - d: Dict[str, Scraper] = {} + d: dict[str, Scraper] = {} for scraper in self.data: name = scraper.name.lower() if name in d: diff --git a/dosagelib/updater.py b/dosagelib/updater.py index cc7b84b8be..36aa6ea555 100644 --- a/dosagelib/updater.py +++ b/dosagelib/updater.py @@ -2,13 +2,15 @@ # SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs # SPDX-FileCopyrightText: © 2012 Bastian Kleineidam # SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher +from __future__ import annotations + import os import re -from typing import Any, Dict, Optional, Tuple +from typing import Any import dosagelib -from . import http +from . import http UPDATE_URL = 'https://api.github.com/repos/webcomics/dosage/releases/latest' EXTPRIO = { @@ -18,7 +20,7 @@ } -def check_update() -> Optional[Tuple[str, Optional[str]]]: +def check_update() -> tuple[str, str | None] | None: """Return the following values: throws exception - online version could not be determined None - user has newest version @@ -36,11 +38,11 @@ def check_update() -> Optional[Tuple[str, Optional[str]]]: return (version, None) -def asset_key(asset: Dict[str, Any]) -> int: +def asset_key(asset: dict[str, Any]) -> int: return EXTPRIO.get(os.path.splitext(asset['browser_download_url'])[1], 99) -def get_online_version() -> Tuple[str, Optional[str]]: +def get_online_version() -> tuple[str, str | None]: """Download update info and parse it.""" response = http.default_session.get(UPDATE_URL) response.raise_for_status() @@ -57,7 +59,7 @@ def get_online_version() -> Tuple[str, Optional[str]]: return version, url -def version_nums(ver: str) -> Tuple[int, ...]: +def version_nums(ver: str) -> tuple[int, ...]: """Extract all numeric "sub-parts" of a version string. Not very exact, but works for our use case.""" return tuple(int(s) for s in re.split(r'\D+', ver + '0')) diff --git a/dosagelib/util.py b/dosagelib/util.py index 1a7776b59d..b2e92af3ba 100644 --- a/dosagelib/util.py +++ b/dosagelib/util.py @@ -227,7 +227,7 @@ def check_content_size(url, headers, max_content_bytes): if 'content-length' in headers: size = int(headers['content-length']) if size > max_content_bytes: - raise IOError( + raise OSError( 'URL content of %s with %d bytes exceeds %d bytes.' % (url, size, max_content_bytes)) diff --git a/pyproject.toml b/pyproject.toml index b284dd3940..397c3a68ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,62 +1,61 @@ [build-system] -requires = ["setuptools>=66.0", "setuptools_scm>=7.1"] build-backend = "setuptools.build_meta" +requires = [ "setuptools>=77", "setuptools-scm>=8" ] [project] name = "dosage" description = "a comic strip downloader and archiver" readme = "README.md" -maintainers = [{name = "Tobias Gruetzmacher", email = "tobias-dosage@23.gs"}] -license = {text = "MIT License"} +keywords = [ "archiver", "comic", "crawler", "downloader", "webcomic" ] +license = "MIT" +license-files = [ "COPYING" ] +maintainers = [ { name = "Tobias Gruetzmacher", email = "tobias-dosage@23.gs" } ] +requires-python = ">=3.9" classifiers = [ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Multimedia :: Graphics", + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: End Users/Desktop", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Multimedia :: Graphics", ] -keywords = ["comic", "webcomic", "downloader", "archiver", "crawler"] -requires-python = ">=3.8" +dynamic = [ "version" ] + dependencies = [ - "imagesize", - "lxml>=4.0.0", - "platformdirs", - "requests>=2.0", - "rich", - "importlib_resources>=5.0.0;python_version<'3.9'", + "imagesize", + "importlib_resources>=5.0.0;python_version<'3.9'", + "lxml>=4", + "platformdirs", + "requests>=2", + "rich", ] -dynamic = ["version"] - -[project.urls] -Homepage = "https://dosage.rocks" -Code = "https://github.com/webcomics/dosage" -"Issue tracker" = "https://github.com/webcomics/dosage/issues" [project.optional-dependencies] -bash = ["argcomplete"] +bash = [ "argcomplete" ] compression = [ - "brotli; platform_python_implementation == 'CPython'", - "brotlicffi; platform_python_implementation != 'CPython'", - "zstandard", + "brotli; platform_python_implementation == 'CPython'", + "brotlicffi; platform_python_implementation != 'CPython'", + "zstandard", ] dev = [ - "allure-pytest", - "pytest-cov", - "pytest-xdist", - "responses", + "allure-pytest", + "pytest-cov", + "pytest-xdist", + "responses", ] +[project.urls] +Homepage = "https://dosage.rocks" +Code = "https://github.com/webcomics/dosage" +"Issue tracker" = "https://github.com/webcomics/dosage/issues" + [project.scripts] dosage = "dosagelib.cmd:main" @@ -64,39 +63,35 @@ dosage = "dosagelib.cmd:main" hook-dirs = "dosagelib.__pyinstaller:get_hook_dirs" [tool.setuptools] -platforms = ["Any"] -license-files = ["COPYING"] +platforms = [ "Any" ] [tool.setuptools.packages] -find = {namespaces = false} - -[tool.distutils.bdist_wheel] -universal = true +find = { namespaces = false } [tool.setuptools_scm] [tool.flake8] max_line_length = 100 ignore = [ - 'E127', 'E128', # hanging instead of visual indent - 'W504' # line breaks after binary operator + 'E127', 'E128', # hanging instead of visual indent + 'W504' # line breaks after binary operator ] noqa-require-code = true no-accept-encodings = true -min-version = "3.8" +min-version = "3.9" extend-exclude = [ - '.venv', - 'build', + '.venv', + 'build', ] [tool.isort] profile = "black" -src_paths = ["dosagelib", "scripts", "tests"] -known_third_party = ["responses"] +src_paths = [ "dosagelib", "scripts", "tests" ] +known_third_party = [ "responses" ] [tool.coverage.run] branch = true -source = ["dosagelib", "tests"] +source = [ "dosagelib", "tests" ] [tool.coverage.html] show_contexts = true diff --git a/scripts/arcamax.py b/scripts/arcamax.py index 08f8a4393d..69e75858fa 100755 --- a/scripts/arcamax.py +++ b/scripts/arcamax.py @@ -37,7 +37,7 @@ def collect_results(self): self.handle_url('http://www.arcamax.com/comics') def get_entry(self, name, entry): - return u"cls('%s', '%s')," % (name, entry) + return f"cls('{name}', '{entry}')," if __name__ == '__main__': diff --git a/scripts/create-cbz.py b/scripts/create-cbz.py index 938a315f5d..164562597f 100755 --- a/scripts/create-cbz.py +++ b/scripts/create-cbz.py @@ -1,21 +1,20 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2020 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher """ Creates a CBZ file in the comic directory. Uses an ordered symlink directory (see order-symlinks.py) if it exists, else the plain files are used. """ -import sys import os +import sys import zipfile from dosagelib.configuration import App - # known image file extensions ImageExts = ( ".jpg", @@ -35,13 +34,13 @@ def get_cbz_comment(): """Return a UTF-8 encoded comment no longer than 65535 bytes. At the moment this just returns the application name and version, since cbz readers do not seem to use the comment string anyway.""" - return (u"Created by %s" % App).encode('utf-8') + return (f"Created by {App}").encode() def create_cbz(directory): """Creates or updates a CBZ from files in the given comic directory.""" if not os.path.isdir(directory): - print("ERROR: Directory", directory, "not found.") + print(f"ERROR: Directory {directory} not found.") return base = os.path.basename(directory.rstrip(os.path.sep)) zipname = '%s.cbz' % base @@ -58,7 +57,7 @@ def create_cbz(directory): if is_image(fullname): myzip.write(fullname) myzip.comment = get_cbz_comment() - print("INFO: Created", zipname) + print(f"INFO: Created {zipname}") if __name__ == '__main__': @@ -66,4 +65,4 @@ def create_cbz(directory): for arg in sys.argv[1:]: create_cbz(arg) else: - print("Usage: %s ..." % os.path.basename(sys.argv[0])) + print(f"Usage: {os.path.basename(sys.argv[0])} ...") diff --git a/scripts/creators.py b/scripts/creators.py index 8b6a2347fc..efbe23da71 100755 --- a/scripts/creators.py +++ b/scripts/creators.py @@ -37,7 +37,7 @@ def collect_results(self): def get_entry(self, name, path): langopt = ", 'es'" if name.lower().endswith('spanish') else '' - return u"cls('%s', '%s'%s)," % (name, path, langopt) + return f"cls('{name}', '{path}'{langopt})," if __name__ == '__main__': diff --git a/scripts/gocomics.py b/scripts/gocomics.py index 9fe3b0ebe7..72411b8810 100755 --- a/scripts/gocomics.py +++ b/scripts/gocomics.py @@ -55,8 +55,8 @@ def collect_results(self) -> None: def get_entry(self, name: str, data: tuple[str, str]) -> str: url, lang = data - langopt = ", '%s'" % lang if lang else '' - return u"cls('%s', '%s'%s)," % (name, url, langopt) + langopt = f", '{lang}'" if lang else '' + return f"cls('{name}', '{url}'{langopt})," if __name__ == '__main__': diff --git a/scripts/keenspot.py b/scripts/keenspot.py index 80dc7a2c65..8525a62d74 100755 --- a/scripts/keenspot.py +++ b/scripts/keenspot.py @@ -11,8 +11,8 @@ from urllib import parse -from scriptutil import ComicListUpdater from dosagelib import http +from scriptutil import ComicListUpdater class KeenSpotUpdater(ComicListUpdater): @@ -50,8 +50,8 @@ def collect_results(self): http.check_robotstxt(comicurl + 'd/', self.session) else: http.check_robotstxt(comicurl, self.session) - except IOError as e: - print('[%s] INFO: robots.txt denied: %s' % (name, e)) + except OSError as e: + print(f'[{name}] INFO: robots.txt denied: {e}') continue self.add_comic(name, comicurl) @@ -62,7 +62,7 @@ def get_entry(self, name, url): extra = ', ' + self.extra[name] else: extra = '' - return u"cls('%s', '%s'%s)," % (name, sub, extra) + return f"cls('{name}', '{sub}'{extra})," if __name__ == '__main__': diff --git a/scripts/mklanguages.py b/scripts/mklanguages.py index cf8920fca7..6713ec83a7 100755 --- a/scripts/mklanguages.py +++ b/scripts/mklanguages.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2020 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher '''update languages.py from pycountry''' -import os import codecs +import os from dosagelib.scraper import scrapers @@ -35,7 +35,7 @@ def write_languages(f, langs): """Write language information.""" f.write("Languages = {%s" % os.linesep) for lang in sorted(langs): - f.write(" %r: %r,%s" % (lang, langs[lang], os.linesep)) + f.write(f" {lang!r}: {langs[lang]!r},{os.linesep}") f.write("}%s" % os.linesep) diff --git a/scripts/scriptutil.py b/scripts/scriptutil.py index c4091489fd..77bc66379c 100644 --- a/scripts/scriptutil.py +++ b/scripts/scriptutil.py @@ -43,7 +43,7 @@ def get_url(self, url: str, expand=True): if self.sleep > 0: time.sleep(self.sleep) return data - except IOError as msg: + except OSError as msg: print("ERROR:", msg, file=sys.stderr) raise @@ -121,7 +121,7 @@ def write_entry(self, fp, name, entry, min_comics, indent): dup = self.find_dups(name) fp.write(" " * indent) if dup is not None: - fp.write(u"# %s has a duplicate in %s\n" % (name, dup)) + fp.write(f"# {name} has a duplicate in {dup}\n") else: fp.write(self.get_entry( truncate_name(name), diff --git a/scripts/tapas.py b/scripts/tapas.py index 3f63bf79e6..51e51af124 100755 --- a/scripts/tapas.py +++ b/scripts/tapas.py @@ -28,7 +28,7 @@ def get_entry(self, name, url): shortName = name.replace(' ', '').replace('\'', '') titleNum = int(parse.parse_qs(parse.urlsplit(url).query)['title_no'][0]) url = url.rsplit('/', 1)[0].replace('/series/', '') - return u"cls('%s', '%s', %d)," % (shortName, url, titleNum) + return f"cls('{shortName}', '{url}', {titleNum})," if __name__ == '__main__': diff --git a/scripts/webcomicfactory.py b/scripts/webcomicfactory.py index 7214bc9d17..52b1670f85 100755 --- a/scripts/webcomicfactory.py +++ b/scripts/webcomicfactory.py @@ -17,8 +17,7 @@ def find_first(self, url): firstlinks = self.xpath(data, '//a[d:class("comic-nav-first")]') if not firstlinks: - print("INFO:", "No first link on »%s«, already first page?" % - (url)) + print("INFO:", "No first link on »{url}«, already first page?") return url return firstlinks[0].attrib['href'] @@ -38,7 +37,7 @@ def collect_results(self): self.add_comic(name, comicurl) def get_entry(self, name, url): - return (u"cls('%s',\n '%s')," % (name, url)) + return (f"cls('{name}',\n '{url}'),") if __name__ == '__main__': diff --git a/tests/httpmocks.py b/tests/httpmocks.py index 157b4cad36..608d6c8962 100644 --- a/tests/httpmocks.py +++ b/tests/httpmocks.py @@ -12,13 +12,13 @@ def _file(name): return os.path.join(os.path.dirname(__file__), 'responses', name) -@lru_cache() +@lru_cache def content(name, ext: str = 'html'): with gzip.open(_file(f'{name}.{ext}.gz'), 'r') as f: return f.read() -@lru_cache() +@lru_cache def _img(name): with open(_file(name + '.png'), 'rb') as f: return f.read() @@ -43,5 +43,5 @@ def jpeg(url, name='empty'): def xkcd(): page('https://xkcd.com/', 'xkcd-1899') for num in (302, 303, 1898, 1899): - page('https://xkcd.com/%i/' % num, 'xkcd-%i' % num) + page(f'https://xkcd.com/{num}/', f'xkcd-{num}') png(re.compile(r'https://imgs\.xkcd\.com/.*\.png')) diff --git a/tests/test_rss.py b/tests/test_rss.py index f3e3b6a2ed..2e4f3e9da9 100644 --- a/tests/test_rss.py +++ b/tests/test_rss.py @@ -1,10 +1,11 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2019 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2019 Tobias Gruetzmacher import time + from dosagelib.rss import parseFeed -class TestFeed(object): +class TestFeed: """ Tests for rss.py """ @@ -15,5 +16,5 @@ def test_parseFeed(self): xmlBlob = feed.getXML() - assert u'PlumedDotage - 4034.png'.encode() in xmlBlob - assert u'PachinkoParlor - 20190626.jpg'.encode() not in xmlBlob + assert b'PlumedDotage - 4034.png' in xmlBlob + assert b'PachinkoParlor - 20190626.jpg' not in xmlBlob diff --git a/tests/test_scraper.py b/tests/test_scraper.py index 8c0caaadda..9bfa861a5b 100644 --- a/tests/test_scraper.py +++ b/tests/test_scraper.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2013-2014 Bastian Kleineidam -# Copyright (C) 2015-2022 Tobias Gruetzmacher +# SPDX-FileCopyrightText: © 2013 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher from pathlib import Path import pytest @@ -8,13 +8,13 @@ from dosagelib.scraper import scrapers -class TestScraper(object): +class TestScraper: """Test scraper module functions.""" def test_get_scrapers(self): for scraperobj in scrapers.all(): scraperobj.indexes = ["bla"] - assert scraperobj.url, "missing url in %s" % scraperobj.name + assert scraperobj.url, f"missing url in {scraperobj.name}" def test_find_scrapers_single(self): assert scrapers.find("xkcd") diff --git a/tests/test_singletoninstance.py b/tests/test_singletoninstance.py index 4be7da6f5e..e77758a654 100644 --- a/tests/test_singletoninstance.py +++ b/tests/test_singletoninstance.py @@ -3,15 +3,16 @@ # License: PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 # Author: Sorin Sbarnea # Changes: changed logging and formatting -from dosagelib import singleton from multiprocessing import Process +from dosagelib import singleton + def f(flavor_id): return singleton.SingleInstance(flavor_id=flavor_id, exit_code=1) -class TestSingleton(object): +class TestSingleton: def test_1(self): # test in current process me = singleton.SingleInstance(flavor_id="test-1") diff --git a/tests/test_util.py b/tests/test_util.py index a520def7bf..1d7c207979 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,13 +1,15 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs -# Copyright (C) 2012-2014 Bastian Kleineidam -# Copyright (C) 2015-2020 Tobias Gruetzmacher -import pytest +# SPDX-FileCopyrightText: © 2004 Tristan Seligmann and Jonathan Jacobs +# SPDX-FileCopyrightText: © 2012 Bastian Kleineidam +# SPDX-FileCopyrightText: © 2015 Tobias Gruetzmacher import re -from dosagelib.util import normaliseURL, tagre, get_system_uid + +import pytest + +from dosagelib.util import get_system_uid, normaliseURL, tagre -class TestURL(object): +class TestURL: """ Tests for URL utility functions. """ @@ -15,10 +17,10 @@ class TestURL(object): def test_normalisation(self): # Test URL normalisation. assert (normaliseURL('http://example.com//bar/baz&baz') == - u'http://example.com/bar/baz&baz') + 'http://example.com/bar/baz&baz') -class TestRegex(object): +class TestRegex: ValuePrefix = '/bla/' @@ -41,14 +43,13 @@ def match_tag(self, matcher, tag, value, domatch=True): text = tag % value match = matcher.search(text) if domatch: - assert match, "%s should match %s" % (matcher.pattern, text) + assert match, f"{matcher.pattern!r} should match {text!r}" assert match.group(1) == value else: - assert not match, "%s should not match %s" % (matcher.pattern, - text) + assert not match, f"{matcher.pattern!r} should not match {text!r}" -class TestUid(object): +class TestUid: """ Tests for unique system IDs. """ diff --git a/tests/test_vote.py b/tests/test_vote.py index b5c1400f47..01f7dcc8d7 100644 --- a/tests/test_vote.py +++ b/tests/test_vote.py @@ -13,7 +13,7 @@ class ATestScraper(scraper.BasicScraper): pass -class TestVote(object): +class TestVote: @responses.activate def test_vote(self): responses.add(responses.POST, 'https://buildbox.23.gs/count/') diff --git a/tox.ini b/tox.ini index ae204fbba2..a3dc83b36e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,9 @@ [tox] min_version = 4.0 -envlist = py38, py39, py310, py311, py312, py313 +envlist = py39, py310, py311, py312, py313 [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311