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
3 changes: 3 additions & 0 deletions changelog.d/336.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**`/cached-hits` reports `entries`, `total_entries`, `valid_entries` and
`expired_entries`.** They list and count the cached route entries under names
that say so; the route never counted hits.
4 changes: 4 additions & 0 deletions changelog.d/336.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**The `cached_hits`, `total_hits`, `valid_hits` and `expired_hits` fields of
`/cached-hits` are deprecated and will be removed in 0.6.0.** They keep their
values until then and are marked `deprecated` in the OpenAPI schema; read
`entries`, `total_entries`, `valid_entries` and `expired_entries` instead.
8 changes: 6 additions & 2 deletions docs/HTTP_CACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,12 @@ add_routes(

- `GET {prefix}/cached-hits` — every cached entry split into method, host, path
and query (`sha256:<hex>` for a query over 200 bytes), with its ETag and
expiry, plus counts of valid and expired entries
and the distinct cached paths. It does not count hits.
expiry, in `entries`, counted in `total_entries`, `valid_entries` and
`expired_entries`, plus the distinct cached paths. Despite the route's name,
it does not count hits. The older `cached_hits`, `total_hits`, `valid_hits`
and `expired_hits` hold the same values; they are deprecated (marked so in
the OpenAPI schema) and removed in 0.6.0, so read the `*entries` fields
([#336](https://github.com/allen0099/FastAPI-CacheX/issues/336)).
- `GET {prefix}/cached-records` — every cached record with its size, expiry,
`media_type` (the stored response's media type, `null` if it had none) and,
with `include_content_preview=True`, a preview of the first 100 bytes of the
Expand Down
54 changes: 43 additions & 11 deletions fastapi_cachex/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from dataclasses import dataclass
from dataclasses import field
from typing import TYPE_CHECKING
from typing import Annotated
from typing import Any

from pydantic import Field

from .cache_key import CacheKey
from .exceptions import BackendNotFoundError
from .proxy import BackendProxy
Expand Down Expand Up @@ -42,19 +45,42 @@ class CacheHitSummary:
cached_paths: list[str]


# Why the ``*_hits`` fields are deprecated, shown in the OpenAPI schema.
_HITS_DEPRECATED = (
"Counts cache entries, not hits; use {0} instead. Deprecated in 0.5.1, "
"removed in 0.6.0 (#336)."
)


@dataclass
class CacheHitsResponse:
"""Response for the ``/cached-hits`` endpoint.

The ``*_hits`` fields count cached entries, not requests served from them.
``entries``, ``total_entries``, ``valid_entries`` and ``expired_entries``
list and count the cached route entries. The ``cached_hits`` and
``*_hits`` fields hold the same values under names that suggested
requests served from the cache; they are deprecated (marked so in the
OpenAPI schema) and removed in 0.6.0 (#336).
"""

cached_hits: list[CacheHitRecord]
total_hits: int
valid_hits: int
expired_hits: int
entries: list[CacheHitRecord]
total_entries: int
valid_entries: int
expired_entries: int
unique_routes: int
summary: CacheHitSummary
cached_hits: Annotated[
list[CacheHitRecord], Field(deprecated=_HITS_DEPRECATED.format("entries"))
]
total_hits: Annotated[
int, Field(deprecated=_HITS_DEPRECATED.format("total_entries"))
]
valid_hits: Annotated[
int, Field(deprecated=_HITS_DEPRECATED.format("valid_entries"))
]
expired_hits: Annotated[
int, Field(deprecated=_HITS_DEPRECATED.format("expired_entries"))
]


@dataclass
Expand Down Expand Up @@ -174,17 +200,23 @@ def _cached_hits(entries: list[_Entry]) -> CacheHitsResponse:
valid_hits = [h for h in cached_hits if not h.is_expired]
routes_hit = {h.path for h in valid_hits}

total = len(cached_hits)
valid = len(valid_hits)
return CacheHitsResponse(
cached_hits=cached_hits,
total_hits=len(cached_hits),
valid_hits=len(valid_hits),
expired_hits=len(cached_hits) - len(valid_hits),
entries=cached_hits,
total_entries=total,
valid_entries=valid,
expired_entries=total - valid,
unique_routes=len(routes_hit),
summary=CacheHitSummary(
total_cached_entries=len(cached_hits),
active_entries=len(valid_hits),
total_cached_entries=total,
active_entries=valid,
cached_paths=sorted(routes_hit),
),
cached_hits=cached_hits,
total_hits=total,
valid_hits=valid,
expired_hits=total - valid,
)


Expand Down
2 changes: 1 addition & 1 deletion i18n/zh-TW/docs/HTTP_CACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ add_routes(
)
```

- `GET {prefix}/cached-hits`:列出每筆快取項目,拆分為方法、主機、路徑與查詢(超過 200 位元組的查詢為 `sha256:<十六進位>`),附上 ETag 與到期時間,另外統計有效與已過期的項目數,以及不重複的快取路徑。它不會計算命中次數。
- `GET {prefix}/cached-hits`:列出每筆快取項目,拆分為方法、主機、路徑與查詢(超過 200 位元組的查詢為 `sha256:<十六進位>`),附上 ETag 與到期時間,放在 `entries` 中,並以 `total_entries`、`valid_entries` 與 `expired_entries` 統計項目數,另外列出不重複的快取路徑。雖然路由名稱有 hits,它並不會計算命中次數。舊的 `cached_hits`、`total_hits`、`valid_hits` 與 `expired_hits` 內容相同,但已棄用(OpenAPI schema 中會標示),並將在 0.6.0 移除,請改讀 `*entries` 欄位([#336](https://github.com/allen0099/FastAPI-CacheX/issues/336))。
- `GET {prefix}/cached-records`:列出每筆快取紀錄的大小、到期時間、`media_type`(儲存的回應的媒體類型,沒有時為 `null`),以及在 `include_content_preview=True` 時快取內容前 100 個位元組的預覽。預設 `content_preview` 為 `null`,不會有任何回應本文離開伺服器;鍵、大小與到期時間仍會回報。`content_type` 一律是 `"bytes"`,只為相容而保留;請改讀 `media_type`。

兩個路由都只列出路由項目(格式為 `http:v2|method|host|path|query` 的鍵);`CacheManager` 與鎖的鍵都會略過,未使用 `build_cache_key()` 的 `key_builder` 產生的鍵也一樣。
Expand Down
67 changes: 67 additions & 0 deletions tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,70 @@ def test_routes_answer_empty_when_no_backend_is_configured(self, app, client):
assert hits["summary"]["cached_paths"] == []
assert records["total_records"] == 0
assert records["summary"]["estimated_cache_size_kb"] == 0.0


class TestEntryNamedFields:
"""``/cached-hits`` counts entries under entry names (#336).

The ``cached_hits`` and ``*_hits`` fields stay, with the same values, and
are marked deprecated in the OpenAPI schema until 0.6.0 removes them.
"""

def test_new_fields_list_and_count_the_entries(
self, app: FastAPI, client: TestClient, setup_cache: MemoryBackend
) -> None:
add_routes(app, dependencies=[])

@app.get("/a")
@cache(ttl=60)
async def a() -> dict[str, str]:
return {"a": "1"}

@app.get("/b")
@cache(ttl=60)
async def b() -> dict[str, str]:
return {"b": "1"}

client.get("/a")
client.get("/b")
data = client.get("/cached-hits").json()

assert data["total_entries"] == 2
assert data["valid_entries"] == 2
assert data["expired_entries"] == 0
assert {e["path"] for e in data["entries"]} == {"/a", "/b"}
# The deprecated names carry the same values.
assert data["cached_hits"] == data["entries"]
assert data["total_hits"] == data["total_entries"]
assert data["valid_hits"] == data["valid_entries"]
assert data["expired_hits"] == data["expired_entries"]

def test_an_expired_entry_is_counted_as_expired(
self, app: FastAPI, client: TestClient, setup_cache: MemoryBackend
) -> None:
add_routes(app, dependencies=[])
_report_expired(
setup_cache,
"http:v2|GET|testserver|/old|",
CacheEntry(fingerprint='W/"old"', content=b"old"),
)

data = client.get("/cached-hits").json()

assert data["total_entries"] == 1
assert data["valid_entries"] == 0
assert data["expired_entries"] == 1
assert data["expired_hits"] == 1

def test_openapi_marks_only_the_hits_fields_deprecated(
self, app: FastAPI, client: TestClient
) -> None:
add_routes(app, include_in_schema=True, dependencies=[])

schema = client.get("/openapi.json").json()["components"]["schemas"]
properties = schema["CacheHitsResponse"]["properties"]

for name in ("cached_hits", "total_hits", "valid_hits", "expired_hits"):
assert properties[name]["deprecated"] is True, name
for name in ("entries", "total_entries", "valid_entries", "expired_entries"):
assert "deprecated" not in properties[name], name
Loading