Merge branch 'master' into location-fallback
This commit is contained in:
commit
f05e81cee5
36 changed files with 626 additions and 232 deletions
|
@ -344,8 +344,8 @@ def _requires(modules: Sequence[str]) -> Sequence[str]:
|
|||
|
||||
reqs = mod.requires
|
||||
if reqs is None:
|
||||
error(f"Module {mod.name} has no REQUIRES specification")
|
||||
sys.exit(1)
|
||||
warning(f"Module {mod.name} has no REQUIRES specification")
|
||||
continue
|
||||
for r in reqs:
|
||||
if r not in res:
|
||||
res.append(r)
|
||||
|
@ -369,6 +369,10 @@ def module_install(*, user: bool, module: Sequence[str], parallel: bool=False) -
|
|||
|
||||
requirements = _requires(module)
|
||||
|
||||
if len(requirements) == 0:
|
||||
warning('requirements list is empty, no need to install anything')
|
||||
return
|
||||
|
||||
pre_cmd = [
|
||||
sys.executable, '-m', 'pip',
|
||||
'install',
|
||||
|
|
|
@ -28,7 +28,7 @@ F = TypeVar('F')
|
|||
from contextlib import contextmanager
|
||||
from typing import Iterator
|
||||
@contextmanager
|
||||
def override_config(config: F) -> Iterator[F]:
|
||||
def _override_config(config: F) -> Iterator[F]:
|
||||
'''
|
||||
Temporary override for config's parameters, useful for testing/fake data/etc.
|
||||
'''
|
||||
|
@ -44,12 +44,53 @@ def override_config(config: F) -> Iterator[F]:
|
|||
delattr(config, k)
|
||||
|
||||
|
||||
# helper for tests? not sure if could be useful elsewhere
|
||||
import importlib
|
||||
import sys
|
||||
from typing import Optional, Set
|
||||
ModuleRegex = str
|
||||
@contextmanager
|
||||
def tmp_config():
|
||||
import my.config as C
|
||||
with override_config(C):
|
||||
yield C # todo not sure?
|
||||
def _reload_modules(modules: ModuleRegex) -> Iterator[None]:
|
||||
def loaded_modules() -> Set[str]:
|
||||
return {name for name in sys.modules if re.fullmatch(modules, name)}
|
||||
|
||||
modules_before = loaded_modules()
|
||||
|
||||
for m in modules_before:
|
||||
importlib.reload(sys.modules[m])
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
modules_after = loaded_modules()
|
||||
for m in modules_after:
|
||||
if m in modules_before:
|
||||
# was previously loaded, so need to reload to pick up old config
|
||||
importlib.reload(sys.modules[m])
|
||||
else:
|
||||
# wasn't previously loaded, so need to unload it
|
||||
# otherwise it might fail due to missing config etc
|
||||
sys.modules.pop(m, None)
|
||||
|
||||
|
||||
from contextlib import ExitStack
|
||||
import re
|
||||
@contextmanager
|
||||
def tmp_config(*, modules: Optional[ModuleRegex]=None, config=None):
|
||||
if modules is None:
|
||||
assert config is None
|
||||
if modules is not None:
|
||||
assert config is not None
|
||||
|
||||
import my.config
|
||||
with ExitStack() as module_reload_stack, _override_config(my.config) as new_config:
|
||||
if config is not None:
|
||||
overrides = {k: v for k, v in vars(config).items() if not k.startswith('__')}
|
||||
for k, v in overrides.items():
|
||||
setattr(new_config, k, v)
|
||||
|
||||
if modules is not None:
|
||||
module_reload_stack.enter_context(_reload_modules(modules))
|
||||
yield new_config
|
||||
|
||||
|
||||
def test_tmp_config() -> None:
|
||||
|
@ -63,3 +104,8 @@ def test_tmp_config() -> None:
|
|||
# todo hmm. not sure what should do about new properties??
|
||||
assert not hasattr(c, 'extra')
|
||||
assert c.google != 'whatever'
|
||||
|
||||
|
||||
###
|
||||
# todo properly deprecate, this isn't really meant for public use
|
||||
override_config = _override_config
|
||||
|
|
|
@ -123,8 +123,8 @@ from contextlib import contextmanager as ctx
|
|||
@ctx
|
||||
def _reset_config() -> Iterator[Config]:
|
||||
# todo maybe have this decorator for the whole of my.config?
|
||||
from .cfg import override_config
|
||||
with override_config(config) as cc:
|
||||
from .cfg import _override_config
|
||||
with _override_config(config) as cc:
|
||||
cc.enabled_modules = None
|
||||
cc.disabled_modules = None
|
||||
cc.cache_dir = None
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
from .common import assert_subpackage; assert_subpackage(__name__)
|
||||
|
||||
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sqlite3
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Tuple, Any, Iterator, Callable, Optional, Union
|
||||
|
||||
|
||||
from .common import PathIsh
|
||||
from .common import PathIsh, assert_never
|
||||
from .compat import Literal
|
||||
|
||||
|
||||
def sqlite_connect_immutable(db: PathIsh) -> sqlite3.Connection:
|
||||
# https://www.sqlite.org/draft/uri.html#uriimmutable
|
||||
return sqlite3.connect(f'file:{db}?immutable=1', uri=True)
|
||||
|
||||
|
||||
|
@ -30,6 +32,44 @@ def test_sqlite_connect_immutable(tmp_path: Path) -> None:
|
|||
conn.execute('DROP TABLE testtable')
|
||||
|
||||
|
||||
SqliteRowFactory = Callable[[sqlite3.Cursor, sqlite3.Row], Any]
|
||||
|
||||
def dict_factory(cursor, row):
|
||||
fields = [column[0] for column in cursor.description]
|
||||
return {key: value for key, value in zip(fields, row)}
|
||||
|
||||
|
||||
Factory = Union[SqliteRowFactory, Literal['row', 'dict']]
|
||||
|
||||
@contextmanager
|
||||
def sqlite_connection(db: PathIsh, *, immutable: bool=False, row_factory: Optional[Factory]=None) -> Iterator[sqlite3.Connection]:
|
||||
dbp = f'file:{db}'
|
||||
# https://www.sqlite.org/draft/uri.html#uriimmutable
|
||||
if immutable:
|
||||
# assert results in nicer error than sqlite3.OperationalError
|
||||
assert Path(db).exists(), db
|
||||
dbp = f'{dbp}?immutable=1'
|
||||
row_factory_: Any = None
|
||||
if row_factory is not None:
|
||||
if callable(row_factory):
|
||||
row_factory_ = row_factory
|
||||
elif row_factory == 'row':
|
||||
row_factory_ = sqlite3.Row
|
||||
elif row_factory == 'dict':
|
||||
row_factory_ = dict_factory
|
||||
else:
|
||||
assert_never()
|
||||
|
||||
conn = sqlite3.connect(dbp, uri=True)
|
||||
try:
|
||||
conn.row_factory = row_factory_
|
||||
with conn:
|
||||
yield conn
|
||||
finally:
|
||||
# Connection context manager isn't actually closing the connection, only keeps transaction
|
||||
conn.close()
|
||||
|
||||
|
||||
# TODO come up with a better name?
|
||||
# NOTE: this is tested by tests/sqlite.py::test_sqlite_read_with_wal
|
||||
def sqlite_copy_and_open(db: PathIsh) -> sqlite3.Connection:
|
||||
|
@ -52,8 +92,6 @@ def sqlite_copy_and_open(db: PathIsh) -> sqlite3.Connection:
|
|||
return dest
|
||||
|
||||
|
||||
from typing import Tuple, Any, Iterator
|
||||
|
||||
# NOTE hmm, so this kinda works
|
||||
# V = TypeVar('V', bound=Tuple[Any, ...])
|
||||
# def select(cols: V, rest: str, *, db: sqlite3.Connetion) -> Iterator[V]:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue