diff --git a/.github/workflows/macos_crash_report.py b/.github/workflows/macos_crash_report.py index ab86a1abf0..d353779bea 100755 --- a/.github/workflows/macos_crash_report.py +++ b/.github/workflows/macos_crash_report.py @@ -5,10 +5,11 @@ import json import posixpath import sys from collections import namedtuple +from collections.abc import Mapping from datetime import datetime from enum import Enum from functools import cached_property -from typing import IO, List, Mapping, Optional +from typing import IO Frame = namedtuple('Frame', 'image_name image_base image_offset symbol symbol_offset') Register = namedtuple('Register', 'name value') @@ -269,7 +270,7 @@ class UserModeCrashReport(CrashReportBase): return int(self._parse_field('Triggered by Thread')) @cached_property - def frames(self) -> List[Frame]: + def frames(self) -> list[Frame]: result = [] if self._is_json: thread_index = self.faulting_thread @@ -304,7 +305,7 @@ class UserModeCrashReport(CrashReportBase): return result @cached_property - def registers(self) -> List[Register]: + def registers(self) -> list[Register]: result = [] if self._is_json: thread_index = self._data['faultingThread'] @@ -318,9 +319,8 @@ class UserModeCrashReport(CrashReportBase): if name == 'x': for j, reg_x in enumerate(value): result.append(Register(name=f'x{j}', value=reg_x['value'])) - else: - if isinstance(value, dict): - result.append(Register(name=name, value=value['value'])) + elif isinstance(value, dict): + result.append(Register(name=name, value=value['value'])) else: in_frames = False for line in self._data.split('\n'): @@ -353,14 +353,14 @@ class UserModeCrashReport(CrashReportBase): return self._parse_field('Exception Type') @cached_property - def exception_subtype(self) -> Optional[str]: + def exception_subtype(self) -> str | None: if self._is_json: return self._data['exception'].get('subtype') else: return self._parse_field('Exception Subtype') @cached_property - def application_specific_information(self) -> Optional[str]: + def application_specific_information(self) -> str | None: result = '' if self._is_json: asi = self._data.get('asi') diff --git a/setup/unix-ci.py b/setup/unix-ci.py index d99a3e82c3..9f0392cc01 100644 --- a/setup/unix-ci.py +++ b/setup/unix-ci.py @@ -23,7 +23,7 @@ def setenv(key, val): os.environ[key] = os.path.expandvars(val) -def download_with_retry(url: 'str | Request', count: int = 5) -> bytes: +def download_with_retry(url: str | Request, count: int = 5) -> bytes: for i in range(count): try: print('Downloading', getattr(url, 'full_url', url), flush=True) diff --git a/setup/win-ci.py b/setup/win-ci.py index 979f282d5d..f1ad59ff66 100644 --- a/setup/win-ci.py +++ b/setup/win-ci.py @@ -16,7 +16,7 @@ def printf(*args, **kw): sys.stdout.flush() -def download_with_retry(url: 'str | Request', count: int = 5) -> bytes: +def download_with_retry(url: str | Request, count: int = 5) -> bytes: for i in range(count): try: print('Downloading', getattr(url, 'full_url', url), flush=True) diff --git a/src/calibre/ai/github/backend.py b/src/calibre/ai/github/backend.py index de85d461e2..565df717b3 100644 --- a/src/calibre/ai/github/backend.py +++ b/src/calibre/ai/github/backend.py @@ -65,7 +65,7 @@ class Model(NamedTuple): publisher: str @classmethod - def from_dict(cls, x: dict[str, object]) -> 'Model': + def from_dict(cls, x: dict[str, object]) -> Model: mid = x['id'] caps = AICapabilities.none if 'embedding' in x['capabilities'] or 'embeddings' in x['supported_output_modalities']: diff --git a/src/calibre/ai/google/backend.py b/src/calibre/ai/google/backend.py index a183455690..e7de4189e3 100644 --- a/src/calibre/ai/google/backend.py +++ b/src/calibre/ai/google/backend.py @@ -139,7 +139,7 @@ class Model(NamedTuple): pricing: Pricing | None @classmethod - def from_dict(cls, x: dict[str, object]) -> 'Model': + def from_dict(cls, x: dict[str, object]) -> Model: caps = AICapabilities.text_to_text mid = x['name'] if 'embedContent' in x['supportedGenerationMethods']: @@ -183,7 +183,7 @@ def parse_models_list(entries: list[dict[str, Any]]) -> dict[str, Model]: @lru_cache(2) -def get_available_models() -> dict[str, 'Model']: +def get_available_models() -> dict[str, Model]: api_key = decoded_api_key() cache_loc = os.path.join(cache_dir(), 'ai', f'{GoogleAI.name}-models-v1.json') data = get_cached_resource(cache_loc, MODELS_URL, headers=(('X-goog-api-key', api_key),)) diff --git a/src/calibre/ai/lm_studio/__init__.py b/src/calibre/ai/lm_studio/__init__.py index f4d06af927..7bf71512b5 100644 --- a/src/calibre/ai/lm_studio/__init__.py +++ b/src/calibre/ai/lm_studio/__init__.py @@ -4,6 +4,7 @@ from calibre.customize import AIProviderPlugin + class LMStudioAI(AIProviderPlugin): DEFAULT_URL = 'http://localhost:1234' name = 'LMStudio' @@ -15,4 +16,4 @@ class LMStudioAI(AIProviderPlugin): @property def capabilities(self): from calibre.ai import AICapabilities - return AICapabilities.text_to_text \ No newline at end of file + return AICapabilities.text_to_text diff --git a/src/calibre/ai/lm_studio/backend.py b/src/calibre/ai/lm_studio/backend.py index 570d4ba47d..fa1649c0fa 100644 --- a/src/calibre/ai/lm_studio/backend.py +++ b/src/calibre/ai/lm_studio/backend.py @@ -31,7 +31,7 @@ class Model(NamedTuple): owner: str @classmethod - def from_dict(cls, x: dict[str, Any]) -> 'Model': + def from_dict(cls, x: dict[str, Any]) -> Model: return Model(id=x['id'], owner=x.get('owned_by', 'local')) diff --git a/src/calibre/ai/ollama/backend.py b/src/calibre/ai/ollama/backend.py index 028e7a6e0e..26b3b88225 100644 --- a/src/calibre/ai/ollama/backend.py +++ b/src/calibre/ai/ollama/backend.py @@ -44,7 +44,7 @@ class Model(NamedTuple): can_think: bool @classmethod - def from_dict(cls, x: dict[str, Any], details: dict[str, Any]) -> 'Model': + def from_dict(cls, x: dict[str, Any], details: dict[str, Any]) -> Model: d = x.get('details', {}) return Model( name=x['name'], id=x['model'], family=d.get('family', ''), families=d.get('families', ()), diff --git a/src/calibre/ai/open_router/backend.py b/src/calibre/ai/open_router/backend.py index 0ef4d72960..779982cf18 100644 --- a/src/calibre/ai/open_router/backend.py +++ b/src/calibre/ai/open_router/backend.py @@ -25,7 +25,7 @@ def pref(key: str, defval: Any = None) -> Any: @lru_cache(2) -def get_available_models() -> dict[str, 'Model']: +def get_available_models() -> dict[str, Model]: cache_loc = os.path.join(cache_dir(), 'ai', f'{OpenRouterAI.name}-models-v1.json') data = get_cached_resource(cache_loc, MODELS_URL) return parse_models_list(json.loads(data)) @@ -49,7 +49,7 @@ class Pricing(NamedTuple): input_cache_write: float = 0 # cost per cached input token write @classmethod - def from_dict(cls, x: dict[str, str]) -> 'Pricing': + def from_dict(cls, x: dict[str, str]) -> Pricing: return Pricing( input_token=float(x['prompt']), output_token=float(x['completion']), request=float(x.get('request', 0)), image=float(x.get('image', 0)), web_search=float(x.get('web_search', 0)), @@ -95,7 +95,7 @@ class Model(NamedTuple): return re.sub(r' \(free\)$', '', self.name.partition(':')[-1].strip()).strip() @classmethod - def from_dict(cls, x: dict[str, object]) -> 'Model': + def from_dict(cls, x: dict[str, object]) -> Model: arch = x['architecture'] capabilities = AICapabilities.none if 'text' in arch['input_modalities']: diff --git a/src/calibre/ai/open_router/config.py b/src/calibre/ai/open_router/config.py index f6d5c3523d..0108467c64 100644 --- a/src/calibre/ai/open_router/config.py +++ b/src/calibre/ai/open_router/config.py @@ -170,7 +170,7 @@ class ModelDetails(QTextBrowser):

{_('Another criterion to look for is if the model is moderated (that is, its output is filtered by the provider).')}

''') - def show_model_details(self, m: 'AIModel'): + def show_model_details(self, m: AIModel): if m.pricing.is_free: price = f"{_('Free')}" else: diff --git a/src/calibre/ai/openai/backend.py b/src/calibre/ai/openai/backend.py index 72f19c495a..cc741a73a4 100644 --- a/src/calibre/ai/openai/backend.py +++ b/src/calibre/ai/openai/backend.py @@ -57,7 +57,7 @@ class Model(NamedTuple): version: float @classmethod - def from_dict(cls, x: dict[str, object]) -> 'Model': + def from_dict(cls, x: dict[str, object]) -> Model: id_parts = tuple(x['id'].split('-')) try: version = float(id_parts[1]) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index a306b41fc7..390325d669 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -879,7 +879,7 @@ class AIProviderPlugin(Plugin): # {{{ return self.builtin_live_module.save_settings(config_widget) raise NotImplementedError() - def text_chat(self, messages: 'Iterable[ChatMessage]', use_model: str = '') -> 'Iterator[ChatResponse]': + def text_chat(self, messages: Iterable[ChatMessage], use_model: str = '') -> Iterator[ChatResponse]: ''' Send the specified chat messages to the AI and return an iterable over its streaming responses. The :code:`use_model` parameter will cause the plugin to use a specific model, useful when having diff --git a/src/calibre/db/tests/notes.py b/src/calibre/db/tests/notes.py index 822a235505..7c486ef8dc 100644 --- a/src/calibre/db/tests/notes.py +++ b/src/calibre/db/tests/notes.py @@ -12,7 +12,7 @@ from calibre.db.tests.base import BaseTest from calibre.utils.resources import get_image_path -def test_notes_restore(self: 'NotesTest'): +def test_notes_restore(self: NotesTest): cache, notes = self.create_notes_db() authors = sorted(cache.all_field_ids('authors')) doc = 'simple notes for an author' @@ -23,7 +23,7 @@ def test_notes_restore(self: 'NotesTest'): cache.set_notes_for('authors', authors[1], doc2, resource_hashes=(h2,)) -def test_notes_api(self: 'NotesTest'): +def test_notes_api(self: NotesTest): cache, notes = self.create_notes_db() authors = sorted(cache.all_field_ids('authors')) self.ae(cache.notes_for('authors', authors[0]), '') @@ -83,7 +83,7 @@ def test_notes_api(self: 'NotesTest'): self.ae(len(os.listdir(notes.retired_dir)), 1) -def test_cache_api(self: 'NotesTest'): +def test_cache_api(self: NotesTest): cache, notes = self.create_notes_db() authors = cache.field_for('authors', 1) author_id = cache.get_item_id('authors', authors[0]) @@ -170,7 +170,7 @@ def test_cache_api(self: 'NotesTest'): self.ae(sorted(res, key=itemgetter('name')), sorted(res2, key=itemgetter('name'))) -def test_fts(self: 'NotesTest'): +def test_fts(self: NotesTest): cache, _ = self.create_notes_db() authors = sorted(cache.all_field_ids('authors')) cache.set_notes_for('authors', authors[0], 'Wunderbar wunderkind common') diff --git a/src/calibre/devices/mtp/filesystem_cache.py b/src/calibre/devices/mtp/filesystem_cache.py index 19709b50c1..794fa74b47 100644 --- a/src/calibre/devices/mtp/filesystem_cache.py +++ b/src/calibre/devices/mtp/filesystem_cache.py @@ -35,7 +35,7 @@ def convert_timestamp(md): class ListEntry: - def __init__(self, entry: 'FileOrFolder'): + def __init__(self, entry: FileOrFolder): self.is_dir = entry.is_folder self.is_readonly = not entry.can_delete self.path = '/'.join(entry.full_path) @@ -46,7 +46,7 @@ class ListEntry: class FileOrFolder: - def __init__(self, entry, fs_cache: 'FilesystemCache', is_storage: bool = False): + def __init__(self, entry, fs_cache: FilesystemCache, is_storage: bool = False): self.object_id = entry['id'] self.is_storage = is_storage self.is_folder = entry['is_folder'] @@ -126,7 +126,7 @@ class FileOrFolder: return not self.files and not self.folders @property - def id_map(self) -> dict[int, 'FileOrFolder']: + def id_map(self) -> dict[int, FileOrFolder]: return self.fs_cache().id_maps[self.storage_id] @property diff --git a/src/calibre/gui2/central.py b/src/calibre/gui2/central.py index ee0486f591..717415c2b8 100644 --- a/src/calibre/gui2/central.py +++ b/src/calibre/gui2/central.py @@ -120,7 +120,7 @@ class LayoutButton(QToolButton): on_action_trigger = pyqtSignal(bool) - def __init__(self, name: str, icon: str, label: str, central: 'CentralContainer', shortcut=None): + def __init__(self, name: str, icon: str, label: str, central: CentralContainer, shortcut=None): super().__init__(central) self.central = central self.label = label diff --git a/src/calibre/gui2/dialogs/palette.py b/src/calibre/gui2/dialogs/palette.py index 2becff51b1..d8d60b1c53 100644 --- a/src/calibre/gui2/dialogs/palette.py +++ b/src/calibre/gui2/dialogs/palette.py @@ -33,7 +33,7 @@ class Color(QWidget): changed = pyqtSignal() - def __init__(self, key: str, desc: str, parent: 'PaletteColors', palette: QPalette, default_palette: QPalette, mode_name: str, group=''): + def __init__(self, key: str, desc: str, parent: PaletteColors, palette: QPalette, default_palette: QPalette, mode_name: str, group=''): super().__init__(parent) self.key = key self.setting_key = (key + '-' + group) if group else key diff --git a/src/calibre/gui2/library/bookshelf_view.py b/src/calibre/gui2/library/bookshelf_view.py index 5669c82386..eeba63cc3d 100644 --- a/src/calibre/gui2/library/bookshelf_view.py +++ b/src/calibre/gui2/library/bookshelf_view.py @@ -151,7 +151,7 @@ class WoodTheme(NamedTuple): cavity_color: QColor @classmethod - def light_theme(cls) -> 'WoodTheme': + def light_theme(cls) -> WoodTheme: # Light oak/pine colors for light mode return WoodTheme( background=QColor(245, 245, 245), @@ -185,7 +185,7 @@ class WoodTheme(NamedTuple): ) @classmethod - def dark_theme(cls) -> 'WoodTheme': + def dark_theme(cls) -> WoodTheme: # Dark walnut/mahogany colors for dark mode return WoodTheme( background=QColor(30, 30, 35), @@ -476,7 +476,7 @@ class PixmapWithDominantColor(QPixmap): dominant_color: QColor = QColor() @staticmethod - def fromImage(img: QImage) -> 'PixmapWithDominantColor': + def fromImage(img: QImage) -> PixmapWithDominantColor: ans = PixmapWithDominantColor(QPixmap.fromImage(img)) if not hasattr(img, 'dominant_color'): img = ImageWithDominantColor(img) @@ -656,7 +656,7 @@ class ShelfItem(NamedTuple): def contains(self, x: int, gap: int = 0) -> bool: return self.start_x <= x < self.start_x + self.width + gap - def overlap_length(self, X: 'ShelfItem') -> int: + def overlap_length(self, X: ShelfItem) -> int: xs, xl = X.start_x, X.width ys, yl = self.start_x, self.width xe = xs + xl @@ -747,7 +747,7 @@ class CaseItem: def is_shelf(self) -> bool: return self.items is None - def shift_for_expanded_cover(self, shelf_item: ShelfItem, lc: LayoutConstraints, width: int) -> 'CaseItem': + def shift_for_expanded_cover(self, shelf_item: ShelfItem, lc: LayoutConstraints, width: int) -> CaseItem: if (extra := width - shelf_item.width) <= 0: return self ans = CaseItem(y=self.start_y, height=self.height, idx=self.idx) @@ -1172,7 +1172,7 @@ class ExpandedCover(QObject): # {{{ updated = pyqtSignal() - def __init__(self, parent: 'BookshelfView'): + def __init__(self, parent: BookshelfView): super().__init__(parent) self._opacity = 0 self._size = QSize() diff --git a/src/calibre/gui2/llm.py b/src/calibre/gui2/llm.py index 31c484008b..7f2bd6bbf0 100644 --- a/src/calibre/gui2/llm.py +++ b/src/calibre/gui2/llm.py @@ -109,7 +109,7 @@ class ConversationHistory: def append(self, x: ChatMessage) -> None: self.items.append(x) - def copy(self, upto: int | None = None) -> 'ConversationHistory': + def copy(self, upto: int | None = None) -> ConversationHistory: ans = ConversationHistory() ans.model_used = self.model_used if upto is None: @@ -118,7 +118,7 @@ class ConversationHistory: ans.items = self.items[:upto] return ans - def only(self, message_index: int) -> 'ConversationHistory': + def only(self, message_index: int) -> ConversationHistory: ans = self.copy(message_index + 1) ans.items = [ans.items[-1]] return ans @@ -527,7 +527,7 @@ class ActionData(NamedTuple): return {'disabled': self.is_disabled, 'title': self.human_name, 'prompt_template': self.prompt_template} @classmethod - def unserialize(cls, p: dict[str, Any], default_actions: tuple['ActionData', ...], include_disabled=False) -> Iterator['ActionData']: + def unserialize(cls, p: dict[str, Any], default_actions: tuple[ActionData, ...], include_disabled=False) -> Iterator[ActionData]: dd = p.get('disabled_default_actions', ()) for x in default_actions: x = x._replace(is_disabled=x.name in dd) diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index 8ac8a74914..4edae0aa28 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -102,7 +102,7 @@ class TrashList(QListWidget): restore_item = pyqtSignal(object, object) - def __init__(self, entries: list[TrashEntry], parent: 'TrashView', is_books: bool): + def __init__(self, entries: list[TrashEntry], parent: TrashView, is_books: bool): super().__init__(parent) self.is_books = is_books self.db = parent.db diff --git a/src/calibre/gui2/tts/manager.py b/src/calibre/gui2/tts/manager.py index b71e1aaf7f..568202b533 100644 --- a/src/calibre/gui2/tts/manager.py +++ b/src/calibre/gui2/tts/manager.py @@ -142,7 +142,7 @@ class TTSManager(QObject): self.state_event.emit(event) @property - def tts(self) -> 'TTSBackend': + def tts(self) -> TTSBackend: if self._tts is None: with BusyCursor(): from calibre.gui2.tts.types import create_tts_backend diff --git a/src/calibre/gui2/tts/types.py b/src/calibre/gui2/tts/types.py index 3a0747c7f8..a9ef058ae4 100644 --- a/src/calibre/gui2/tts/types.py +++ b/src/calibre/gui2/tts/types.py @@ -55,7 +55,7 @@ class Quality(Enum): ExtraLow: int = auto() @classmethod - def from_piper_quality(self, x: str) -> 'Quality': + def from_piper_quality(self, x: str) -> Quality: return {'x_low': Quality.ExtraLow, 'low': Quality.Low, 'medium': Quality.Medium, 'high': Quality.High}[x] @property @@ -132,7 +132,7 @@ class EngineSpecificSettings(NamedTuple): preferred_voices: dict[str, str] | None = None @classmethod - def create_from_prefs(cls, engine_name: str, prefs: dict[str, object] | None = None) -> 'EngineSpecificSettings': + def create_from_prefs(cls, engine_name: str, prefs: dict[str, object] | None = None) -> EngineSpecificSettings: prefs = prefs or {} adev = prefs.get('audio_device_id') audio_device_id = None @@ -161,7 +161,7 @@ class EngineSpecificSettings(NamedTuple): audio_device_id=audio_device_id, rate=rate, pitch=pitch, volume=volume, engine_name=engine_name) @classmethod - def create_from_config(cls, engine_name: str, config_name: str = CONFIG_NAME) -> 'EngineSpecificSettings': + def create_from_config(cls, engine_name: str, config_name: str = CONFIG_NAME) -> EngineSpecificSettings: prefs = load_config(config_name) val = prefs.get('engines', {}).get(engine_name, {}) return cls.create_from_prefs(engine_name, val) diff --git a/src/calibre/library/page_count.py b/src/calibre/library/page_count.py index 8e39ad81da..771155cfa7 100644 --- a/src/calibre/library/page_count.py +++ b/src/calibre/library/page_count.py @@ -246,7 +246,7 @@ class Server: self.shutdown_worker() raise - def __enter__(self) -> 'Server': + def __enter__(self) -> Server: return self def __exit__(self, *a) -> None: diff --git a/src/calibre/scraper/qt_backend.py b/src/calibre/scraper/qt_backend.py index e86feea1d3..cd5e215d73 100644 --- a/src/calibre/scraper/qt_backend.py +++ b/src/calibre/scraper/qt_backend.py @@ -86,7 +86,7 @@ class DownloadRequest(QObject): worth_retry: bool = False - def __init__(self, url: str, output_path: str, reply: QNetworkReply, timeout: float, req_id: int, parent: 'FetchBackend'): + def __init__(self, url: str, output_path: str, reply: QNetworkReply, timeout: float, req_id: int, parent: FetchBackend): super().__init__(parent) self.url, self.filename = url, os.path.basename(output_path) self.output_path = output_path diff --git a/src/calibre/scraper/webengine_backend.py b/src/calibre/scraper/webengine_backend.py index c28683c1b7..d69f72067b 100644 --- a/src/calibre/scraper/webengine_backend.py +++ b/src/calibre/scraper/webengine_backend.py @@ -48,7 +48,7 @@ class DownloadRequest(QObject): aborted_on_timeout: bool = False response_received = pyqtSignal(object) - def __init__(self, url: str, output_path: str, timeout: float, req_id: int, parent: 'FetchBackend'): + def __init__(self, url: str, output_path: str, timeout: float, req_id: int, parent: FetchBackend): super().__init__(parent) self.url, self.filename = url, os.path.basename(output_path) self.output_path = output_path diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 643b904c8a..c3a84446bd 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -102,7 +102,7 @@ class WindowsFileCopier: self.path_to_fileid_map[path] = winutil.get_file_id(make_long_path_useable(path)) self.folders.append(path) - def _open_file(self, path: str, retry_on_sharing_violation: bool = True, is_folder: bool = False) -> 'winutil.Handle': + def _open_file(self, path: str, retry_on_sharing_violation: bool = True, is_folder: bool = False) -> winutil.Handle: flags = winutil.FILE_FLAG_BACKUP_SEMANTICS if is_folder else winutil.FILE_FLAG_SEQUENTIAL_SCAN # Do not open symbolic link target to prevent unwanted delete_on_close flags |= getattr(winutil, 'FILE_FLAG_OPEN_REPARSE_POINT', 0x00200000) diff --git a/src/calibre/utils/forked_map.py b/src/calibre/utils/forked_map.py index c12627e56b..0b8615fa76 100644 --- a/src/calibre/utils/forked_map.py +++ b/src/calibre/utils/forked_map.py @@ -64,7 +64,7 @@ class Worker: self.pipe = open(pipe_fd, 'rb') self.unpickler = pickle.Unpickler(self.pipe) - def __enter__(self) -> 'Worker': + def __enter__(self) -> Worker: return self def __exit__(self, exc_type, exc_value, tb) -> None: diff --git a/src/calibre/utils/shm.py b/src/calibre/utils/shm.py index c2fafa44dc..af1f2ef018 100644 --- a/src/calibre/utils/shm.py +++ b/src/calibre/utils/shm.py @@ -174,7 +174,7 @@ class SharedMemory: except OSError: pass - def __enter__(self) -> 'SharedMemory': + def __enter__(self) -> SharedMemory: return self def __exit__(self, *a: object) -> None: