core: cleanup/sort imports according to ruff check --select I

This commit is contained in:
Dima Gerasimov 2024-08-16 13:25:19 +03:00 committed by karlicoss
parent 7023088d13
commit 7bfce72b7c
45 changed files with 235 additions and 170 deletions

View file

@ -1,23 +1,21 @@
# this file only keeps the most common & critical types/utility functions # this file only keeps the most common & critical types/utility functions
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .common import get_files, PathIsh, Paths from .cfg import make_config
from .stats import stat, Stats from .common import PathIsh, Paths, get_files
from .compat import assert_never
from .error import Res, unwrap
from .logging import (
make_logger,
)
from .stats import Stats, stat
from .types import ( from .types import (
Json, Json,
datetime_aware, datetime_aware,
datetime_naive, datetime_naive,
) )
from .compat import assert_never
from .utils.itertools import warn_if_empty
from .cfg import make_config
from .error import Res, unwrap
from .logging import (
make_logger,
)
from .util import __NOT_HPI_MODULE__ from .util import __NOT_HPI_MODULE__
from .utils.itertools import warn_if_empty
LazyLogger = make_logger # TODO deprecate this in favor of make_logger LazyLogger = make_logger # TODO deprecate this in favor of make_logger

View file

@ -1,17 +1,17 @@
from contextlib import ExitStack
import functools import functools
import importlib import importlib
import inspect import inspect
from itertools import chain
import os import os
import shlex import shlex
import shutil import shutil
import sys import sys
import tempfile import tempfile
import traceback import traceback
from typing import Optional, Sequence, Iterable, List, Type, Any, Callable from contextlib import ExitStack
from itertools import chain
from pathlib import Path from pathlib import Path
from subprocess import check_call, run, PIPE, CompletedProcess, Popen from subprocess import PIPE, CompletedProcess, Popen, check_call, run
from typing import Any, Callable, Iterable, List, Optional, Sequence, Type
import click import click
@ -221,6 +221,8 @@ See https://github.com/karlicoss/HPI/blob/master/doc/SETUP.org#setting-up-module
from .util import HPIModule, modules from .util import HPIModule, modules
def _modules(*, all: bool=False) -> Iterable[HPIModule]: def _modules(*, all: bool=False) -> Iterable[HPIModule]:
skipped = [] skipped = []
for m in modules(): for m in modules():
@ -243,9 +245,9 @@ def modules_check(*, verbose: bool, list_all: bool, quick: bool, for_modules: Li
import contextlib import contextlib
from .util import HPIModule
from .stats import get_stats, quick_stats
from .error import warn_my_config_import_error from .error import warn_my_config_import_error
from .stats import get_stats, quick_stats
from .util import HPIModule
mods: Iterable[HPIModule] mods: Iterable[HPIModule]
if len(for_modules) == 0: if len(for_modules) == 0:
@ -437,7 +439,7 @@ def _ui_getchar_pick(choices: Sequence[str], prompt: str = 'Select from: ') -> i
def _locate_functions_or_prompt(qualified_names: List[str], prompt: bool = True) -> Iterable[Callable[..., Any]]: def _locate_functions_or_prompt(qualified_names: List[str], prompt: bool = True) -> Iterable[Callable[..., Any]]:
from .query import locate_qualified_function, QueryException from .query import QueryException, locate_qualified_function
from .stats import is_data_provider from .stats import is_data_provider
# if not connected to a terminal, can't prompt # if not connected to a terminal, can't prompt
@ -511,8 +513,7 @@ def query_hpi_functions(
raise_exceptions: bool, raise_exceptions: bool,
drop_exceptions: bool, drop_exceptions: bool,
) -> None: ) -> None:
from .query_range import select_range, RangeTuple from .query_range import RangeTuple, select_range
import my.core.error as err
# chain list of functions from user, in the order they wrote them on the CLI # chain list of functions from user, in the order they wrote them on the CLI
input_src = chain(*(f() for f in _locate_functions_or_prompt(qualified_names))) input_src = chain(*(f() for f in _locate_functions_or_prompt(qualified_names)))
@ -825,7 +826,7 @@ def query_cmd(
hpi query --order-type datetime --after '2016-01-01' --before '2019-01-01' my.reddit.all.comments hpi query --order-type datetime --after '2016-01-01' --before '2019-01-01' my.reddit.all.comments
''' '''
from datetime import datetime, date from datetime import date, datetime
chosen_order_type: Optional[Type] chosen_order_type: Optional[Type]
if order_type == "datetime": if order_type == "datetime":

View file

@ -10,10 +10,9 @@ how many cores we want to dedicate to the DAL.
Enabled by the env variable, specifying how many cores to dedicate Enabled by the env variable, specifying how many cores to dedicate
e.g. "HPI_CPU_POOL=4 hpi query ..." e.g. "HPI_CPU_POOL=4 hpi query ..."
""" """
from concurrent.futures import ProcessPoolExecutor
import os import os
from typing import cast, Optional from concurrent.futures import ProcessPoolExecutor
from typing import Optional, cast
_NOT_SET = cast(ProcessPoolExecutor, object()) _NOT_SET = cast(ProcessPoolExecutor, object())
_INSTANCE: Optional[ProcessPoolExecutor] = _NOT_SET _INSTANCE: Optional[ProcessPoolExecutor] = _NOT_SET

View file

@ -4,13 +4,13 @@ Various helpers for compression
# fmt: off # fmt: off
from __future__ import annotations from __future__ import annotations
from datetime import datetime
from functools import total_ordering
import io import io
import pathlib import pathlib
from pathlib import Path
import sys import sys
from typing import Union, IO, Sequence, Any, Iterator from datetime import datetime
from functools import total_ordering
from pathlib import Path
from typing import IO, Any, Iterator, Sequence, Union
PathIsh = Union[Path, str] PathIsh = Union[Path, str]
@ -101,8 +101,8 @@ def kopen(path: PathIsh, *args, mode: str='rt', **kwargs) -> IO:
return pp.open(mode, *args, **kwargs) return pp.open(mode, *args, **kwargs)
import typing
import os import os
import typing
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
# otherwise mypy can't figure out that BasePath is a type alias.. # otherwise mypy can't figure out that BasePath is a type alias..
@ -147,6 +147,7 @@ def kexists(path: PathIsh, subpath: str) -> bool:
import zipfile import zipfile
if sys.version_info[:2] >= (3, 8): if sys.version_info[:2] >= (3, 8):
# meh... zipfile.Path is not available on 3.7 # meh... zipfile.Path is not available on 3.7
zipfile_Path = zipfile.Path zipfile_Path = zipfile.Path

View file

@ -1,11 +1,22 @@
from .internal import assert_subpackage; assert_subpackage(__name__) from .internal import assert_subpackage; assert_subpackage(__name__)
from contextlib import contextmanager
import logging import logging
from pathlib import Path
import sys import sys
from typing import Optional, Iterator, cast, TYPE_CHECKING, TypeVar, Callable, overload, Union, Any, Type
import warnings import warnings
from contextlib import contextmanager
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
Callable,
Iterator,
Optional,
Type,
TypeVar,
Union,
cast,
overload,
)
import appdirs # type: ignore[import-untyped] import appdirs # type: ignore[import-untyped]

View file

@ -1,6 +1,10 @@
from __future__ import annotations from __future__ import annotations
from typing import TypeVar, Type, Callable, Dict, Any import importlib
import re
import sys
from contextlib import ExitStack, contextmanager
from typing import Any, Callable, Dict, Iterator, Optional, Type, TypeVar
Attrs = Dict[str, Any] Attrs = Dict[str, Any]
@ -27,8 +31,8 @@ def make_config(cls: Type[C], migration: Callable[[Attrs], Attrs]=lambda x: x) -
F = TypeVar('F') F = TypeVar('F')
from contextlib import contextmanager
from typing import Iterator
@contextmanager @contextmanager
def _override_config(config: F) -> Iterator[F]: def _override_config(config: F) -> Iterator[F]:
''' '''
@ -46,9 +50,6 @@ def _override_config(config: F) -> Iterator[F]:
delattr(config, k) delattr(config, k)
import importlib
import sys
from typing import Optional
ModuleRegex = str ModuleRegex = str
@contextmanager @contextmanager
def _reload_modules(modules: ModuleRegex) -> Iterator[None]: def _reload_modules(modules: ModuleRegex) -> Iterator[None]:
@ -79,8 +80,6 @@ def _reload_modules(modules: ModuleRegex) -> Iterator[None]:
sys.modules.pop(m, None) sys.modules.pop(m, None)
from contextlib import ExitStack
import re
@contextmanager @contextmanager
def tmp_config(*, modules: Optional[ModuleRegex]=None, config=None): def tmp_config(*, modules: Optional[ModuleRegex]=None, config=None):
if modules is None: if modules is None:

View file

