{"id":20540427,"url":"https://github.com/izo0x90/versioned_lru_cache","last_synced_at":"2026-06-05T17:31:22.884Z","repository":{"id":144719637,"uuid":"496105019","full_name":"izo0x90/versioned_lru_cache","owner":"izo0x90","description":"Versioned LRU Cache","archived":false,"fork":false,"pushed_at":"2023-03-20T02:35:23.000Z","size":31,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-18T17:51:12.866Z","etag":null,"topics":["cache","caching-library","caching-strategies","flask","flask-web","library","python","python-package","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/izo0x90.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-25T06:12:57.000Z","updated_at":"2023-04-14T15:26:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"7ac4085e-5d47-4434-bf00-fbfe86ff3b5a","html_url":"https://github.com/izo0x90/versioned_lru_cache","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izo0x90%2Fversioned_lru_cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izo0x90%2Fversioned_lru_cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izo0x90%2Fversioned_lru_cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izo0x90%2Fversioned_lru_cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/izo0x90","download_url":"https://codeload.github.com/izo0x90/versioned_lru_cache/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242144578,"owners_count":20078970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","caching-library","caching-strategies","flask","flask-web","library","python","python-package","python3"],"created_at":"2024-11-16T01:15:17.808Z","updated_at":"2025-12-03T17:03:11.956Z","avatar_url":"https://github.com/izo0x90.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Versioned LRU Cache (Cache with invalidation support)\n\n## Short description/ summary\nVersioned_lru_cache_with_ttl is a decorator that can provide versioned lru caching\nof function return results.\n\nBy being provided with an invalidation function that can determine if the cached\nreturn results have gone stale, the function wrapper will either return the\ncached value or rerun the function and return and re-cache the new results in the\ncase of stale cache.\n\nThe idea is that recalculating the work of the function is costly, but there is a\nmuch \"cheaper\" invalidation function that can tell us when work needs to be redone.\n\nThe decorator factory (versioned_lru_cache_with_ttl) also expects being passed a\nproxy to a mutable mapping, this proxy should provide a \"fresh\" version of the\nmapping per session. Ex. here being the g request context object used by the Flask\nweb framework. This allows to calculate the invalidation/ version only once per\nsession and can make the caching even more efficient.\n\n## Motivation\nThere a quite a few caveats I want to cover here but I will leave those for the \nsection bellow.\n\nThe general idea here is that we have some time consuming work that takes place\nhowever there is a much less costly way we can determine if the output of this has\ngone stale.\n\nWhen a requester attempts to access the output of the work we can cheaply check if it\nactually needs to be redone or just return the cached work output. \n\nAdditionally we this \"version\" of the work that is being calculated is also being \ncached on the session object which we expect to be provided by the user of the caching\ndecorator.\n\nThe assumption here is that consistency/ atomicity is expected with in the duration of\nthe session, so even the calculation of a version/ check if the work output is\ninvalidated is only done once per session.\n\nAs a more concrete example, we can imagine a Flask Webframewok app:\n- First flask request handler receives an HTTP request from a client\n\n- As part of the handler the function that needs to do the \"expensive\" work gets\ncalled since the output of its work is needed to process the request\n\n- Because the function has been decorated with the versioned LRU decorator first\nwe check if version/ invalidation calculation has been made already and stored on the\nglobal request context \"g\"\n\n- If the version is already on the request context we know that the \"expensive\" work\noutput that is cached can just get returned because we have already check for \ninvalidation for this request\n\n- If the version is not available on the request context object \"g\", we run the\nversioning/ invalidation function\n\n- If the cache is not invalidated as a result of the version check we can just return\nthe work output that is already cached\n\n- If the work output is invalidated the function that does the \"expensive\" work\nis ran and its output is cached, and finally the now update and recached work output\nis returned\n\n```\n           Client\n             │\n             ▼\n            ┌──┐\n            │..│\n           ┌┴──┴┐\n           │    │\n           │    │\n           │    │\n           └─┬──┘\n             │\n             │\n         HTTP│Request\n             ▼\n      ┌────────────────┐\n      │                │\n      │Flask           │\n      │WebFramework    │\n      │request handler │\n      │                │\n      └──────┬─────────┘\n             │\n             │\n             ▼\n      ┌────────────────┐\n      │                │\n      │  Caching       │\n      │  Decorator     │\n      │                │\n      └──────┬─────────┘\n             │\n             ▼\n┌───────────────────────────┐\n│                           │\n│    Get cached             │\n│ version from requet       │         xx\n│ context or recalc         │       xx  xx             ┌──────────────────┐\n│  the version.             │     xx      xx           │                  │\n│                           │   xx          xx         │  Redo expensive  │\n│  Aka is existing work     ├─►x  Redo work?  x──Yes──►│  work/ calc.     │\n│  output invalidated, do   │   xx          xx         │  Return new      │\n│  we need to redo the      │     xx      xx           │  work output     │\n│  expensive work           │       xx  xx             │                  │\n│                           │         xx               └────────┬─────────┘\n│                           │         │                         │\n└───────────────────────────┘         │                         ▼\n                                      │                ┌──────────────────┐\n                                      │                │                  │\n                                      │                │                  │\n                                      │                │  Cache new work  │\n                                     No                │  output          │\n                                      │                │                  │\n                                      │                └────────┬─────────┘\n                                      │                         │\n                                      │                         │\n                                      │                         │\n                                      │                         │\n                                      ▼                         │\n                            ┌────────────────────┐              │\n                            │                    │              │\n                            │   Return cached    │              │\n                            │   work to Flask    │              │\n                            │   which forwards   │◄─────────────┘\n                            │   to client        │\n                            │                    │\n                            └────────────────────┘\n\n```\n## Intended usecase and caveats\nThere is a lot to be said for the overuse of caching in software engineering as\nopposed to structurally fixing systems too often is caching used as a bandaid.\n\nThere is of course plenty of valid use cases for caching, I am just not entire sure\nthere is one for this specific implementation.\n\nThe way that this module of code got created was as a prototype of a possible solution\nto a work problem. The reality was that along side it I had a some number of other\nsolutions this one being the least preferred IMO.\n\nHere are some alternatives:\n- Can the work output be periodically recalculated by an async task. Is the use case\nfine with eventual consistency.\n\n- Can work output instead simply be cached on an external system ex. Redis or even\napplication operational DB. Can work execution to redo the output be triggered by\nsome event signifying a state change and/ or invalidation as opposed triggered by\nthe client request.\n\nWe could keep going with possible alternative solutioning here however the point is\nthat a specific use case should drive solution choice and optimization, that is after\nall the actual discipline of engineering, be it only software in this case.\n\nWhile the use case was not there for me the proto. was too close not to do something\nwith it, frankly the project setup and all this narration took longer that the actual\nprogramming.\n\nSo if happen to:\n- Need to check for invalidation on some session request\n\n- The work output can not be eventually consistent\n\n- Want to return the work output as soon as possible even avoiding external systems\nand network request lag\n\n- You are OK with an occasional requester getting penalized with a longer processing\ntime as a trade off that other requesters get almost instant results from the cache\n\n- You want atomicity of the state of the work output across a request/ session\n\nWell then you  might possibly have a use case for this peculiar cache implementation!\n\n## How to setup\n\n### To setup for development:\n```\nmake dev-install \n```\n\n### To build for distribution:\n```\nmake build \n```\n\n### To run tests (pytest), python type check (mypy) and linting (flake8):\n```\nmake test\n```\n\n### To run live Flask-example/ test:\n```\nmake test-example\n```\n\n### To format all code (black):\n```\nmake format\n```\n\n### To get any outstanding to-dos in code (Marked with \"# TODO:\"): \n```\nmake todo\n```\n\n### To clean up all build artifacts and dev envir. leaving only repo:\n```\nmake clean\n```\n\n### To properly begin your day:\n```\nmake hello\n```\n\n## Copy-pastable quick start code example\n\n```\nimport random\nfrom typing import Any\n\nfrom versioned_lru_cache import versioned_lru_cache_with_ttl\n\ng: dict = {}  # Fake global context for testing\n\n\ndef test_gen_version(*args: Any, **kwargs: Any) -\u003e str:\n    return str(random.randint(0, 1000000000000))\n\n\n@versioned_lru_cache_with_ttl(\n    proxy_to_session_context_object=g,\n    generate_version_func=test_gen_version,\n    module_name=\"test_module_name\",\n)\ndef test_function_we_want_to_cache(test: Any = None) -\u003e str:\n    # Some very heavy computation or database query here\n    print(\"Doing heavy work! aka. test function body is executing.\")\n    return 'This is the result of the \"heavy\" work.'\n\n\ndef main() -\u003e int:\n    print(\n        \"Call one no params\",\n        test_function_we_want_to_cache(),\n        test_function_we_want_to_cache.__name__,\n    )\n    print(\n        \"Call two no params\",\n        test_function_we_want_to_cache(),\n        test_function_we_want_to_cache.__name__,\n    )\n    print(\n        \"Call three test=1 as params\",\n        test_function_we_want_to_cache(test=1),\n        test_function_we_want_to_cache.__name__,\n    )\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    SystemExit(main())\n```\n\n## Extended example with Flask\n\nLook in: `examples/flask_fauxdb_app_example.py`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fizo0x90%2Fversioned_lru_cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fizo0x90%2Fversioned_lru_cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fizo0x90%2Fversioned_lru_cache/lists"}