309 lines
8.3 KiB
Python
309 lines
8.3 KiB
Python
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
|
|
import frappe
|
|
|
|
common_default_keys = ["__default", "__global"]
|
|
|
|
doctypes_for_mapping = {
|
|
"Assignment Rule",
|
|
"Milestone Tracker",
|
|
"Document Naming Rule",
|
|
}
|
|
|
|
|
|
def get_doctype_map_key(doctype, name="*") -> str:
|
|
return frappe.scrub(doctype) + f"_map::{name}"
|
|
|
|
|
|
doctype_map_keys = tuple(map(get_doctype_map_key, doctypes_for_mapping))
|
|
|
|
bench_cache_keys = ("assets_json",)
|
|
|
|
global_cache_keys = (
|
|
"app_hooks",
|
|
"installed_apps",
|
|
"all_apps",
|
|
"app_modules",
|
|
"installed_app_modules",
|
|
"module_app",
|
|
"module_installed_app",
|
|
"system_settings",
|
|
"scheduler_events",
|
|
"time_zone",
|
|
"webhooks",
|
|
"active_domains",
|
|
"active_modules",
|
|
"assignment_rule",
|
|
"server_script_map",
|
|
"wkhtmltopdf_version",
|
|
"domain_restricted_doctypes",
|
|
"domain_restricted_pages",
|
|
"information_schema:counts",
|
|
"db_tables",
|
|
"server_script_autocompletion_items",
|
|
*doctype_map_keys,
|
|
)
|
|
|
|
user_cache_keys = (
|
|
"bootinfo",
|
|
"user_recent",
|
|
"roles",
|
|
"user_doc",
|
|
"lang",
|
|
"defaults",
|
|
"user_permissions",
|
|
"home_page",
|
|
"linked_with",
|
|
"desktop_icons",
|
|
"portal_menu_items",
|
|
"user_perm_can_read",
|
|
"has_role:Page",
|
|
"has_role:Report",
|
|
"desk_sidebar_items",
|
|
"contacts",
|
|
)
|
|
|
|
doctype_cache_keys = (
|
|
"last_modified",
|
|
"linked_doctypes",
|
|
"workflow",
|
|
"data_import_column_header_map",
|
|
)
|
|
|
|
wildcard_keys = (
|
|
"document_cache::*",
|
|
"table_columns::*",
|
|
*doctype_map_keys,
|
|
)
|
|
|
|
|
|
def clear_user_cache(user=None):
|
|
from frappe.desk.notifications import clear_notifications
|
|
|
|
# this will automatically reload the global cache
|
|
# so it is important to clear this first
|
|
clear_notifications(user)
|
|
|
|
if user:
|
|
frappe.cache.hdel_names(user_cache_keys, user)
|
|
frappe.cache.delete_keys("user:" + user)
|
|
clear_defaults_cache(user)
|
|
else:
|
|
frappe.cache.delete_key(user_cache_keys)
|
|
clear_defaults_cache()
|
|
clear_global_cache()
|
|
|
|
|
|
def clear_domain_cache(user=None):
|
|
domain_cache_keys = ("domain_restricted_doctypes", "domain_restricted_pages")
|
|
frappe.cache.delete_value(domain_cache_keys)
|
|
|
|
|
|
def clear_global_cache():
|
|
from frappe.website.utils import clear_website_cache
|
|
|
|
clear_doctype_cache()
|
|
clear_website_cache()
|
|
frappe.cache.delete_value(global_cache_keys + bench_cache_keys)
|
|
frappe.setup_module_map()
|
|
|
|
|
|
def clear_defaults_cache(user=None):
|
|
if user:
|
|
for key in [user, *common_default_keys]:
|
|
frappe.client_cache.delete_value(f"defaults::{key}")
|
|
elif frappe.flags.in_install != "frappe":
|
|
frappe.client_cache.delete_keys("defaults::*")
|
|
|
|
|
|
def clear_doctype_cache(doctype=None):
|
|
clear_controller_cache(doctype)
|
|
frappe.client_cache.erase_persistent_caches(doctype=doctype)
|
|
|
|
_clear_doctype_cache_from_redis(doctype)
|
|
if hasattr(frappe.db, "after_commit"):
|
|
frappe.db.after_commit.add(lambda: _clear_doctype_cache_from_redis(doctype))
|
|
frappe.db.after_rollback.add(lambda: _clear_doctype_cache_from_redis(doctype))
|
|
|
|
|
|
def _clear_doctype_cache_from_redis(doctype: str | None = None):
|
|
from frappe.desk.notifications import delete_notification_count_for
|
|
from frappe.email.doctype.notification.notification import clear_notification_cache
|
|
from frappe.model.meta import clear_meta_cache
|
|
|
|
to_del = ["is_table", "doctype_modules"]
|
|
|
|
if doctype:
|
|
|
|
def clear_single(dt):
|
|
frappe.clear_document_cache(dt)
|
|
# Wild card for all keys containing this doctype.
|
|
# this can be excessive but this function isn't called often... ideally.
|
|
frappe.client_cache.delete_keys(f"*{dt}*")
|
|
frappe.cache.hdel_names(doctype_cache_keys, dt)
|
|
clear_meta_cache(dt)
|
|
|
|
clear_single(doctype)
|
|
|
|
# clear all parent doctypes
|
|
for dt in frappe.get_all(
|
|
"DocField", "parent", dict(fieldtype=["in", frappe.model.table_fields], options=doctype)
|
|
):
|
|
clear_single(dt.parent)
|
|
|
|
# clear all parent doctypes
|
|
if not frappe.flags.in_install:
|
|
for dt in frappe.get_all(
|
|
"Custom Field", "dt", dict(fieldtype=["in", frappe.model.table_fields], options=doctype)
|
|
):
|
|
clear_single(dt.dt)
|
|
|
|
# clear all notifications
|
|
delete_notification_count_for(doctype)
|
|
|
|
else:
|
|
# clear all
|
|
to_del += doctype_cache_keys
|
|
for pattern in wildcard_keys:
|
|
to_del += frappe.cache.get_keys(pattern)
|
|
clear_meta_cache()
|
|
|
|
clear_notification_cache()
|
|
frappe.cache.delete_value(to_del)
|
|
|
|
|
|
def clear_controller_cache(doctype=None, *, site=None):
|
|
if not doctype:
|
|
frappe.controllers.pop(site or frappe.local.site, None)
|
|
return
|
|
|
|
if site_controllers := frappe.controllers.get(site or frappe.local.site):
|
|
site_controllers.pop(doctype, None)
|
|
|
|
|
|
def get_doctype_map(doctype, name, filters=None, order_by=None):
|
|
return frappe.client_cache.get_value(
|
|
get_doctype_map_key(doctype, name),
|
|
generator=lambda: frappe.get_all(doctype, filters=filters, order_by=order_by, ignore_ddl=True),
|
|
)
|
|
|
|
|
|
def clear_doctype_map(doctype, name="*"):
|
|
frappe.client_cache.delete_keys(get_doctype_map_key(doctype, name))
|
|
|
|
|
|
def build_table_count_cache():
|
|
if (
|
|
frappe.flags.in_patch
|
|
or frappe.flags.in_install
|
|
or frappe.flags.in_migrate
|
|
or frappe.flags.in_import
|
|
or frappe.flags.in_setup_wizard
|
|
):
|
|
return
|
|
|
|
if frappe.db.db_type != "sqlite":
|
|
table_name = frappe.qb.Field("table_name").as_("name")
|
|
table_rows = frappe.qb.Field("table_rows").as_("count")
|
|
information_schema = frappe.qb.Schema("information_schema")
|
|
|
|
data = (frappe.qb.from_(information_schema.tables).select(table_name, table_rows)).run(as_dict=True)
|
|
counts = {d.get("name").replace("tab", "", 1): d.get("count", None) for d in data}
|
|
frappe.cache.set_value("information_schema:counts", counts)
|
|
else:
|
|
counts = {}
|
|
name = frappe.qb.Field("name")
|
|
type = frappe.qb.Field("type")
|
|
sqlite_master = frappe.qb.Schema("sqlite_master")
|
|
data = frappe.qb.from_(sqlite_master).select(name).where(type == "table").run(as_dict=True)
|
|
for table in data:
|
|
count = frappe.db.sql(f"SELECT COUNT(*) FROM `{table.name}`")[0][0]
|
|
counts[table.name.replace("tab", "", 1)] = count
|
|
frappe.cache.set_value("information_schema:counts", counts)
|
|
|
|
return counts
|
|
|
|
|
|
def build_domain_restricted_doctype_cache(*args, **kwargs):
|
|
if (
|
|
frappe.flags.in_patch
|
|
or frappe.flags.in_install
|
|
or frappe.flags.in_migrate
|
|
or frappe.flags.in_import
|
|
or frappe.flags.in_setup_wizard
|
|
):
|
|
return
|
|
active_domains = frappe.get_active_domains()
|
|
doctypes = frappe.get_all("DocType", filters={"restrict_to_domain": ("IN", active_domains)})
|
|
doctypes = [doc.name for doc in doctypes]
|
|
frappe.cache.set_value("domain_restricted_doctypes", doctypes)
|
|
|
|
return doctypes
|
|
|
|
|
|
def build_domain_restricted_page_cache(*args, **kwargs):
|
|
if (
|
|
frappe.flags.in_patch
|
|
or frappe.flags.in_install
|
|
or frappe.flags.in_migrate
|
|
or frappe.flags.in_import
|
|
or frappe.flags.in_setup_wizard
|
|
):
|
|
return
|
|
active_domains = frappe.get_active_domains()
|
|
pages = frappe.get_all("Page", filters={"restrict_to_domain": ("IN", active_domains)})
|
|
pages = [page.name for page in pages]
|
|
frappe.cache.set_value("domain_restricted_pages", pages)
|
|
|
|
return pages
|
|
|
|
|
|
def clear_cache(user: str | None = None, doctype: str | None = None):
|
|
"""Clear **User**, **DocType** or global cache.
|
|
|
|
:param user: If user is given, only user cache is cleared.
|
|
:param doctype: If doctype is given, only DocType cache is cleared."""
|
|
import frappe.cache_manager
|
|
import frappe.utils.caching
|
|
from frappe.website.router import clear_routing_cache
|
|
|
|
if doctype:
|
|
frappe.cache_manager.clear_doctype_cache(doctype)
|
|
reset_metadata_version()
|
|
elif user:
|
|
frappe.cache_manager.clear_user_cache(user)
|
|
else: # everything
|
|
# Delete ALL keys associated with this site.
|
|
keys_to_delete = set(frappe.cache.get_keys(""))
|
|
for key in frappe.get_hooks("persistent_cache_keys"):
|
|
keys_to_delete.difference_update(frappe.cache.get_keys(key))
|
|
frappe.cache.delete_value(list(keys_to_delete), make_keys=False)
|
|
|
|
reset_metadata_version()
|
|
frappe.local.cache = {}
|
|
frappe.local.new_doc_templates = {}
|
|
|
|
for fn in frappe.get_hooks("clear_cache"):
|
|
frappe.get_attr(fn)()
|
|
|
|
if (not doctype and not user) or doctype == "DocType":
|
|
frappe.utils.caching._SITE_CACHE.clear()
|
|
frappe.client_cache.clear_cache()
|
|
|
|
frappe.local.role_permissions = {}
|
|
if hasattr(frappe.local, "request_cache"):
|
|
frappe.local.request_cache.clear()
|
|
if hasattr(frappe.local, "system_settings"):
|
|
del frappe.local.system_settings
|
|
if hasattr(frappe.local, "website_settings"):
|
|
del frappe.local.website_settings
|
|
|
|
clear_routing_cache()
|
|
|
|
|
|
def reset_metadata_version():
|
|
"""Reset `metadata_version` (Client (Javascript) build ID) hash."""
|
|
v = frappe.generate_hash()
|
|
frappe.client_cache.set_value("metadata_version", v)
|
|
return v
|