fix(backends): recognise counters the same way on every backend - #158
Merged
Merged
Conversation
allen0099
force-pushed
the
fix/strict-counter-detection
branch
from
September 26, 2026 11:31
6757535 to
87e7d48
Compare
counter_value() parsed any entry whose content was a number, so on the memory backend and the base fallback increment() on a cached response with body "42" returned 43 and overwrote it, while Redis and Memcached raised CacheXError. It now also requires COUNTER_FINGERPRINT. A counter written with set(key, counter_entry(n)) was stored as a JSON document on Redis and Memcached, which INCR rejects. encode_entry now stores exactly what counter_entry builds as a bare integer. The codec read any value int() accepts as a counter, including " 7" and "1_0". parse_counter accepts only an optional minus sign and ASCII digits, plus the trailing spaces Memcached pads a value with when DECR shortens it. Closes #111
allen0099
force-pushed
the
fix/strict-counter-detection
branch
from
September 26, 2026 11:35
87e7d48 to
2e9e72d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #111.
Problem
Reproduced on all three backends before the change:
After the change, every backend gives
CacheXErrorfor the response and6for the counter, and both stored values decode toNone.Change
counter_value()(types.py) now requiresfingerprint == COUNTER_FINGERPRINTas well as an integer body. The memory backend and the base fallback therefore refuse a cached response like the network backends do.parse_counter()(types.py) replacesint()in bothcounter_valueand the codec. It accepts an optional-, ASCII digits and trailing spaces. It rejects leading whitespace,_,+, newlines and non-ASCII digits.^-?\d+$. Memcached pads a value thatDECRshortens rather than resizing it:DECRon10leavesb"9 "(checked against a live server). A strict pattern would break every Memcached counter after a shrinking decrement.encode_entry()stores exactly whatcounter_entry(n)builds as a bare integer, which is the formINCR/INCRBYwork on. This is the issue's optional part. Without it, theset()-then-increment()inconsistency would remain. An entry with a different fingerprint, a media type, or a non-canonical body such as042is still stored as a JSON document, and it round-trips unchanged.These edge cases are the only behaviour change; nothing in the API changes.
Tests
tests/backends/test_counter_contract.pyruns the same assertions on memory, the base fallback, Redis and Memcached:set(counter_entry(5))continues withincrement();It also covers
parse_counterandcounter_valuedirectly.test_codec.py:" 7\n", which used to be accepted, is now rejected;Mutation checks, each with the rest of the fix in place:
int()The full suite passes against live Redis and Memcached (
CACHEX_REQUIRE_LIVE_SERVERS=1): 826 passed, 100% coverage.mypy --strictis clean.Like #156 and #157, this adds a
### Fixedsection to## [Unreleased], so the later merges need a small CHANGELOG rebase.