Merge pull request #29159 from ankush/perf/faster_render

perf: ~3x faster `/app` loading
This commit is contained in:
Ankush Menat 2025-01-15 09:47:08 +05:30 committed by GitHub
commit 9fac1a06f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 113 additions and 69 deletions

View file

@ -385,25 +385,6 @@ def load_print_css(bootinfo, print_settings):
)
def get_unseen_notes():
note = DocType("Note")
nsb = DocType("Note Seen By").as_("nsb")
return (
frappe.qb.from_(note)
.select(note.name, note.title, note.content, note.notify_on_every_login)
.where(
(note.notify_on_login == 1)
& (note.expire_notification_on > frappe.utils.now())
& (
ParameterizedValueWrapper(frappe.session.user).notin(
SubQuery(frappe.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name))
)
)
)
).run(as_dict=1)
def get_success_action():
return frappe.get_all("Success Action", fields=["*"])

View file

@ -4,6 +4,8 @@
import frappe
from frappe.model.document import Document
UNSEEN_NOTES_KEY = "unseen_notes::"
class Note(Document):
# begin: auto-generated types
@ -39,6 +41,10 @@ class Note(Document):
self.print_heading = self.name
self.sub_heading = ""
def clear_cache(self):
frappe.cache.delete_keys(UNSEEN_NOTES_KEY)
return super().clear_cache()
def mark_seen_by(self, user: str) -> None:
if user in [d.user for d in self.seen_by]:
return
@ -62,3 +68,33 @@ def get_permission_query_conditions(user):
def has_permission(doc, user):
return bool(doc.public or doc.owner == user)
def get_unseen_notes():
from frappe.query_builder.terms import ParameterizedValueWrapper, SubQuery
def _get_unseen_notes():
note = frappe.qb.DocType("Note")
nsb = frappe.qb.DocType("Note Seen By").as_("nsb")
return (
frappe.qb.from_(note)
.select(note.name, note.title, note.content, note.notify_on_every_login)
.where(
(note.notify_on_login == 1)
& (note.expire_notification_on > frappe.utils.now())
& (
ParameterizedValueWrapper(frappe.session.user).notin(
SubQuery(frappe.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name))
)
)
)
).run(as_dict=1)
return (
frappe.cache.get_value(
f"{UNSEEN_NOTES_KEY}{frappe.session.user}",
generator=_get_unseen_notes,
)
or []
)

View file

@ -126,7 +126,8 @@ def clear_expired_sessions():
def get():
"""get session boot info"""
from frappe.boot import get_bootinfo, get_unseen_notes
from frappe.boot import get_bootinfo
from frappe.desk.doctype.note.note import get_unseen_notes
from frappe.utils.change_log import get_change_log
bootinfo = None
@ -175,9 +176,9 @@ def get():
"default_path": get_default_path() or "",
}
bootinfo["desk_theme"] = frappe.db.get_value("User", frappe.session.user, "desk_theme") or "Light"
bootinfo["desk_theme"] = frappe.get_cached_value("User", frappe.session.user, "desk_theme") or "Light"
bootinfo["user"]["impersonated_by"] = frappe.session.data.get("impersonated_by")
bootinfo["navbar_settings"] = frappe.get_cached_doc("Navbar Settings")
bootinfo["navbar_settings"] = frappe.client_cache.get_doc("Navbar Settings")
bootinfo.has_app_updates = has_app_update_notifications()
return bootinfo

View file

@ -1,6 +1,6 @@
import frappe
from frappe.boot import get_unseen_notes, get_user_pages_or_reports
from frappe.desk.doctype.note.note import mark_as_seen
from frappe.boot import get_user_pages_or_reports
from frappe.desk.doctype.note.note import get_unseen_notes, mark_as_seen
from frappe.tests import IntegrationTestCase

View file