@ -1,20 +1,20 @@
import os
import warnings
from glob import glob as do_glob from glob import glob as do_glob
from pathlib import Path from pathlib import Path
import os
from typing import ( from typing import (
TYPE_CHECKING,
Callable, Callable,
Iterable, Iterable,
List, List,
Sequence, Sequence,
TYPE_CHECKING,
Tuple, Tuple,
TypeVar, TypeVar,
Union, Union,
) )
import warnings
from . import warnings as core_warnings
from . import compat from . import compat
from . import warnings as core_warnings
# some helper functions # some helper functions
# TODO start deprecating this? soon we'd be able to use Path | str syntax which is shorter and more explicit # TODO start deprecating this? soon we'd be able to use Path | str syntax which is shorter and more explicit
@ -92,7 +92,7 @@ def get_files(
traceback.print_stack() traceback.print_stack()
if guess_compression: if guess_compression:
from .kompress import CPath, is_compressed, ZipPath from .kompress import CPath, ZipPath, is_compressed
# NOTE: wrap is just for backwards compat with vendorized kompress # NOTE: wrap is just for backwards compat with vendorized kompress
# with kompress library, only is_compressed check and Cpath should be enough # with kompress library, only is_compressed check and Cpath should be enough
@ -109,7 +109,7 @@ def get_files(
return tuple(paths) return tuple(paths)
from typing import TypeVar, Callable, Generic from typing import Callable, Generic, TypeVar
_R = TypeVar('_R') _R = TypeVar('_R')
@ -133,6 +133,8 @@ class classproperty(Generic[_R]):
import re import re
# https://stackoverflow.com/a/295466/706389 # https://stackoverflow.com/a/295466/706389
def get_valid_filename(s: str) -> str: def get_valid_filename(s: str) -> str:
s = str(s).strip().replace(' ', '_') s = str(s).strip().replace(' ', '_')
@ -142,7 +144,6 @@ def get_valid_filename(s: str) -> str:
# TODO deprecate and suggest to use one from my.core directly? not sure # TODO deprecate and suggest to use one from my.core directly? not sure
from .utils.itertools import unique_everseen from .utils.itertools import unique_everseen
### legacy imports, keeping them here for backwards compatibility ### legacy imports, keeping them here for backwards compatibility
## hiding behind TYPE_CHECKING so it works in runtime ## 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? ## in principle, warnings.deprecated decorator should cooperate with mypy, but doesn't look like it works atm?
@ -225,8 +226,8 @@ if not TYPE_CHECKING:
from .stats import Stats from .stats import Stats
from .types import ( from .types import (
Json, Json,
datetime_naive,
datetime_aware, datetime_aware,
datetime_naive,
) )
tzdatetime = datetime_aware tzdatetime = datetime_aware

View file

@ -6,7 +6,6 @@ If something is relevant to HPI itself, please put it in .hpi_compat instead
import sys import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if sys.version_info[:2] >= (3, 13): if sys.version_info[:2] >= (3, 13):
from warnings import deprecated from warnings import deprecated
else: else:
@ -48,7 +47,7 @@ else:
# bisect_left doesn't have a 'key' parameter (which we use) # bisect_left doesn't have a 'key' parameter (which we use)
# till python3.10 # till python3.10
if sys.version_info[:2] <= (3, 9): if sys.version_info[:2] <= (3, 9):
from typing import List, TypeVar, Any, Optional, Callable from typing import Any, Callable, List, Optional, TypeVar
X = TypeVar('X') X = TypeVar('X')
@ -131,6 +130,6 @@ else:
if sys.version_info[:2] >= (3, 11): if sys.version_info[:2] >= (3, 11):
from typing import assert_never, assert_type, Never from typing import Never, assert_never, assert_type
else: else:
from typing_extensions import assert_never, assert_type, Never from typing_extensions import Never, assert_never, assert_type

View file

@ -2,12 +2,12 @@
Bindings for the 'core' HPI configuration Bindings for the 'core' HPI configuration
''' '''
import re
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import re from typing import Optional, Sequence
from typing import Sequence, Optional
from . import warnings, PathIsh from . import PathIsh, warnings
try: 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]
@ -116,12 +116,15 @@ class Config(user_config):
from .cfg import make_config from .cfg import make_config
config = make_config(Config) config = make_config(Config)
### tests start ### tests start
from typing import Iterator
from contextlib import contextmanager as ctx from contextlib import contextmanager as ctx
from typing import Iterator
@ctx @ctx
def _reset_config() -> Iterator[Config]: def _reset_config() -> Iterator[Config]:
# todo maybe have this decorator for the whole of my.config? # todo maybe have this decorator for the whole of my.config?

View file

@ -5,19 +5,19 @@ A helper module for defining denylists for sources programmatically
For docs, see doc/DENYLIST.md For docs, see doc/DENYLIST.md
""" """
import sys
import json
import functools import functools
import json
import sys
from collections import defaultdict from collections import defaultdict
from typing import TypeVar, Set, Any, Mapping, Iterator, Dict, List
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Mapping, Set, TypeVar
import click import click
from more_itertools import seekable from more_itertools import seekable
from my.core.serialize import dumps
from my.core.common import PathIsh
from my.core.warnings import medium
from my.core.common import PathIsh
from my.core.serialize import dumps
from my.core.warnings import medium
T = TypeVar("T") T = TypeVar("T")

View file

@ -16,11 +16,11 @@ NOT_HPI_MODULE_VAR = '__NOT_HPI_MODULE__'
### ###
import ast import ast
import os
from typing import Optional, Sequence, List, NamedTuple, Iterable, cast, Any
from pathlib import Path
import re
import logging import logging
import os
import re
from pathlib import Path
from typing import Any, Iterable, List, NamedTuple, Optional, Sequence, cast
''' '''
None means that requirements weren't defined (different from empty requirements) None means that requirements weren't defined (different from empty requirements)

View file

@ -3,12 +3,26 @@ Various error handling helpers
See https://beepb00p.xyz/mypy-error-handling.html#kiss for more detail See https://beepb00p.xyz/mypy-error-handling.html#kiss for more detail
""" """
import traceback
from datetime import datetime
from itertools import tee from itertools import tee
from typing import Union, TypeVar, Iterable, List, Tuple, Type, Optional, Callable, Any, cast, Iterator, Literal from typing import (
Any,
Callable,
Iterable,
Iterator,
List,
Literal,
Optional,
Tuple,
Type,
TypeVar,
Union,
cast,
)
from .types import Json from .types import Json
T = TypeVar('T') T = TypeVar('T')
E = TypeVar('E', bound=Exception) # TODO make covariant? E = TypeVar('E', bound=Exception) # TODO make covariant?
@ -18,6 +32,7 @@ Res = ResT[T, Exception]
ErrorPolicy = Literal["yield", "raise", "drop"] ErrorPolicy = Literal["yield", "raise", "drop"]
def notnone(x: Optional[T]) -> T: def notnone(x: Optional[T]) -> T:
assert x is not None assert x is not None
return x return x
@ -29,6 +44,7 @@ def unwrap(res: Res[T]) -> T:
else: else:
return res return res
def drop_exceptions(itr: Iterator[Res[T]]) -> Iterator[T]: def drop_exceptions(itr: Iterator[Res[T]]) -> Iterator[T]:
"""Return non-errors from the iterable""" """Return non-errors from the iterable"""
for o in itr: for o in itr:
@ -146,23 +162,23 @@ def test_sort_res_by() -> None:
# helpers to associate timestamps with the errors (so something meaningful could be displayed on the plots, for example) # helpers to associate timestamps with the errors (so something meaningful could be displayed on the plots, for example)
# todo document it under 'patterns' somewhere... # todo document it under 'patterns' somewhere...
# todo proper typevar? # todo proper typevar?
from datetime import datetime
def set_error_datetime(e: Exception, dt: Optional[datetime]) -> None: def set_error_datetime(e: Exception, dt: Optional[datetime]) -> None:
if dt is None: if dt is None:
return return
e.args = e.args + (dt,) e.args = e.args + (dt,)
# todo not sure if should return new exception? # todo not sure if should return new exception?
def attach_dt(e: Exception, *, dt: Optional[datetime]) -> Exception: def attach_dt(e: Exception, *, dt: Optional[datetime]) -> Exception:
set_error_datetime(e, dt) set_error_datetime(e, dt)
return e return e
# todo it might be problematic because might mess with timezones (when it's converted to string, it's converted to a shift) # todo it might be problematic because might mess with timezones (when it's converted to string, it's converted to a shift)
def extract_error_datetime(e: Exception) -> Optional[datetime]: def extract_error_datetime(e: Exception) -> Optional[datetime]:
import re import re
from datetime import datetime
for x in reversed(e.args): for x in reversed(e.args):
if isinstance(x, datetime): if isinstance(x, datetime):
return x return x
@ -177,7 +193,6 @@ def extract_error_datetime(e: Exception) -> Optional[datetime]:
return None return None
import traceback
def error_to_json(e: Exception) -> Json: def error_to_json(e: Exception) -> Json:
estr = ''.join(traceback.format_exception(Exception, e, e.__traceback__)) estr = ''.join(traceback.format_exception(Exception, e, e.__traceback__))
return {'error': estr} return {'error': estr}
@ -185,6 +200,7 @@ def error_to_json(e: Exception) -> Json:
MODULE_SETUP_URL = 'https://github.com/karlicoss/HPI/blob/master/doc/SETUP.org#private-configuration-myconfig' MODULE_SETUP_URL = 'https://github.com/karlicoss/HPI/blob/master/doc/SETUP.org#private-configuration-myconfig'
def warn_my_config_import_error(err: Union[ImportError, AttributeError], help_url: Optional[str] = None) -> bool: def warn_my_config_import_error(err: Union[ImportError, AttributeError], help_url: Optional[str] = None) -> bool:
""" """
If the user tried to import something from my.config but it failed, If the user tried to import something from my.config but it failed,
@ -193,7 +209,9 @@ def warn_my_config_import_error(err: Union[ImportError, AttributeError], help_ur
Returns True if it matched a possible config error Returns True if it matched a possible config error
""" """
import re import re
import click import click
if help_url is None: if help_url is None:
help_url = MODULE_SETUP_URL help_url = MODULE_SETUP_URL
if type(err) is ImportError: if type(err) is ImportError:
@ -226,7 +244,8 @@ See {help_url} or check the corresponding module.py file for an example\
def test_datetime_errors() -> None: def test_datetime_errors() -> None:
import pytz import pytz # noqa: I001
dt_notz = datetime.now() dt_notz = datetime.now()
dt_tz = datetime.now(tz=pytz.timezone('Europe/Amsterdam')) dt_tz = datetime.now(tz=pytz.timezone('Europe/Amsterdam'))
for dt in [dt_tz, dt_notz]: for dt in [dt_tz, dt_notz]:

View file

@ -1,6 +1,6 @@
import sys import sys
from typing import Any, Dict, Optional
import types import types
from typing import Any, Dict, Optional
# The idea behind this one is to support accessing "overlaid/shadowed" modules from namespace packages # The idea behind this one is to support accessing "overlaid/shadowed" modules from namespace packages

View file

@ -2,7 +2,7 @@ from .internal import assert_subpackage; assert_subpackage(__name__)
import dataclasses as dcl import dataclasses as dcl
import inspect import inspect
from typing import TypeVar, Type, Any from typing import Any, Type, TypeVar
D = TypeVar('D') D = TypeVar('D')
@ -22,6 +22,8 @@ def _freeze_dataclass(Orig: Type[D]):
# todo need some decorator thingie? # todo need some decorator thingie?
from typing import Generic from typing import Generic
class Freezer(Generic[D]): class Freezer(Generic[D]):
''' '''
Some magic which converts dataclass properties into fields. Some magic which converts dataclass properties into fields.

View file

@ -2,8 +2,8 @@
Contains various backwards compatibility/deprecation helpers relevant to HPI itself. Contains various backwards compatibility/deprecation helpers relevant to HPI itself.
(as opposed to .compat module which implements compatibility between python versions) (as opposed to .compat module which implements compatibility between python versions)
""" """
import os
import inspect import inspect
import os
import re import re
from types import ModuleType from types import ModuleType
from typing import Iterator, List, Optional, TypeVar from typing import Iterator, List, Optional, TypeVar

View file

@ -4,11 +4,12 @@ TODO doesn't really belong to 'core' morally, but can think of moving out later
from .internal import assert_subpackage; assert_subpackage(__name__) from .internal import assert_subpackage; assert_subpackage(__name__)
from typing import Iterable, Any, Optional, Dict from typing import Any, Dict, Iterable, Optional
import click
from .logging import make_logger from .logging import make_logger
from .types import asdict, Json from .types import Json, asdict
logger = make_logger(__name__) logger = make_logger(__name__)
@ -106,6 +107,7 @@ def magic_fill(it, *, name: Optional[str]=None, reset: bool=RESET_DEFAULT) -> No
it = it() it = it()
from itertools import tee from itertools import tee
from more_itertools import first, one from more_itertools import first, one
it, x = tee(it) it, x = tee(it)
f = first(x, default=None) f = first(x, default=None)
@ -125,8 +127,6 @@ def magic_fill(it, *, name: Optional[str]=None, reset: bool=RESET_DEFAULT) -> No
fill(it, measurement=name, reset=reset, dt_col=dtf) fill(it, measurement=name, reset=reset, dt_col=dtf)
import click
@click.group() @click.group()
def main() -> None: def main() -> None:
pass pass

View file

@ -14,9 +14,9 @@ Please let me know if you are aware of a better way of dealing with this!
# separate function to present namespace pollution # separate function to present namespace pollution
def setup_config() -> None: def setup_config() -> None:
from pathlib import Path
import sys import sys
import warnings import warnings
from pathlib import Path
from .preinit import get_mycfg_dir from .preinit import get_mycfg_dir
mycfg_dir = get_mycfg_dir() mycfg_dir = get_mycfg_dir()

View file

@ -94,6 +94,8 @@ class Wvalue(Zoomable):
from typing import Tuple from typing import Tuple
def _wrap(j, parent=None) -> Tuple[Zoomable, List[Zoomable]]: def _wrap(j, parent=None) -> Tuple[Zoomable, List[Zoomable]]:
res: Zoomable res: Zoomable
cc: List[Zoomable] cc: List[Zoomable]
@ -123,6 +125,7 @@ def _wrap(j, parent=None) -> Tuple[Zoomable, List[Zoomable]]:
from contextlib import contextmanager from contextlib import contextmanager
from typing import Iterator from typing import Iterator
class UnconsumedError(Exception): class UnconsumedError(Exception):
pass pass
@ -146,6 +149,8 @@ Expected {c} to be fully consumed by the parser.
from typing import cast from typing import cast
def test_unconsumed() -> None: def test_unconsumed() -> None:
import pytest import pytest
with pytest.raises(UnconsumedError): with pytest.raises(UnconsumedError):

View file

@ -1,11 +1,11 @@
from __future__ import annotations from __future__ import annotations
from functools import lru_cache
import logging import logging
import os import os
import sys import sys
from typing import Union, TYPE_CHECKING
import warnings import warnings
from functools import lru_cache
from typing import TYPE_CHECKING, Union
def test() -> None: def test() -> None:
@ -222,7 +222,9 @@ def make_logger(name: str, *, level: LevelIsh = None) -> logging.Logger:
# OK, when stdout is not a tty, enlighten doesn't log anything, good # OK, when stdout is not a tty, enlighten doesn't log anything, good
def get_enlighten(): def get_enlighten():
# TODO could add env variable to disable enlighten for a module? # TODO could add env variable to disable enlighten for a module?
from unittest.mock import Mock # Mock to return stub so cients don't have to think about it from unittest.mock import (
Mock, # Mock to return stub so cients don't have to think about it
)
# for now hidden behind the flag since it's a little experimental # for now hidden behind the flag since it's a little experimental
if os.environ.get('ENLIGHTEN_ENABLE', None) is None: if os.environ.get('ENLIGHTEN_ENABLE', None) is None:

View file

@ -2,6 +2,8 @@
Various helpers for reading org-mode data Various helpers for reading org-mode data
""" """
from datetime import datetime from datetime import datetime
def parse_org_datetime(s: str) -> datetime: def parse_org_datetime(s: str) -> datetime:
s = s.strip('[]') s = s.strip('[]')
for fmt, cl in [ for fmt, cl in [
@ -21,8 +23,10 @@ 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 # 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 orgparse import OrgNode from orgparse import OrgNode
from typing import Iterable, TypeVar, Callable
V = TypeVar('V') V = TypeVar('V')
def collect(n: OrgNode, cfun: Callable[[OrgNode], Iterable[V]]) -> Iterable[V]: def collect(n: OrgNode, cfun: Callable[[OrgNode], Iterable[V]]) -> Iterable[V]:
@ -32,6 +36,8 @@ def collect(n: OrgNode, cfun: Callable[[OrgNode], Iterable[V]]) -> Iterable[V]:
from more_itertools import one from more_itertools import one
from orgparse.extra import Table from orgparse.extra import Table
def one_table(o: OrgNode) -> Table: def one_table(o: OrgNode) -> Table:
return one(collect(o, lambda n: (x for x in n.body_rich if isinstance(x, Table)))) return one(collect(o, lambda n: (x for x in n.body_rich if isinstance(x, Table))))

View file

@ -8,15 +8,14 @@ from __future__ import annotations
import dataclasses import dataclasses
from datetime import datetime, timezone from datetime import datetime, timezone
from pprint import pformat from pprint import pformat
from typing import TYPE_CHECKING, Any, Iterable, Type, Dict, Literal, Callable, TypeVar from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Literal, Type, TypeVar
from decorator import decorator from decorator import decorator
from . import warnings, Res from . import Res, warnings
from .error import error_to_json, extract_error_datetime
from .logging import make_logger from .logging import make_logger
from .types import Json, asdict from .types import Json, asdict
from .error import error_to_json, extract_error_datetime
logger = make_logger(__name__) logger = make_logger(__name__)

View file

@ -1,11 +1,13 @@
from pathlib import Path from pathlib import Path
# todo preinit isn't really a good name? it's only in a separate file because # todo preinit isn't really a good name? it's only in a separate file because
# - it's imported from my.core.init (so we wan't to keep this file as small/reliable as possible, hence not common or something) # - it's imported from my.core.init (so we wan't to keep this file as small/reliable as possible, hence not common or something)
# - we still need this function in __main__, so has to be separate from my/core/init.py # - we still need this function in __main__, so has to be separate from my/core/init.py
def get_mycfg_dir() -> Path: def get_mycfg_dir() -> Path:
import appdirs # type: ignore[import-untyped]
import os import os
import appdirs # type: ignore[import-untyped]
# not sure if that's necessary, i.e. could rely on PYTHONPATH instead # 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? # on the other hand, by using MY_CONFIG we are guaranteed to load it from the desired path?
mvar = os.environ.get('MY_CONFIG') mvar = os.environ.get('MY_CONFIG')

View file

@ -10,16 +10,27 @@ import importlib
import inspect import inspect
import itertools import itertools
from datetime import datetime from datetime import datetime
from typing import TypeVar, Tuple, Optional, Union, Callable, Iterable, Iterator, Dict, Any, NamedTuple, List from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
NamedTuple,
Optional,
Tuple,
TypeVar,
Union,
)
import more_itertools import more_itertools
from . import error as err from . import error as err
from .types import is_namedtuple
from .error import Res, unwrap from .error import Res, unwrap
from .types import is_namedtuple
from .warnings import low from .warnings import low
T = TypeVar("T") T = TypeVar("T")
ET = Res[T] ET = Res[T]
@ -687,9 +698,10 @@ def test_raise_exceptions() -> None:
def test_wrap_unsortable_with_error_and_warning() -> None: def test_wrap_unsortable_with_error_and_warning() -> None:
import pytest
from collections import Counter from collections import Counter
import pytest
# by default should wrap unsortable (error) # by default should wrap unsortable (error)
with pytest.warns(UserWarning, match=r"encountered exception"): with pytest.warns(UserWarning, match=r"encountered exception"):
res = list(select(_mixed_iter_errors(), order_value=lambda o: isinstance(o, datetime))) res = list(select(_mixed_iter_errors(), order_value=lambda o: isinstance(o, datetime)))

View file

@ -9,24 +9,22 @@ See the select_range function below
import re import re
import time import time
from datetime import date, datetime, timedelta
from functools import lru_cache from functools import lru_cache
from datetime import datetime, timedelta, date from typing import Any, Callable, Iterator, NamedTuple, Optional, Type
from typing import Callable, Iterator, NamedTuple, Optional, Any, Type
import more_itertools import more_itertools
from .compat import fromisoformat
from .query import ( from .query import (
QueryException, ET,
select,
OrderFunc, OrderFunc,
QueryException,
Where, Where,
_handle_generate_order_by, _handle_generate_order_by,
ET, select,
) )
from .compat import fromisoformat
timedelta_regex = re.compile(r"^((?P<weeks>[\.\d]+?)w)?((?P<days>[\.\d]+?)d)?((?P<hours>[\.\d]+?)h)?((?P<minutes>[\.\d]+?)m)?((?P<seconds>[\.\d]+?)s)?$") timedelta_regex = re.compile(r"^((?P<weeks>[\.\d]+?)w)?((?P<days>[\.\d]+?)d)?((?P<hours>[\.\d]+?)h)?((?P<minutes>[\.\d]+?)m)?((?P<seconds>[\.\d]+?)s)?$")

View file

@ -1,13 +1,13 @@
import datetime import datetime
from dataclasses import is_dataclass, asdict from dataclasses import asdict, is_dataclass
from pathlib import Path
from decimal import Decimal from decimal import Decimal
from typing import Any, Optional, Callable, NamedTuple
from functools import lru_cache from functools import lru_cache
from pathlib import Path
from typing import Any, Callable, NamedTuple, Optional
from .error import error_to_json from .error import error_to_json
from .types import is_namedtuple
from .pytest import parametrize from .pytest import parametrize
from .types import is_namedtuple
# note: it would be nice to combine the 'asdict' and _default_encode to some function # note: it would be nice to combine the 'asdict' and _default_encode to some function
# that takes a complex python object and returns JSON-compatible fields, while still # that takes a complex python object and returns JSON-compatible fields, while still
@ -117,6 +117,7 @@ def _dumps_factory(**kwargs) -> Callable[[Any], str]:
def stdlib_factory() -> Optional[Dumps]: def stdlib_factory() -> Optional[Dumps]:
import json import json
from .warnings import high from .warnings import high
high( high(

View file

@ -3,9 +3,9 @@ Decorator to gracefully handle importing a data source, or warning
and yielding nothing (or a default) when its not available and yielding nothing (or a default) when its not available
""" """
from functools import wraps
from typing import Any, Iterator, TypeVar, Callable, Optional, Iterable
import warnings import warnings
from functools import wraps
from typing import Any, Callable, Iterable, Iterator, Optional, TypeVar
from .warnings import medium from .warnings import medium

View file

@ -1,13 +1,12 @@
from .internal import assert_subpackage; assert_subpackage(__name__) from .internal import assert_subpackage; assert_subpackage(__name__)
from contextlib import contextmanager
from pathlib import Path
import shutil import shutil
import sqlite3 import sqlite3
from contextlib import contextmanager
from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Tuple, Any, Iterator, Callable, Optional, Union, Literal from typing import Any, Callable, Iterator, Literal, Optional, Tuple, Union, overload
from .common import PathIsh from .common import PathIsh
from .compat import assert_never from .compat import assert_never
@ -98,7 +97,6 @@ def sqlite_copy_and_open(db: PathIsh) -> sqlite3.Connection:
# and then the return type ends up as Iterator[Tuple[str, ...]], which isn't desirable :( # and then the return type ends up as Iterator[Tuple[str, ...]], which isn't desirable :(
# a bit annoying to have this copy-pasting, but hopefully not a big issue # a bit annoying to have this copy-pasting, but hopefully not a big issue
from typing import overload
@overload @overload
def select(cols: Tuple[str ], rest: str, *, db: sqlite3.Connection) -> \ def select(cols: Tuple[str ], rest: str, *, db: sqlite3.Connection) -> \
Iterator[Tuple[Any ]]: ... Iterator[Tuple[Any ]]: ...

View file

@ -3,13 +3,13 @@ Helpers for hpi doctor/stats functionality.
''' '''
import collections import collections
from contextlib import contextmanager
from datetime import datetime
import importlib import importlib
import inspect import inspect
import typing
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
import typing
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@ -26,7 +26,6 @@ from typing import (
from .types import asdict from .types import asdict
Stats = Dict[str, Any] Stats = Dict[str, Any]
@ -133,8 +132,8 @@ def test_stat() -> None:
# #
# works with pandas dataframes # works with pandas dataframes
import pandas as pd
import numpy as np import numpy as np
import pandas as pd
def df() -> pd.DataFrame: def df() -> pd.DataFrame:
dates = pd.date_range(start='2024-02-10 08:00', end='2024-02-11 16:00', freq='5h') dates = pd.date_range(start='2024-02-10 08:00', end='2024-02-11 16:00', freq='5h')
@ -357,7 +356,7 @@ def _stat_item(item):
def _stat_iterable(it: Iterable[Any], quick: bool = False) -> Stats: def _stat_iterable(it: Iterable[Any], quick: bool = False) -> Stats:
from more_itertools import ilen, take, first from more_itertools import first, ilen, take
# todo not sure if there is something in more_itertools to compute this? # todo not sure if there is something in more_itertools to compute this?
total = 0 total = 0
@ -448,6 +447,7 @@ def _guess_datetime(x: Any) -> Optional[datetime]:
def test_guess_datetime() -> None: def test_guess_datetime() -> None:
from dataclasses import dataclass from dataclasses import dataclass
from typing import NamedTuple from typing import NamedTuple
from .compat import fromisoformat from .compat import fromisoformat
dd = fromisoformat('2021-02-01T12:34:56Z') dd = fromisoformat('2021-02-01T12:34:56Z')

View file

@ -1,16 +1,14 @@
import atexit
import os import os
import shutil import shutil
import tempfile import tempfile
import zipfile import zipfile
import atexit
from typing import Sequence, Generator, List, Union, Tuple
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Generator, List, Sequence, Tuple, Union
from .logging import make_logger from .logging import make_logger
logger = make_logger(__name__, level="info") logger = make_logger(__name__, level="info")

View file

@ -6,7 +6,7 @@ from contextlib import contextmanager
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Iterable, Sequence, Iterator from typing import Iterable, Iterator, Sequence
@dataclass @dataclass

View file

@ -1,10 +1,9 @@
from contextlib import contextmanager
import os import os
from contextlib import contextmanager
from typing import Iterator, Optional from typing import Iterator, Optional
import pytest import pytest
V = 'HPI_TESTS_USES_OPTIONAL_DEPS' V = 'HPI_TESTS_USES_OPTIONAL_DEPS'
# TODO use it for serialize tests that are using simplejson/orjson? # TODO use it for serialize tests that are using simplejson/orjson?

View file

@ -1,8 +1,8 @@
from datetime import datetime
import json import json
from pathlib import Path
from typing import NamedTuple, Iterator
import warnings import warnings
from datetime import datetime
from pathlib import Path
from typing import Iterator, NamedTuple
from ..denylist import DenyList from ..denylist import DenyList

View file

@ -1,7 +1,7 @@
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
import shutil import shutil
import sqlite3 import sqlite3
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from ..sqlite import sqlite_connect_immutable, sqlite_copy_and_open from ..sqlite import sqlite_connect_immutable, sqlite_copy_and_open

View file

@ -1,9 +1,8 @@
from pathlib import Path from pathlib import Path
from ..structure import match_structure
import pytest import pytest
from ..structure import match_structure
structure_data: Path = Path(__file__).parent / "structure_data" structure_data: Path = Path(__file__).parent / "structure_data"

View file

@ -35,8 +35,7 @@ def test_cachew_dir_none() -> None:
settings.ENABLE = True # by default it's off in tests (see conftest.py) settings.ENABLE = True # by default it's off in tests (see conftest.py)
from my.core.cachew import cache_dir from my.core.cachew import cache_dir, mcachew
from my.core.cachew import mcachew
from my.core.core_config import _reset_config as reset from my.core.core_config import _reset_config as reset
with reset() as cc: with reset() as cc:

View file

@ -1,6 +1,6 @@
import os import os
from subprocess import check_call
import sys import sys
from subprocess import check_call
def test_lists_modules() -> None: def test_lists_modules() -> None:

View file

@ -1,15 +1,15 @@
import os import os
from pathlib import Path
import shutil import shutil
import tempfile import tempfile
from typing import TYPE_CHECKING
import zipfile import zipfile
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from ..common import get_files from ..common import get_files
from ..kompress import CPath, ZipPath from ..kompress import CPath, ZipPath
import pytest
# hack to replace all /tmp with 'real' tmp dir # hack to replace all /tmp with 'real' tmp dir
# not ideal, but makes tests more concise # not ideal, but makes tests more concise

View file

@ -1,5 +1,5 @@
from functools import lru_cache from functools import lru_cache
from typing import Sequence, Dict from typing import Dict, Sequence
import pytz import pytz

View file

@ -1,13 +1,13 @@
from .internal import assert_subpackage; assert_subpackage(__name__) from .internal import assert_subpackage; assert_subpackage(__name__)
from dataclasses import is_dataclass, asdict as dataclasses_asdict from dataclasses import asdict as dataclasses_asdict
from dataclasses import is_dataclass
from datetime import datetime from datetime import datetime
from typing import ( from typing import (
Any, Any,
Dict, Dict,
) )
Json = Dict[str, Any] Json = Dict[str, Any]

View file

@ -1,21 +1,24 @@
from pathlib import Path
from itertools import chain
import os import os
import pkgutil import pkgutil
import sys import sys
from typing import List, Iterable, Optional from itertools import chain
from pathlib import Path
from types import ModuleType
from typing import Iterable, List, Optional
from .discovery_pure import HPIModule, ignored, _is_not_module_src, has_stats from .discovery_pure import HPIModule, _is_not_module_src, has_stats, ignored
def modules() -> Iterable[HPIModule]: def modules() -> Iterable[HPIModule]:
import my import my
for m in _iter_all_importables(my): for m in _iter_all_importables(my):
yield m yield m
__NOT_HPI_MODULE__ = 'Import this to mark a python file as a helper, not an actual HPI module' __NOT_HPI_MODULE__ = 'Import this to mark a python file as a helper, not an actual HPI module'
from .discovery_pure import NOT_HPI_MODULE_VAR from .discovery_pure import NOT_HPI_MODULE_VAR
assert NOT_HPI_MODULE_VAR in globals() # check name consistency assert NOT_HPI_MODULE_VAR in globals() # check name consistency
def is_not_hpi_module(module: str) -> Optional[str]: def is_not_hpi_module(module: str) -> Optional[str]:
@ -23,6 +26,7 @@ def is_not_hpi_module(module: str) -> Optional[str]:
None if a module, otherwise returns reason None if a module, otherwise returns reason
''' '''
import importlib import importlib
path: Optional[str] = None path: Optional[str] = None
try: try:
# TODO annoying, this can cause import of the parent module? # TODO annoying, this can cause import of the parent module?
@ -41,7 +45,6 @@ def is_not_hpi_module(module: str) -> Optional[str]:
return None return None
from types import ModuleType
# todo reuse in readme/blog post # todo reuse in readme/blog post
# borrowed from https://github.com/sanitizers/octomachinery/blob/24288774d6dcf977c5033ae11311dbff89394c89/tests/circular_imports_test.py#L22-L55 # borrowed from https://github.com/sanitizers/octomachinery/blob/24288774d6dcf977c5033ae11311dbff89394c89/tests/circular_imports_test.py#L22-L55
def _iter_all_importables(pkg: ModuleType) -> Iterable[HPIModule]: def _iter_all_importables(pkg: ModuleType) -> Iterable[HPIModule]:
@ -192,6 +195,7 @@ from my.core import __NOT_HPI_MODULE__
''') ''')
import sys import sys
orig_path = list(sys.path) orig_path = list(sys.path)
try: try:
sys.path.insert(0, str(badp)) sys.path.insert(0, str(badp))
@ -226,6 +230,7 @@ def stats():
''') ''')
import sys import sys
orig_path = list(sys.path) orig_path = list(sys.path)
try: try:
sys.path.insert(0, str(badp)) sys.path.insert(0, str(badp))

View file

@ -1,10 +1,9 @@
from concurrent.futures import Future, Executor
import sys import sys
from typing import Any, Callable, Optional, TypeVar, TYPE_CHECKING from concurrent.futures import Executor, Future
from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
from ..compat import ParamSpec from ..compat import ParamSpec
_P = ParamSpec('_P') _P = ParamSpec('_P')
_T = TypeVar('_T') _T = TypeVar('_T')
@ -15,6 +14,7 @@ class DummyExecutor(Executor):
This is useful if you're already using Executor for parallelising, This is useful if you're already using Executor for parallelising,
but also want to provide an option to run the code serially (e.g. for debugging) 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: Optional[int] = 1) -> None:
self._shutdown = False self._shutdown = False
self._max_workers = max_workers self._max_workers = max_workers

View file

@ -1,9 +1,9 @@
import importlib import importlib
import importlib.util import importlib.util
from pathlib import Path
import sys import sys
from typing import Optional from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import Optional
from ..common import PathIsh from ..common import PathIsh

View file

@ -4,8 +4,10 @@ Various helpers/transforms of iterators
Ideally this should be as small as possible and we should rely on stdlib itertools or more_itertools Ideally this should be as small as possible and we should rely on stdlib itertools or more_itertools
""" """
import warnings
from collections.abc import Hashable from collections.abc import Hashable
from typing import ( from typing import (
TYPE_CHECKING,
Callable, Callable,
Dict, Dict,
Iterable, Iterable,
@ -13,18 +15,16 @@ from typing import (
List, List,
Optional, Optional,
Sized, Sized,
Union,
TypeVar, TypeVar,
Union,
cast, cast,
TYPE_CHECKING,
) )
import warnings
import more_itertools
from decorator import decorator
from ..compat import ParamSpec from ..compat import ParamSpec
from decorator import decorator
import more_itertools
T = TypeVar('T') T = TypeVar('T')
K = TypeVar('K') K = TypeVar('K')
V = TypeVar('V') V = TypeVar('V')
@ -268,7 +268,9 @@ def check_if_hashable(iterable: Iterable[_HT]) -> Iterable[_HT]:
def test_check_if_hashable() -> None: def test_check_if_hashable() -> None:
from dataclasses import dataclass from dataclasses import dataclass
from typing import Set, Tuple from typing import Set, Tuple
import pytest import pytest
from ..compat import assert_type from ..compat import assert_type
x1: List[int] = [1, 2] x1: List[int] = [1, 2]
@ -353,6 +355,7 @@ def unique_everseen(
def test_unique_everseen() -> None: def test_unique_everseen() -> None:
import pytest import pytest
from ..tests.common import tmp_environ_set from ..tests.common import tmp_environ_set
def fun_good() -> Iterator[int]: def fun_good() -> Iterator[int]:

View file

@ -6,8 +6,8 @@ E.g. would be nice to propagate the warnings in the UI (it's even a subclass of
''' '''
import sys import sys
from typing import Optional
import warnings import warnings
from typing import TYPE_CHECKING, Optional
import click import click
@ -48,5 +48,11 @@ def high(message: str, *args, **kwargs) -> None:
_warn(message, *args, **kwargs) _warn(message, *args, **kwargs)
# NOTE: deprecated -- legacy import if not TYPE_CHECKING:
from warnings import warn from .compat import deprecated
@deprecated('use warnings.warn directly instead')
def warn(*args, **kwargs):
import warnings
return warnings.warn(*args, **kwargs)

View file

@ -41,9 +41,9 @@ def locations_to_gpx(locations: Iterable[LocationProtocol], buffer: TextIO) -> I
try: try:
import gpxpy.gpx import gpxpy.gpx
except ImportError as ie: except ImportError as ie:
from my.core.warnings import warn from my.core.warnings import high
warn("gpxpy not installed, cannot write to gpx. 'pip install gpxpy'") high("gpxpy not installed, cannot write to gpx. 'pip install gpxpy'")
raise ie raise ie
gpx = gpxpy.gpx.GPX() gpx = gpxpy.gpx.GPX()