{"id":10368,"library":"zope-cachedescriptors","title":"Zope Cache Descriptors","description":"Zope Cache Descriptors is a Python library from the Zope Foundation providing `cached` method and `CachedProperty` decorators for easy result caching. It's currently at version 6.0, supports Python 3.9+, and sees active maintenance with releases roughly aligning with major Python version support cycles. It's designed for simple, in-memory caching of computation results.","status":"active","version":"6.0","language":"en","source_language":"en","source_url":"https://github.com/zopefoundation/zope.cachedescriptors","tags":["caching","decorators","zope","performance"],"install":[{"cmd":"pip install zope-cachedescriptors","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Required for core functionality and introspection within the Zope ecosystem.","package":"zope.interface","optional":false},{"reason":"Required for security integration, especially when used in Zope applications. Considered a core dependency.","package":"zope.security","optional":false}],"imports":[{"note":"CachedProperty is located in the 'property' submodule.","wrong":"from zope.cachedescriptors import CachedProperty","symbol":"CachedProperty","correct":"from zope.cachedescriptors.property import CachedProperty"},{"note":"The cached decorator is located in the 'method' submodule.","wrong":"from zope.cachedescriptors import cached","symbol":"cached","correct":"from zope.cachedescriptors.method import cached"}],"quickstart":{"code":"from zope.cachedescriptors.property import CachedProperty\nfrom zope.cachedescriptors.method import cached\nimport time\n\nclass DataFetcher:\n    def __init__(self, id_val):\n        self.id = id_val\n        self._fetch_count = 0\n\n    @CachedProperty\n    def expensive_property(self):\n        self._fetch_count += 1\n        print(f\"  [Property] Fetching data for {self.id}... (call {self._fetch_count})\")\n        time.sleep(0.1) # Simulate network delay\n        return f\"Data for {self.id}-{time.time()}\"\n\n    @cached\n    def expensive_method(self, param):\n        self._fetch_count += 1\n        print(f\"  [Method] Fetching data for {self.id} with param {param}... (call {self._fetch_count})\")\n        time.sleep(0.1) # Simulate network delay\n        return f\"Result for {self.id}-{param}-{time.time()}\"\n\n# Example Usage\nfetcher = DataFetcher(\"user123\")\n\nprint(\"--- CachedProperty Demo ---\")\nresult1 = fetcher.expensive_property\nprint(f\"Property 1: {result1}\")\nresult2 = fetcher.expensive_property # Should be cached\nprint(f\"Property 2: {result2}\")\nassert result1 == result2\n\n# Invalidate the property cache by deleting the attribute\ndel fetcher.expensive_property\nresult3 = fetcher.expensive_property # Should re-calculate\nprint(f\"Property 3 (after invalidation): {result3}\")\nassert result1 != result3\n\nprint(\"\\n--- CachedMethod Demo ---\")\nmethod_result1 = fetcher.expensive_method(\"report_a\")\nprint(f\"Method 1: {method_result1}\")\nmethod_result2 = fetcher.expensive_method(\"report_a\") # Should be cached\nprint(f\"Method 2: {method_result2}\")\nassert method_result1 == method_result2\n\n# Invalidate a specific method call with arguments\nfetcher.expensive_method.invalidate(fetcher, \"report_a\")\nmethod_result3 = fetcher.expensive_method(\"report_a\") # Should re-calculate\nprint(f\"Method 3 (after invalidation): {method_result3}\")\nassert method_result1 != method_result3\n\nmethod_result4 = fetcher.expensive_method(\"report_b\") # New argument, new cache entry\nprint(f\"Method 4: {method_result4}\")\n","lang":"python","description":"Demonstrates basic usage of `CachedProperty` and `cached` decorators, including how to access cached values and perform explicit invalidation for both properties and methods. Note the distinct invalidation patterns for each."},"warnings":[{"fix":"Ensure your environment uses Python 3.9+ and Zope 5+ before upgrading to zope-cachedescriptors 6.0.0 or newer.","message":"Version 6.0 drops support for Python 3.8 and Zope 4. Upgrading on older environments will lead to import errors or runtime issues.","severity":"breaking","affected_versions":"6.0.0+"},{"fix":"Update any type checks or code that expects `CachedProperty` to inherit from `property`. Consider using `hasattr(obj, 'my_prop')` or checking the specific `CachedProperty` type if necessary.","message":"In version 6.0, `CachedProperty` no longer subclasses Python's built-in `property`. Code relying on `isinstance(obj.my_prop, property)` will now return `False`.","severity":"breaking","affected_versions":"6.0.0+"},{"fix":"If you need access to the original unwrapped function, use `obj.my_method.__wrapped__`. Adjust any code that assumed `obj.my_method` would be the plain function object.","message":"Starting with version 6.0, `cached` methods (e.g., `obj.my_method`) are now instances of `CachedMethod` (a callable object) rather than the raw function. This can impact introspection or direct manipulation of the underlying function.","severity":"breaking","affected_versions":"6.0.0+"},{"fix":"Always remember to explicitly invalidate caches when the source data changes: use `del obj.property_name` for `CachedProperty` and `obj.method_name.invalidate(obj, *args)` or `obj.method_name.invalidate_all(obj)` for `cached` methods.","message":"Cache invalidation is explicit. Forgetting to invalidate a cache entry after the underlying data changes will lead to stale data being returned.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure cached methods/properties return immutable objects, or return a *copy* of a mutable object if modifications are expected elsewhere. E.g., `return my_list[:]` or `return my_dict.copy()`.","message":"If the result of a cached method/property is a mutable object (e.g., list, dict), subsequent modifications to that object will be reflected in all cached accesses. This can lead to unexpected side effects or data corruption.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-17T00:00:00.000Z","next_check":"2026-07-16T00:00:00.000Z","problems":[{"fix":"Cached properties are invalidated by deleting the attribute: `del obj.your_property`.","cause":"Attempting to invalidate a `CachedProperty` using a method-style invalidation call (like `obj.prop.invalidate()`) which is meant for `cached` methods.","error":"AttributeError: 'CachedProperty' object has no attribute 'invalidate'"},{"fix":"`@cached` is a decorator without arguments, so use `@cached` (no parentheses). Ensure it's placed directly above a method definition.","cause":"Trying to use `@cached()` without parentheses for the decorator, or trying to call the `cached` decorator directly without providing a function.","error":"TypeError: cached() missing 1 required positional argument: 'func'"},{"fix":"Use the correct import paths: `from zope.cachedescriptors.property import CachedProperty` and `from zope.cachedescriptors.method import cached`.","cause":"Incorrect import path. `CachedProperty` and `cached` are in specific submodules, not directly under `zope.cachedescriptors`.","error":"ImportError: cannot import name 'CachedProperty' from 'zope.cachedescriptors'"}]}