diff --git a/my/bumble/android.py b/my/bumble/android.py index 3a159da..54a0441 100644 --- a/my/bumble/android.py +++ b/my/bumble/android.py @@ -54,9 +54,10 @@ class Message(_BaseMessage): import json from typing import Union -from ..core import Res, assert_never +from ..core import Res import sqlite3 from ..core.sqlite import sqlite_connect_immutable, select +from my.core.compat import assert_never EntitiesRes = Res[Union[Person, _Message]] diff --git a/my/codeforces.py b/my/codeforces.py index 7b37ec6..f2d150a 100644 --- a/my/codeforces.py +++ b/my/codeforces.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Dict, Iterator, Sequence from my.core import get_files, Res, datetime_aware -from my.core.common import assert_never from my.config import codeforces as config # type: ignore[attr-defined] @@ -73,7 +72,7 @@ class Parser: # these contain only contests the user participated in yield from self._parse_competitions(path) else: - raise RuntimeError("shouldn't happen") # TODO switch to compat.assert_never + raise RuntimeError(f"shouldn't happen: {path.name}") def data() -> Iterator[Res[Competition]]: diff --git a/my/core/__init__.py b/my/core/__init__.py index d753760..c79e36e 100644 --- a/my/core/__init__.py +++ b/my/core/__init__.py @@ -4,7 +4,7 @@ from .common import Json from .common import warn_if_empty from .common import stat, Stats from .common import datetime_naive, datetime_aware -from .common import assert_never +from .compat import assert_never from .cfg import make_config from .error import Res, unwrap @@ -26,7 +26,7 @@ __all__ = [ 'warn_if_empty', 'stat', 'Stats', 'datetime_aware', 'datetime_naive', - 'assert_never', + 'assert_never', # TODO maybe deprecate from use in my.core? will be in stdlib soon 'make_config', @@ -34,7 +34,7 @@ __all__ = [ 'Res', 'unwrap', - 'dataclass', 'Path', + 'dataclass', 'Path', # TODO deprecate these from use in my.core ] diff --git a/my/core/common.py b/my/core/common.py index 98dbacb..9874bed 100644 --- a/my/core/common.py +++ b/my/core/common.py @@ -27,7 +27,10 @@ from typing import ( get_origin, ) import warnings + from . import warnings as core_warnings +from . import compat +from .compat import deprecated # some helper functions PathIsh = Union[Path, str] @@ -633,11 +636,6 @@ class DummyExecutor(Executor): self._shutdown = True -# see https://hakibenita.com/python-mypy-exhaustive-checking#exhaustiveness-checking -def assert_never(value: NoReturn) -> NoReturn: - assert False, f'Unhandled value: {value} ({type(value).__name__})' - - def _check_all_hashable(fun): # TODO ok, take callable? hints = get_type_hints(fun) @@ -693,6 +691,13 @@ def unique_everseen( ## legacy imports, keeping them here for backwards compatibility +## hiding behind TYPE_CHECKING so it works in runtime +## in principle, warnings.deprecated decorator should cooperate with mypy, but doesn't look like it works atm? +## perhaps it doesn't work when it's used from typing_extensions +if not TYPE_CHECKING: + assert_never = deprecated('use my.core.compat.assert_never instead')(compat.assert_never) + +# TODO wrap in deprecated decorator as well? from functools import cached_property as cproperty from typing import Literal from .cachew import mcachew diff --git a/my/core/compat.py b/my/core/compat.py index e984695..2c1687d 100644 --- a/my/core/compat.py +++ b/my/core/compat.py @@ -121,3 +121,15 @@ if sys.version_info[:2] >= (3, 10): from types import NoneType else: NoneType = type(None) + + +if sys.version_info[:2] >= (3, 13): + from warnings import deprecated +else: + from typing_extensions import deprecated + + +if sys.version_info[:2] >= (3, 11): + from typing import assert_never +else: + from typing_extensions import assert_never diff --git a/my/core/sqlite.py b/my/core/sqlite.py index e04f6fc..2580e15 100644 --- a/my/core/sqlite.py +++ b/my/core/sqlite.py @@ -9,7 +9,8 @@ from tempfile import TemporaryDirectory from typing import Tuple, Any, Iterator, Callable, Optional, Union, Literal -from .common import PathIsh, assert_never +from .common import PathIsh +from .compat import assert_never def sqlite_connect_immutable(db: PathIsh) -> sqlite3.Connection: diff --git a/my/experimental/destructive_parsing.py b/my/experimental/destructive_parsing.py index 3fc739c..05c5920 100644 --- a/my/experimental/destructive_parsing.py +++ b/my/experimental/destructive_parsing.py @@ -1,8 +1,7 @@ from dataclasses import dataclass from typing import Any, Iterator, List, Tuple -from my.core import assert_never -from my.core.compat import NoneType +from my.core.compat import NoneType, assert_never # TODO Popper? not sure diff --git a/my/fbmessenger/android.py b/my/fbmessenger/android.py index 8a4bf4c..bc06114 100644 --- a/my/fbmessenger/android.py +++ b/my/fbmessenger/android.py @@ -10,8 +10,9 @@ from pathlib import Path import sqlite3 from typing import Iterator, Sequence, Optional, Dict, Union, List -from my.core import get_files, Paths, datetime_aware, Res, assert_never, LazyLogger, make_config +from my.core import get_files, Paths, datetime_aware, Res, LazyLogger, make_config from my.core.common import unique_everseen +from my.core.compat import assert_never from my.core.error import echain from my.core.sqlite import sqlite_connection diff --git a/my/tinder/android.py b/my/tinder/android.py index 56ee1cb..d9b256b 100644 --- a/my/tinder/android.py +++ b/my/tinder/android.py @@ -11,8 +11,9 @@ from pathlib import Path import sqlite3 from typing import Sequence, Iterator, Union, Dict, List, Mapping -from my.core import Paths, get_files, Res, assert_never, stat, Stats, datetime_aware, make_logger +from my.core import Paths, get_files, Res, stat, Stats, datetime_aware, make_logger from my.core.common import unique_everseen +from my.core.compat import assert_never from my.core.error import echain from my.core.sqlite import sqlite_connection import my.config diff --git a/setup.py b/setup.py index ab96616..cf4b79f 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ from setuptools import setup, find_namespace_packages # type: ignore INSTALL_REQUIRES = [ 'pytz', # even though it's not needed by the core, it's so common anyway... + 'typing-extensions', # one of the most common pypi packages, ok to depend for core 'appdirs', # very common, and makes it portable 'more-itertools', # it's just too useful and very common anyway 'decorator' , # less pain in writing correct decorators. very mature and stable, so worth keeping in core