52 Commits

Author SHA1 Message Date
Jinlong Peng
15576b482a Merge remote-tracking branch 'origin/master' 2022-08-07 21:11:55 +08:00
Jinlong Peng
f80bfdb18d upgrade deps 2022-08-07 21:11:47 +08:00
long2ice
aaed438d8f Merge pull request #69 from jegork/feature/support-cache-for-normal-functions
Add support for normal def functions
2022-08-01 15:05:16 +08:00
Jegor Kitskerkin
d6c52408d2 Add support for normal def functions 2022-08-01 00:06:39 +02:00
long2ice
8c92cc59ae Fix test 2022-06-26 19:18:34 +08:00
long2ice
824e2e145f Replace aioredis with redis-py 2022-06-17 11:01:47 +08:00
long2ice
7fa54d311f Merge remote-tracking branch 'origin/master' 2022-06-10 08:43:42 +08:00
long2ice
9582e04d43 update deps 2022-06-10 08:43:35 +08:00
long2ice
fd8cf2da11 Merge pull request #60 from cnkailyn/master
bugfix: '+' is more prior than 'or'
2022-04-24 11:44:25 +08:00
kailyn
2f1b1409b9 bugfix: '+' is more prior than 'or' 2022-04-24 11:19:20 +08:00
long2ice
269c1ca616 update deps 2022-03-30 14:19:53 +08:00
long2ice
81d2bf2cc6 update version and changelog 2021-11-12 09:37:02 +08:00
long2ice
70f53566aa Merge pull request #40 from jimtheyounger/feature/add-dynamodb-backend
Add dynamodb backend support
2021-10-29 10:45:28 +08:00
long2ice
9928f4cda0 Add enable param to init 2021-10-28 15:52:21 +08:00
long2ice
4faa5b7101 update workflows 2021-10-19 11:56:51 +08:00
long2ice
c3be2eca19 Fix default json coder for datetime. 2021-10-09 16:51:05 +08:00
Jimmy
11f01a21f5 Remove unused variable assignment 2021-10-07 17:34:06 +02:00
Jimmy
cdcfdc6ae6 Apply styling 2021-10-06 10:10:22 +02:00
Jimmy
a40c54e9e7 Update README & add usage 2021-09-29 16:22:04 +02:00
Jimmy
d67797a1d5 Add DynamoDB backend 2021-09-29 16:14:28 +02:00
long2ice
8a8eb395ec Merge pull request #38 from joeflack4/docs-minorFix
Fix: README.md: Corrected some syntactically incorrect commands
2021-09-24 10:23:25 +08:00
Joe Flack
e397dcb16b Updated readme.md
Corrected some syntactically incorrect commands
2021-09-23 16:10:31 -04:00
long2ice
37a2fa85db update CONVERTERS 2021-09-17 10:19:56 +08:00
long2ice
6888c10d6c update deps 2021-08-29 22:33:50 +08:00
long2ice
943935870d Update FUNDING.yml 2021-08-26 20:34:50 +08:00
long2ice
46c7ada364 Fix ci 2021-07-26 16:37:19 +08:00
long2ice
767241be41 - Fix redis cache.
- Encode key builder.
2021-07-26 16:33:22 +08:00
long2ice
de1bde39fd update version 2021-07-23 09:38:47 +08:00
long2ice
8490ad36f0 Merge pull request #25 from dveleztx/master
This fixes #24.
2021-07-23 09:38:04 +08:00
David Velez
57fe4ce24b Updated changelog to 1.4 from 1.5 2021-07-22 10:54:10 -05:00
David Velez
3dc2b53e41 Updated changelog and version for the project. 2021-07-22 10:47:02 -05:00
David Velez
2dd37b09ab This fixes #24. Looking at aioredis library, the client.py keyword for set is now 'ex', not 'expire'. Tested this fix and fast-cache now works without issue. 2021-07-22 10:17:52 -05:00
long2ice
0bc8c6c20e Merge pull request #22 from heliumbrain/master
Adapt to aioredis 2.0
2021-07-16 10:02:10 +08:00
heliumbrain
9e3c9816c5 Update README.md
Updated readme to reflect the changes in aioredis 2.0
2021-07-15 23:15:01 +02:00
long2ice
7c7aa26a88 update deps 2021-07-15 10:00:27 +08:00
long2ice
1edb0ba1fe Create FUNDING.yml 2021-04-30 16:49:36 +08:00
long2ice
7c2007847f Merge pull request #8 from rushilsrivastava/patch-2
Use FastAPI's built in jsonable_encoder
2021-04-30 16:48:07 +08:00
long2ice
80de421a2a Merge pull request #18 from Agri1988/master
add timezone serializer to coder.JsonEncoder for datetime.datetime instances
2021-04-29 09:36:48 +08:00
alexandr.yagolovich
eb55b01be9 timezone serializer to coder.JsonEncoder for datetime.datetime instances 2021-04-28 20:44:35 +03:00
long2ice
8573eeace6 fix ci 2021-03-20 14:47:46 +08:00
long2ice
1d0c245a70 - Fix default expire for memcached. (#13)
- Update default key builder. (#12)
2021-03-20 14:42:29 +08:00
long2ice
c665189d90 Merge pull request #7 from shershen08/patch-1
Update README.md
2021-01-12 09:50:25 +08:00
Mikhail Kuznetcov
ba7276ba98 Update README.md
add other params description
2021-01-11 17:37:39 +01:00
Rushil Srivastava
30e5246cf5 Use FastAPI's built in jsonable_encoder 2021-01-11 03:11:35 -08:00
Mikhail Kuznetcov
cdae610432 Update README.md
add explanation about expire
2021-01-10 16:26:41 +01:00
long2ice
9157412d6a Merge pull request #5 from rushilsrivastava/patch-1
Add minimum dependencies version
2021-01-10 19:59:15 +08:00
Rushil Srivastava
a9b0b9d913 add min dependencies 2021-01-10 03:53:26 -08:00
long2ice
fec3c78291 bug fix 2021-01-06 20:00:58 +08:00
long2ice
361a25857b bug fix 2021-01-06 19:57:16 +08:00
long2ice
6f5d4900a9 bug fix 2021-01-06 14:41:46 +08:00
long2ice
7f7252d151 bug fix 2021-01-06 10:44:22 +08:00
long2ice
c9e03ed9af update pypi.yml 2021-01-06 10:38:30 +08:00
17 changed files with 1019 additions and 506 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
custom: ["https://sponsor.long2ice.io"]

View File

@@ -8,9 +8,8 @@ jobs:
- uses: actions/setup-python@v2 - uses: actions/setup-python@v2
with: with:
python-version: "3.x" python-version: "3.x"
- name: Install and configure Poetry - uses: abatilo/actions-poetry@v2.1.3
uses: snok/install-poetry@v1.1.1 - name: Config poetry
with: run: poetry config experimental.new-installer false
virtualenvs-create: false
- name: CI - name: CI
run: make ci run: make ci

View File

@@ -11,11 +11,13 @@ jobs:
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
with: with:
python-version: '3.x' python-version: '3.x'
- uses: dschep/install-poetry-action@v1.3 - uses: abatilo/actions-poetry@v2.1.3
- name: Config poetry
run: poetry config experimental.new-installer false
- name: Build dists - name: Build dists
run: make build run: make build
- name: Pypi Publish - name: Pypi Publish
uses: pypa/gh-action-pypi-publish@master uses: pypa/gh-action-pypi-publish@master
with: with:
user: __token__ user: __token__
password: ${{ secrets.pypi_password }} password: ${{ secrets.pypi_password }}

View File

@@ -2,6 +2,34 @@
## 0.1 ## 0.1
### 0.1.9
- Replace `aioredis` with `redis-py`.
### 0.1.8
- Support `dynamodb` backend.
### 0.1.7
- Fix default json coder for datetime.
- Add `enable` param to `init`.
### 0.1.6
- Fix redis cache.
- Encode key builder.
### 0.1.5
- Fix setting expire for redis (#24)
- Update expire key
### 0.1.4
- Fix default expire for memcached. (#13)
- Update default key builder. (#12)
### 0.1.3 ### 0.1.3
- Fix cache key builder. - Fix cache key builder.

View File

@@ -1,19 +1,6 @@
checkfiles = fastapi_cache/ examples/ tests/ checkfiles = fastapi_cache/ examples/ tests/
black_opts = -l 100 -t py38
py_warn = PYTHONDEVMODE=1 py_warn = PYTHONDEVMODE=1
help:
@echo "FastAPI-Cache development makefile"
@echo
@echo "usage: make <target>"
@echo "Targets:"
@echo " up Ensure dev/test dependencies are updated"
@echo " deps Ensure dev/test dependencies are installed"
@echo " check Checks that build is sane"
@echo " test Runs all tests"
@echo " style Auto-formats the code"
@echo " build Build package"
up: up:
@poetry update @poetry update
@@ -21,18 +8,20 @@ deps:
@poetry install --no-root -E all @poetry install --no-root -E all
style: deps style: deps
@isort -src $(checkfiles) @poetry run isort -src $(checkfiles)
@black $(black_opts) $(checkfiles) @poetry run black $(checkfiles)
check: deps check: deps
@black --check $(black_opts) $(checkfiles) || (echo "Please run 'make style' to auto-fix style issues" && false) @poetry run black $(checkfiles) || (echo "Please run 'make style' to auto-fix style issues" && false)
@flake8 $(checkfiles) @poetry run flake8 $(checkfiles)
@bandit -r $(checkfiles)
test: deps test: deps
$(py_warn) pytest $(py_warn) poetry run pytest
build: deps build: clean deps
@poetry build @poetry build
clean:
@rm -rf ./dist
ci: check test ci: check test

View File

@@ -7,11 +7,11 @@
## Introduction ## Introduction
`fastapi-cache` is a tool to cache fastapi response and function result, with backends support `redis` and `memcache`. `fastapi-cache` is a tool to cache fastapi response and function result, with backends support `redis`, `memcache`, and `dynamodb`.
## Features ## Features
- Support `redis` and `memcache` and `in-memory` backends. - Support `redis`, `memcache`, `dynamodb`, and `in-memory` backends.
- Easily integration with `fastapi`. - Easily integration with `fastapi`.
- Support http cache like `ETag` and `Cache-Control`. - Support http cache like `ETag` and `Cache-Control`.
@@ -20,6 +20,7 @@
- `asyncio` environment. - `asyncio` environment.
- `redis` if use `RedisBackend`. - `redis` if use `RedisBackend`.
- `memcache` if use `MemcacheBackend`. - `memcache` if use `MemcacheBackend`.
- `aiobotocore` if use `DynamoBackend`.
## Install ## Install
@@ -30,13 +31,19 @@
or or
```shell ```shell
> pip install fastapi-cache2[redis] > pip install "fastapi-cache2[redis]"
``` ```
or or
```shell ```shell
> pip install fastapi-cache2[memcache] > pip install "fastapi-cache2[memcache]"
```
or
```shell
> pip install "fastapi-cache2[dynamodb]"
``` ```
## Usage ## Usage
@@ -69,7 +76,7 @@ async def index(request: Request, response: Response):
@app.on_event("startup") @app.on_event("startup")
async def startup(): async def startup():
redis = await aioredis.create_redis_pool("redis://localhost", encoding="utf8") redis = aioredis.from_url("redis://localhost", encoding="utf8", decode_responses=True)
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache") FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
``` ```
@@ -82,6 +89,14 @@ Firstly you must call `FastAPICache.init` on startup event of `fastapi`, there a
If you want cache `fastapi` response transparently, you can use `cache` as decorator between router decorator and view function and must pass `request` as param of view function. If you want cache `fastapi` response transparently, you can use `cache` as decorator between router decorator and view function and must pass `request` as param of view function.
Parameter | type, description
------------ | -------------
expire | int, states a caching time in seconds
namespace | str, namespace to use to store certain cache items
coder | which coder to use, e.g. JsonCoder
key_builder | which key builder to use, default to builtin
And if you want use `ETag` and `Cache-Control` features, you must pass `response` param also. And if you want use `ETag` and `Cache-Control` features, you must pass `response` param also.
You can also use `cache` as decorator like other cache tools to cache common function result. You can also use `cache` as decorator like other cache tools to cache common function result.

View File

@@ -1,10 +1,15 @@
from datetime import date, datetime
import time
import redis.asyncio as redis
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from redis.asyncio.connection import ConnectionPool
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import Response
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache from fastapi_cache.decorator import cache
app = FastAPI() app = FastAPI()
@@ -20,7 +25,7 @@ async def get_ret():
@app.get("/") @app.get("/")
@cache(namespace="test", expire=2) @cache(namespace="test", expire=20)
async def index(request: Request, response: Response): async def index(request: Request, response: Response):
return dict(ret=await get_ret()) return dict(ret=await get_ret())
@@ -30,9 +35,30 @@ async def clear():
return await FastAPICache.clear(namespace="test") return await FastAPICache.clear(namespace="test")
@app.get("/date")
@cache(namespace="test", expire=20)
async def get_data(request: Request, response: Response):
return date.today()
@app.get("/blocking")
@cache(namespace="test", expire=20)
def blocking(request: Request, response: Response):
time.sleep(5)
return dict(ret=get_ret())
@app.get("/datetime")
@cache(namespace="test", expire=20)
async def get_datetime(request: Request, response: Response):
return datetime.now()
@app.on_event("startup") @app.on_event("startup")
async def startup(): async def startup():
FastAPICache.init(InMemoryBackend(), prefix="fastapi-cache") pool = ConnectionPool.from_url(url="redis://localhost")
r = redis.Redis(connection_pool=pool)
FastAPICache.init(RedisBackend(r), prefix="fastapi-cache")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -11,6 +11,7 @@ class FastAPICache:
_init = False _init = False
_coder = None _coder = None
_key_builder = None _key_builder = None
_enable = True
@classmethod @classmethod
def init( def init(
@@ -20,6 +21,7 @@ class FastAPICache:
expire: int = None, expire: int = None,
coder: Coder = JsonCoder, coder: Coder = JsonCoder,
key_builder: Callable = default_key_builder, key_builder: Callable = default_key_builder,
enable: bool = True,
): ):
if cls._init: if cls._init:
return return
@@ -29,6 +31,7 @@ class FastAPICache:
cls._expire = expire cls._expire = expire
cls._coder = coder cls._coder = coder
cls._key_builder = key_builder cls._key_builder = key_builder
cls._enable = enable
@classmethod @classmethod
def get_backend(cls): def get_backend(cls):
@@ -51,6 +54,10 @@ class FastAPICache:
def get_key_builder(cls): def get_key_builder(cls):
return cls._key_builder return cls._key_builder
@classmethod
def get_enable(cls):
return cls._enable
@classmethod @classmethod
async def clear(cls, namespace: str = None, key: str = None): async def clear(cls, namespace: str = None, key: str = None):
namespace = cls._prefix + ":" + namespace if namespace else None namespace = cls._prefix + ":" + namespace if namespace else None

View File

@@ -0,0 +1,92 @@
import datetime
from typing import Tuple
from aiobotocore.session import get_session
from fastapi_cache.backends import Backend
class DynamoBackend(Backend):
"""
Amazon DynamoDB backend provider
This backend requires an existing table within your AWS environment to be passed during
backend init. If ttl is going to be used, this needs to be manually enabled on the table
using the `ttl` key. Dynamo will take care of deleting outdated objects, but this is not
instant so don't be alarmed when they linger around for a bit.
As with all AWS clients, credentials will be taken from the environment. Check the AWS SDK
for more information.
Usage:
>> dynamodb = DynamoBackend(table_name="your-cache", region="eu-west-1")
>> await dynamodb.init()
>> FastAPICache.init(dynamodb)
"""
def __init__(self, table_name, region=None):
self.session = get_session()
self.client = None # Needs async init
self.table_name = table_name
self.region = region
async def init(self):
self.client = await self.session.create_client(
"dynamodb", region_name=self.region
).__aenter__()
async def close(self):
self.client = await self.client.__aexit__(None, None, None)
async def get_with_ttl(self, key: str) -> Tuple[int, str]:
response = await self.client.get_item(TableName=self.table_name, Key={"key": {"S": key}})
if "Item" in response:
value = response["Item"].get("value", {}).get("S")
ttl = response["Item"].get("ttl", {}).get("N")
if not ttl:
return -1, value
# It's only eventually consistent so we need to check ourselves
expire = int(ttl) - int(datetime.datetime.now().timestamp())
if expire > 0:
return expire, value
return 0, None
async def get(self, key) -> str:
response = await self.client.get_item(TableName=self.table_name, Key={"key": {"S": key}})
if "Item" in response:
return response["Item"].get("value", {}).get("S")
async def set(self, key: str, value: str, expire: int = None):
ttl = (
{
"ttl": {
"N": str(
int(
(
datetime.datetime.now() + datetime.timedelta(seconds=expire)
).timestamp()
)
)
}
}
if expire
else {}
)
await self.client.put_item(
TableName=self.table_name,
Item={
**{
"key": {"S": key},
"value": {"S": value},
},
**ttl,
},
)
async def clear(self, namespace: str = None, key: str = None) -> int:
raise NotImplementedError

View File

@@ -43,7 +43,7 @@ class InMemoryBackend(Backend):
async def set(self, key: str, value: str, expire: int = None): async def set(self, key: str, value: str, expire: int = None):
async with self._lock: async with self._lock:
self._store[key] = Value(value, self._now + expire) self._store[key] = Value(value, self._now + (expire or 0))
async def clear(self, namespace: str = None, key: str = None) -> int: async def clear(self, namespace: str = None, key: str = None) -> int:
count = 0 count = 0

View File

@@ -16,7 +16,7 @@ class MemcachedBackend(Backend):
return await self.mcache.get(key, key.encode()) return await self.mcache.get(key, key.encode())
async def set(self, key: str, value: str, expire: int = None): async def set(self, key: str, value: str, expire: int = None):
return await self.mcache.set(key.encode(), value.encode(), exptime=expire) return await self.mcache.set(key.encode(), value.encode(), exptime=expire or 0)
async def clear(self, namespace: str = None, key: str = None): async def clear(self, namespace: str = None, key: str = None):
raise NotImplementedError raise NotImplementedError

View File

@@ -1,6 +1,6 @@
from typing import Tuple from typing import Tuple
from aioredis import Redis from redis.asyncio.client import Redis
from fastapi_cache.backends import Backend from fastapi_cache.backends import Backend
@@ -10,20 +10,18 @@ class RedisBackend(Backend):
self.redis = redis self.redis = redis
async def get_with_ttl(self, key: str) -> Tuple[int, str]: async def get_with_ttl(self, key: str) -> Tuple[int, str]:
p = self.redis.pipeline() async with self.redis.pipeline(transaction=True) as pipe:
p.ttl(key) return await (pipe.ttl(key).get(key).execute())
p.get(key)
return await p.execute()
async def get(self, key) -> str: async def get(self, key) -> str:
return await self.redis.get(key) return await self.redis.get(key)
async def set(self, key: str, value: str, expire: int = None): async def set(self, key: str, value: str, expire: int = None):
return await self.redis.set(key, value, expire=expire) return await self.redis.set(key, value, ex=expire)
async def clear(self, namespace: str = None, key: str = None) -> int: async def clear(self, namespace: str = None, key: str = None) -> int:
if namespace: if namespace:
lua = f"for i, name in ipairs(redis.call('KEYS', '{namespace}:*')) do redis.call('DEL', name); end" lua = f"for i, name in ipairs(redis.call('KEYS', '{namespace}:*')) do redis.call('DEL', name); end"
return await self.redis.eval(lua) return await self.redis.eval(lua, numkeys=0)
elif key: elif key:
return await self.redis.delete(key) return await self.redis.delete(key)

View File

@@ -4,11 +4,12 @@ import pickle # nosec:B403
from decimal import Decimal from decimal import Decimal
from typing import Any from typing import Any
import dateutil.parser import pendulum
from fastapi.encoders import jsonable_encoder
CONVERTERS = { CONVERTERS = {
"date": dateutil.parser.parse, "date": lambda x: pendulum.parse(x, exact=True),
"datetime": dateutil.parser.parse, "datetime": lambda x: pendulum.parse(x, exact=True),
"decimal": Decimal, "decimal": Decimal,
} }
@@ -16,13 +17,13 @@ CONVERTERS = {
class JsonEncoder(json.JSONEncoder): class JsonEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
if isinstance(obj, datetime.datetime): if isinstance(obj, datetime.datetime):
return {"val": obj.strftime("%Y-%m-%d %H:%M:%S"), "_spec_type": "datetime"} return {"val": str(obj), "_spec_type": "datetime"}
elif isinstance(obj, datetime.date): elif isinstance(obj, datetime.date):
return {"val": obj.strftime("%Y-%m-%d"), "_spec_type": "date"} return {"val": str(obj), "_spec_type": "date"}
elif isinstance(obj, Decimal): elif isinstance(obj, Decimal):
return {"val": str(obj), "_spec_type": "decimal"} return {"val": str(obj), "_spec_type": "decimal"}
else: else:
return super().default(obj) return jsonable_encoder(obj)
def object_hook(obj): def object_hook(obj):
@@ -63,4 +64,4 @@ class PickleCoder(Coder):
@classmethod @classmethod
def decode(cls, value: Any): def decode(cls, value: Any):
return pickle.loads(value) # nosec:B403 return pickle.loads(value) # nosec:B403,B301

View File

@@ -1,15 +1,21 @@
from functools import wraps import asyncio
from typing import Callable, Optional, Type from functools import wraps, partial
import inspect
from typing import TYPE_CHECKING, Callable, Optional, Type
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.coder import Coder from fastapi_cache.coder import Coder
if TYPE_CHECKING:
import concurrent.futures
def cache( def cache(
expire: int = None, expire: int = None,
coder: Type[Coder] = None, coder: Type[Coder] = None,
key_builder: Callable = None, key_builder: Callable = None,
namespace: Optional[str] = "", namespace: Optional[str] = "",
executor: Optional["concurrent.futures.Executor"] = None,
): ):
""" """
cache all function cache all function
@@ -17,6 +23,8 @@ def cache(
:param expire: :param expire:
:param coder: :param coder:
:param key_builder: :param key_builder:
:param executor:
:return: :return:
""" """
@@ -26,17 +34,22 @@ def cache(
nonlocal coder nonlocal coder
nonlocal expire nonlocal expire
nonlocal key_builder nonlocal key_builder
request = kwargs.get("request") copy_kwargs = kwargs.copy()
if request.headers.get("Cache-Control") == "no-store": request = copy_kwargs.pop("request", None)
response = copy_kwargs.pop("response", None)
if (
request and request.headers.get("Cache-Control") == "no-store"
) or not FastAPICache.get_enable():
return await func(*args, **kwargs) return await func(*args, **kwargs)
coder = coder or FastAPICache.get_coder() coder = coder or FastAPICache.get_coder()
expire = expire or FastAPICache.get_expire() expire = expire or FastAPICache.get_expire()
key_builder = key_builder or FastAPICache.get_key_builder() key_builder = key_builder or FastAPICache.get_key_builder()
request = kwargs.pop("request", None)
response = kwargs.pop("response", None)
backend = FastAPICache.get_backend() backend = FastAPICache.get_backend()
cache_key = key_builder(func, namespace, args=args, kwargs=kwargs)
cache_key = key_builder(
func, namespace, request=request, response=response, args=args, kwargs=copy_kwargs
)
ttl, ret = await backend.get_with_ttl(cache_key) ttl, ret = await backend.get_with_ttl(cache_key)
if not request: if not request:
if ret is not None: if ret is not None:
@@ -49,7 +62,6 @@ def cache(
return await func(request, *args, **kwargs) return await func(request, *args, **kwargs)
if_none_match = request.headers.get("if-none-match") if_none_match = request.headers.get("if-none-match")
if ret is not None: if ret is not None:
response = kwargs.get("response")
if response: if response:
response.headers["Cache-Control"] = f"max-age={ttl}" response.headers["Cache-Control"] = f"max-age={ttl}"
etag = f"W/{hash(ret)}" etag = f"W/{hash(ret)}"
@@ -59,7 +71,12 @@ def cache(
response.headers["ETag"] = etag response.headers["ETag"] = etag
return coder.decode(ret) return coder.decode(ret)
ret = await func(*args, **kwargs) if inspect.iscoroutinefunction(func):
ret = await func(*args, **kwargs)
else:
loop = asyncio.get_event_loop()
ret = await loop.run_in_executor(executor, partial(func, *args, **kwargs))
await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire()) await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire())
return ret return ret

View File

@@ -1,3 +1,4 @@
import hashlib
from typing import Optional from typing import Optional
from starlette.requests import Request from starlette.requests import Request
@@ -14,6 +15,11 @@ def default_key_builder(
): ):
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
prefix = FastAPICache.get_prefix() prefix = f"{FastAPICache.get_prefix()}:{namespace}:"
cache_key = f"{prefix}:{namespace}:{func.__module__}:{func.__name__}:{args}:{kwargs}" cache_key = (
prefix
+ hashlib.md5( # nosec:B303
f"{func.__module__}:{func.__name__}:{args}:{kwargs}".encode()
).hexdigest()
)
return cache_key return cache_key

1200
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "fastapi-cache2" name = "fastapi-cache2"
version = "0.1.3" version = "0.1.9"
description = "Cache for FastAPI" description = "Cache for FastAPI"
authors = ["long2ice <long2ice@gmail.com>"] authors = ["long2ice <long2ice@gmail.com>"]
license = "Apache-2.0" license = "Apache-2.0"
@@ -18,22 +18,27 @@ include = ["LICENSE", "README.md"]
python = "^3.7" python = "^3.7"
fastapi = "*" fastapi = "*"
uvicorn = "*" uvicorn = "*"
aioredis = {version = "*", optional = true} redis = { version = "^4.2.0rc1", optional = true }
aiomcache = {version = "*", optional = true} aiomcache = { version = "*", optional = true }
python-dateutil = "*" pendulum = "*"
aiobotocore = { version = "^1.4.1", optional = true }
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "*" flake8 = "*"
isort = "*" isort = "*"
black = "^19.10b0" black = "*"
pytest = "*" pytest = "*"
bandit = "*"
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api" build-backend = "poetry.masonry.api"
[tool.poetry.extras] [tool.poetry.extras]
redis = ["aioredis"] redis = ["redis"]
memcache = ["aiomcache"] memcache = ["aiomcache"]
all = ["aioredis","aiomcache"] dynamodb = ["aiobotocore"]
all = ["redis", "aiomcache", "aiobotocore"]
[tool.black]
line-length = 100
target-version = ['py36', 'py37', 'py38', 'py39']