Skip to main content
ClaudeWave
Install in Claude Code
Copy
git clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-core-cache && cp -r /tmp/frappe-core-cache/skills/source/core/frappe-core-cache ~/.claude/skills/frappe-core-cache
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Frappe Cache & Locking

## Quick Reference

| Action | Method | Notes |
|--------|--------|-------|
| Set value | `frappe.cache.set_value(key, val)` | With optional TTL |
| Get value | `frappe.cache.get_value(key)` | Returns `None` if missing |
| Get or generate | `frappe.cache.get_value(key, generator=fn)` | Calls `fn()` on cache miss |
| Delete value | `frappe.cache.delete_value(key)` | Single key or list of keys |
| Delete by pattern | `frappe.cache.delete_keys(pattern)` | Wildcard `*` matching |
| Hash set | `frappe.cache.hset(name, key, val)` | Redis hash field |
| Hash get | `frappe.cache.hget(name, key)` | Single hash field |
| Hash get all | `frappe.cache.hgetall(name)` | Full hash as dict |
| Hash delete | `frappe.cache.hdel(name, key)` | Remove hash field |
| Hash exists | `frappe.cache.hexists(name, key)` | Returns bool |
| Cached document | `frappe.get_cached_doc(dt, dn)` | Full doc from cache |
| Clear doc cache | `frappe.clear_document_cache(dt, dn)` | Invalidate cached doc |
| Decorator cache | `@redis_cache` | Auto-cache function result |
| Request cache | `frappe.local.cache` | Per-request dict (not Redis) |

---

## Decision Tree

```
What caching pattern do you need?
│
├─ Cache a function result automatically?
│  ├─ Pure function (same args → same result) → @redis_cache
│  └─ Need custom key/TTL → manual get_value/set_value
│
├─ Cache a document?
│  ├─ Read-only access → frappe.get_cached_doc()
│  └─ Need to invalidate → frappe.clear_document_cache()
│
├─ Cache structured data (multiple fields)?
│  └─ Redis hash → hset/hget/hgetall
│
├─ Per-request cache (avoid repeated DB calls in one request)?
│  └─ frappe.local.cache dict
│
├─ Prevent concurrent execution?
│  └─ Distributed lock → frappe.lock("resource_name")
│
└─ Invalidate cache?
   ├─ Single key → delete_value(key)
   ├─ Pattern → delete_keys("prefix*")
   └─ All site cache → frappe.clear_cache()
```

---

## String Operations

### Set and Get

```python
# Set a value (persists until evicted or deleted)
frappe.cache.set_value("exchange_rate_USD", 1.08)

# Set with TTL (expires after N seconds)
frappe.cache.set_value("exchange_rate_USD", 1.08, expires_in_sec=3600)

# Get value (returns None if missing)
rate = frappe.cache.get_value("exchange_rate_USD")

# Get with generator (calls function on cache miss, stores result)
rate = frappe.cache.get_value(
    "exchange_rate_USD",
    generator=lambda: fetch_exchange_rate("USD"),
)
```

### User-Scoped Values

```python
# Store per-user preference
frappe.cache.set_value("dashboard_layout", "compact", user="user@example.com")

# Retrieve for specific user
layout = frappe.cache.get_value("dashboard_layout", user="user@example.com")
```

### Delete

```python
# Single key
frappe.cache.delete_value("exchange_rate_USD")

# Multiple keys
frappe.cache.delete_value(["exchange_rate_USD", "exchange_rate_EUR"])

# Pattern-based deletion (wildcard)
frappe.cache.delete_keys("exchange_rate*")
```

---

## Hash Operations

Use hashes to group related fields under a single key.

```python
# Set hash fields
frappe.cache.hset("config|notifications", "email_enabled", True)
frappe.cache.hset("config|notifications", "sms_enabled", False)
frappe.cache.hset("config|notifications", "max_retries", 3)

# Get single field
email_on = frappe.cache.hget("config|notifications", "email_enabled")

# Get all fields as dict
config = frappe.cache.hgetall("config|notifications")
# {"email_enabled": True, "sms_enabled": False, "max_retries": 3}

# Delete field
frappe.cache.hdel("config|notifications", "sms_enabled")

# Check existence
exists = frappe.cache.hexists("config|notifications", "email_enabled")
```

### Hash with Generator

```python
# hget with generator — calls function on miss
value = frappe.cache.hget(
    "user|permissions",
    "user@example.com",
    generator=lambda: compute_permissions("user@example.com"),
)
```

---

## @redis_cache Decorator

Automatically cache function return values based on arguments.

```python
from frappe.utils.caching import redis_cache

@redis_cache
def get_item_price(item_code, price_list):
    """Expensive query — cached automatically."""
    return frappe.db.get_value("Item Price",
        {"item_code": item_code, "price_list": price_list},
        "price_list_rate",
    )

# First call — hits database, stores in Redis
price = get_item_price("ITEM-001", "Standard Selling")

# Second call — returns from cache
price = get_item_price("ITEM-001", "Standard Selling")

# Clear all cached results for this function
get_item_price.clear_cache()
```

### With TTL

```python
@redis_cache(ttl=300)  # expires after 5 minutes
def get_exchange_rate(from_currency, to_currency):
    return fetch_rate_from_api(from_currency, to_currency)
```

**Rules for @redis_cache:**
- ALWAYS ensure arguments are hashable (strings, numbers, tuples). NEVER pass dicts or lists as arguments.
- ALWAYS call `.clear_cache()` when underlying data changes.
- NEVER use on functions with side effects — the function will NOT execute on cache hits.

---

## frappe.local.cache: Request-Scoped Cache

`frappe.local.cache` is a plain Python dict that lives for the duration of a single HTTP request. It is NOT stored in Redis.

```python
def get_user_settings():
    """Avoid repeated DB calls within a single request."""
    if "user_settings" not in frappe.local.cache:
        frappe.local.cache["user_settings"] = frappe.get_doc(
            "User Settings", frappe.session.user
        )
    return frappe.local.cache["user_settings"]
```

Use `frappe.local.cache` when:
- The same data is needed multiple times in one request
- The data does NOT need to persist across requests
- You want zero Redis overhead

---

## Document Caching

```python
# Get cached document (read-only, no permission check)
settings = frappe.get_cached_doc("System Settings")
item = frappe.get_cached_doc("Item", "ITEM-001")

# Invalidate when document changes
frappe.clear_document_cache("Item", "ITEM-001")

# Cached single value
val = f