@ -1,5 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
from frappe.utils.caching import site_cache
def get_jenv():
import frappe
@ -88,22 +92,23 @@ def render_template(template, context=None, is_path=None, safe_render=True):
if context is None:
context = {}
jenv: SandboxedEnvironment = get_jenv()
if is_path or guess_is_path(template):
is_path = True
compiled_template = jenv.get_template(template)
else:
if safe_render and ".__" in template:
throw(_("Illegal template"))
try:
compiled_template = jenv.from_string(template)
except TemplateError:
import html
try:
if is_path or guess_is_path(template):
is_path = True
compiled_template = compile_template(template)
else:
jenv: SandboxedEnvironment = get_jenv()
if safe_render and ".__" in template:
throw(_("Illegal template"))
throw(
title="Jinja Template Error",
msg=f"<pre>{template}</pre><pre>{html.escape(get_traceback())}</pre>",
)
compiled_template = jenv.from_string(template)
except TemplateError:
import html
throw(
title="Jinja Template Error",
msg=f"<pre>{template}</pre><pre>{html.escape(get_traceback())}</pre>",
)
import time
@ -136,30 +141,38 @@ def guess_is_path(template):
def get_jloader():
jloader = _get_jloader()
frappe.local.jloader = jloader # backward compat
return jloader
@site_cache(ttl=10 * 60, maxsize=16)
def compile_template(path):
jenv = get_jenv()
return jenv.get_template(path)
@site_cache(ttl=10 * 60, maxsize=8)
def _get_jloader():
from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader
import frappe
if not getattr(frappe.local, "jloader", None):
from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader
apps = frappe.get_hooks("template_apps")
if not apps:
apps = list(reversed(frappe.get_installed_apps(_ensure_on_bench=True)))
apps = frappe.get_hooks("template_apps")
if not apps:
apps = list(
reversed(
frappe.local.flags.web_pages_apps or frappe.get_installed_apps(_ensure_on_bench=True)
)
)
if "frappe" not in apps:
apps.append("frappe")
if "frappe" not in apps:
apps.append("frappe")
jloader = ChoiceLoader(
# search for something like app/templates/...
[PrefixLoader({app: PackageLoader(app, ".") for app in apps})]
# search for something like templates/...
+ [PackageLoader(app, ".") for app in apps]
)
frappe.local.jloader = ChoiceLoader(
# search for something like app/templates/...
[PrefixLoader({app: PackageLoader(app, ".") for app in apps})]
# search for something like templates/...
+ [PackageLoader(app, ".") for app in apps]
)
return frappe.local.jloader
return jloader
def set_filters(jenv):

View file

@ -287,8 +287,8 @@ def get_user_fullname(user: str) -> str:
def get_fullname_and_avatar(user: str) -> _dict:
first_name, last_name, avatar, name = frappe.db.get_value(
"User", user, ["first_name", "last_name", "user_image", "name"], order_by=None
first_name, last_name, avatar, name = frappe.get_cached_value(
"User", user, ["first_name", "last_name", "user_image", "name"]
)
return _dict(
{

View file

@ -20,3 +20,9 @@ class WebsiteRouteMeta(Document):
def autoname(self):
if self.name and self.name.startswith("/"):
self.name = self.name[1:]
def clear_cache(self):
from frappe.website.website_components.metatags import has_meta_tags
has_meta_tags.clear_cache()
return super().clear_cache()

View file

@ -148,7 +148,7 @@ class TemplatePage(BaseTemplatePage):
def setup_template_source(self):
"""Setup template source, frontmatter and markdown conversion"""
self.source = self.get_raw_template()
self.original_source = self.source = self.get_raw_template()
self.extract_frontmatter()
self.convert_from_markdown()
@ -233,7 +233,10 @@ class TemplatePage(BaseTemplatePage):
else:
safe_render = True
html = frappe.render_template(self.source, self.context, safe_render=safe_render)
src_modified = self.source is not self.original_source
html = frappe.render_template(
self.source if src_modified else self.context.template, self.context, safe_render=safe_render
)
return html

View file

@ -89,7 +89,7 @@ def get_pages(app=None):
if app:
apps = [app]
else:
apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps()
apps = frappe.get_installed_apps()
for app in apps:
app_path = frappe.get_app_path(app)

View file

@ -423,7 +423,7 @@ def get_frontmatter(string):
body = result.group(2)
return {
"attributes": yaml.safe_load(frontmatter),
"attributes": yaml.safe_load(frontmatter) if frontmatter else "",
"body": body,
}

View file

@ -1,4 +1,5 @@
import frappe
from frappe.utils.caching import site_cache
METATAGS = ("title", "description", "image", "author", "published_on")
@ -58,14 +59,17 @@ class MetaTags:
route = self.path
if route == "":
# homepage
route = frappe.db.get_single_value("Website Settings", "home_page")
route = frappe.get_website_settings("home_page")
route_exists = (
route and not route.endswith((".js", ".css")) and frappe.db.exists("Website Route Meta", route)
)
route_exists = route and not route.endswith((".js", ".css")) and has_meta_tags(route)
if route_exists:
website_route_meta = frappe.get_doc("Website Route Meta", route)
for meta_tag in website_route_meta.meta_tags:
d = meta_tag.get_meta_dict()
self.tags.update(d)
@site_cache(ttl=10 * 60, maxsize=16)
def has_meta_tags(route):
return bool(frappe.db.exists("Website Route Meta", route))

View file

@ -23,7 +23,7 @@ def get_context(context):
frappe.msgprint(_("Log in to access this page."))
frappe.redirect(f"/login?{urlencode({'redirect-to': frappe.request.path})}")
elif frappe.db.get_value("User", frappe.session.user, "user_type", order_by=None) == "Website User":
elif frappe.session.data.user_type == "Website User":
frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError)
try: