perf: faster local attr lookup
This commit is contained in:
parent
20220ef894
commit
3c8912f6e7
4 changed files with 71 additions and 59 deletions
|
|
@ -45,6 +45,7 @@ from frappe.query_builder.utils import (
|
|||
from frappe.utils.caching import deprecated_local_cache as local_cache
|
||||
from frappe.utils.caching import request_cache
|
||||
from frappe.utils.data import as_unicode, bold, cint, cstr, safe_decode, safe_encode, sbool
|
||||
from frappe.utils.local import FrappeLocal
|
||||
|
||||
# Local application imports
|
||||
from .exceptions import *
|
||||
|
|
@ -75,7 +76,7 @@ if TYPE_CHECKING: # pragma: no cover
|
|||
from frappe.utils.redis_wrapper import ClientCache, RedisWrapper
|
||||
|
||||
controllers: dict[str, "Document"] = {}
|
||||
local = Local()
|
||||
local = FrappeLocal()
|
||||
cache: Optional["RedisWrapper"] = None
|
||||
client_cache: Optional["ClientCache"] = None
|
||||
STANDARD_USERS = ("Guest", "Administrator")
|
||||
|
|
@ -87,27 +88,6 @@ if _dev_server:
|
|||
warnings.simplefilter("always", PendingDeprecationWarning)
|
||||
|
||||
|
||||
def _get_local_proxy(self: Local, name: str) -> LocalProxy:
|
||||
"""Get local proxy object by name."""
|
||||
|
||||
_local_contextvar = self._Local__storage
|
||||
|
||||
def _get_current_object() -> Any:
|
||||
obj = _local_contextvar.get(None)
|
||||
|
||||
if obj is not None and name in obj:
|
||||
return obj[name]
|
||||
|
||||
raise RuntimeError("object is not bound") from None
|
||||
|
||||
lp = LocalProxy(_get_current_object)
|
||||
object.__setattr__(lp, "_get_current_object", _get_current_object)
|
||||
return lp
|
||||
|
||||
|
||||
Local.__call__ = _get_local_proxy
|
||||
|
||||
|
||||
def _(msg: str, lang: str | None = None, context: str | None = None) -> str:
|
||||
"""Return translated string in current lang, if exists.
|
||||
Usage:
|
||||
|
|
|
|||
|
|
@ -202,42 +202,6 @@ def deprecation_warning(marked: str, graduation: str, msg: str):
|
|||
|
||||
### Party starts here
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from werkzeug.local import Local
|
||||
|
||||
|
||||
def get_local_with_deprecations() -> "Local":
|
||||
from werkzeug.local import Local
|
||||
|
||||
class DeprecatedLocalAttribute:
|
||||
def __init__(self, name, warning):
|
||||
self.name = name
|
||||
self.warning = warning
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
self.warning()
|
||||
return obj.__getattr__(self.name)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
return obj.__setattr__(self.name, value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
return obj.__delattr__(self.name)
|
||||
|
||||
class LocalWithDeprecations(Local):
|
||||
"""Can deprecate local attributes."""
|
||||
|
||||
# sites_path = DeprecatedLocalAttribute(
|
||||
# "sites_path",
|
||||
# lambda: deprecation_warning(
|
||||
# "2024-12-06",
|
||||
# "v17",
|
||||
# "'local.sites_path' will be deprecated: use 'frappe.bench.sites.path instead'",
|
||||
# ),
|
||||
# )
|
||||
|
||||
return LocalWithDeprecations()
|
||||
|
||||
|
||||
def _old_deprecated(func):
|
||||
return deprecated(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from contextlib import contextmanager
|
|||
from unittest.mock import patch
|
||||
|
||||
from rq import Queue
|
||||
from werkzeug.local import Local
|
||||
|
||||
import frappe
|
||||
from frappe.core.doctype.rq_job.rq_job import remove_failed_jobs
|
||||
|
|
@ -93,7 +94,7 @@ def after_job(*args, **kwargs):
|
|||
@contextmanager
|
||||
def freeze_local():
|
||||
locals = frappe.local
|
||||
frappe.local = frappe.Local()
|
||||
frappe.local = Local()
|
||||
yield locals
|
||||
frappe.local = locals
|
||||
|
||||
|
|
|
|||
67
frappe/utils/local.py
Normal file
67
frappe/utils/local.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
from contextvars import ContextVar
|
||||
from typing import Any
|
||||
|
||||
from werkzeug.local import Local, LocalProxy
|
||||
|
||||
_contextvar = ContextVar("frappe_local")
|
||||
_local_attributes = frozenset(dir(Local))
|
||||
_local_proxy_attributes = frozenset(dir(LocalProxy))
|
||||
|
||||
|
||||
class FrappeLocal(Local):
|
||||
"""
|
||||
For internal use only. Do not use this class directly.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(_contextvar)
|
||||
|
||||
def __getattribute__(self, name: str) -> Any:
|
||||
if name in _local_attributes:
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
obj = _contextvar.get(None)
|
||||
if obj is not None and name in obj:
|
||||
return obj[name]
|
||||
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
obj = _contextvar.get(None)
|
||||
if obj is None:
|
||||
obj = {}
|
||||
_contextvar.set(obj)
|
||||
|
||||
obj[name] = value
|
||||
|
||||
def __delattr__(self, name: str) -> None:
|
||||
obj = _contextvar.get(None)
|
||||
if obj is not None and name in obj:
|
||||
del obj[name]
|
||||
return
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
def __call__(self, name: str) -> LocalProxy:
|
||||
def _get_current_object() -> Any:
|
||||
obj = _contextvar.get(None)
|
||||
if obj is not None and name in obj:
|
||||
return obj[name]
|
||||
|
||||
raise RuntimeError("object is not bound") from None
|
||||
|
||||
lp = FrappeLocalProxy(_get_current_object)
|
||||
object.__setattr__(lp, "_get_current_object", _get_current_object)
|
||||
return lp
|
||||
|
||||
|
||||
class FrappeLocalProxy(LocalProxy):
|
||||
__slots__ = ()
|
||||
|
||||
def __getattribute__(self, name: str) -> Any:
|
||||
if name in _local_proxy_attributes:
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
return getattr(object.__getattribute__(self, "_get_current_object")(), name)
|
||||
Loading…
Add table
Reference in a new issue