diff --git a/my/bluemaestro.py b/my/bluemaestro.py index ee85f21..b50c77c 100644 --- a/my/bluemaestro.py +++ b/my/bluemaestro.py @@ -40,7 +40,7 @@ class Measurement: # fixme: later, rely on the timezone provider # NOTE: the timezone should be set with respect to the export date!!! -import pytz # type: ignore +import pytz tz = pytz.timezone('Europe/London') # TODO when I change tz, check the diff diff --git a/my/body/blood.py b/my/body/blood.py index e282068..fb035eb 100644 --- a/my/body/blood.py +++ b/my/body/blood.py @@ -9,7 +9,7 @@ from ..core.error import Res from ..core.orgmode import parse_org_datetime, one_table -import pandas as pd # type: ignore +import pandas as pd import orgparse diff --git a/my/body/exercise/all.py b/my/body/exercise/all.py index 4fee9d3..e86a5af 100644 --- a/my/body/exercise/all.py +++ b/my/body/exercise/all.py @@ -10,7 +10,7 @@ def dataframe() -> DataFrameT: from ...endomondo import dataframe as EDF from ...runnerup import dataframe as RDF - import pandas as pd # type: ignore + import pandas as pd return pd.concat([ EDF(), RDF(), diff --git a/my/body/exercise/cross_trainer.py b/my/body/exercise/cross_trainer.py index b25985c..d073f43 100644 --- a/my/body/exercise/cross_trainer.py +++ b/my/body/exercise/cross_trainer.py @@ -78,7 +78,7 @@ def cross_trainer_manual_dataframe() -> DataFrameT: ''' Only manual org-mode entries ''' - import pandas as pd # type: ignore[import] + import pandas as pd df = pd.DataFrame(cross_trainer_data()) return df @@ -91,7 +91,7 @@ def dataframe() -> DataFrameT: ''' Attaches manually logged data (which Endomondo can't capture) and attaches it to Endomondo ''' - import pandas as pd # type: ignore[import] + import pandas as pd from ...endomondo import dataframe as EDF edf = EDF() diff --git a/my/body/sleep/common.py b/my/body/sleep/common.py index a07b3fa..7bc1021 100644 --- a/my/body/sleep/common.py +++ b/my/body/sleep/common.py @@ -8,7 +8,7 @@ class Combine: @cdf def dataframe(self, with_temperature: bool=True) -> DataFrameT: - import pandas as pd # type: ignore + import pandas as pd # todo include 'source'? df = pd.concat([m.dataframe() for m in self.modules]) diff --git a/my/body/weight.py b/my/body/weight.py index 659b759..def3e87 100644 --- a/my/body/weight.py +++ b/my/body/weight.py @@ -56,7 +56,7 @@ def from_orgmode() -> Iterator[Result]: def make_dataframe(data: Iterator[Result]): - import pandas as pd # type: ignore + import pandas as pd def it(): for e in data: if isinstance(e, Exception): diff --git a/my/coding/commits.py b/my/coding/commits.py index 7786055..67ee77d 100644 --- a/my/coding/commits.py +++ b/my/coding/commits.py @@ -38,8 +38,8 @@ def config() -> commits_cfg: ########################## -import git # type: ignore -from git.repo.fun import is_git_dir # type: ignore +import git +from git.repo.fun import is_git_dir log = LazyLogger(__name__, level='info') diff --git a/my/core/__main__.py b/my/core/__main__.py index 620cb5f..feb83bb 100644 --- a/my/core/__main__.py +++ b/my/core/__main__.py @@ -135,7 +135,7 @@ def config_ok() -> bool: # at this point 'my' should already be imported, so doesn't hurt to extract paths from it import my try: - paths: List[str] = list(my.__path__) # type: ignore[attr-defined] + paths: List[str] = list(my.__path__) except Exception as e: errors.append(e) error('failed to determine module import path') @@ -152,7 +152,7 @@ def config_ok() -> bool: ## check we're not using stub config import my.core try: - core_pkg_path = str(Path(my.core.__path__[0]).parent) # type: ignore[attr-defined] + core_pkg_path = str(Path(my.core.__path__[0]).parent) if str(cfg_path).startswith(core_pkg_path): error(f''' Seems that the stub config is used ({cfg_path}). This is likely not going to work. diff --git a/my/core/cachew.py b/my/core/cachew.py index 9959120..dbc4d49 100644 --- a/my/core/cachew.py +++ b/my/core/cachew.py @@ -30,7 +30,7 @@ def disabled_cachew() -> Iterator[None]: def _appdirs_cache_dir() -> Path: - import appdirs # type: ignore + import appdirs cd = Path(appdirs.user_cache_dir('my')) cd.mkdir(exist_ok=True, parents=True) return cd diff --git a/my/core/cfg.py b/my/core/cfg.py index 3cddcf7..f298e7f 100644 --- a/my/core/cfg.py +++ b/my/core/cfg.py @@ -21,7 +21,7 @@ def make_config(cls: Type[C], migration: Callable[[Attrs], Attrs]=lambda x: x) - if k in {f.name for f in fields(cls)} # type: ignore[arg-type] # see https://github.com/python/typing_extensions/issues/115 } # todo maybe return type here? - return cls(**params) # type: ignore[call-arg] + return cls(**params) F = TypeVar('F') diff --git a/my/core/common.py b/my/core/common.py index 090c564..0b3dc1e 100644 --- a/my/core/common.py +++ b/my/core/common.py @@ -3,6 +3,7 @@ from pathlib import Path from datetime import datetime import functools from contextlib import contextmanager +import sys import types from typing import Union, Callable, Dict, Iterable, TypeVar, Sequence, List, Optional, Any, cast, Tuple, TYPE_CHECKING, NoReturn import warnings @@ -21,13 +22,12 @@ def import_file(p: PathIsh, name: Optional[str] = None) -> types.ModuleType: assert spec is not None, f"Fatal error; Could not create module spec from {name} {p}" foo = importlib.util.module_from_spec(spec) loader = spec.loader; assert loader is not None - loader.exec_module(foo) # type: ignore[attr-defined] + loader.exec_module(foo) return foo def import_from(path: PathIsh, name: str) -> types.ModuleType: path = str(path) - import sys try: sys.path.append(path) import importlib @@ -94,7 +94,7 @@ def ensure_unique( def test_ensure_unique() -> None: - import pytest # type: ignore + import pytest assert list(ensure_unique([1, 2, 3], key=lambda i: i)) == [1, 2, 3] dups = [1, 2, 1, 4] @@ -432,7 +432,7 @@ def warn_if_empty(f): def wrapped(*args, **kwargs): res = f(*args, **kwargs) return _warn_iterable(res, f=f) - return wrapped # type: ignore + return wrapped # global state that turns on/off quick stats @@ -620,6 +620,10 @@ def assert_subpackage(name: str) -> None: assert name == '__main__' or 'my.core' in name, f'Expected module __name__ ({name}) to be __main__ or start with my.core' +from .compat import ParamSpec +_P = ParamSpec('_P') +_T = TypeVar('_T') + # https://stackoverflow.com/a/10436851/706389 from concurrent.futures import Future, Executor class DummyExecutor(Executor): @@ -627,26 +631,31 @@ class DummyExecutor(Executor): self._shutdown = False self._max_workers = max_workers - # TODO: once support for 3.7 is dropped, - # can make 'fn' a positional only parameter, - # which fixes the mypy error this throws without the type: ignore - def submit(self, fn, *args, **kwargs) -> Future: # type: ignore[override] - if self._shutdown: - raise RuntimeError('cannot schedule new futures after shutdown') - - f: Future[Any] = Future() - try: - result = fn(*args, **kwargs) - except KeyboardInterrupt: - raise - except BaseException as e: - f.set_exception(e) + if TYPE_CHECKING: + if sys.version_info[:2] <= (3, 8): + # 3.8 doesn't support ParamSpec as Callable arg :( + # and any attempt to type results in incompatible supertype.. so whatever + def submit(self, fn, *args, **kwargs): ... else: - f.set_result(result) + def submit(self, fn: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]: ... + else: + def submit(self, fn, *args, **kwargs): + if self._shutdown: + raise RuntimeError('cannot schedule new futures after shutdown') - return f + f: Future[Any] = Future() + try: + result = fn(*args, **kwargs) + except KeyboardInterrupt: + raise + except BaseException as e: + f.set_exception(e) + else: + f.set_result(result) - def shutdown(self, wait: bool=True) -> None: # type: ignore[override] + return f + + def shutdown(self, wait: bool=True, **kwargs) -> None: self._shutdown = True diff --git a/my/core/compat.py b/my/core/compat.py index 8bdb401..0b47bdd 100644 --- a/my/core/compat.py +++ b/my/core/compat.py @@ -86,7 +86,7 @@ else: def cached_property(f: Callable[[Cl], R]) -> R: import functools - return property(functools.lru_cache(maxsize=1)(f)) # type: ignore + return property(functools.lru_cache(maxsize=1)(f)) del Cl del R @@ -111,7 +111,7 @@ if sys.version_info[:2] >= (3, 8): from typing import Protocol else: if TYPE_CHECKING: - from typing_extensions import Protocol # type: ignore[misc] + from typing_extensions import Protocol else: # todo could also use NamedTuple? Protocol = object @@ -121,12 +121,29 @@ if sys.version_info[:2] >= (3, 8): from typing import TypedDict else: if TYPE_CHECKING: - from typing_extensions import TypedDict # type: ignore[misc] + from typing_extensions import TypedDict else: from typing import Dict TypedDict = Dict +if sys.version_info[:2] >= (3, 10): + from typing import ParamSpec +else: + if TYPE_CHECKING: + from typing_extensions import ParamSpec + else: + from typing import NamedTuple, Any + # erm.. I guess as long as it's not crashing, whatever... + class _ParamSpec: + def __call__(self, args): + class _res: + args = None + kwargs = None + return _res + ParamSpec = _ParamSpec() + + # bisect_left doesn't have a 'key' parameter (which we use) # till python3.10 if sys.version_info[:2] <= (3, 9): @@ -156,4 +173,4 @@ if sys.version_info[:2] <= (3, 9): hi = mid return lo else: - from bisect import bisect_left # type: ignore[misc] + from bisect import bisect_left diff --git a/my/core/core_config.py b/my/core/core_config.py index f87a1ba..5c696ce 100644 --- a/my/core/core_config.py +++ b/my/core/core_config.py @@ -7,16 +7,16 @@ from typing import Sequence, Optional from . import warnings, PathIsh, Path try: - from my.config import core as user_config # type: ignore[attr-defined] + from my.config import core as user_config # type: ignore[attr-defined] except Exception as e: try: - from my.config import common as user_config # type: ignore[attr-defined, assignment, misc] + from my.config import common as user_config # type: ignore[attr-defined] warnings.high("'common' config section is deprecated. Please rename it to 'core'.") except Exception as e2: # make it defensive, because it's pretty commonly used and would be annoying if it breaks hpi doctor etc. # this way it'll at least use the defaults # todo actually not sure if needs a warning? Perhaps it's okay without it, because the defaults are reasonable enough - user_config = object # type: ignore[assignment, misc] + user_config = object _HPI_CACHE_DIR_DEFAULT = '' diff --git a/my/core/discovery_pure.py b/my/core/discovery_pure.py index c88ef1c..85b75ab 100644 --- a/my/core/discovery_pure.py +++ b/my/core/discovery_pure.py @@ -144,7 +144,7 @@ def all_modules() -> Iterable[HPIModule]: def _iter_my_roots() -> Iterable[Path]: import my # doesn't import any code, because of namespace package - paths: List[str] = list(my.__path__) # type: ignore[attr-defined] + paths: List[str] = list(my.__path__) if len(paths) == 0: # should probably never happen?, if this code is running, it was imported # because something was added to __path__ to match this name diff --git a/my/core/error.py b/my/core/error.py index 09c1733..236bd30 100644 --- a/my/core/error.py +++ b/my/core/error.py @@ -125,7 +125,7 @@ def test_sort_res_by() -> None: 1, Exc('last'), ] - results = sort_res_by(ress, lambda x: int(x)) # type: ignore + results = sort_res_by(ress, lambda x: int(x)) assert results == [ 1, 'bad', @@ -137,11 +137,11 @@ def test_sort_res_by() -> None: Exc('last'), ] - results2 = sort_res_by(ress + [0], lambda x: int(x)) # type: ignore + results2 = sort_res_by(ress + [0], lambda x: int(x)) assert results2 == [Exc('last'), 0] + results[:-1] assert sort_res_by(['caba', 'a', 'aba', 'daba'], key=lambda x: len(x)) == ['a', 'aba', 'caba', 'daba'] - assert sort_res_by([], key=lambda x: x) == [] # type: ignore + assert sort_res_by([], key=lambda x: x) == [] # helpers to associate timestamps with the errors (so something meaningful could be displayed on the plots, for example) @@ -215,7 +215,7 @@ See {help_url}\ if hasattr(err, 'obj') and hasattr(err, "name"): config_obj = cast(object, getattr(err, 'obj')) # the object that caused the attribute error # e.g. active_browser for my.browser - nested_block_name = err.name # type: ignore[attr-defined] + nested_block_name = err.name if config_obj.__module__ == 'my.config': click.secho(f"""You're likely missing the nested config block for '{getattr(config_obj, '__name__', str(config_obj))}.{nested_block_name}'. See {help_url} or check the corresponding module.py file for an example\ diff --git a/my/core/kompress.py b/my/core/kompress.py index 5ba32d3..8ee1cfa 100644 --- a/my/core/kompress.py +++ b/my/core/kompress.py @@ -82,7 +82,7 @@ def kopen(path: PathIsh, *args, mode: str='rt', **kwargs) -> IO: ifile.read1 = ifile.read # type: ignore # TODO pass all kwargs here?? # todo 'expected "BinaryIO"'?? - return io.TextIOWrapper(ifile, encoding=encoding) # type: ignore[arg-type] + return io.TextIOWrapper(ifile, encoding=encoding) elif name.endswith(Ext.lz4): import lz4.frame # type: ignore return lz4.frame.open(str(pp), mode, *args, **kwargs) @@ -95,7 +95,7 @@ def kopen(path: PathIsh, *args, mode: str='rt', **kwargs) -> IO: tf = tarfile.open(pp) # TODO pass encoding? x = tf.extractfile(*args); assert x is not None - return x # type: ignore[return-value] + return x else: return pp.open(mode, *args, **kwargs) @@ -209,7 +209,7 @@ class ZipPath(zipfile_Path): def __truediv__(self, key) -> ZipPath: # need to implement it so the return type is not zipfile.Path tmp = zipfile_Path(self.root) / self.at / key - return ZipPath(self.root, tmp.at) # type: ignore[attr-defined] + return ZipPath(self.root, tmp.at) def iterdir(self) -> Iterator[ZipPath]: for s in self._as_dir().iterdir(): diff --git a/my/core/konsume.py b/my/core/konsume.py index b4cf7b6..588bfe1 100644 --- a/my/core/konsume.py +++ b/my/core/konsume.py @@ -19,7 +19,7 @@ def zoom(w, *keys): # TODO need to support lists class Zoomable: def __init__(self, parent, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) # type: ignore + super().__init__(*args, **kwargs) self.parent = parent # TODO not sure, maybe do it via del?? @@ -147,7 +147,7 @@ Expected {c} to be fully consumed by the parser. from typing import cast def test_unconsumed() -> None: - import pytest # type: ignore + import pytest with pytest.raises(UnconsumedError): with wrap({'a': 1234}) as w: w = cast(Wdict, w) @@ -200,7 +200,7 @@ def test_consume_few() -> None: def test_zoom() -> None: - import pytest # type: ignore + import pytest with wrap({'aaa': 'whatever'}) as w: w = cast(Wdict, w) with pytest.raises(KeyError): diff --git a/my/core/logging.py b/my/core/logging.py index a948dd8..e7061fa 100644 --- a/my/core/logging.py +++ b/my/core/logging.py @@ -62,20 +62,21 @@ def setup_logger(logger: logging.Logger, level: LevelIsh) -> None: lvl = mklevel(level) try: import logzero # type: ignore[import] + except ModuleNotFoundError: + warnings.warn("You might want to install 'logzero' for nice colored logs!") + formatter = logging.Formatter(fmt=FORMAT_NOCOLOR, datefmt=DATEFMT) + use_logzero = False + else: formatter = logzero.LogFormatter( fmt=FORMAT_COLOR, datefmt=DATEFMT, ) use_logzero = True - except ModuleNotFoundError: - warnings.warn("You might want to install 'logzero' for nice colored logs!") - formatter = logging.Formatter(fmt=FORMAT_NOCOLOR, datefmt=DATEFMT) - use_logzero = False logger.addFilter(AddExceptionTraceback()) if use_logzero and not COLLAPSE_DEBUG_LOGS: # all set, nothing to do # 'simple' setup - logzero.setup_logger(logger.name, level=lvl, formatter=formatter) + logzero.setup_logger(logger.name, level=lvl, formatter=formatter) # type: ignore[possibly-undefined] return h = CollapseDebugHandler() if COLLAPSE_DEBUG_LOGS else logging.StreamHandler() @@ -101,7 +102,7 @@ class LazyLogger(logging.Logger): # oh god.. otherwise might go into an inf loop if not hasattr(logger, _init_done): setattr(logger, _init_done, False) # will setup on the first call - logger.isEnabledFor = isEnabledFor_lazyinit # type: ignore[assignment] + logger.isEnabledFor = isEnabledFor_lazyinit # type: ignore[method-assign] return cast(LazyLogger, logger) diff --git a/my/core/pandas.py b/my/core/pandas.py index 8ccacd2..ee4bcff 100644 --- a/my/core/pandas.py +++ b/my/core/pandas.py @@ -15,7 +15,7 @@ logger = LazyLogger(__name__) if TYPE_CHECKING: # this is kinda pointless at the moment, but handy to annotate DF returning methods now # later will be unignored when they implement type annotations - import pandas as pd # type: ignore + import pandas as pd # DataFrameT = pd.DataFrame # TODO ugh. pretty annoying, having any is not very useful since it would allow arbitrary coercions.. # ideally want to use a type that's like Any but doesn't allow arbitrary coercions?? @@ -26,7 +26,7 @@ else: def check_dateish(s) -> Iterable[str]: - import pandas as pd # type: ignore # noqa: F811 not actually a redefinition + import pandas as pd # noqa: F811 not actually a redefinition ctype = s.dtype if str(ctype).startswith('datetime64'): return diff --git a/my/core/preinit.py b/my/core/preinit.py index c05ee40..9d6b374 100644 --- a/my/core/preinit.py +++ b/my/core/preinit.py @@ -1,7 +1,7 @@ from pathlib import Path def get_mycfg_dir() -> Path: - import appdirs # type: ignore[import] + import appdirs import os # not sure if that's necessary, i.e. could rely on PYTHONPATH instead # on the other hand, by using MY_CONFIG we are guaranteed to load it from the desired path? diff --git a/my/core/query.py b/my/core/query.py index 8a497db..f78a1f7 100644 --- a/my/core/query.py +++ b/my/core/query.py @@ -495,7 +495,7 @@ Will attempt to call iter() on the value""") unsortable, itr = _handle_unsorted(itr, order_by_chosen, drop_unsorted, wrap_unsorted) # run the sort, with the computed order by function - itr = iter(sorted(itr, key=order_by_chosen, reverse=reverse)) # type: ignore[arg-type, type-var] + itr = iter(sorted(itr, key=order_by_chosen, reverse=reverse)) # type: ignore[arg-type] # re-attach unsortable values to the front/back of the list if reverse: diff --git a/my/core/query_range.py b/my/core/query_range.py index 33eb03c..3fdc12e 100644 --- a/my/core/query_range.py +++ b/my/core/query_range.py @@ -40,7 +40,7 @@ def parse_timedelta_string(timedelta_str: str) -> timedelta: if parts is None: raise ValueError(f"Could not parse time duration from {timedelta_str}.\nValid examples: '8h', '1w2d8h5m20s', '2m4s'") time_params = {name: float(param) for name, param in parts.groupdict().items() if param} - return timedelta(**time_params) # type: ignore[arg-type] + return timedelta(**time_params) def parse_timedelta_float(timedelta_str: str) -> float: @@ -83,7 +83,7 @@ def parse_datetime_float(date_str: str) -> float: pass try: - import dateparser # type: ignore[import] + import dateparser except ImportError: pass else: diff --git a/my/core/serialize.py b/my/core/serialize.py index ca68fef..1ef7bc0 100644 --- a/my/core/serialize.py +++ b/my/core/serialize.py @@ -188,8 +188,8 @@ def test_nt_serialize() -> None: # test orjson option kwarg data = {datetime.date(year=1970, month=1, day=1): 5} - res = jsn.loads(dumps(data, option=orjson.OPT_NON_STR_KEYS)) - assert res == {'1970-01-01': 5} + res2 = jsn.loads(dumps(data, option=orjson.OPT_NON_STR_KEYS)) + assert res2 == {'1970-01-01': 5} def test_default_serializer() -> None: diff --git a/my/core/sqlite.py b/my/core/sqlite.py index 80dbc3f..e712a77 100644 --- a/my/core/sqlite.py +++ b/my/core/sqlite.py @@ -22,7 +22,7 @@ def test_sqlite_connect_immutable(tmp_path: Path) -> None: with sqlite3.connect(db) as conn: conn.execute('CREATE TABLE testtable (col)') - import pytest # type: ignore + import pytest with pytest.raises(sqlite3.OperationalError, match='readonly database'): with sqlite_connect_immutable(db) as conn: conn.execute('DROP TABLE testtable') diff --git a/my/core/util.py b/my/core/util.py index f12b578..1ca2de1 100644 --- a/my/core/util.py +++ b/my/core/util.py @@ -62,7 +62,7 @@ def _iter_all_importables(pkg: ModuleType) -> Iterable[HPIModule]: _discover_path_importables(Path(p), pkg.__name__) # todo might need to handle __path__ for individual modules too? # not sure why __path__ was duplicated, but it did happen.. - for p in set(pkg.__path__) # type: ignore[attr-defined] + for p in set(pkg.__path__) ) diff --git a/my/emfit/__init__.py b/my/emfit/__init__.py index 0a1eb73..acaa303 100644 --- a/my/emfit/__init__.py +++ b/my/emfit/__init__.py @@ -133,7 +133,7 @@ def dataframe() -> DataFrameT: dicts.append(d) - import pandas # type: ignore + import pandas return pandas.DataFrame(dicts) diff --git a/my/endomondo.py b/my/endomondo.py index 0fa396f..d314e97 100644 --- a/my/endomondo.py +++ b/my/endomondo.py @@ -66,7 +66,7 @@ def dataframe(defensive: bool=True) -> DataFrameT: # todo check for 'defensive' d = {'error': f'{e} {w}'} yield d - import pandas as pd # type: ignore + import pandas as pd df = pd.DataFrame(it()) # pandas guesses integer, which is pointless for this field (might get coerced to float too) df['id'] = df['id'].astype(str) diff --git a/my/github/gdpr.py b/my/github/gdpr.py index c41fb6c..6f7efe4 100644 --- a/my/github/gdpr.py +++ b/my/github/gdpr.py @@ -133,7 +133,7 @@ def _parse_repository(d: Dict) -> Event: rt = d['type'] assert url.startswith(pref); name = url[len(pref):] eid = EventIds.repo_created(dts=dts, name=name, ref_type=rt, ref=None) - return Event( # type: ignore[misc] + return Event( **_parse_common(d), summary='created ' + name, eid=eid, @@ -143,7 +143,7 @@ def _parse_repository(d: Dict) -> Event: def _parse_issue_comment(d: Dict) -> Event: url = d['url'] is_bot = "[bot]" in d["user"] - return Event( # type: ignore[misc] + return Event( **_parse_common(d), summary=f'commented on issue {url}', eid='issue_comment_' + url, @@ -155,7 +155,7 @@ def _parse_issue(d: Dict) -> Event: url = d['url'] title = d['title'] is_bot = "[bot]" in d["user"] - return Event( # type: ignore[misc] + return Event( **_parse_common(d), summary=f'opened issue {title}', eid='issue_comment_' + url, @@ -168,7 +168,7 @@ def _parse_pull_request(d: Dict) -> Event: url = d['url'] title = d['title'] is_bot = "[bot]" in d["user"] - return Event( # type: ignore[misc] + return Event( **_parse_common(d), # TODO distinguish incoming/outgoing? # TODO action? opened/closed?? @@ -195,7 +195,7 @@ def _parse_project(d: Dict) -> Event: def _parse_release(d: Dict) -> Event: tag = d['tag_name'] - return Event( # type: ignore[misc] + return Event( **_parse_common(d), summary=f'released {tag}', eid='release_' + tag, @@ -204,7 +204,7 @@ def _parse_release(d: Dict) -> Event: def _parse_commit_comment(d: Dict) -> Event: url = d['url'] - return Event( # type: ignore[misc] + return Event( **_parse_common(d), summary=f'commented on {url}', eid='commit_comment_' + url, diff --git a/my/location/fallback/via_ip.py b/my/location/fallback/via_ip.py index 303074f..f637552 100644 --- a/my/location/fallback/via_ip.py +++ b/my/location/fallback/via_ip.py @@ -71,7 +71,7 @@ def estimate_location(dt: DateExact) -> Iterator[FallbackLocation]: # search to find the first possible location which contains dt (something that started up to # config.for_duration ago, and ends after dt) - idx = bisect_left(fl, dt_ts - config.for_duration.total_seconds(), key=lambda l: l.dt.timestamp()) # type: ignore[operator,call-arg,type-var] + idx = bisect_left(fl, dt_ts - config.for_duration.total_seconds(), key=lambda l: l.dt.timestamp()) # all items are before the given dt if idx == len(fl): diff --git a/my/location/gpslogger.py b/my/location/gpslogger.py index 46fc381..17f828f 100644 --- a/my/location/gpslogger.py +++ b/my/location/gpslogger.py @@ -22,7 +22,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import Iterator, Sequence, List -import gpxpy # type: ignore[import] +import gpxpy from more_itertools import unique_everseen from my.core import Stats, LazyLogger diff --git a/my/orgmode.py b/my/orgmode.py index d6d31d2..bb186d1 100644 --- a/my/orgmode.py +++ b/my/orgmode.py @@ -36,7 +36,7 @@ _rgx = re.compile(orgparse.date.gene_timestamp_regex(brtype='inactive'), re.VERB def _created(n: orgparse.OrgNode) -> Tuple[Optional[datetime], str]: heading = n.heading # meh.. support in orgparse? - pp = {} if n.is_root() else n.properties # type: ignore + pp = {} if n.is_root() else n.properties createds = pp.get('CREATED', None) if createds is None: # try to guess from heading diff --git a/my/photos/utils.py b/my/photos/utils.py index 8c16dc5..c614c4a 100644 --- a/my/photos/utils.py +++ b/my/photos/utils.py @@ -1,8 +1,8 @@ from pathlib import Path from typing import Dict -import PIL.Image # type: ignore -from PIL.ExifTags import TAGS, GPSTAGS # type: ignore +import PIL.Image +from PIL.ExifTags import TAGS, GPSTAGS Exif = Dict diff --git a/my/reddit/rexport.py b/my/reddit/rexport.py index a8ce651..b1f9e3b 100644 --- a/my/reddit/rexport.py +++ b/my/reddit/rexport.py @@ -224,9 +224,9 @@ def events(*args, **kwargs) -> List[Event]: inp = inputs() # 2.2s for 300 files without cachew # 0.2s for 300 files with cachew - evit = _get_events(inp, *args, **kwargs) # type: ignore[call-arg] + evit = _get_events(inp, *args, **kwargs) # todo mypy is confused here and thinks it's iterable of Path? perhaps something to do with mcachew? - return list(sorted(evit, key=lambda e: e.cmp_key)) # type: ignore[attr-defined,arg-type] + return list(sorted(evit, key=lambda e: e.cmp_key)) def stats() -> Stats: diff --git a/my/rss/feedbin.py b/my/rss/feedbin.py index 4cd1b8d..8ba25b8 100644 --- a/my/rss/feedbin.py +++ b/my/rss/feedbin.py @@ -33,7 +33,7 @@ from typing import Iterable from .common import SubscriptionState def states() -> Iterable[SubscriptionState]: # meh - from dateutil.parser import isoparse # type: ignore + from dateutil.parser import isoparse for f in inputs(): # TODO ugh. depends on my naming. not sure if useful? dts = f.stem.split('_')[-1] diff --git a/my/runnerup.py b/my/runnerup.py index 6140236..1f20525 100644 --- a/my/runnerup.py +++ b/my/runnerup.py @@ -78,7 +78,7 @@ def dataframe() -> DataFrameT: yield error_to_row(w) else: yield w - import pandas as pd # type: ignore + import pandas as pd df = pd.DataFrame(it()) if 'error' not in df: df['error'] = None diff --git a/my/smscalls.py b/my/smscalls.py index c383a36..dbcf8b2 100644 --- a/my/smscalls.py +++ b/my/smscalls.py @@ -22,7 +22,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import NamedTuple, Iterator, Set, Tuple, Optional -from lxml import etree # type: ignore +from lxml import etree from my.core.common import get_files, Stats from my.core.error import Res diff --git a/my/time/tz/via_location.py b/my/time/tz/via_location.py index 7716be0..1ed1ba7 100644 --- a/my/time/tz/via_location.py +++ b/my/time/tz/via_location.py @@ -65,7 +65,7 @@ logger = LazyLogger(__name__, level='warning') def _timezone_finder(fast: bool) -> Any: if fast: # less precise, but faster - from timezonefinder import TimezoneFinderL as Finder # type: ignore + from timezonefinder import TimezoneFinderL as Finder else: from timezonefinder import TimezoneFinder as Finder # type: ignore return Finder(in_memory=True) @@ -158,7 +158,7 @@ def _iter_local_dates_fallback() -> Iterator[DayWithZone]: def most_common(lst: List[DayWithZone]) -> DayWithZone: - res, _ = Counter(lst).most_common(1)[0] # type: ignore[var-annotated] + res, _ = Counter(lst).most_common(1)[0] return res diff --git a/my/twitter/archive.py b/my/twitter/archive.py index bdd1497..3f56fa0 100644 --- a/my/twitter/archive.py +++ b/my/twitter/archive.py @@ -12,7 +12,7 @@ except ImportError as ie: # must be caused by something else raise ie try: - from my.config import twitter as user_config # type: ignore[misc,assignment] + from my.config import twitter as user_config # type: ignore[assignment] except ImportError: raise ie # raise the original exception.. must be something else else: diff --git a/mypy.ini b/mypy.ini index bc85b74..ebc81a5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,9 +1,19 @@ [mypy] +namespace_packages = True pretty = True show_error_context = True show_error_codes = True +show_column_numbers = True +show_error_end = True +warn_unused_ignores = True check_untyped_defs = True -namespace_packages = True +enable_error_code = possibly-undefined +strict_equality = True + +# a bit annoying, it has optional ipython import which should be ignored in mypy-core configuration.. +[mypy-my.core.__main__] +warn_unused_ignores = False + # todo ok, maybe it wasn't such a good idea.. # mainly because then tox picks it up and running against the user config, not the repository config # mypy_path=~/.config/my diff --git a/tests/calendar.py b/tests/calendar.py index f897efe..3435da3 100644 --- a/tests/calendar.py +++ b/tests/calendar.py @@ -1,6 +1,6 @@ from pathlib import Path -import pytest # type: ignore +import pytest from my.calendar.holidays import is_holiday diff --git a/tests/config.py b/tests/config.py index cef3787..e69f726 100644 --- a/tests/config.py +++ b/tests/config.py @@ -23,7 +23,7 @@ def test_dynamic_configuration(notes: Path) -> None: 0.0, ] -import pytest # type: ignore +import pytest def test_environment_variable(tmp_path: Path) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 334cd19..4e67f71 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -import pytest # type: ignore +import pytest # I guess makes sense by default @pytest.fixture(autouse=True) diff --git a/tests/core/test_kompress.py b/tests/core/test_kompress.py index 0e7d71b..1f37c34 100644 --- a/tests/core/test_kompress.py +++ b/tests/core/test_kompress.py @@ -6,7 +6,7 @@ import zipfile from my.core.kompress import kopen, kexists, CPath -import pytest # type: ignore +import pytest structure_data: Path = Path(__file__).parent / "structure_data" diff --git a/tests/demo.py b/tests/demo.py index 6ac937c..73a6c65 100644 --- a/tests/demo.py +++ b/tests/demo.py @@ -38,7 +38,7 @@ def test_dynamic_config_2(tmp_path: Path) -> None: assert item1.username == 'user2' -import pytest # type: ignore +import pytest @pytest.mark.skip(reason="won't work at the moment because of inheritance") def test_dynamic_config_simplenamespace(tmp_path: Path) -> None: diff --git a/tests/extra/polar.py b/tests/extra/polar.py index 1091f2a..b2bc562 100644 --- a/tests/extra/polar.py +++ b/tests/extra/polar.py @@ -7,7 +7,7 @@ ROOT = Path(__file__).parent.absolute() OUTPUTS = ROOT / 'outputs' -import pytest # type: ignore +import pytest def test_hpi(prepare: str) -> None: @@ -19,7 +19,7 @@ def test_orger(prepare: str, tmp_path: Path) -> None: om = import_file(ROOT / 'orger/modules/polar.py') # reload(om) - pv = om.PolarView() # type: ignore + pv = om.PolarView() # TODO hmm. worth making public? OUTPUTS.mkdir(exist_ok=True) out = OUTPUTS / (get_valid_filename(prepare) + '.org') diff --git a/tests/get_files.py b/tests/get_files.py index a81b34f..daeef71 100644 --- a/tests/get_files.py +++ b/tests/get_files.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from my.core.compat import windows from my.core.common import get_files -import pytest # type: ignore +import pytest # hack to replace all /tmp with 'real' tmp dir diff --git a/tests/location.py b/tests/location.py index c47849e..2597d5e 100644 --- a/tests/location.py +++ b/tests/location.py @@ -1,6 +1,6 @@ from pathlib import Path -import pytest # type: ignore +import pytest def test() -> None: diff --git a/tests/pdfs.py b/tests/pdfs.py index 343a209..1c5eab8 100644 --- a/tests/pdfs.py +++ b/tests/pdfs.py @@ -49,7 +49,7 @@ def with_config(): import my.core.cfg as C with C.tmp_config() as config: - config.pdfs = user_config # type: ignore + config.pdfs = user_config try: yield finally: diff --git a/tests/reddit.py b/tests/reddit.py index d18b18d..6e3e65e 100644 --- a/tests/reddit.py +++ b/tests/reddit.py @@ -64,7 +64,7 @@ def test_preserves_extra_attr() -> None: assert isinstance(getattr(config, 'please_keep_me'), str) -import pytest # type: ignore +import pytest @pytest.fixture(autouse=True, scope='module') def prepare(): from .common import testdata diff --git a/tests/takeout.py b/tests/takeout.py index 7cc2164..a40e218 100644 --- a/tests/takeout.py +++ b/tests/takeout.py @@ -18,7 +18,7 @@ def test_location_perf() -> None: # in theory should support any HTML takeout file? # although IIRC bookmarks and search-history.html weren't working -import pytest # type: ignore +import pytest @pytest.mark.parametrize( 'path', [ 'YouTube/history/watch-history.html', diff --git a/tests/test_tmp_config.py b/tests/test_tmp_config.py index 197d3f7..b22f9cf 100644 --- a/tests/test_tmp_config.py +++ b/tests/test_tmp_config.py @@ -10,7 +10,7 @@ def _init_default_config() -> None: import my.config class default_config: count = 5 - my.config.simple = default_config # type: ignore[attr-defined,assignment,misc] + my.config.simple = default_config # type: ignore[assignment,misc] def test_tmp_config() -> None: diff --git a/tests/tz.py b/tests/tz.py index f2498a2..d86c5cb 100644 --- a/tests/tz.py +++ b/tests/tz.py @@ -2,8 +2,8 @@ import sys from datetime import datetime, timedelta from pathlib import Path -import pytest # type: ignore -import pytz # type: ignore +import pytest +import pytz from my.core.error import notnone