diff --git a/ruff.toml b/ruff.toml index 341f0db..35bda57 100644 --- a/ruff.toml +++ b/ruff.toml @@ -26,7 +26,7 @@ lint.extend-select = [ "TID", # various imports suggestions "TRY", # various exception handling rules "UP", # detect deprecated python stdlib stuff - # "FA", # TODO enable later after we make sure cachew works? + "FA", # suggest using from __future__ import annotations "PTH", # pathlib migration "ARG", # unused argument checks "A", # builtin shadowing @@ -69,11 +69,6 @@ lint.ignore = [ "F401", # imported but unused ### -### TODO should be fine to use these with from __future__ import annotations? -### there was some issue with cachew though... double check this? - "UP006", # use type instead of Type - "UP007", # use X | Y instead of Union -### "RUF100", # unused noqa -- handle later "RUF012", # mutable class attrs should be annotated with ClassVar... ugh pretty annoying for user configs diff --git a/src/orger/common.py b/src/orger/common.py index c2ef4be..281340e 100644 --- a/src/orger/common.py +++ b/src/orger/common.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import traceback import warnings from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from .inorganic import OrgNode, TimestampStyle, timestamp, timestamp_with_style @@ -14,7 +16,7 @@ class settings: _timezones = set() # type: ignore -def dt_heading(dt: Optional[datetime], heading: str) -> str: +def dt_heading(dt: datetime | None, heading: str) -> str: """ Helper to inline datetime in heading """ diff --git a/src/orger/inorganic.py b/src/orger/inorganic.py index 954776c..288fb1d 100644 --- a/src/orger/inorganic.py +++ b/src/orger/inorganic.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os import re @@ -12,12 +14,8 @@ TYPE_CHECKING, Any, Callable, - Dict, - List, Mapping, - Optional, Sequence, - Tuple, TypeVar, Union, ) @@ -36,7 +34,7 @@ class TimestampStyle(Enum): PathIsh = Union[str, Path] -def link(*, url: PathIsh, title: Optional[str]) -> str: +def link(*, url: PathIsh, title: str | None) -> str: """ >>> link(url='http://reddit.com', title='[R]eddit!') '[[http://reddit.com][Reddit!]]' @@ -53,7 +51,7 @@ def link(*, url: PathIsh, title: Optional[str]) -> str: return f'[[{U}]]' -def docview_link(*, path: PathIsh, title: Optional[str], page1: Optional[int]=None) -> str: +def docview_link(*, path: PathIsh, title: str | None, page1: int | None = None) -> str: """ Generates a docview link to open pdfs. page1: 1-indexed (!) pdf page number. @@ -130,14 +128,14 @@ def quoted(self) -> str: # TODO priority # TODO for sanitizing, have two strategies: error and replace? def asorgoutline( - heading: Optional[str] = None, + heading: str | None = None, *, - todo: Optional[str] = None, + todo: str | None = None, tags: Sequence[str] = [], - scheduled: Optional[Dateish] = None, + scheduled: Dateish | None = None, # TODO perhaps allow list of tuples? properties might be repeating - properties: Optional[Mapping[str, str]] = None, - body: Optional[Body] = None, + properties: Mapping[str, str] | None = None, + body: Body | None = None, level: int = 1, escaped: bool = False, ) -> str: @@ -176,7 +174,7 @@ def asorgoutline( heading = re.sub(r'\s', ' ', heading) # TODO not great that we always pad body I guess. maybe needs some sort of raw_body argument? - safe_body: Optional[str] = None + safe_body: str | None = None if body is not None: # todo https://github.com/karlicoss/orger/issues/16 # maybe need a policy for body behaviour... I guess policy could be Union[Literate]; controlled by env variables? @@ -205,7 +203,7 @@ def asorgoutline( 'SCHEDULED: ' + timestamp(scheduled, active=True) ] - props_lines: List[str] = [] + props_lines: list[str] = [] props = {} if properties is None else properties if len(props) > 0: props_lines.append(':PROPERTIES:') @@ -238,13 +236,13 @@ class OrgNode: """ heading: Lazy[str] - todo: Optional[str] = None + todo: str | None = None tags: Sequence[str] = () - scheduled: Optional[Dateish] = None - properties: Optional[Mapping[str, str]] = None + scheduled: Dateish | None = None + properties: Mapping[str, str] | None = None # TODO make body lazy as well? - body: Optional[Body] = None - children: Sequence['OrgNode'] = () + body: Body | None = None + children: Sequence[OrgNode] = () # NOTE: this is a 'private' attribute escaped: bool = False @@ -261,7 +259,7 @@ def _render_self(self) -> str: escaped=self.escaped, ) - def _render_hier(self) -> List[Tuple[int, str]]: + def _render_hier(self) -> list[tuple[int, str]]: res = [(0, self._render_self())] for ch in self.children: # TODO make sure there is a space?? @@ -307,7 +305,7 @@ def _from_lazy(x: Lazy[T]) -> T: return x -def maketrans(d: Dict[str, str]) -> Dict[int, str]: +def maketrans(d: dict[str, str]) -> dict[int, str]: # make mypy happy... https://github.com/python/mypy/issues/4374 return str.maketrans(d) diff --git a/src/orger/modules/auto.py b/src/orger/modules/auto.py index 3648f61..8cafe4f 100755 --- a/src/orger/modules/auto.py +++ b/src/orger/modules/auto.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 +from __future__ import annotations + from orger import Mirror from orger.inorganic import node, link, Quoted from orger.common import dt_heading, error from datetime import datetime -from typing import Optional, List, Iterator, Any +from typing import Iterator, Any from pprint import pformat import string @@ -22,12 +24,12 @@ class Auto(Mirror): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # TODO move this functionality to base module? might be useful for all modules - self.extra_warnings: List[str] = [] + self.extra_warnings: list[str] = [] # these will be set below - self.group_by_attr: Optional[str] = None - self.body_attr : Optional[str] = None - self.title_attr : Optional[str] = None + self.group_by_attr: str | None = None + self.body_attr : str | None = None + self.title_attr : str | None = None def get_items(self) -> Mirror.Results: @@ -91,7 +93,7 @@ def render_one(self, thing) -> Iterator[node]: cls = thing.__class__ datetimes = [(k, v) for k, v in thing_dict.items() if isinstance(v, datetime)] - dt: Optional[datetime] = None + dt: datetime | None = None if len(datetimes) == 1: [(k, dt)] = datetimes # todo maybe warn that datetime is guessed @@ -107,7 +109,7 @@ def render_one(self, thing) -> Iterator[node]: except KeyError as e: yield error(e) - def fmt_attr(attr: Optional[str]) -> Optional[str]: + def fmt_attr(attr: str | None) -> str | None: if attr is None: return None diff --git a/src/orger/modules/pinboard.py b/src/orger/modules/pinboard.py index 11aafce..b83fbe2 100755 --- a/src/orger/modules/pinboard.py +++ b/src/orger/modules/pinboard.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import List +from __future__ import annotations from orger import Mirror from orger.common import dt_heading, error @@ -10,7 +10,7 @@ class PinboardView(Mirror): def get_items(self) -> Mirror.Results: - items: List[pinboard.Bookmark] = [] + items: list[pinboard.Bookmark] = [] for b in pinboard.bookmarks(): if isinstance(b, Exception): yield error(b) diff --git a/src/orger/modules/zotero.py b/src/orger/modules/zotero.py index 0beb374..61404d7 100755 --- a/src/orger/modules/zotero.py +++ b/src/orger/modules/zotero.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 +from __future__ import annotations from orger import Mirror from orger.inorganic import node, link, docview_link, literal from orger.common import dt_heading, error -from typing import Optional from textwrap import indent, wrap from more_itertools import bucket @@ -77,7 +77,7 @@ def chit(): properties['ZOTERO_TAGS'] = ', '.join(a.tags) # not sure what's the best separator... # todo not sure about it... - mtodo: Optional[str] = None + mtodo: str | None = None if 'todo' in {t.lower() for t in tags}: mtodo = 'TODO' diff --git a/src/orger/org_view.py b/src/orger/org_view.py index 309d6e2..f9c32f9 100644 --- a/src/orger/org_view.py +++ b/src/orger/org_view.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import inspect import json @@ -6,7 +8,7 @@ from collections import Counter from pathlib import Path from subprocess import check_call -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Callable, Iterable, Tuple, Union from .atomic_append import assert_not_edited, atomic_append_check from .common import orger_user_dir @@ -21,20 +23,20 @@ OrgWithKey = Tuple[Key, OrgNode] -_style_map: Dict[str, TimestampStyle] = { +_style_map: dict[str, TimestampStyle] = { k.lower(): v # type: ignore[misc] for k, v in TimestampStyle._member_map_.items() } class OrgView: - logger_tag: Optional[str] = None + logger_tag: str | None = None DEFAULT_HEADER: str = '# should be overridden' # TODO cmdline args shouldn't be none? def __init__( - self, - cmdline_args: Optional[Namespace]=None, - file_header: Optional[str]=None, + self, + cmdline_args: Namespace | None = None, + file_header: str | None = None, ) -> None: self.cmdline_args: Namespace = cmdline_args if cmdline_args is not None else Namespace() tag = self.name() if self.logger_tag is None else self.logger_tag @@ -136,7 +138,7 @@ def _run(self, to: Path, *, stdout: bool) -> None: def make_tree(self) -> OrgNode: - items: List[OrgNode] = [] + items: list[OrgNode] = [] for p in self.get_items(): # it's ok to use items without keys in View if isinstance(p, OrgNode): @@ -156,9 +158,9 @@ def make_tree(self) -> OrgNode: ) @classmethod - def make_test(cls, *, heading: str, contains: Optional[str]=None) -> Callable[[], None]: + def make_test(cls, *, heading: str, contains: str | None = None) -> Callable[[], None]: from .inorganic import _from_lazy - def pick_heading(root: OrgNode, text: str) -> Optional[OrgNode]: + def pick_heading(root: OrgNode, text: str) -> OrgNode | None: if text in _from_lazy(root.heading): return root for ch in root.children: @@ -276,7 +278,7 @@ def main(cls, setup_parser=None) -> None: def test_org_view_overwrite(tmp_path: Path): class TestView(StaticView): - def __init__(self, items: List[OrgWithKey], *args, **kwargs) -> None: + def __init__(self, items: list[OrgWithKey], *args, **kwargs) -> None: super().__init__(*args, file_header='# autogenerated!\n#+TITLE: sometitle\nalso text\n', **kwargs) # type: ignore self.items = items @@ -309,7 +311,7 @@ def get_items(self): def test_org_view_append(tmp_path: Path) -> None: class TestView(Queue): - def __init__(self, items: List[OrgWithKey], *args, **kwargs) -> None: + def __init__(self, items: list[OrgWithKey], *args, **kwargs) -> None: super().__init__(*args, file_header='# autogenerated!', **kwargs) # type: ignore self.items = items diff --git a/src/orger/state.py b/src/orger/state.py index 7e17900..fa5ffec 100644 --- a/src/orger/state.py +++ b/src/orger/state.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import json import logging import os import sys import warnings from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable, Dict, List, Union from atomicwrites import atomic_write # type: ignore[import-untyped] @@ -20,7 +22,7 @@ def __init__( path: PathIsh, *, dry_run: bool = False, - default: Optional[State] = None, + default: State | None = None, logger: logging.Logger=logging.getLogger('orger'), # noqa: B008 ) -> None: self.path = Path(path) @@ -30,7 +32,7 @@ def __init__( default = {} self.default = default - self.state: Optional[State] = None + self.state: State | None = None self.logger = logger # TODO for simplicity, write empty if file doesn't exist??