A tool for light-duty persistent memoization of API calls

Overview

JSON Memoize

What is this?

json_memoize is a straightforward tool for light-duty persistent memoization, created with API calls in mind. It stores the arguments passed to a function and that function call's returned value in a dict, and writes that dict's contents to disk in a .json file.

Arguments at a glance

  • max_age - sets the maximum allowed age in seconds before a cached entry is considered invalid.
  • max_size - sets the maximum number of entries that can be stored in the cache.
  • force_update - overwrites cached values with fresh ones.
  • cache_folder_path - sets the location of the associated .json file.
  • app_name - if no cache_folder_path is provided, app_name is used to create a folder in the default user cache folder.
  • cache_file_name - manually sets the name of the cache file.

Basic Use

Import and add the decorator @memoize to memoize a function.

Warning: json_memoize stores arguments passed to memoized functions in a plain text format. Do not pass your API key, or any other sensitive information, to memoized functions.

Here's a slow api call:

def slow_api_call(arg_1:str, arg_2: str) -> str:
    response = requests.get(f"https://wowthistakesforever.slow/arg-1={arg_1}&arg-2={arg_2}")
    return response.text

Add the @memoize decorator to memoize it.

from json_memoize import memoize

@memoize
def slow_api_call(arg_1:str, arg_2: str) -> str:
    response = requests.get(f"https://wowthistakesforever.slow/arg-1={arg_1}&arg-2={arg_2}")
    return response.text

If the function is called again with the same arguments, the resulting value will be retrieved from the cache without executing the function.

max_age

If you don't want to keep data that's too old, you can set a max age.

@memoize(max_age=600)
def slow_api_call(arg_1:str, arg_2: str) -> str:
    ...

The age of an entry is determined from the time it was first added to the cache. If the difference between that time and the current time exceeds the max_age value, the cached value will be overwritten with a fresh one. Entries that have exceeded max_age will not be written to disk. If max_age is not set, cache entries will not expire. Note: max_age is in seconds. Consider creating variables for measures of time that are inconvenient or unclear when written in seconds, e.g.:

one_week = 604_800
@memoize(max_age=one_week)
    ...

max_size

If you don't want to cache too many entries, you can set a maximum number of entries to store.

@memoize(max_size=10)
def slow_api_call(arg_1:str, arg_2: str) -> str:
    ...

If max_size is set, json_memoize will delete cache entries from oldest to youngest until it meets the specified size limit before it saves the file to disk. As with max_age, the age of an entry is determined by the time at which it was first added to the cache, not when it was most recently used. Note: The size limit is only enforced when the cache file is being written. While the JsonCache object is live in memory, the limit can be exceeded.

force_update

If something in your ecosystem has changed and you want to force the cached values to be updated with fresh information, you can do that too.

@memoize(force_update=True)
def slow_api_call(arg_1:str, arg_2: str) -> str:
    ...

If force_update is True, all entries in the cache will be overwritten, even if they have not yet reached max_age.

Setting the Cache Folder

To reduce the likelihood of name collisions, json_memoize stores its cache files in named folders. There are multiple ways to specify where this folder is located.

Automatic folder creation using app_name

If a value is provided for app_name, json_memoize will use this value to name a new folder within the operating systems preferred user cache folder. e.g.:

@memoize(app_name='my_app') will create a folder structure like ".cache/my_app/"

Manual cache folder assignment

If a cache_folder argument is supplied to the decorator, it will store cache files in that folder. Note: if cache_folder is supplied, it will overrule app_name.

Default folder location

Warning: Not recommended!

If neither cache_folder nor app_name is provided, json_memoize will use its default folder name, yielding a folder structure like ".cache/json_memoize/"

This is not recommended, as intermingling cache files from multiple apps increases the risk of file name collisions, which could cause apps to behave unpredictably.

Naming Cache Files

By default, json_memoize will create each cache file using the name of the function being memoized, e.g.:

@memoize
def slow_api_call():
    ... 

This will create a file called "slow_api_call_cahce.json".

Setting a custom file name with cahce_file_name

If a value is provided for cache_file_name, json_memoize will instead use this value to name the cache file.

Storage and Performance Details

Storage

When a call is made to a memoized function, json_memoize will generate a string from the passed arguments, and use that string as the key in its internal cache dictionary. The value returned by the call is stored as the associated value. Writing this dict to disk is accomplished using json.dump(). Seperate cache files are made for each memoized function.

Warning: It is assumed here that @memoize will be invoked in situations where both the arguments and the returned value of a function have consistent, unambiguous string representations. Passing arguments with unreliable string representation will cause the cache to behave unpredictably. json_memoize will log a warning if it detects something that looks like a repr() output that points to a memory address in an incoming argument. Also, once again, do not pass security-relevant information to memoized functions.

Performance

json_memoize is intended to be performant relative to a slow API call, and has not been optimized further than that. If max_size is exceeded, the entries in the dict are sorted so the oldest ones can be dropped. Setting aside hard drive performance, this sorting operation is the most costly step of the process, and it occurs every time the cahce file is saved.

An example project that shows how to check if a certain macro is active in a file.

PlatformIO Check Compiler Flags Example Description Demonstrates the usage of an extra script and a special compilter invocation to get the active mac

Maximilian Gerhardt 1 Oct 28, 2021
Submission from Team OMR for the TRI-NIT Hackathon

Submission from Team OMR for the TRI-NIT Hackathon

0 Feb 01, 2022
Your missing PO formatter and linter

pofmt Your missing PO formatter and linter Features Wrap msgid and msgstr with a constant max width. Can act as a pre-commit hook. Display lint errors

Frost Ming 5 Mar 22, 2022
A collection of examples of using cocotb for functional verification of VHDL designs with GHDL.

At the moment, this repo is in an early state and serves as a learning tool for me. So it contains a a lot of quirks and code which can be done much better by cocotb-professionals.

T. Meissner 7 Mar 10, 2022
STAC in Jupyter Notebooks

stac-nb STAC in Jupyter Notebooks Install pip install stac-nb Usage To use stac-nb in a project, start Jupyter Lab (jupyter lab), create a new noteboo

Darren Wiens 32 Oct 04, 2022
Kolibri: the offline app for universal education

Kolibri This repository is for software developers wishing to contribute to Kolibri. If you are looking for help installing, configuring and using Kol

Learning Equality 564 Jan 02, 2023
A small script I made that takes any standard Decklist of magic the gathering cards and pulls all card images from scryfall at once!

A small script I made that takes any standard Decklist of magic the gathering cards and pulls all card images from scryfall at once!

15 Aug 26, 2022
Python Multilingual Ucrel Semantic Analysis System

PymUSAS Python Multilingual Ucrel Semantic Analysis System, it currently is a rule based token level semantic tagger which can be added to any spaCy p

UCREL 13 Nov 18, 2022
Python Common things by Problem Fighter Library, (Exception, Debug Log, etc.)

In the name of God, the Most Gracious, the Most Merciful. PF-PY-Common Documentation Install and update using pip: pip install -U xxxx Please find the

Problem Fighter 3 Jan 15, 2022
Code and yara rules to detect and analyze Cobalt Strike

Cobalt Strike Resources This repository contains: analyze.py: a script to analyze a Cobalt Strike beacon (python analyze.py BEACON) extract.py; extrac

Tek 224 Jan 04, 2023
This is Gaurav's IP Project Completed in the year session of 2021-2022.

The Analyser by Gaurav Rayat Why this Project? Today we are continuously hearing about growth in Crime rates and the number of murders executed day by

1 Dec 30, 2021
Backups made easy, automated, monitored and SECURED with an audited encryption

Backup Controller Backups made easy, automated, monitored and SECURED with an audited encryption. Schedules backup tasks executed by Backup Maker, upl

RiotKit 1 Jan 30, 2022
Xoroshiro-cairo - A xoroshiro128** pseudorandom number generator implementation in Cairo

xoroshiro-cairo A xoroshiro128** pseudorandom number generator implementation in

Milan Cermak 26 Oct 05, 2022
Cross-platform .NET Core pre-commit hooks

dotnet-core-pre-commit Cross-platform .NET Core pre-commit hooks How to use Add this to your .pre-commit-config.yaml - repo: https://github.com/juan

Juan Odicio 5 Jul 20, 2021
Cirq is a Python library for writing, manipulating, and optimizing quantum circuits and running them against quantum computers and simulators

Cirq is a Python library for writing, manipulating, and optimizing quantum circuits and running them against quantum computers and simulators. Install

quantumlib 3.6k Jan 07, 2023
Time tracking program that will format output to be easily put into Gitlab

time_tracker Time tracking program that will format output to be easily put into Gitlab. Feel free to branch and use it yourself! Getting Started Clon

Jake Strasler 2 Oct 13, 2022
Creates a release pull request updating changelog and tags with standard-version

standard version release branch Github action to open releases following convent

8 Sep 13, 2022
Comprehensive Python Cheatsheet

Comprehensive Python Cheatsheet

Jure Ĺ orn 31.3k Dec 30, 2022
A web project to control the daily life budget planing

Budget Planning - API In this repo there's only the API and Back-End of the this project. Install and run the project # install virtualenv --python=py

Leonardo Da Vinci 1 Oct 24, 2021
Python bindings for the Plex API.

Python-PlexAPI Overview Unofficial Python bindings for the Plex API. Our goal is to match all capabilities of the official Plex Web Client. A few of t

Michael Shepanski 931 Jan 07, 2023