Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
"outputs": [],
"source": [
"# FIXME hmm seems like this doesn't work if there are type annotations on cachew_impl? odd\n",
"# likely this? https://github.com/davidhalter/jedi/issues/2025\n",
"doc = getdoc('cachew_impl').split('Usage example:')[-1].lstrip()\n",
"dmd(f\"\"\"```python\n",
"{doc}\n",
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Cachew gives the best of two worlds and makes it both **easy and efficient**. Th
- first your objects get [converted](src/cachew/marshall/cachew.py#L29) into a simpler JSON-like representation
- after that, they are mapped into byte blobs via [`orjson`](https://github.com/ijl/orjson).

When the function is called, cachew [computes the hash of your function's arguments ](src/cachew/__init__.py#L586)
When the function is called, cachew [computes the hash of your function's arguments ](src/cachew/__init__.py#L580)
and compares it against the previously stored hash value.

- If they match, it would deserialize and yield whatever is stored in the cache database
Expand All @@ -145,13 +145,13 @@ and compares it against the previously stored hash value.

* primitive: `str`, `int`, `float`, `bool`, `datetime`, `date`, `Exception`

See [tests.test_types](src/cachew/tests/test_cachew.py#L683), [tests.test_primitive](src/cachew/tests/test_cachew.py#L721), [tests.test_dates](src/cachew/tests/test_cachew.py#L633), [tests.test_exceptions](src/cachew/tests/test_cachew.py#L1125)
* [@dataclass and NamedTuple](src/cachew/tests/test_cachew.py#L598)
* [Optional](src/cachew/tests/test_cachew.py#L525) types
* [Union](src/cachew/tests/test_cachew.py#L828) types
* [nested datatypes](src/cachew/tests/test_cachew.py#L441)
See [tests.test_types](src/cachew/tests/test_cachew.py#L682), [tests.test_primitive](src/cachew/tests/test_cachew.py#L720), [tests.test_dates](src/cachew/tests/test_cachew.py#L632), [tests.test_exceptions](src/cachew/tests/test_cachew.py#L1124)
* [@dataclass and NamedTuple](src/cachew/tests/test_cachew.py#L597)
* [Optional](src/cachew/tests/test_cachew.py#L524) types
* [Union](src/cachew/tests/test_cachew.py#L827) types
* [nested datatypes](src/cachew/tests/test_cachew.py#L440)

* detects [datatype schema changes](src/cachew/tests/test_cachew.py#L471) and discards old data automatically
* detects [datatype schema changes](src/cachew/tests/test_cachew.py#L470) and discards old data automatically


# Performance
Expand All @@ -165,12 +165,12 @@ You can find some of my performance tests in [benchmarks/](benchmarks) dir, and


# Using
See [docstring](src/cachew/__init__.py#L285) for up-to-date documentation on parameters and return types.
See [docstring](src/cachew/__init__.py#L279) for up-to-date documentation on parameters and return types.
You can also use [extensive unit tests](src/cachew/tests/test_cachew.py#L1) as a reference.

Some useful (but optional) arguments of `@cachew` decorator:

* `cache_path` can be a directory, or a callable that [returns a path](src/cachew/tests/test_cachew.py#L418) and depends on function's arguments.
* `cache_path` can be a directory, or a callable that [returns a path](src/cachew/tests/test_cachew.py#L417) and depends on function's arguments.

By default, `settings.DEFAULT_CACHEW_DIR` is used.

Expand Down Expand Up @@ -274,7 +274,7 @@ Now you can use `@mcachew` in place of `@cachew`, and be certain things don't br
## Settings


[cachew.settings](src/cachew/__init__.py#L59) exposes some parameters that allow you to control `cachew` behaviour:
[cachew.settings](src/cachew/__init__.py#L55) exposes some parameters that allow you to control `cachew` behaviour:
- `ENABLE`: set to `False` if you want to disable caching for without removing the decorators (useful for testing and debugging).
You can also use [cachew.extra.disabled_cachew](src/cachew/extra.py#L25) context manager to do it temporarily.
- `DEFAULT_CACHEW_DIR`: override to set a different base directory. The default is the "user cache directory" (see [platformdirs docs](https://github.com/tox-dev/platformdirs?tab=readme-ov-file#example-output)).
Expand Down
10 changes: 3 additions & 7 deletions src/cachew/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,15 @@ def orjson_dumps(*args, **kwargs): # type: ignore[misc]
from .backend.common import AbstractBackend
from .backend.file import FileBackend
from .backend.sqlite import SqliteBackend
from .common import SourceHash
from .common import CachewException, SourceHash, TypeNotSupported
from .logging_helper import make_logger
from .marshall.cachew import CachewMarshall, build_schema
from .utils import (
CachewException,
TypeNotSupported,
resolve_type_parameters,
)
from .utils import resolve_type_parameters

# in case of changes in the way cachew stores data, this should be changed to discard old caches
CACHEW_VERSION: str = importlib.metadata.version(__name__)

PathIsh = Path | str
type PathIsh = Path | str

Backend = Literal['sqlite', 'file']

Expand Down
3 changes: 0 additions & 3 deletions src/cachew/backend/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ def set_sqlite_pragma(dbapi_connection, connection_record): # noqa: ARG001
"""

@event.listens_for(self.connection, 'begin')
# pylint: disable=unused-variable
def do_begin(conn):
# NOTE there is also BEGIN CONCURRENT in newer versions of sqlite. could use it later?
conn.execute(text('BEGIN DEFERRED'))
Expand Down Expand Up @@ -178,7 +177,6 @@ def flush_blobs(self, chunk: Sequence[bytes]) -> None:

def finalize(self, new_hash: SourceHash) -> None:
# delete hash first, so if we are interrupted somewhere, it mismatches next time and everything is recomputed
# pylint: disable=no-value-for-parameter
self.connection.execute(self.table_hash.delete())

# checkfirst is necessary since it might not have existed in the first place
Expand All @@ -189,5 +187,4 @@ def finalize(self, new_hash: SourceHash) -> None:
# also seems like sqlalchemy doesn't have any primitives to escape table names.. sigh
self.connection.execute(text(f"ALTER TABLE `{self.table_cache_tmp.name}` RENAME TO `{self.table_cache.name}`"))

# pylint: disable=no-value-for-parameter
self.connection.execute(self.table_hash.insert().values([{'value': new_hash}]))
17 changes: 16 additions & 1 deletion src/cachew/common.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
from dataclasses import dataclass

# TODO better name to represent what it means?
SourceHash = str
type SourceHash = str


class CachewException(RuntimeError):
pass


@dataclass
class TypeNotSupported(CachewException):
type_: type
reason: str

def __str__(self) -> str:
return f"{self.type_} isn't supported by cachew: {self.reason}. See https://github.com/karlicoss/cachew#features for the list of supported types."
17 changes: 9 additions & 8 deletions src/cachew/experimental.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from .compat import deprecated
from typing import TYPE_CHECKING

if not TYPE_CHECKING:
from .compat import deprecated

@deprecated("Exceptions are not an experimental feature anymore and enabled by default.")
def enable_exceptions() -> None:
pass
@deprecated("Exceptions are not an experimental feature anymore and enabled by default.")
def enable_exceptions() -> None:
pass


@deprecated("Exceptions are not an experimental feature anymore and enabled by default.")
def disable_exceptions() -> None:
pass
@deprecated("Exceptions are not an experimental feature anymore and enabled by default.")
def disable_exceptions() -> None:
pass
4 changes: 2 additions & 2 deletions src/cachew/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from sqlalchemy import Column

from .pytest import parametrize
from .utils import CachewException
from .common import CachewException


def get_union_args(cls) -> Optional[tuple[type]]:
Expand Down Expand Up @@ -300,7 +300,7 @@ class NTBinder(Generic[NT]):
>>> binder.from_row(('ann', 25, True, None, None, 'extra'))
Traceback (most recent call last):
...
cachew.utils.CachewException: unconsumed items in iterator ['extra']
cachew.common.CachewException: unconsumed items in iterator ['extra']
"""

name: Optional[str] # None means toplevel
Expand Down
8 changes: 5 additions & 3 deletions src/cachew/logging_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import warnings
from functools import lru_cache
from typing import TYPE_CHECKING


def test() -> None:
Expand Down Expand Up @@ -34,7 +35,7 @@ def test() -> None:
M(
"\n Also exception logging is kinda lame, doesn't print traceback by default unless you remember to pass exc_info:"
)
l.exception(ex) # type: ignore[possibly-undefined] # pylint: disable=used-before-assignment
l.exception(ex) # type: ignore[possibly-undefined]

M(
"\n\n With make_logger you get a reasonable logging format, colours (via colorlog library) and other neat things:"
Expand Down Expand Up @@ -245,6 +246,7 @@ def get_enlighten():


## legacy/deprecated methods for backwards compatilibity
LazyLogger = make_logger
logger = make_logger
if not TYPE_CHECKING:
LazyLogger = make_logger
logger = make_logger
##
3 changes: 2 additions & 1 deletion src/cachew/marshall/cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
)
from zoneinfo import ZoneInfo

from ..utils import TypeNotSupported, is_namedtuple, resolve_type_parameters
from ..common import TypeNotSupported
from ..utils import is_namedtuple, resolve_type_parameters
from .common import AbstractMarshall, Json


Expand Down
5 changes: 1 addition & 4 deletions src/cachew/tests/marshall.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import (
Any,
Literal,
)
from typing import Any, Literal

import orjson
import pytest
Expand Down
1 change: 0 additions & 1 deletion src/cachew/tests/test_cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ def test_return_type_none(tmp_path: Path) -> None:
with pytest.raises(CachewException):

@cachew(tmp_path)
# pylint: disable=unused-variable
def data():
return []

Expand Down
14 changes: 0 additions & 14 deletions src/cachew/utils.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
from collections.abc import Mapping
from dataclasses import dataclass
from types import UnionType
from typing import TypeAliasType, TypeVar, get_args, get_origin


class CachewException(RuntimeError):
pass


@dataclass
class TypeNotSupported(CachewException):
type_: type
reason: str

def __str__(self) -> str:
return f"{self.type_} isn't supported by cachew: {self.reason}. See https://github.com/karlicoss/cachew#features for the list of supported types."


# https://stackoverflow.com/a/2166841/706389
def is_namedtuple(t) -> bool:
b = getattr(t, '__bases__', None)
Expand Down