From 7945051b94c84b590a7f29bb673aebff6bd7672a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 12:57:11 +0200 Subject: [PATCH 1/8] Use importlib instead of pkg_resources --- ocrd_browser/application.py | 4 ++-- ocrd_browser/view/__init__.py | 2 +- ocrd_browser/view/registry.py | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ocrd_browser/application.py b/ocrd_browser/application.py index 9cea6b3..85156b0 100644 --- a/ocrd_browser/application.py +++ b/ocrd_browser/application.py @@ -1,8 +1,8 @@ from gi.repository import Gio, Gtk, GLib, Gdk -import pkg_resources from typing import List +from importlib.metadata import entry_points from ocrd_browser.util.gtk import ActionRegistry from ocrd_browser.ui import MainWindow, AboutDialog, OpenDialog from ocrd_browser.view import ViewRegistry @@ -36,7 +36,7 @@ def do_startup(self) -> None: self.set_accels_for_action('view.zoom_to::width', ['numbersign']) self.set_accels_for_action('view.zoom_to::page', ['numbersign']) - for entry_point in pkg_resources.iter_entry_points('ocrd_browser_ext'): + for entry_point in entry_points().get('ocrd_browser_ext', []): (entry_point.load())(self) self.load_css() diff --git a/ocrd_browser/view/__init__.py b/ocrd_browser/view/__init__.py index f83bd31..1b630db 100644 --- a/ocrd_browser/view/__init__.py +++ b/ocrd_browser/view/__init__.py @@ -1,5 +1,4 @@ from .base import View -from .registry import ViewRegistry from .html import ViewHtml from .images import ViewImages from .text import ViewText @@ -7,6 +6,7 @@ from .empty import ViewEmpty from .diff import ViewDiff from .page import ViewPage +from .registry import ViewRegistry __all__ = ['View', 'ViewRegistry', 'ViewImages', 'ViewText', 'ViewXml', 'ViewHtml', 'ViewEmpty', 'ViewDiff', 'ViewPage'] diff --git a/ocrd_browser/view/registry.py b/ocrd_browser/view/registry.py index 429eb66..97130bc 100644 --- a/ocrd_browser/view/registry.py +++ b/ocrd_browser/view/registry.py @@ -1,5 +1,6 @@ -from pkg_resources import EntryPoint, iter_entry_points -from typing import Dict, Tuple, Optional +from __future__ import annotations +from importlib.metadata import entry_points +from typing import Dict, Tuple, Optional, Type from .base import View ViewInfo = Tuple[type, str, str] @@ -10,10 +11,9 @@ def __init__(self, views: Dict[str, ViewInfo]): self.views = views @classmethod - def create_from_entry_points(cls) -> 'ViewRegistry': + def create_from_entry_points(cls) -> ViewRegistry: views = {} - entry_point: EntryPoint - for entry_point in iter_entry_points('ocrd_browser_view'): + for entry_point in entry_points().get('ocrd_browser_view', []): view_class = entry_point.load() assert issubclass(view_class, View) label = view_class.label if hasattr(view_class, 'label') else view_class.__name__ @@ -24,5 +24,5 @@ def create_from_entry_points(cls) -> 'ViewRegistry': def get_view_options(self) -> Dict[str, str]: return {id_: label for id_, (view_class, label, description) in self.views.items()} - def get_view(self, id_: str) -> Optional[type]: + def get_view(self, id_: str) -> Optional[Type[View]]: return self.views[id_][0] if id_ in self.views else None From 10012381df148ffac5b101ccc8e1c05a71c12131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 12:57:41 +0200 Subject: [PATCH 2/8] Use importlib instead of pkg_resources for resources --- ocrd_browser/ui/dialogs.py | 9 +++++---- ocrd_browser/ui/page_browser.py | 4 ++-- ocrd_browser/ui/window.py | 6 ++---- ocrd_browser/util/gtk.py | 6 ++++++ 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ocrd_browser/ui/dialogs.py b/ocrd_browser/ui/dialogs.py index 5760b83..316ab42 100644 --- a/ocrd_browser/ui/dialogs.py +++ b/ocrd_browser/ui/dialogs.py @@ -1,11 +1,12 @@ from gi.repository import Gtk, GdkPixbuf from typing import Any -from pkg_resources import resource_filename + +from ocrd_browser.util.gtk import resource_string from ocrd_browser import __version__ from ocrd_browser.model import Document -@Gtk.Template(filename=resource_filename(__name__, '../resources/about-dialog.ui')) +@Gtk.Template(string=resource_string('about-dialog.ui')) class AboutDialog(Gtk.AboutDialog): __gtype_name__ = "AboutDialog" @@ -16,7 +17,7 @@ def __init__(self, **kwargs: Any): self.set_version(__version__) -@Gtk.Template(filename=resource_filename(__name__, '../resources/open-dialog.ui')) +@Gtk.Template(string=resource_string('open-dialog.ui')) class OpenDialog(Gtk.FileChooserDialog): __gtype_name__ = "OpenDialog" @@ -36,7 +37,7 @@ def __init__(self, **kwargs: Any): self.add_filter(filter_any) -@Gtk.Template(filename=resource_filename(__name__, '../resources/save-dialog.ui')) +@Gtk.Template(string=resource_string('save-dialog.ui')) class SaveDialog(Gtk.FileChooserDialog): __gtype_name__ = "SaveDialog" diff --git a/ocrd_browser/ui/page_browser.py b/ocrd_browser/ui/page_browser.py index 2342c3f..0c29b5e 100644 --- a/ocrd_browser/ui/page_browser.py +++ b/ocrd_browser/ui/page_browser.py @@ -2,12 +2,12 @@ from typing import List, Callable, Optional, Any, cast -from pkg_resources import resource_filename +from ocrd_browser.util.gtk import resource_string from ocrd_browser.model import Document from .page_store import PageListStore, ChangeList -@Gtk.Template(filename=resource_filename(__name__, '../resources/page-list.ui')) +@Gtk.Template(string=resource_string('page-list.ui')) class PagePreviewList(Gtk.IconView): __gtype_name__ = "PagePreviewList" diff --git a/ocrd_browser/ui/window.py b/ocrd_browser/ui/window.py index a95a5f9..6f4f428 100644 --- a/ocrd_browser/ui/window.py +++ b/ocrd_browser/ui/window.py @@ -3,22 +3,20 @@ from ocrd_browser.model import Document from ocrd_browser.view import ViewRegistry, ViewPage -from ocrd_browser.util.gtk import ActionRegistry +from ocrd_browser.util.gtk import ActionRegistry, resource_string from .dialogs import SaveDialog, SaveChangesDialog from .page_browser import PagePreviewList -from pkg_resources import resource_filename from typing import List, cast, Any, Optional from ..view.manager import ViewManager -@Gtk.Template(filename=resource_filename(__name__, '../resources/main-window.ui')) +@Gtk.Template(string=resource_string('main-window.ui')) class MainWindow(Gtk.ApplicationWindow): __gtype_name__ = "MainWindow" header_bar: Gtk.HeaderBar = Gtk.Template.Child() page_list_scroller: Gtk.ScrolledWindow = Gtk.Template.Child() - panes: Gtk.Paned = Gtk.Template.Child() current_page_label: Gtk.Label = Gtk.Template.Child() view_container: Gtk.Box = Gtk.Template.Child() view_menu_box: Gtk.Box = Gtk.Template.Child() diff --git a/ocrd_browser/util/gtk.py b/ocrd_browser/util/gtk.py index a34c5ae..dd9a62d 100644 --- a/ocrd_browser/util/gtk.py +++ b/ocrd_browser/util/gtk.py @@ -1,5 +1,7 @@ from gi.repository import Gio, GLib, Gtk +from importlib import resources + from typing import Callable, Dict, Optional, Set, Any ActionCallback = Optional[Callable[[Gio.SimpleAction, Any], None]] @@ -102,3 +104,7 @@ def _run(self) -> None: callback() self._callbacks.remove(callback) self._runner_callback(self._run) + + +def resource_string(resource, package = 'ocrd_browser.resources'): + return resources.read_text(package, resource) From f86aa3ce5fb8e78f2293101bd4c555d6ae6c5061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 13:05:59 +0200 Subject: [PATCH 3/8] Optional startup profiling --- ocrd_browser/main.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ocrd_browser/main.py b/ocrd_browser/main.py index 2fdfe5e..024c2a3 100755 --- a/ocrd_browser/main.py +++ b/ocrd_browser/main.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- +import os import sys +from pstats import SortKey +import pstats, io +import cProfile import gi @@ -13,7 +17,7 @@ gi.require_version('WebKit2', '4.0') -from gi.repository import Gtk, Gio # noqa: E402 +from gi.repository import Gtk, Gio, GLib # noqa: E402 from pathlib import Path # noqa: E402 from typing import Type # noqa: E402 from types import TracebackType # noqa: E402 @@ -22,6 +26,10 @@ resources = Gio.resource_load(str(BASE_PATH / "ui.gresource")) Gio.resources_register(resources) +PROFILER = None +if 'STARTUP_PROFILE' in os.environ: + PROFILER = cProfile.Profile() + PROFILER.enable() def install_excepthook() -> None: """ Make sure we exit when an unhandled exception occurs. """ @@ -36,7 +44,17 @@ def new_hook(type_: Type[BaseException], value: BaseException, traceback: Traceb sys.excepthook = new_hook +def startup_time(): + PROFILER.disable() + s = io.StringIO() + ps = pstats.Stats(PROFILER, stream=s).sort_stats(SortKey.TIME) + ps.print_stats(20) + print(s.getvalue()) + + def main() -> None: + if PROFILER: + GLib.idle_add(startup_time) from ocrd_utils import initLogging initLogging() from ocrd_browser.application import OcrdBrowserApplication From 1d8d100c931311f43226639da944d0254dfc8e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 13:58:04 +0200 Subject: [PATCH 4/8] less importing (if TYPECHECKING) and use of annotations --- ocrd_browser/model/document.py | 28 ++++++++++++++++------------ ocrd_browser/util/config.py | 13 +++++++------ ocrd_browser/util/gtk.py | 5 +++-- ocrd_browser/view/base.py | 27 ++++++++++++++------------- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/ocrd_browser/model/document.py b/ocrd_browser/model/document.py index 86a6422..435de8b 100644 --- a/ocrd_browser/model/document.py +++ b/ocrd_browser/model/document.py @@ -1,36 +1,40 @@ +from __future__ import annotations +from typing import Optional, Tuple, List, Set, Union, cast, Callable, Any, Dict, TYPE_CHECKING + import atexit import errno import os import shutil from functools import wraps -from ocrd import Workspace, Resolver +from ocrd import Resolver from ocrd_browser.model.page import Page from ocrd_browser.util.file_groups import best_file_group from ocrd_browser.util.image import add_dpi_to_png_buffer from ocrd_browser.util.streams import SilencedStreams from ocrd_modelfactory import page_from_file -from ocrd_models import OcrdFile -from ocrd_models.ocrd_page_generateds import PcGtsType from ocrd_models.constants import NAMESPACES as NS from ocrd_utils import pushd_popd from ocrd_utils.constants import MIME_TO_EXT from ocrd_utils import getLogger -from typing import Optional, Tuple, List, Set, Union, cast, Callable, Any, Dict from collections import OrderedDict from pathlib import Path from tempfile import mkdtemp from datetime import datetime from urllib.parse import urlparse, unquote -# noinspection PyProtectedMember -from lxml.etree import ElementBase as Element, _ElementTree as ElementTree - -from numpy import array as ndarray from PIL import Image import cv2 +if TYPE_CHECKING: + from ocrd import Workspace + from ocrd_models import OcrdFile + from ocrd_models.ocrd_page_generateds import PcGtsType + # noinspection PyProtectedMember + from lxml.etree import ElementBase as Element, _ElementTree as ElementTree + from numpy import array as ndarray + EventCallBack = Optional[Callable[[str, Any], None]] @@ -60,11 +64,11 @@ def __init__(self, workspace: Optional[Workspace], emitter: Optional[EventCallBa os.chdir(self.workspace.directory) @classmethod - def create(cls, emitter: EventCallBack = None) -> 'Document': + def create(cls, emitter: EventCallBack = None) -> Document: return cls(None, emitter=emitter) @classmethod - def load(cls, mets_url: Union[Path, str] = None, emitter: EventCallBack = None) -> 'Document': + def load(cls, mets_url: Union[Path, str] = None, emitter: EventCallBack = None) -> Document: """ Load a project from an url as a readonly view @@ -80,7 +84,7 @@ def load(cls, mets_url: Union[Path, str] = None, emitter: EventCallBack = None) return doc @classmethod - def clone(cls, mets_url: Union[Path, str], emitter: EventCallBack = None, editable: bool = True) -> 'Document': + def clone(cls, mets_url: Union[Path, str], emitter: EventCallBack = None, editable: bool = True) -> Document: """ Clones a project (mets.xml and all used files) to a temporary directory for editing """ @@ -407,7 +411,7 @@ def delete_page(self, page_id: str) -> None: @check_editable def add_image(self, image: ndarray, page_id: str, file_id: str, file_group: str = 'OCR-D-IMG', dpi: int = 300, - mimetype: str = 'image/png') -> 'OcrdFile': + mimetype: str = 'image/png') -> OcrdFile: extension = MIME_TO_EXT[mimetype] retval, image_array = cv2.imencode(extension, image) image_bytes = add_dpi_to_png_buffer(image_array.tostring(), dpi) diff --git a/ocrd_browser/util/config.py b/ocrd_browser/util/config.py index c924c2f..4049187 100644 --- a/ocrd_browser/util/config.py +++ b/ocrd_browser/util/config.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os from collections import OrderedDict from configparser import ConfigParser @@ -13,7 +14,7 @@ class _SubSettings: @classmethod - def from_section(cls, _name: str, section: ConfigDict) -> '_SubSettings': + def from_section(cls, _name: str, section: ConfigDict) -> _SubSettings: raise NotImplementedError('please override from_section') def validate(self) -> None: @@ -28,7 +29,7 @@ def __init__(self, preferred_images: List[str]): self.preferred_images = preferred_images @classmethod - def from_section(cls, _name: str, section: ConfigDict) -> '_FileGroups': + def from_section(cls, _name: str, section: ConfigDict) -> _FileGroups: preferred_images = section.get('preferredImages', 'OCR-D-IMG, OCR-D-IMG.*') return cls( [grp.strip() for grp in preferred_images.split(',')] @@ -50,7 +51,7 @@ def validate(self) -> None: raise ValueError('Could not locate executable "{}"'.format(executable)) @classmethod - def from_section(cls, name: str, section: ConfigDict) -> '_Tool': + def from_section(cls, name: str, section: ConfigDict) -> _Tool: return cls( name[len(cls.PREFIX):], section['commandline'], @@ -78,13 +79,13 @@ def __repr__(self) -> str: return '{}({})'.format(self.__class__.__name__, repr(vars(self))) @classmethod - def get(cls) -> 'Settings': + def get(cls) -> Settings: if cls._settings is None: cls._settings = Settings.build_default() return cls._settings @classmethod - def build_default(cls, config_dirs: Optional[List[str]] = None, validate: bool = True) -> 'Settings': + def build_default(cls, config_dirs: Optional[List[str]] = None, validate: bool = True) -> Settings: if config_dirs is None: config_dirs = GLib.get_system_config_dirs() + [GLib.get_user_config_dir()] try: @@ -97,7 +98,7 @@ def build_default(cls, config_dirs: Optional[List[str]] = None, validate: bool = return cls.build_from_files(config_files, validate) @classmethod - def build_from_files(cls, files: List[str], validate: bool = True) -> 'Settings': + def build_from_files(cls, files: List[str], validate: bool = True) -> Settings: log = getLogger('ocrd_browser.util.config._Settings.build_from_files') config = ConfigParser() setattr(config, 'optionxform', lambda option: option) diff --git a/ocrd_browser/util/gtk.py b/ocrd_browser/util/gtk.py index dd9a62d..8a1d24c 100644 --- a/ocrd_browser/util/gtk.py +++ b/ocrd_browser/util/gtk.py @@ -1,3 +1,4 @@ +from __future__ import annotations from gi.repository import Gio, GLib, Gtk from importlib import resources @@ -72,14 +73,14 @@ class WhenIdle: Usage: see WhenIdle.call """ - _instance: 'WhenIdle' = None + _instance: WhenIdle = None def __init__(self, runner_callback: Callable): # type: ignore[type-arg] self._runner_callback = runner_callback self._callbacks: Set[Callback] = set() @classmethod - def instance(cls) -> 'WhenIdle': + def instance(cls) -> WhenIdle: if cls._instance is None: cls._instance = cls(GLib.idle_add) return cls._instance diff --git a/ocrd_browser/view/base.py b/ocrd_browser/view/base.py index 5893ac8..81157e3 100644 --- a/ocrd_browser/view/base.py +++ b/ocrd_browser/view/base.py @@ -1,26 +1,27 @@ -from math import log - from gi.repository import Gtk, Pango, GObject -from typing import List, Tuple, Any, Optional, Dict, cast +from typing import List, Tuple, Any, Optional, Dict, cast, TYPE_CHECKING +from math import log from enum import Enum from ocrd_utils.constants import MIMETYPE_PAGE, MIME_TO_EXT -from ocrd_browser.model import Document, Page from ocrd_browser.util.gtk import WhenIdle +if TYPE_CHECKING: + from ocrd_browser.model import Document, Page + class Configurator(Gtk.Widget): def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.document: Optional[Document] = None + self.document: Optional['Document'] = None self.value: Any = None - def set_document(self, document: Document) -> None: + def set_document(self, document: 'Document') -> None: self.document = document - def set_page(self, page: Page) -> None: + def set_page(self, page: 'Page') -> None: pass def set_value(self, value: Any) -> None: @@ -33,7 +34,7 @@ class View: def __init__(self, name: str, window: Gtk.Window): self.name: str = name self.window: Gtk.Window = window - self.document: Document = None + self.document: 'Document' = None self.current: Page = None self.page_id: str = None @@ -54,7 +55,7 @@ def build(self) -> None: self.container.pack_start(self.action_bar, False, True, 0) self.container.pack_start(self.scroller, True, True, 0) - def set_document(self, document: Document) -> None: + def set_document(self, document: 'Document') -> None: self.document = document for configurator in self.configurators.values(): configurator.set_document(document) @@ -226,7 +227,7 @@ def set_value(self, value: Tuple[str, str]) -> None: if active_id: self.groups.set_active_id(active_id) - def set_document(self, document: Document) -> None: + def set_document(self, document: 'Document') -> None: self.groups.set_document(document) self.set_value(self.value) @@ -270,7 +271,7 @@ def set_tooltip(self, _widget: Gtk.Widget, _x: int, _y: int, _keyboard_mode: boo return True return False - def set_document(self, document: Document) -> None: + def set_document(self, document: 'Document') -> None: self.set_model(FileGroupModel.build(document, self.filter)) # self.set_active(0) @@ -284,7 +285,7 @@ def add_renderer(self, column: int, width: int = None) -> Gtk.CellRendererText: class FileGroupModel(Gtk.ListStore): - def __init__(self, document: Document): + def __init__(self, document: 'Document'): super().__init__(str, str, str, str) for group, mime in document.file_groups_and_mimetypes: if mime == 'text/html': @@ -294,7 +295,7 @@ def __init__(self, document: Document): self.append(('{}|{}'.format(group, mime), group, mime, ext)) @classmethod - def build(cls, document: Document, filter_: 'FileGroupFilter' = None) -> 'FileGroupModel': + def build(cls, document: 'Document', filter_: 'FileGroupFilter' = None) -> 'FileGroupModel': model = cls(document) model = model.filter_new() model.set_visible_func(filter_ if filter_ else FileGroupFilter.ALL) From 71b2fecce4d4643422e710d5da91d39e542aed17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 14:08:04 +0200 Subject: [PATCH 5/8] No more OrderedDict, it's default now --- ocrd_browser/model/document.py | 5 ++--- ocrd_browser/util/config.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ocrd_browser/model/document.py b/ocrd_browser/model/document.py index 435de8b..a759d4e 100644 --- a/ocrd_browser/model/document.py +++ b/ocrd_browser/model/document.py @@ -14,11 +14,11 @@ from ocrd_browser.util.streams import SilencedStreams from ocrd_modelfactory import page_from_file from ocrd_models.constants import NAMESPACES as NS +from ocrd_models import OcrdFile from ocrd_utils import pushd_popd from ocrd_utils.constants import MIME_TO_EXT from ocrd_utils import getLogger -from collections import OrderedDict from pathlib import Path from tempfile import mkdtemp from datetime import datetime @@ -29,7 +29,6 @@ if TYPE_CHECKING: from ocrd import Workspace - from ocrd_models import OcrdFile from ocrd_models.ocrd_page_generateds import PcGtsType # noinspection PyProtectedMember from lxml.etree import ElementBase as Element, _ElementTree as ElementTree @@ -226,7 +225,7 @@ def file_groups_and_mimetypes(self) -> List[Tuple[str, str]]: @return: List[Tuple[str,str]] """ - distinct_groups: OrderedDict[Tuple[str, str], None] = OrderedDict() + distinct_groups: Dict[Tuple[str, str], None] = {} for el in self.xpath('mets:fileSec/mets:fileGrp[@USE]/mets:file[@MIMETYPE]'): distinct_groups[(el.getparent().get('USE'), el.get('MIMETYPE'))] = None diff --git a/ocrd_browser/util/config.py b/ocrd_browser/util/config.py index 4049187..5903eba 100644 --- a/ocrd_browser/util/config.py +++ b/ocrd_browser/util/config.py @@ -1,6 +1,5 @@ from __future__ import annotations import os -from collections import OrderedDict from configparser import ConfigParser import shlex from typing import List, Optional, MutableMapping @@ -67,7 +66,7 @@ def __init__(self, config: ConfigParser, validate: bool = True): self.file_groups = _FileGroups.from_section('FileGroups', config['FileGroups'] if 'FileGroups' in config else {'': ''}) if validate: self.file_groups.validate() - self.tools = OrderedDict() + self.tools = {} for name, section in config.items(): if name.startswith(_Tool.PREFIX): tool = _Tool.from_section(name, section) From 3daa04dad5a35f1fe263ea09daaed8a02a862343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 14:08:40 +0200 Subject: [PATCH 6/8] type / style fixes --- ocrd_browser/application.py | 2 +- ocrd_browser/main.py | 6 ++++-- ocrd_browser/util/gtk.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ocrd_browser/application.py b/ocrd_browser/application.py index 85156b0..c7487b2 100644 --- a/ocrd_browser/application.py +++ b/ocrd_browser/application.py @@ -65,7 +65,7 @@ def on_quit(self, _action: Gio.SimpleAction, _param: str = None) -> None: open_windows: int = 0 window: MainWindow for window in self.get_windows(): - if isinstance(window, MainWindow) and window.close_confirm(): # type: ignore[unreachable] + if isinstance(window, MainWindow) and window.close_confirm(): window.destroy() else: open_windows += 1 diff --git a/ocrd_browser/main.py b/ocrd_browser/main.py index 024c2a3..6d37ab7 100755 --- a/ocrd_browser/main.py +++ b/ocrd_browser/main.py @@ -3,7 +3,8 @@ import os import sys from pstats import SortKey -import pstats, io +import pstats +import io import cProfile import gi @@ -31,6 +32,7 @@ PROFILER = cProfile.Profile() PROFILER.enable() + def install_excepthook() -> None: """ Make sure we exit when an unhandled exception occurs. """ old_hook = sys.excepthook @@ -44,7 +46,7 @@ def new_hook(type_: Type[BaseException], value: BaseException, traceback: Traceb sys.excepthook = new_hook -def startup_time(): +def startup_time() -> None: PROFILER.disable() s = io.StringIO() ps = pstats.Stats(PROFILER, stream=s).sort_stats(SortKey.TIME) diff --git a/ocrd_browser/util/gtk.py b/ocrd_browser/util/gtk.py index 8a1d24c..3f79e03 100644 --- a/ocrd_browser/util/gtk.py +++ b/ocrd_browser/util/gtk.py @@ -107,5 +107,5 @@ def _run(self) -> None: self._runner_callback(self._run) -def resource_string(resource, package = 'ocrd_browser.resources'): +def resource_string(resource: str, package: str = 'ocrd_browser.resources') -> str: return resources.read_text(package, resource) From a02717a8b3643fcf4f653383c35311a640e1149b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 14:49:26 +0200 Subject: [PATCH 7/8] python 3.7 compatibility --- ocrd_browser/application.py | 6 +++++- ocrd_browser/util/gtk.py | 10 +++++++--- ocrd_browser/view/registry.py | 7 ++++++- requirements.txt | 4 +++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ocrd_browser/application.py b/ocrd_browser/application.py index c7487b2..0fa2863 100644 --- a/ocrd_browser/application.py +++ b/ocrd_browser/application.py @@ -2,11 +2,15 @@ from typing import List -from importlib.metadata import entry_points from ocrd_browser.util.gtk import ActionRegistry from ocrd_browser.ui import MainWindow, AboutDialog, OpenDialog from ocrd_browser.view import ViewRegistry +try: + from importlib.metadata import entry_points +except ModuleNotFoundError: + from importlib_metadata import entry_points # type: ignore + class OcrdBrowserApplication(Gtk.Application): # TODO: Parse arguments (with Gtk or click) to open certain views by mets+page_id+view(view_configuration_dict) and deep filename e.g. diff --git a/ocrd_browser/util/gtk.py b/ocrd_browser/util/gtk.py index 3f79e03..e9368d1 100644 --- a/ocrd_browser/util/gtk.py +++ b/ocrd_browser/util/gtk.py @@ -1,10 +1,14 @@ from __future__ import annotations from gi.repository import Gio, GLib, Gtk -from importlib import resources - from typing import Callable, Dict, Optional, Set, Any +try: + from importlib.resources import read_text +except ModuleNotFoundError: + from importlib_resources import read_text # type: ignore + + ActionCallback = Optional[Callable[[Gio.SimpleAction, Any], None]] @@ -108,4 +112,4 @@ def _run(self) -> None: def resource_string(resource: str, package: str = 'ocrd_browser.resources') -> str: - return resources.read_text(package, resource) + return read_text(package, resource) diff --git a/ocrd_browser/view/registry.py b/ocrd_browser/view/registry.py index 97130bc..11b475d 100644 --- a/ocrd_browser/view/registry.py +++ b/ocrd_browser/view/registry.py @@ -1,8 +1,13 @@ from __future__ import annotations -from importlib.metadata import entry_points from typing import Dict, Tuple, Optional, Type from .base import View +try: + from importlib.metadata import entry_points +except ModuleNotFoundError: + from importlib_metadata import entry_points # type: ignore + + ViewInfo = Tuple[type, str, str] diff --git a/requirements.txt b/requirements.txt index 2c4dc26..4a17587 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,6 @@ wheel setuptools lxml Shapely -Deprecated \ No newline at end of file +Deprecated +importlib_metadata;python_version<'3.8' +importlib_resources;python_version<'3.8' \ No newline at end of file From c17a3b9e9cae670bc920d4a03d6d4b186f033330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=BCnsebeck?= Date: Wed, 15 Jun 2022 15:06:18 +0200 Subject: [PATCH 8/8] make lgtm/inspector happy --- ocrd_browser/main.py | 3 +-- ocrd_browser/model/document.py | 3 ++- ocrd_browser/model/page.py | 1 + ocrd_browser/view/diff.py | 2 +- serve.py | 2 +- setup.py | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ocrd_browser/main.py b/ocrd_browser/main.py index 6d37ab7..f448d33 100755 --- a/ocrd_browser/main.py +++ b/ocrd_browser/main.py @@ -2,7 +2,6 @@ # -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- import os import sys -from pstats import SortKey import pstats import io import cProfile @@ -49,7 +48,7 @@ def new_hook(type_: Type[BaseException], value: BaseException, traceback: Traceb def startup_time() -> None: PROFILER.disable() s = io.StringIO() - ps = pstats.Stats(PROFILER, stream=s).sort_stats(SortKey.TIME) + ps = pstats.Stats(PROFILER, stream=s).sort_stats(pstats.SortKey.TIME) ps.print_stats(20) print(s.getvalue()) diff --git a/ocrd_browser/model/document.py b/ocrd_browser/model/document.py index a759d4e..b786252 100644 --- a/ocrd_browser/model/document.py +++ b/ocrd_browser/model/document.py @@ -162,7 +162,7 @@ def baseurl_mets(self) -> str: """ return str(self.workspace.baseurl) + '/' + self.mets_filename if self.workspace else None - def path(self, other: Union[OcrdFile, Path, str]) -> Path: + def path(self, other: Union[OcrdFile, Path, str]) -> Optional[Path]: """ Resolves other relative to current workspace """ @@ -445,6 +445,7 @@ def editable(self, editable: bool) -> None: if self._original_url: self.workspace = self._clone_workspace(self._original_url) else: + # noinspection PyTypeChecker self.workspace = Resolver().workspace_from_nothing(directory=None, mets_basename='mets.xml') else: self.workspace = Resolver().workspace_from_url(self.baseurl_mets) diff --git a/ocrd_browser/model/page.py b/ocrd_browser/model/page.py index 029e950..d4b90f9 100644 --- a/ocrd_browser/model/page.py +++ b/ocrd_browser/model/page.py @@ -64,6 +64,7 @@ def file(self) -> Optional[OcrdFile]: elif self.image_files: return next(iter(self.image_files)) else: + # noinspection PyTypeChecker any_files = self.get_files(mimetype=None) if any_files: return next(iter(any_files)) diff --git a/ocrd_browser/view/diff.py b/ocrd_browser/view/diff.py index 4823839..eb8628b 100644 --- a/ocrd_browser/view/diff.py +++ b/ocrd_browser/view/diff.py @@ -80,7 +80,7 @@ def __init__(self, name: str, window: Gtk.Window): self.file_group2: Tuple[Optional[str], Optional[str]] = (None, MIMETYPE_PAGE) self.font_size: Optional[int] = None - self.current2: Page = None + self.current2: Optional[Page] = None # noinspection PyTypeChecker self.text_view: GtkSource.View = None diff --git a/serve.py b/serve.py index de45c5a..f237802 100644 --- a/serve.py +++ b/serve.py @@ -92,7 +92,7 @@ def _browse_workspace(self, path): if not path.endswith('mets.xml'): path = os.path.join(path, 'mets.xml') ## run app - ret = Popen([which('browse-ocrd'), + Popen([which('browse-ocrd'), '--display', ':' + str(self.bwport - 8080), path]) ## proxy does not work, because the follow-up requests would need to be forwarded, too: diff --git a/setup.py b/setup.py index 8ff40cb..0dd043b 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import fastentrypoints +import fastentrypoints # lgtm [py/unused-import] import codecs import subprocess