mirror of
https://github.com/django-components/django-components.git
synced 2025-08-08 08:17:59 +00:00
fix: implement safe retrieval of component classes from registry (#934)
This commit is contained in:
parent
bafa9f7cc5
commit
f01ee1fe8a
3 changed files with 24 additions and 30 deletions
|
@ -1,4 +1,3 @@
|
|||
import inspect
|
||||
import types
|
||||
from collections import deque
|
||||
from contextlib import contextmanager
|
||||
|
@ -52,6 +51,7 @@ from django_components.dependencies import (
|
|||
cache_component_css_vars,
|
||||
cache_component_js,
|
||||
cache_component_js_vars,
|
||||
comp_hash_mapping,
|
||||
postprocess_component_html,
|
||||
set_component_attrs_for_js_and_css,
|
||||
)
|
||||
|
@ -72,7 +72,7 @@ from django_components.slots import (
|
|||
)
|
||||
from django_components.template import cached_template
|
||||
from django_components.util.django_monkeypatch import is_template_cls_patched
|
||||
from django_components.util.misc import gen_id
|
||||
from django_components.util.misc import gen_id, hash_comp_cls
|
||||
from django_components.util.template_tag import TagAttr
|
||||
from django_components.util.validation import validate_typed_dict, validate_typed_tuple
|
||||
|
||||
|
@ -499,7 +499,7 @@ class Component(
|
|||
However, there's a few differences from Django's Media class:
|
||||
|
||||
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list,
|
||||
or (CSS-only) a dictonary (See [`ComponentMediaInput`](../api#django_components.ComponentMediaInput)).
|
||||
or (CSS-only) a dictionary (See [`ComponentMediaInput`](../api#django_components.ComponentMediaInput)).
|
||||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
|
||||
[`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function
|
||||
(See [`ComponentMediaInputPath`](../api#django_components.ComponentMediaInputPath)).
|
||||
|
@ -556,7 +556,7 @@ class Component(
|
|||
# MISC
|
||||
# #####################################
|
||||
|
||||
_class_hash: ClassVar[int]
|
||||
_class_hash: ClassVar[str]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -587,7 +587,8 @@ class Component(
|
|||
self._types: Optional[Union[Tuple[Any, Any, Any, Any, Any, Any], Literal[False]]] = None
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
cls._class_hash = hash(inspect.getfile(cls) + cls.__name__)
|
||||
cls._class_hash = hash_comp_cls(cls)
|
||||
comp_hash_mapping[cls._class_hash] = cls
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
@ -627,7 +628,7 @@ class Component(
|
|||
@property
|
||||
def input(self) -> RenderInput[ArgsType, KwargsType, SlotsType]:
|
||||
"""
|
||||
Input holds the data (like arg, kwargs, slots) that were passsed to
|
||||
Input holds the data (like arg, kwargs, slots) that were passed to
|
||||
the current execution of the `render` method.
|
||||
"""
|
||||
if not len(self._render_stack):
|
||||
|
|
|
@ -5,7 +5,6 @@ import json
|
|||
import re
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache
|
||||
from hashlib import md5
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
|
@ -36,7 +35,7 @@ from django.utils.safestring import SafeString, mark_safe
|
|||
from djc_core_html_parser import set_html_attributes
|
||||
|
||||
from django_components.node import BaseNode
|
||||
from django_components.util.misc import get_import_path, is_nonempty_str
|
||||
from django_components.util.misc import is_nonempty_str
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.component import Component
|
||||
|
@ -103,13 +102,6 @@ else:
|
|||
|
||||
|
||||
# Convert Component class to something like `TableComp_a91d03`
|
||||
@lru_cache(None)
|
||||
def _hash_comp_cls(comp_cls: Type["Component"]) -> str:
|
||||
full_name = get_import_path(comp_cls)
|
||||
comp_cls_hash = md5(full_name.encode()).hexdigest()[0:6]
|
||||
return comp_cls.__name__ + "_" + comp_cls_hash
|
||||
|
||||
|
||||
def _gen_cache_key(
|
||||
comp_cls_hash: str,
|
||||
script_type: ScriptType,
|
||||
|
@ -126,8 +118,7 @@ def _is_script_in_cache(
|
|||
script_type: ScriptType,
|
||||
input_hash: Optional[str],
|
||||
) -> bool:
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type, input_hash)
|
||||
cache_key = _gen_cache_key(comp_cls._class_hash, script_type, input_hash)
|
||||
return comp_media_cache.has(cache_key)
|
||||
|
||||
|
||||
|
@ -141,11 +132,10 @@ def _cache_script(
|
|||
Given a component and it's inlined JS or CSS, store the JS/CSS in a cache,
|
||||
so it can be retrieved via URL endpoint.
|
||||
"""
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
|
||||
# E.g. `__components:MyButton:js:df7c6d10`
|
||||
if script_type in ("js", "css"):
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type, input_hash)
|
||||
cache_key = _gen_cache_key(comp_cls._class_hash, script_type, input_hash)
|
||||
else:
|
||||
raise ValueError(f"Unexpected script_type '{script_type}'")
|
||||
|
||||
|
@ -345,11 +335,7 @@ def _insert_component_comment(
|
|||
will be used by the ComponentDependencyMiddleware to collect all
|
||||
declared JS / CSS scripts.
|
||||
"""
|
||||
# Add components to the cache
|
||||
comp_cls_hash = _hash_comp_cls(component_cls)
|
||||
comp_hash_mapping[comp_cls_hash] = component_cls
|
||||
|
||||
data = f"{comp_cls_hash},{component_id},{js_input_hash or ''},{css_input_hash or ''}"
|
||||
data = f"{component_cls._class_hash},{component_id},{js_input_hash or ''},{css_input_hash or ''}"
|
||||
|
||||
# NOTE: It's important that we put the comment BEFORE the content, so we can
|
||||
# use the order of comments to evaluate components' instance JS code in the correct order.
|
||||
|
@ -868,8 +854,7 @@ def get_script_content(
|
|||
comp_cls: Type["Component"],
|
||||
input_hash: Optional[str],
|
||||
) -> SafeString:
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
cache_key = _gen_cache_key(comp_cls_hash, script_type, input_hash)
|
||||
cache_key = _gen_cache_key(comp_cls._class_hash, script_type, input_hash)
|
||||
script = comp_media_cache.get(cache_key)
|
||||
|
||||
return script
|
||||
|
@ -897,12 +882,10 @@ def get_script_url(
|
|||
comp_cls: Type["Component"],
|
||||
input_hash: Optional[str],
|
||||
) -> str:
|
||||
comp_cls_hash = _hash_comp_cls(comp_cls)
|
||||
|
||||
return reverse(
|
||||
CACHE_ENDPOINT_NAME,
|
||||
kwargs={
|
||||
"comp_cls_hash": comp_cls_hash,
|
||||
"comp_cls_hash": comp_cls._class_hash,
|
||||
"script_type": script_type,
|
||||
**({"input_hash": input_hash} if input_hash is not None else {}),
|
||||
},
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import re
|
||||
from typing import Any, Callable, List, Optional, Type, TypeVar
|
||||
from hashlib import md5
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, TypeVar
|
||||
|
||||
from django_components.util.nanoid import generate
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.component import Component
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
|
@ -71,3 +75,9 @@ def get_last_index(lst: List, key: Callable[[Any], bool]) -> Optional[int]:
|
|||
|
||||
def is_nonempty_str(txt: Optional[str]) -> bool:
|
||||
return txt is not None and bool(txt.strip())
|
||||
|
||||
|
||||
def hash_comp_cls(comp_cls: Type["Component"]) -> str:
|
||||
full_name = get_import_path(comp_cls)
|
||||
comp_cls_hash = md5(full_name.encode()).hexdigest()[0:6]
|
||||
return comp_cls.__name__ + "_" + comp_cls_hash
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue