Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
zmumi authored May 7, 2024
2 parents 190009c + f271ae5 commit fcce8a3
Show file tree
Hide file tree
Showing 27 changed files with 664 additions and 124 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/non-test-tox.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Coverage, mypi

on: [push]
on:
push:
workflow_dispatch:
pull_request:
types: [ opened, synchronize, reopened ]

jobs:
build:
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/tox-tests.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
name: Tox Tests

on: [push]
on:
push:
workflow_dispatch:
pull_request:
types: [ opened, synchronize, reopened ]


jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.7', '3.8', '3.9', '3.10', '3.11']
python: [ '3.7', '3.8', '3.9', '3.10', '3.11', '3.12' ]

steps:
- uses: actions/checkout@v2
Expand Down
38 changes: 38 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"
jobs:
post_create_environment:
- python -m pip install sphinx_rtd_theme

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
# install:
# - requirements: docs/requirements.txt
39 changes: 39 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
2.1.0
-----

* Added configurable postprocessing, that allows to modify value retrieved from the cache
* Added built-in implementation, that applies deep-copy
* Fixed missing invalidation module in api docs
* Fixed MANIFEST.in

2.0.0
-----

* Changed exception handling
* now exceptions are chained (before they were added in `args`)
* timeout errors are now chained (before they were not included at all)
* in case of dogpiling, all callers are now notified about the error (see issue #23)

1.2.2
-----

* Fixed an example, that used deprecated `utcnow`

1.2.1
-----

* Fixed UTC related deprecation warnings in Python 3.12+

1.2.0
-----

* Added support for Python 3.12
* Added warning that support for Tornado is deprecated and will be removed in future
(it causes more and more hacks/workarounds while Tornado importance is diminishing).

1.1.5
-----

* Expanded docs adding section on how to achieve granular expire/update time control (different settings per entry).
* Minor fix for contribution guide (after migration, Travis was still mentioned instead of GitHub Actions).

1.1.4
-----

Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
include requirements.txt
include README.rst
include CHANGELOG.md
include CHANGELOG.rst
107 changes: 96 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ For configuration options see `Configurability`_.
You can use ``memoize`` with both `asyncio <https://docs.python.org/3/library/asyncio.html>`_
and `Tornado <https://github.com/tornadoweb/tornado>`_ - please see the appropriate example:

.. warning::
Support for `Tornado <https://github.com/tornadoweb/tornado>`_ is planned to be removed in the future.

asyncio
~~~~~~~

Expand Down Expand Up @@ -189,6 +192,10 @@ With *memoize* you have under control:
least-recently-updated strategy is already provided;
* entry builder (see :class:`memoize.entrybuilder.CacheEntryBuilder`)
which has control over ``update_after`` & ``expires_after`` described in `Tunable eviction & async refreshing`_
* value post-processing (see :class:`memoize.postprocessing.Postprocessing`);
noop is the default one;
deep-copy post-processing is also provided (be wary of deep-copy cost & limitations,
but deep-copying allows callers to safely modify values retrieved from an in-memory cache).

All of these elements are open for extension (you can implement and plug-in your own).
Please contribute!
Expand All @@ -206,19 +213,21 @@ Example how to customize default config (everything gets overridden):
from memoize.entrybuilder import ProvidedLifeSpanCacheEntryBuilder
from memoize.eviction import LeastRecentlyUpdatedEvictionStrategy
from memoize.key import EncodedMethodNameAndArgsKeyExtractor
from memoize.postprocessing import DeepcopyPostprocessing
from memoize.storage import LocalInMemoryCacheStorage
from memoize.wrapper import memoize
@memoize(configuration=MutableCacheConfiguration
.initialized_with(DefaultInMemoryCacheConfiguration())
.set_method_timeout(value=timedelta(minutes=2))
.set_entry_builder(ProvidedLifeSpanCacheEntryBuilder(update_after=timedelta(minutes=2),
expire_after=timedelta(minutes=5)))
.set_eviction_strategy(LeastRecentlyUpdatedEvictionStrategy(capacity=2048))
.set_key_extractor(EncodedMethodNameAndArgsKeyExtractor(skip_first_arg_as_self=False))
.set_storage(LocalInMemoryCacheStorage())
)
@memoize(
configuration=MutableCacheConfiguration
.initialized_with(DefaultInMemoryCacheConfiguration())
.set_method_timeout(value=timedelta(minutes=2))
.set_entry_builder(ProvidedLifeSpanCacheEntryBuilder(update_after=timedelta(minutes=2),
expire_after=timedelta(minutes=5)))
.set_eviction_strategy(LeastRecentlyUpdatedEvictionStrategy(capacity=2048))
.set_key_extractor(EncodedMethodNameAndArgsKeyExtractor(skip_first_arg_as_self=False))
.set_storage(LocalInMemoryCacheStorage())
.set_postprocessing(DeepcopyPostprocessing())
)
async def cached():
return 'dummy'
Expand All @@ -229,7 +238,8 @@ Still, you can use default configuration which:
* uses in-memory storage;
* uses method instance & arguments to infer cache key;
* stores up to 4096 elements in cache and evicts entries according to least recently updated policy;
* refreshes elements after 10 minutes & ignores unrefreshed elements after 30 minutes.
* refreshes elements after 10 minutes & ignores unrefreshed elements after 30 minutes;
* does not post-process cached values.

If that satisfies you, just use default config:

Expand Down Expand Up @@ -424,5 +434,80 @@ Then you could just pass args and kwargs for which you want to invalidate entry.
# Invalidation # 2
# expensive - computation - 59
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Openness to granular TTL
------------------------

Default configuration sets update and expiry based on fixed values, which are the same for all entries.
If you need to set different TTLs for different entries, you can do so by providing
a custom :class:`memoize.entrybuilder.CacheEntryBuilder`.

.. code-block:: python
import datetime
import asyncio
import random
from dataclasses import dataclass
from memoize.wrapper import memoize
from memoize.configuration import DefaultInMemoryCacheConfiguration, MutableCacheConfiguration
from memoize.entry import CacheKey, CacheEntry
from memoize.entrybuilder import CacheEntryBuilder
from memoize.storage import LocalInMemoryCacheStorage
@dataclass
class ValueWithTTL:
value: str
ttl_seconds: int # for instance, it could be derived from Cache-Control response header
class TtlRespectingCacheEntryBuilder(CacheEntryBuilder):
def build(self, key: CacheKey, value: ValueWithTTL):
now = datetime.datetime.now(datetime.timezone.utc)
ttl_ends_at = now + datetime.timedelta(seconds=value.ttl_seconds)
return CacheEntry(
created=now,
update_after=ttl_ends_at,
# allowing stale data for 10% of TTL
expires_after=ttl_ends_at + datetime.timedelta(seconds=value.ttl_seconds // 10),
value=value
)
storage = LocalInMemoryCacheStorage() # overridden & extracted for demonstration purposes only
@memoize(configuration=MutableCacheConfiguration
.initialized_with(DefaultInMemoryCacheConfiguration())
.set_entry_builder(TtlRespectingCacheEntryBuilder())
.set_storage(storage))
async def external_call(key: str):
return ValueWithTTL(
value=f'{key}-result-{random.randint(1, 100)}',
ttl_seconds=random.randint(60, 300)
)
async def main():
await external_call('a')
await external_call('b')
await external_call('b')
print("Entries persisted in the cache:")
for entry in storage._data.values():
print('Entry: ', entry.value)
print('Effective TTL: ', (entry.update_after - entry.created).total_seconds())
# Entries persisted in the cache:
# Entry: ValueWithTTL(value='a-result-79', ttl_seconds=148)
# Effective TTL: 148.0
# Entry: ValueWithTTL(value='b-result-27', ttl_seconds=192)
# Effective TTL: 192.0
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
2 changes: 1 addition & 1 deletion docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ PyPi
python3 -m pip install --user --upgrade twine
python3 -m twine check dist/*
# actual upload will be done by Travis
# actual upload will be done by GitHub Actions
Loading

0 comments on commit fcce8a3

Please sign in to comment.