Skip to content

Commit

Permalink
Support disk/embedded/remote store via libsql
Browse files Browse the repository at this point in the history
  • Loading branch information
aausch committed Aug 15, 2023
1 parent 2be6a8c commit 1491db1
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang

## 0.2

### 0.2.2

- Support `libsql` backend.

### 0.2.1
- Fix picklecoder
- Fix connection failure transparency and add logging
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
## Introduction

`fastapi-cache` is a tool to cache FastAPI endpoint and function results, with
backends supporting Redis, Memcached, and Amazon DynamoDB.
backends supporting Redis, Memcached, libsql and Amazon DynamoDB.

## Features

- Supports `redis`, `memcache`, `dynamodb`, and `in-memory` backends.
- Supports `redis`, `memcache`, `dynamodb`, `libsql` and `in-memory` backends.
- Easy integration with [FastAPI](https://fastapi.tiangolo.com/).
- Support for HTTP cache headers like `ETag` and `Cache-Control`, as well as conditional `If-Match-None` requests.

Expand All @@ -21,6 +21,7 @@ backends supporting Redis, Memcached, and Amazon DynamoDB.
- `redis` when using `RedisBackend`.
- `memcache` when using `MemcacheBackend`.
- `aiobotocore` when using `DynamoBackend`.
- `libsql-client` when using `libsql`

## Install

Expand All @@ -46,6 +47,10 @@ or
> pip install "fastapi-cache2[dynamodb]"
```

```shell
> pip install "fastapi-cache2[libsql]"
```

## Usage

### Quick Start
Expand Down
7 changes: 7 additions & 0 deletions fastapi_cache/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@
pass
else:
__all__ += ["redis"]

try:
from fastapi_cache.backends import libsql
except ImportError:
pass
else:
__all__ += ["libsql"]
99 changes: 99 additions & 0 deletions fastapi_cache/backends/libsql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import time
from typing import Optional, Tuple

import libsql_client
from libsql_client import ResultSet

from fastapi_cache.types import Backend

EmptyResultSet = ResultSet(
columns=(),
rows=[],
rows_affected=0,
last_insert_rowid=0)

class LibsqlBackend(Backend):
"""
libsql backend provider
This backend requires a table name to be passed during initialization. The table
will be created if it does not exist. If the table does exists, it will be emptied during init
Note that this backend does not fully support TTL. It will only delete outdated objects on get.
Usage:
>> libsql_url = "file:local.db"
>> cache = LibsqlBackend(libsql_url=libsql_url, table_name="your-cache")
>> cache.create_and_flush()
>> FastAPICache.init(cache)
"""

# client: libsql_client.Client
table_name: str
libsql_url: str

def __init__(self, libsql_url: str, table_name: str):
self.libsql_url = libsql_url
self.table_name = table_name

@property
def now(self) -> int:
return int(time.time())

async def _make_request(self, request: str) -> ResultSet:
# TODO: Exception handling. Return EmptyResultSet on error?
async with libsql_client.create_client(self.libsql_url) as client:
return await client.execute(request)


async def create_and_flush(self) -> None:
await self._make_request("CREATE TABLE IF NOT EXISTS `{}` "
"(key STRING PRIMARY KEY, value BLOB, expire INTEGER);"
.format(self.table_name))
await self._make_request("DELETE FROM `{}`;".format(self.table_name))

return None

async def _get(self, key: str) -> Tuple[int, Optional[bytes]]:
result_set = await self._make_request("SELECT * from `{}` WHERE key = \"{}\""
.format(self.table_name,key))
if len(result_set.rows) == 0:
return (0,None)

value = result_set.rows[0]["value"]
ttl_ts = result_set.rows[0]["expire"]

if not value:
return (0,None)
if ttl_ts < self.now:
await self._make_request("DELETE FROM `{}` WHERE key = \"{}\""
.format(self.table_name, key))
return (0, None)

return(ttl_ts, value) # type: ignore[union-attr,no-any-return]

async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
return await self._get(key)

async def get(self, key: str) -> Optional[bytes]:
_, value = await self._get(key)
return value

async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None:
ttl = self.now + expire if expire else 0
await self._make_request("INSERT OR REPLACE INTO `{}`(\"key\", \"value\", \"expire\") "
"VALUES('{}','{}',{});"
.format(self.table_name, key, value.decode("utf-8"), ttl))
return None

async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int:

if namespace:
result_set = await self._make_request("DELETE FROM `{}` WHERE key = \"{}%\""
.format(self.table_name, namespace))
return result_set.rowcount # type: ignore
elif key:
result_set = await self._make_request("DELETE FROM `{}` WHERE key = \"{}\""
.format(self.table_name, key))
return result_set.rowcount # type: ignore
return 0
20 changes: 18 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pendulum = "*"
aiobotocore = { version = ">=1.4.1,<3.0.0", optional = true }
typing-extensions = { version = ">=4.1.0" }
importlib-metadata = {version = "^6.6.0", python = "<3.8"}
libsql-client = { version = "^0.3.0", optional = true }

[tool.poetry.group.linting]
optional = true
Expand Down Expand Up @@ -53,7 +54,8 @@ twine = { version = "^4.0.2", python = "^3.10" }
redis = ["redis"]
memcache = ["aiomcache"]
dynamodb = ["aiobotocore"]
all = ["redis", "aiomcache", "aiobotocore"]
libsql = ["libsql-client"]
all = ["redis", "aiomcache", "aiobotocore", "libsql-client"]

[tool.mypy]
files = ["."]
Expand Down

0 comments on commit 1491db1

Please sign in to comment.