Speedups, changes, typings
This commit is contained in:
parent
cb237826b9
commit
6b5d3554c4
16 changed files with 106 additions and 49 deletions
0
py.typed
Normal file
0
py.typed
Normal file
5
setup.py
5
setup.py
|
@ -46,6 +46,9 @@ setup(
|
||||||
keywords='python django boilerplate',
|
keywords='python django boilerplate',
|
||||||
url='http://git.ksol.io/karolyi/py-ktools',
|
url='http://git.ksol.io/karolyi/py-ktools',
|
||||||
package_dir={'': 'src'},
|
package_dir={'': 'src'},
|
||||||
package_data={'ktools.django': ['jinja/ktools/macros/*.html']},
|
package_data={
|
||||||
|
'': ['py.typed'],
|
||||||
|
'ktools.django': ['jinja/ktools/macros/*.html']
|
||||||
|
},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license='MIT+NIGGER')
|
license='MIT+NIGGER')
|
||||||
|
|
46
src/ktools/cache/functional.py
vendored
46
src/ktools/cache/functional.py
vendored
|
@ -1,22 +1,32 @@
|
||||||
from functools import lru_cache, wraps
|
from functools import _lru_cache_wrapper, lru_cache, wraps
|
||||||
|
from typing import Callable, TypeVar
|
||||||
from weakref import ref
|
from weakref import ref
|
||||||
|
|
||||||
|
|
||||||
def memoized_method(*lru_args, **lru_kwargs):
|
_T = TypeVar(name='_T')
|
||||||
'http://stackoverflow.com/a/33672499/1067833'
|
MemoizedCallable = Callable[..., _T]
|
||||||
def decorator(func):
|
|
||||||
@wraps(func)
|
|
||||||
def wrapped_func(self, *args, **kwargs):
|
|
||||||
# We're storing the wrapped method inside the instance. If we had
|
|
||||||
# a strong reference to self the instance would be never
|
|
||||||
# garbage collected.
|
|
||||||
self_weak = ref(self)
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
@lru_cache(*lru_args, **lru_kwargs)
|
def memoized_method(
|
||||||
def cached_method(*args, **kwargs):
|
maxsize: int | None = 128, typed: bool = False
|
||||||
return func(self_weak(), *args, **kwargs)
|
) -> Callable[[MemoizedCallable], _lru_cache_wrapper]:
|
||||||
setattr(self, func.__name__, cached_method)
|
"""
|
||||||
return cached_method(*args, **kwargs)
|
LRU Cache decorator that keeps a weak reference to `self`, by
|
||||||
return wrapped_func
|
Raymond Hettinger: https://stackoverflow.com/a/68052994
|
||||||
return decorator
|
"""
|
||||||
|
def wrapper(func: Callable[..., _T]):
|
||||||
|
|
||||||
|
@lru_cache(maxsize=maxsize, typed=typed)
|
||||||
|
def _func(_weakself, *args, **kwargs) -> _T:
|
||||||
|
return func(_weakself(), *args, **kwargs)
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def inner(self, *args, **kwargs) -> _T:
|
||||||
|
return _func(ref(self), *args, **kwargs)
|
||||||
|
|
||||||
|
setattr(inner, 'cache_info', _func.cache_info)
|
||||||
|
setattr(inner, 'cache_clear', _func.cache_clear)
|
||||||
|
setattr(inner, 'cache_parameters', _func.cache_parameters)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
9
src/ktools/cache/functional.pyi
vendored
Normal file
9
src/ktools/cache/functional.pyi
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from functools import _lru_cache_wrapper
|
||||||
|
from typing import Callable, TypeVar
|
||||||
|
|
||||||
|
_T = TypeVar('_T')
|
||||||
|
|
||||||
|
|
||||||
|
def memoized_method(
|
||||||
|
maxsize: int | None = 128, typed: bool = False
|
||||||
|
) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ...
|
|
@ -4,7 +4,7 @@ from django.template import engines
|
||||||
|
|
||||||
from .conf import setup_template_backend
|
from .conf import setup_template_backend
|
||||||
from .conf.constants import TEMPLATE_ENGINE_NAME
|
from .conf.constants import TEMPLATE_ENGINE_NAME
|
||||||
from .jinja import templatetags
|
from .jinja.templatetags import template_getter
|
||||||
from .utils.translation import gettext_safelazy as _
|
from .utils.translation import gettext_safelazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,8 +20,10 @@ class KtoolsDjangoConfig(AppConfig):
|
||||||
Set up the instance of `KtoolsJinjaBackend` for later private
|
Set up the instance of `KtoolsJinjaBackend` for later private
|
||||||
use.
|
use.
|
||||||
"""
|
"""
|
||||||
engines.templates[TEMPLATE_ENGINE_NAME] = \
|
backend_cfg = setup_template_backend(DEBUG=settings.DEBUG)
|
||||||
setup_template_backend(DEBUG=settings.DEBUG)
|
engines.templates[TEMPLATE_ENGINE_NAME] = backend_cfg
|
||||||
templatetags._k_jinja_backend = engines[TEMPLATE_ENGINE_NAME]
|
template_getter.setup(
|
||||||
|
backend=engines[TEMPLATE_ENGINE_NAME],
|
||||||
|
cache_enabled=backend_cfg['OPTIONS']['bytecode_cache']['enabled'])
|
||||||
del engines.templates[TEMPLATE_ENGINE_NAME]
|
del engines.templates[TEMPLATE_ENGINE_NAME]
|
||||||
del engines._engines[TEMPLATE_ENGINE_NAME]
|
del engines._engines[TEMPLATE_ENGINE_NAME]
|
||||||
|
|
|
@ -21,7 +21,7 @@ def setup_template_backend(DEBUG: bool) -> _BackendType:
|
||||||
'DIRS': [],
|
'DIRS': [],
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
# It's already got a sensible default but repeat it here
|
# It's already got a sensible default but repeat it here
|
||||||
'app_dirname': 'ktools-jinjatemplates',
|
'app_dirname': 'ktemplates',
|
||||||
'match_extension': None,
|
'match_extension': None,
|
||||||
'newstyle_gettext': True,
|
'newstyle_gettext': True,
|
||||||
'extensions': DEFAULT_EXTENSIONS + [
|
'extensions': DEFAULT_EXTENSIONS + [
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from functools import lru_cache
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.forms.boundfield import BoundField
|
from django.forms.boundfield import BoundField
|
||||||
|
@ -15,15 +14,48 @@ from jinja2.utils import pass_context
|
||||||
|
|
||||||
from ktools.django.jinja.backend import KtoolsJinjaBackend
|
from ktools.django.jinja.backend import KtoolsJinjaBackend
|
||||||
|
|
||||||
# See assignment in apps.py's `ready`
|
|
||||||
_k_jinja_backend: KtoolsJinjaBackend
|
class _TemplateGetter(object):
|
||||||
|
# See assignment in apps.py's `ready`
|
||||||
|
cache_enabled = False
|
||||||
|
k_jinja_backend: KtoolsJinjaBackend
|
||||||
|
|
||||||
|
def _get_template(self, template_name: str) -> Template:
|
||||||
|
return self.k_jinja_backend.get_template(
|
||||||
|
template_name=template_name) # pyright: ignore[reportReturnType]
|
||||||
|
|
||||||
|
def setup(self, backend: KtoolsJinjaBackend, cache_enabled: bool):
|
||||||
|
self.k_jinja_backend = backend
|
||||||
|
self.cache_enabled = cache_enabled or True
|
||||||
|
if cache_enabled or True:
|
||||||
|
self.hits = self.misses = 0
|
||||||
|
self.cached = dict[str, Template]()
|
||||||
|
|
||||||
|
def stats(self) -> str:
|
||||||
|
if not self.cache_enabled:
|
||||||
|
return 'Cache not enabled.'
|
||||||
|
return (
|
||||||
|
f'Hits: {self.hits}, Misses: {self.misses}, '
|
||||||
|
f'Size: {len(self.cached)}')
|
||||||
|
|
||||||
|
def __call__(self, template_name: str) -> Template:
|
||||||
|
if not self.cache_enabled:
|
||||||
|
obj: Template = self.k_jinja_backend.get_template(
|
||||||
|
template_name=template_name
|
||||||
|
) # pyright: ignore[reportAssignmentType]
|
||||||
|
return obj
|
||||||
|
elif template := self.cached.get(template_name):
|
||||||
|
self.hits += 1
|
||||||
|
return template
|
||||||
|
self.misses += 1
|
||||||
|
obj: Template = self.k_jinja_backend.get_template(
|
||||||
|
template_name=template_name
|
||||||
|
) # pyright: ignore[reportAssignmentType]
|
||||||
|
self.cached[template_name] = obj
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
template_getter = _TemplateGetter()
|
||||||
def _get_template(template_name: str) -> Template:
|
|
||||||
'Return (and cache) a `Template` to render.'
|
|
||||||
return _k_jinja_backend.get_template(
|
|
||||||
template_name=template_name) # pyright: ignore[reportReturnType]
|
|
||||||
|
|
||||||
|
|
||||||
@pass_context
|
@pass_context
|
||||||
|
@ -33,8 +65,8 @@ def ktools_render_messages(
|
||||||
'Rendering a HTML message in various formats from ktools templates.'
|
'Rendering a HTML message in various formats from ktools templates.'
|
||||||
match variant:
|
match variant:
|
||||||
case 'bootstrap5-v1':
|
case 'bootstrap5-v1':
|
||||||
template = _get_template(template_name=(
|
template = template_getter(template_name=(
|
||||||
'ktools/render-content/messages-bootstrap5-v1.html.jinja'))
|
'render-content/messages-bootstrap5-v1.html.jinja'))
|
||||||
return template.render(context=context, request=request)
|
return template.render(context=context, request=request)
|
||||||
case _:
|
case _:
|
||||||
raise TemplateNotFound(
|
raise TemplateNotFound(
|
||||||
|
@ -48,27 +80,25 @@ _TEMPLATE_MAPPINGS = {
|
||||||
'bootstrap5-floating': {
|
'bootstrap5-floating': {
|
||||||
'TextInput': {
|
'TextInput': {
|
||||||
'template_name': (
|
'template_name': (
|
||||||
'ktools/widgets/bootstrap5/floating/input-textual.html.jinja')
|
'widgets/bootstrap5/floating/input-textual.html.jinja')
|
||||||
},
|
},
|
||||||
'EmailInput': {
|
'EmailInput': {
|
||||||
'template_name': (
|
'template_name': (
|
||||||
'ktools/widgets/bootstrap5/floating/input-textual.html.jinja')
|
'widgets/bootstrap5/floating/input-textual.html.jinja')
|
||||||
},
|
},
|
||||||
'RegionalPhoneNumberWidget': {
|
'RegionalPhoneNumberWidget': {
|
||||||
'template_name': (
|
'template_name': (
|
||||||
'ktools/widgets/bootstrap5/floating/input-textual.html.jinja')
|
'widgets/bootstrap5/floating/input-textual.html.jinja')
|
||||||
},
|
},
|
||||||
'PasswordInput': {
|
'PasswordInput': {
|
||||||
'template_name': (
|
'template_name': (
|
||||||
'ktools/widgets/bootstrap5/floating/input-textual.html.jinja')
|
'widgets/bootstrap5/floating/input-textual.html.jinja')
|
||||||
},
|
},
|
||||||
'CheckboxSelectMultiple': {
|
'CheckboxSelectMultiple': {
|
||||||
'template_name': (
|
'template_name': (
|
||||||
'ktools/widgets/bootstrap5/floating/multiple-input.html'
|
'widgets/bootstrap5/floating/multiple-input.html.jinja'),
|
||||||
'.jinja'),
|
|
||||||
'option_template_name': (
|
'option_template_name': (
|
||||||
'ktools/widgets/bootstrap5/floating/input-option.html'
|
'widgets/bootstrap5/floating/input-option.html.jinja')
|
||||||
'.jinja')
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +117,7 @@ class KTemplatesSetting(BaseRenderer):
|
||||||
Add the `_KBoundField` back to the context so that the template
|
Add the `_KBoundField` back to the context so that the template
|
||||||
can use it.
|
can use it.
|
||||||
"""
|
"""
|
||||||
template = _get_template(template_name=template_name)
|
template = template_getter(template_name=template_name)
|
||||||
context['_k_boundfield'] = self._k_boundfield
|
context['_k_boundfield'] = self._k_boundfield
|
||||||
return template.render(context=context, request=request).strip()
|
return template.render(context=context, request=request).strip()
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{- attr_class(widget, 'form-check-input') -}}
|
{{- attr_class(widget, 'form-check-input') -}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'ktools/widgets/bootstrap5/floating/attrs-wo-class.html.jinja' %}
|
{% include 'widgets/bootstrap5/floating/attrs-wo-class.html.jinja' %}
|
||||||
placeholder="{{ _k_boundfield.label|e }}">
|
placeholder="{{ _k_boundfield.label|e }}">
|
||||||
<label
|
<label
|
||||||
{%- if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}
|
{%- if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% from 'ktools/widgets/bootstrap5/floating/attr-class.html.jinja' import attr_class with context %}
|
{% from 'widgets/bootstrap5/floating/attr-class.html.jinja' import attr_class with context %}
|
||||||
<div class="form-floating mb-3" data-ktools-field-type="{{ dash_case_convert(name=_k_boundfield.field.widget.__class__.__name__) }}">
|
<div class="form-floating mb-3" data-ktools-field-type="{{ dash_case_convert(name=_k_boundfield.field.widget.__class__.__name__) }}">
|
||||||
<input type="{{ widget.type }}" name="{{ widget.name|e }}"
|
<input type="{{ widget.type }}" name="{{ widget.name|e }}"
|
||||||
{%- if widget.value != None %}
|
{%- if widget.value != None %}
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{- attr_class(widget, 'form-control') -}}
|
{{- attr_class(widget, 'form-control') -}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'ktools/widgets/bootstrap5/floating/attrs-wo-class.html.jinja' %}
|
{% include 'widgets/bootstrap5/floating/attrs-wo-class.html.jinja' %}
|
||||||
placeholder="{{ _k_boundfield.label|e }}">
|
placeholder="{{ _k_boundfield.label|e }}">
|
||||||
<label for="{{ widget.attrs.id }}">{{ _k_boundfield.label }}</label>
|
<label for="{{ widget.attrs.id }}">{{ _k_boundfield.label }}</label>
|
||||||
{% if _k_boundfield.form.is_bound and _k_boundfield.errors %}
|
{% if _k_boundfield.form.is_bound and _k_boundfield.errors %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% from 'ktools/widgets/bootstrap5/floating/attr-class.html.jinja' import attr_class with context %}
|
{% from 'widgets/bootstrap5/floating/attr-class.html.jinja' import attr_class with context %}
|
||||||
{% set id = widget.attrs.id %}
|
{% set id = widget.attrs.id %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<fieldset data-ktools-field-type="{{ dash_case_convert(name=_k_boundfield.field.widget.__class__.__name__) }}"
|
<fieldset data-ktools-field-type="{{ dash_case_convert(name=_k_boundfield.field.widget.__class__.__name__) }}"
|
|
@ -87,6 +87,9 @@ class TokenLink(object):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def url(self) -> str:
|
def urlpath(self) -> str:
|
||||||
'Return the URL to use this `TokenLink`.'
|
"""
|
||||||
|
Return the URL path to use this `TokenLink`. That is, without
|
||||||
|
`scheme` and `hostname`.
|
||||||
|
"""
|
||||||
return reverse(viewname=TOKENLINK_NS, kwargs=dict(token=self.token))
|
return reverse(viewname=TOKENLINK_NS, kwargs=dict(token=self.token))
|
||||||
|
|
Loading…
Reference in a new issue