tmp
This commit is contained in:
parent
bc7c3ac253
commit
d1511929a8
33 changed files with 117 additions and 117 deletions
|
@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
|
|||
from .cfg import make_config
|
||||
from .common import PathIsh, Paths, get_files
|
||||
from .compat import assert_never
|
||||
from .error import Res, unwrap, notnone
|
||||
from .error import Res, notnone, unwrap
|
||||
from .logging import (
|
||||
make_logger,
|
||||
)
|
||||
|
|
|
@ -7,11 +7,12 @@ import shutil
|
|||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
from collections.abc import Iterable, Sequence
|
||||
from contextlib import ExitStack
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, CompletedProcess, Popen, check_call, run
|
||||
from typing import Any, Callable, Iterable, List, Optional, Sequence, Type
|
||||
from typing import Any, Callable, List, Optional, Type
|
||||
|
||||
import click
|
||||
|
||||
|
|
|
@ -7,10 +7,11 @@ from __future__ import annotations
|
|||
import io
|
||||
import pathlib
|
||||
import sys
|
||||
from collections.abc import Iterator, Sequence
|
||||
from datetime import datetime
|
||||
from functools import total_ordering
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, Iterator, Sequence, Union
|
||||
from typing import IO, Any, Union
|
||||
|
||||
PathIsh = Union[Path, str]
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@ from .internal import assert_subpackage; assert_subpackage(__name__)
|
|||
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
|
@ -21,7 +21,6 @@ import appdirs # type: ignore[import-untyped]
|
|||
|
||||
from . import warnings
|
||||
|
||||
|
||||
PathIsh = Union[str, Path] # avoid circular import from .common
|
||||
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ from __future__ import annotations
|
|||
import importlib
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Iterator
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from typing import Any, Callable, Dict, Iterator, Optional, Type, TypeVar
|
||||
from typing import Any, Callable, Dict, Optional, Type, TypeVar
|
||||
|
||||
Attrs = Dict[str, Any]
|
||||
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Iterable, Sequence
|
||||
from glob import glob as do_glob
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Generic,
|
||||
Iterable,
|
||||
List,
|
||||
Sequence,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from . import compat
|
||||
from . import warnings
|
||||
from . import compat, warnings
|
||||
|
||||
# some helper functions
|
||||
# TODO start deprecating this? soon we'd be able to use Path | str syntax which is shorter and more explicit
|
||||
|
@ -30,14 +28,14 @@ def get_files(
|
|||
*,
|
||||
sort: bool=True,
|
||||
guess_compression: bool=True,
|
||||
) -> Tuple[Path, ...]:
|
||||
) -> tuple[Path, ...]:
|
||||
"""
|
||||
Helper function to avoid boilerplate.
|
||||
|
||||
Tuple as return type is a bit friendlier for hashing/caching, so hopefully makes sense
|
||||
"""
|
||||
# TODO FIXME mm, some wrapper to assert iterator isn't empty?
|
||||
sources: List[Path]
|
||||
sources: list[Path]
|
||||
if isinstance(pp, Path):
|
||||
sources = [pp]
|
||||
elif isinstance(pp, str):
|
||||
|
@ -54,7 +52,7 @@ def get_files(
|
|||
# TODO ugh. very flaky... -3 because [<this function>, get_files(), <actual caller>]
|
||||
return traceback.extract_stack()[-3].filename
|
||||
|
||||
paths: List[Path] = []
|
||||
paths: list[Path] = []
|
||||
for src in sources:
|
||||
if src.parts[0] == '~':
|
||||
src = src.expanduser()
|
||||
|
@ -234,16 +232,14 @@ if not TYPE_CHECKING:
|
|||
return types.asdict(*args, **kwargs)
|
||||
|
||||
# todo wrap these in deprecated decorator as well?
|
||||
# TODO hmm how to deprecate these in runtime?
|
||||
# tricky cause they are actually classes/types
|
||||
from typing import Literal # noqa: F401
|
||||
|
||||
from .cachew import mcachew # noqa: F401
|
||||
|
||||
# this is kinda internal, should just use my.core.logging.setup_logger if necessary
|
||||
from .logging import setup_logger
|
||||
|
||||
# TODO hmm how to deprecate these in runtime?
|
||||
# tricky cause they are actually classes/types
|
||||
|
||||
from typing import Literal # noqa: F401
|
||||
|
||||
from .stats import Stats
|
||||
from .types import (
|
||||
Json,
|
||||
|
|
|
@ -3,9 +3,10 @@ Bindings for the 'core' HPI configuration
|
|||
'''
|
||||
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional, Sequence
|
||||
from typing import Optional
|
||||
|
||||
from . import PathIsh, warnings
|
||||
|
||||
|
@ -121,8 +122,8 @@ config = make_config(Config)
|
|||
|
||||
|
||||
### tests start
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager as ctx
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
@ctx
|
||||
|
|
|
@ -9,8 +9,9 @@ import functools
|
|||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterator, Mapping
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterator, List, Mapping, Set, TypeVar
|
||||
from typing import Any, Dict, List, Set, TypeVar
|
||||
|
||||
import click
|
||||
from more_itertools import seekable
|
||||
|
|
|
@ -19,8 +19,9 @@ import ast
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Iterable, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, List, NamedTuple, Optional, Sequence, cast
|
||||
from typing import Any, List, NamedTuple, Optional, cast
|
||||
|
||||
'''
|
||||
None means that requirements weren't defined (different from empty requirements)
|
||||
|
|
|
@ -4,13 +4,12 @@ See https://beepb00p.xyz/mypy-error-handling.html#kiss for more detail
|
|||
"""
|
||||
|
||||
import traceback
|
||||
from collections.abc import Iterable, Iterator
|
||||
from datetime import datetime
|
||||
from itertools import tee
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
|
|
|
@ -6,8 +6,9 @@ Contains various backwards compatibility/deprecation helpers relevant to HPI its
|
|||
import inspect
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Iterator, Sequence
|
||||
from types import ModuleType
|
||||
from typing import Iterator, List, Optional, Sequence, TypeVar
|
||||
from typing import List, Optional, TypeVar
|
||||
|
||||
from . import warnings
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ TODO doesn't really belong to 'core' morally, but can think of moving out later
|
|||
|
||||
from .internal import assert_subpackage; assert_subpackage(__name__)
|
||||
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import click
|
||||
|
||||
|
|
|
@ -122,8 +122,8 @@ def _wrap(j, parent=None) -> Tuple[Zoomable, List[Zoomable]]:
|
|||
raise RuntimeError(f'Unexpected type: {type(j)} {j}')
|
||||
|
||||
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
class UnconsumedError(Exception):
|
||||
|
|
|
@ -22,7 +22,8 @@ def parse_org_datetime(s: str) -> datetime:
|
|||
|
||||
# TODO I guess want to borrow inspiration from bs4? element type <-> tag; and similar logic for find_one, find_all
|
||||
|
||||
from typing import Callable, Iterable, TypeVar
|
||||
from collections.abc import Iterable
|
||||
from typing import Callable, TypeVar
|
||||
|
||||
from orgparse import OrgNode
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from __future__ import annotations
|
|||
# todo not sure if belongs to 'core'. It's certainly 'more' core than actual modules, but still not essential
|
||||
# NOTE: this file is meant to be importable without Pandas installed
|
||||
import dataclasses
|
||||
from collections.abc import Iterable, Iterator
|
||||
from datetime import datetime, timezone
|
||||
from pprint import pformat
|
||||
from typing import (
|
||||
|
@ -14,8 +15,6 @@ from typing import (
|
|||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Literal,
|
||||
Type,
|
||||
TypeVar,
|
||||
|
|
|
@ -9,13 +9,12 @@ import dataclasses
|
|||
import importlib
|
||||
import inspect
|
||||
import itertools
|
||||
from collections.abc import Iterable, Iterator
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
|
|
|
@ -9,9 +9,10 @@ See the select_range function below
|
|||
|
||||
import re
|
||||
import time
|
||||
from collections.abc import Iterator
|
||||
from datetime import date, datetime, timedelta
|
||||
from functools import lru_cache
|
||||
from typing import Any, Callable, Iterator, NamedTuple, Optional, Type
|
||||
from functools import cache, lru_cache
|
||||
from typing import Any, Callable, NamedTuple, Optional, Type
|
||||
|
||||
import more_itertools
|
||||
|
||||
|
@ -98,7 +99,7 @@ def parse_datetime_float(date_str: str) -> float:
|
|||
# probably DateLike input? but a user could specify an order_key
|
||||
# which is an epoch timestamp or a float value which they
|
||||
# expect to be converted to a datetime to compare
|
||||
@lru_cache(maxsize=None)
|
||||
@cache
|
||||
def _datelike_to_float(dl: Any) -> float:
|
||||
if isinstance(dl, datetime):
|
||||
return dl.timestamp()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import datetime
|
||||
from dataclasses import asdict, is_dataclass
|
||||
from decimal import Decimal
|
||||
from functools import lru_cache
|
||||
from functools import cache, lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, NamedTuple, Optional
|
||||
|
||||
|
@ -57,7 +57,7 @@ def _default_encode(obj: Any) -> Any:
|
|||
# could possibly run multiple times/raise warning if you provide different 'default'
|
||||
# functions or change the kwargs? The alternative is to maintain all of this at the module
|
||||
# level, which is just as annoying
|
||||
@lru_cache(maxsize=None)
|
||||
@cache
|
||||
def _dumps_factory(**kwargs) -> Callable[[Any], str]:
|
||||
use_default: DefaultEncoder = _default_encode
|
||||
# if the user passed an additional 'default' parameter,
|
||||
|
|
|
@ -4,8 +4,9 @@ and yielding nothing (or a default) when its not available
|
|||
"""
|
||||
|
||||
import warnings
|
||||
from collections.abc import Iterable, Iterator
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Iterable, Iterator, Optional, TypeVar
|
||||
from typing import Any, Callable, Optional, TypeVar
|
||||
|
||||
from .warnings import medium
|
||||
|
||||
|
|
|
@ -3,10 +3,11 @@ from .internal import assert_subpackage; assert_subpackage(__name__)
|
|||
|
||||
import shutil
|
||||
import sqlite3
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Any, Callable, Iterator, Literal, Optional, Tuple, Union, overload
|
||||
from typing import Any, Callable, Literal, Optional, Tuple, Union, overload
|
||||
|
||||
from .common import PathIsh
|
||||
from .compat import assert_never
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
'''
|
||||
Helpers for hpi doctor/stats functionality.
|
||||
'''
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import importlib
|
||||
import inspect
|
||||
import typing
|
||||
from collections.abc import Iterable, Iterator, Sequence
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
@ -13,20 +15,13 @@ from types import ModuleType
|
|||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Protocol,
|
||||
Sequence,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from .types import asdict
|
||||
|
||||
Stats = Dict[str, Any]
|
||||
Stats = dict[str, Any]
|
||||
|
||||
|
||||
class StatsFun(Protocol):
|
||||
|
@ -55,10 +50,10 @@ def quick_stats():
|
|||
|
||||
|
||||
def stat(
|
||||
func: Union[Callable[[], Iterable[Any]], Iterable[Any]],
|
||||
func: Callable[[], Iterable[Any]] | Iterable[Any],
|
||||
*,
|
||||
quick: bool = False,
|
||||
name: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
) -> Stats:
|
||||
"""
|
||||
Extracts various statistics from a passed iterable/callable, e.g.:
|
||||
|
@ -153,8 +148,8 @@ def test_stat() -> None:
|
|||
#
|
||||
|
||||
|
||||
def get_stats(module_name: str, *, guess: bool = False) -> Optional[StatsFun]:
|
||||
stats: Optional[StatsFun] = None
|
||||
def get_stats(module_name: str, *, guess: bool = False) -> StatsFun | None:
|
||||
stats: StatsFun | None = None
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except Exception:
|
||||
|
@ -167,7 +162,7 @@ def get_stats(module_name: str, *, guess: bool = False) -> Optional[StatsFun]:
|
|||
|
||||
# TODO maybe could be enough to annotate OUTPUTS or something like that?
|
||||
# then stats could just use them as hints?
|
||||
def guess_stats(module: ModuleType) -> Optional[StatsFun]:
|
||||
def guess_stats(module: ModuleType) -> StatsFun | None:
|
||||
"""
|
||||
If the module doesn't have explicitly defined 'stat' function,
|
||||
this is used to try to guess what could be included in stats automatically
|
||||
|
@ -206,7 +201,7 @@ def test_guess_stats() -> None:
|
|||
}
|
||||
|
||||
|
||||
def _guess_data_providers(module: ModuleType) -> Dict[str, Callable]:
|
||||
def _guess_data_providers(module: ModuleType) -> dict[str, Callable]:
|
||||
mfunctions = inspect.getmembers(module, inspect.isfunction)
|
||||
return {k: v for k, v in mfunctions if is_data_provider(v)}
|
||||
|
||||
|
@ -263,7 +258,7 @@ def test_is_data_provider() -> None:
|
|||
lam = lambda: [1, 2]
|
||||
assert not idp(lam)
|
||||
|
||||
def has_extra_args(count) -> List[int]:
|
||||
def has_extra_args(count) -> list[int]:
|
||||
return list(range(count))
|
||||
|
||||
assert not idp(has_extra_args)
|
||||
|
@ -340,10 +335,10 @@ def test_type_is_iterable() -> None:
|
|||
assert not fun(None)
|
||||
assert not fun(int)
|
||||
assert not fun(Any)
|
||||
assert not fun(Dict[int, int])
|
||||
assert not fun(dict[int, int])
|
||||
|
||||
assert fun(List[int])
|
||||
assert fun(Sequence[Dict[str, str]])
|
||||
assert fun(list[int])
|
||||
assert fun(Sequence[dict[str, str]])
|
||||
assert fun(Iterable[Any])
|
||||
|
||||
|
||||
|
@ -434,7 +429,7 @@ def test_stat_iterable() -> None:
|
|||
|
||||
|
||||
# experimental, not sure about it..
|
||||
def _guess_datetime(x: Any) -> Optional[datetime]:
|
||||
def _guess_datetime(x: Any) -> datetime | None:
|
||||
# todo hmm implement without exception..
|
||||
try:
|
||||
d = asdict(x)
|
||||
|
|
|
@ -5,9 +5,10 @@ import sys
|
|||
import tarfile
|
||||
import tempfile
|
||||
import zipfile
|
||||
from collections.abc import Generator, Sequence
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Generator, List, Sequence, Tuple, Union
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
from .logging import make_logger
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
Helper 'module' for test_guess_stats
|
||||
"""
|
||||
|
||||
from collections.abc import Iterable, Iterator, Sequence
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Iterator, Sequence
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -15,7 +17,7 @@ skip_if_uses_optional_deps = pytest.mark.skipif(
|
|||
|
||||
# TODO maybe move to hpi core?
|
||||
@contextmanager
|
||||
def tmp_environ_set(key: str, value: Optional[str]) -> Iterator[None]:
|
||||
def tmp_environ_set(key: str, value: str | None) -> Iterator[None]:
|
||||
prev_value = os.environ.get(key)
|
||||
if value is None:
|
||||
os.environ.pop(key, None)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import json
|
||||
import warnings
|
||||
from collections.abc import Iterator
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Iterator, NamedTuple
|
||||
from typing import NamedTuple
|
||||
|
||||
from ..denylist import DenyList
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from .common import skip_if_uses_optional_deps as pytestmark
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
from .common import skip_if_uses_optional_deps as pytestmark
|
||||
|
||||
# TODO ugh, this is very messy.. need to sort out config overriding here
|
||||
|
||||
|
@ -16,7 +16,7 @@ def test_cachew() -> None:
|
|||
|
||||
# TODO ugh. need doublewrap or something to avoid having to pass parens
|
||||
@mcachew()
|
||||
def cf() -> List[int]:
|
||||
def cf() -> list[int]:
|
||||
nonlocal called
|
||||
called += 1
|
||||
return [1, 2, 3]
|
||||
|
@ -43,7 +43,7 @@ def test_cachew_dir_none() -> None:
|
|||
called = 0
|
||||
|
||||
@mcachew(cache_path=cache_dir() / 'ctest')
|
||||
def cf() -> List[int]:
|
||||
def cf() -> list[int]:
|
||||
nonlocal called
|
||||
called += 1
|
||||
return [called, called, called]
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
Various tests that are checking behaviour of user config wrt to various things
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from functools import lru_cache
|
||||
from typing import Dict, Sequence
|
||||
from collections.abc import Sequence
|
||||
from functools import cache, lru_cache
|
||||
from typing import Dict
|
||||
|
||||
import pytz
|
||||
|
||||
|
@ -43,7 +44,7 @@ def _abbr_to_timezone_map() -> Dict[str, pytz.BaseTzInfo]:
|
|||
return res
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
@cache
|
||||
def abbr_to_timezone(abbr: str) -> pytz.BaseTzInfo:
|
||||
return _abbr_to_timezone_map()[abbr]
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Iterable, List, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from .discovery_pure import HPIModule, _is_not_module_src, has_stats, ignored
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from concurrent.futures import Executor, Future
|
||||
from typing import Any, Callable, Optional, TypeVar
|
||||
from typing import Any, Callable, TypeVar
|
||||
|
||||
from ..compat import ParamSpec
|
||||
|
||||
|
@ -15,7 +17,7 @@ class DummyExecutor(Executor):
|
|||
but also want to provide an option to run the code serially (e.g. for debugging)
|
||||
"""
|
||||
|
||||
def __init__(self, max_workers: Optional[int] = 1) -> None:
|
||||
def __init__(self, max_workers: int | None = 1) -> None:
|
||||
self._shutdown = False
|
||||
self._max_workers = max_workers
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import importlib.util
|
||||
import sys
|
||||
|
@ -5,23 +7,22 @@ from pathlib import Path
|
|||
from types import ModuleType
|
||||
from typing import Optional
|
||||
|
||||
from ..common import PathIsh
|
||||
|
||||
|
||||
# TODO only used in tests? not sure if useful at all.
|
||||
def import_file(p: PathIsh, name: Optional[str] = None) -> ModuleType:
|
||||
def import_file(p: Path | str, name: str | None = None) -> ModuleType:
|
||||
p = Path(p)
|
||||
if name is None:
|
||||
name = p.stem
|
||||
spec = importlib.util.spec_from_file_location(name, p)
|
||||
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 = spec.loader
|
||||
assert loader is not None
|
||||
loader.exec_module(foo)
|
||||
return foo
|
||||
|
||||
|
||||
def import_from(path: PathIsh, name: str) -> ModuleType:
|
||||
def import_from(path: Path | str, name: str) -> ModuleType:
|
||||
path = str(path)
|
||||
sys.path.append(path)
|
||||
try:
|
||||
|
@ -30,7 +31,7 @@ def import_from(path: PathIsh, name: str) -> ModuleType:
|
|||
sys.path.remove(path)
|
||||
|
||||
|
||||
def import_dir(path: PathIsh, extra: str = '') -> ModuleType:
|
||||
def import_dir(path: Path | str, extra: str = '') -> ModuleType:
|
||||
p = Path(path)
|
||||
if p.parts[0] == '~':
|
||||
p = p.expanduser() # TODO eh. not sure about this..
|
||||
|
|
|
@ -4,17 +4,13 @@ Various helpers/transforms of iterators
|
|||
Ideally this should be as small as possible and we should rely on stdlib itertools or more_itertools
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from collections.abc import Hashable
|
||||
from collections.abc import Hashable, Iterable, Iterator, Sized
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sized,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
|
@ -23,9 +19,8 @@ from typing import (
|
|||
import more_itertools
|
||||
from decorator import decorator
|
||||
|
||||
from ..compat import ParamSpec
|
||||
from .. import warnings as core_warnings
|
||||
|
||||
from ..compat import ParamSpec
|
||||
|
||||
T = TypeVar('T')
|
||||
K = TypeVar('K')
|
||||
|
@ -39,7 +34,7 @@ def _identity(v: T) -> V: # type: ignore[type-var]
|
|||
# ugh. nothing in more_itertools?
|
||||
# perhaps duplicates_everseen? but it doesn't yield non-unique elements?
|
||||
def ensure_unique(it: Iterable[T], *, key: Callable[[T], K]) -> Iterable[T]:
|
||||
key2item: Dict[K, T] = {}
|
||||
key2item: dict[K, T] = {}
|
||||
for i in it:
|
||||
k = key(i)
|
||||
pi = key2item.get(k, None)
|
||||
|
@ -72,10 +67,10 @@ def make_dict(
|
|||
key: Callable[[T], K],
|
||||
# TODO make value optional instead? but then will need a typing override for it?
|
||||
value: Callable[[T], V] = _identity,
|
||||
) -> Dict[K, V]:
|
||||
) -> dict[K, V]:
|
||||
with_keys = ((key(i), i) for i in it)
|
||||
uniques = ensure_unique(with_keys, key=lambda p: p[0])
|
||||
res: Dict[K, V] = {}
|
||||
res: dict[K, V] = {}
|
||||
for k, i in uniques:
|
||||
res[k] = i if value is None else value(i)
|
||||
return res
|
||||
|
@ -93,8 +88,8 @@ def test_make_dict() -> None:
|
|||
d = make_dict(it, key=lambda i: i % 2, value=lambda i: i)
|
||||
|
||||
# check type inference
|
||||
d2: Dict[str, int] = make_dict(it, key=lambda i: str(i))
|
||||
d3: Dict[str, bool] = make_dict(it, key=lambda i: str(i), value=lambda i: i % 2 == 0)
|
||||
d2: dict[str, int] = make_dict(it, key=lambda i: str(i))
|
||||
d3: dict[str, bool] = make_dict(it, key=lambda i: str(i), value=lambda i: i % 2 == 0)
|
||||
|
||||
|
||||
LFP = ParamSpec('LFP')
|
||||
|
@ -102,7 +97,7 @@ LV = TypeVar('LV')
|
|||
|
||||
|
||||
@decorator
|
||||
def _listify(func: Callable[LFP, Iterable[LV]], *args: LFP.args, **kwargs: LFP.kwargs) -> List[LV]:
|
||||
def _listify(func: Callable[LFP, Iterable[LV]], *args: LFP.args, **kwargs: LFP.kwargs) -> list[LV]:
|
||||
"""
|
||||
Wraps a function's return value in wrapper (e.g. list)
|
||||
Useful when an algorithm can be expressed more cleanly as a generator
|
||||
|
@ -115,7 +110,7 @@ def _listify(func: Callable[LFP, Iterable[LV]], *args: LFP.args, **kwargs: LFP.k
|
|||
# so seems easiest to just use specialize instantiations of decorator instead
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def listify(func: Callable[LFP, Iterable[LV]]) -> Callable[LFP, List[LV]]: ... # noqa: ARG001
|
||||
def listify(func: Callable[LFP, Iterable[LV]]) -> Callable[LFP, list[LV]]: ... # noqa: ARG001
|
||||
|
||||
else:
|
||||
listify = _listify
|
||||
|
@ -130,7 +125,7 @@ def test_listify() -> None:
|
|||
yield 2
|
||||
|
||||
res = it()
|
||||
assert_type(res, List[int])
|
||||
assert_type(res, list[int])
|
||||
assert res == [1, 2]
|
||||
|
||||
|
||||
|
@ -201,24 +196,24 @@ def test_warn_if_empty_list() -> None:
|
|||
ll = [1, 2, 3]
|
||||
|
||||
@warn_if_empty
|
||||
def nonempty() -> List[int]:
|
||||
def nonempty() -> list[int]:
|
||||
return ll
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
res1 = nonempty()
|
||||
assert len(w) == 0
|
||||
assert_type(res1, List[int])
|
||||
assert_type(res1, list[int])
|
||||
assert isinstance(res1, list)
|
||||
assert res1 is ll # object should be unchanged!
|
||||
|
||||
@warn_if_empty
|
||||
def empty() -> List[str]:
|
||||
def empty() -> list[str]:
|
||||
return []
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
res2 = empty()
|
||||
assert len(w) == 1
|
||||
assert_type(res2, List[str])
|
||||
assert_type(res2, list[str])
|
||||
assert isinstance(res2, list)
|
||||
assert res2 == []
|
||||
|
||||
|
@ -242,7 +237,7 @@ def check_if_hashable(iterable: Iterable[_HT]) -> Iterable[_HT]:
|
|||
"""
|
||||
NOTE: Despite Hashable bound, typing annotation doesn't guarantee runtime safety
|
||||
Consider hashable type X, and Y that inherits from X, but not hashable
|
||||
Then l: List[X] = [Y(...)] is a valid expression, and type checks against Hashable,
|
||||
Then l: list[X] = [Y(...)] is a valid expression, and type checks against Hashable,
|
||||
but isn't runtime hashable
|
||||
"""
|
||||
# Sadly this doesn't work 100% correctly with dataclasses atm...
|
||||
|
@ -268,28 +263,27 @@ def check_if_hashable(iterable: Iterable[_HT]) -> Iterable[_HT]:
|
|||
# TODO different policies -- error/warn/ignore?
|
||||
def test_check_if_hashable() -> None:
|
||||
from dataclasses import dataclass
|
||||
from typing import Set, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from ..compat import assert_type
|
||||
|
||||
x1: List[int] = [1, 2]
|
||||
x1: list[int] = [1, 2]
|
||||
r1 = check_if_hashable(x1)
|
||||
assert_type(r1, Iterable[int])
|
||||
assert r1 is x1
|
||||
|
||||
x2: Iterator[Union[int, str]] = iter((123, 'aba'))
|
||||
x2: Iterator[int | str] = iter((123, 'aba'))
|
||||
r2 = check_if_hashable(x2)
|
||||
assert_type(r2, Iterable[Union[int, str]])
|
||||
assert list(r2) == [123, 'aba']
|
||||
|
||||
x3: Tuple[object, ...] = (789, 'aba')
|
||||
x3: tuple[object, ...] = (789, 'aba')
|
||||
r3 = check_if_hashable(x3)
|
||||
assert_type(r3, Iterable[object])
|
||||
assert r3 is x3 # object should be unchanged
|
||||
|
||||
x4: List[Set[int]] = [{1, 2, 3}, {4, 5, 6}]
|
||||
x4: list[set[int]] = [{1, 2, 3}, {4, 5, 6}]
|
||||
with pytest.raises(Exception):
|
||||
# should be rejected by mypy sice set isn't Hashable, but also throw at runtime
|
||||
r4 = check_if_hashable(x4) # type: ignore[type-var]
|
||||
|
@ -307,7 +301,7 @@ def test_check_if_hashable() -> None:
|
|||
class X:
|
||||
a: int
|
||||
|
||||
x6: List[X] = [X(a=123)]
|
||||
x6: list[X] = [X(a=123)]
|
||||
r6 = check_if_hashable(x6)
|
||||
assert x6 is r6
|
||||
|
||||
|
@ -316,7 +310,7 @@ def test_check_if_hashable() -> None:
|
|||
class Y(X):
|
||||
b: str
|
||||
|
||||
x7: List[Y] = [Y(a=123, b='aba')]
|
||||
x7: list[Y] = [Y(a=123, b='aba')]
|
||||
with pytest.raises(Exception):
|
||||
# ideally that would also be rejected by mypy, but currently there is a bug
|
||||
# which treats all dataclasses as hashable: https://github.com/python/mypy/issues/11463
|
||||
|
@ -331,11 +325,8 @@ _UEU = TypeVar('_UEU')
|
|||
# instead of just iterator
|
||||
# TODO maybe deprecated Callable support? not sure
|
||||
def unique_everseen(
|
||||
fun: Union[
|
||||
Callable[[], Iterable[_UET]],
|
||||
Iterable[_UET]
|
||||
],
|
||||
key: Optional[Callable[[_UET], _UEU]] = None,
|
||||
fun: Callable[[], Iterable[_UET]] | Iterable[_UET],
|
||||
key: Callable[[_UET], _UEU] | None = None,
|
||||
) -> Iterator[_UET]:
|
||||
import os
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ TODO ideally would be great to replace with some existing solution, or find a be
|
|||
since who looks at the terminal output?
|
||||
E.g. would be nice to propagate the warnings in the UI (it's even a subclass of Exception!)
|
||||
'''
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue