Skip to content

Commit 639204d

Browse files
Consolidate asset and component registries (#87)
1 parent d50db48 commit 639204d

File tree

9 files changed

+151
-288
lines changed

9 files changed

+151
-288
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
2020

2121
### Changed
2222

23-
- **Internal**: Renamed Component and Asset registries.
23+
- **Internal**: Consolidated Component and Asset registries into a single `ComponentRegistry`.
2424

2525
## [0.7.2]
2626

src/django_bird/components.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from dataclasses import dataclass
44
from dataclasses import field
5+
from pathlib import Path
56
from typing import Any
67

78
from cachetools import LRUCache
@@ -11,7 +12,7 @@
1112

1213
from django_bird.staticfiles import Asset
1314

14-
from .staticfiles import get_template_assets
15+
from .staticfiles import AssetType
1516
from .templates import get_template_names
1617

1718

@@ -32,26 +33,40 @@ def nodelist(self):
3233
def from_name(cls, name: str):
3334
template_names = get_template_names(name)
3435
template = select_template(template_names)
35-
assets = get_template_assets(template)
36-
return cls(name=name, template=template, assets=assets)
36+
37+
assets: set[Asset] = set()
38+
template_path = Path(template.template.origin.name)
39+
for asset_type in AssetType:
40+
asset_path = template_path.with_suffix(asset_type.ext)
41+
if asset_path.exists():
42+
asset = Asset.from_path(asset_path)
43+
assets.add(asset)
44+
45+
return cls(name=name, template=template, assets=frozenset(assets))
3746

3847

3948
class ComponentRegistry:
4049
def __init__(self, maxsize: int = 100):
41-
self._cache: LRUCache[str, Component] = LRUCache(maxsize=maxsize)
50+
self._components: LRUCache[str, Component] = LRUCache(maxsize=maxsize)
51+
52+
def clear(self) -> None:
53+
"""Clear the registry. Mainly useful for testing."""
54+
self._components.clear()
4255

4356
def get_component(self, name: str) -> Component:
4457
try:
45-
return self._cache[name]
58+
return self._components[name]
4659
except KeyError:
4760
component = Component.from_name(name)
4861
if not settings.DEBUG:
49-
self._cache[name] = component
62+
self._components[name] = component
5063
return component
5164

52-
def clear(self) -> None:
53-
"""Clear the component cache. Mainly useful for testing."""
54-
self._cache.clear()
65+
def get_assets(self, asset_type: AssetType) -> list[Asset]:
66+
assets: list[Asset] = []
67+
for component in self._components.values():
68+
assets.extend(a for a in component.assets if a.type == asset_type)
69+
return assets
5570

5671

5772
components = ComponentRegistry()

src/django_bird/loader.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from ._typing import override
1818
from .compiler import Compiler
1919
from .components import components
20-
from .staticfiles import assets
2120
from .templatetags.tags.bird import TAG
2221
from .templatetags.tags.bird import BirdNode
2322

@@ -50,16 +49,14 @@ def get_contents(self, origin: Origin) -> str:
5049

5150
def _scan_for_components(self, node: Template | Node, context: Context) -> None:
5251
if isinstance(node, BirdNode):
53-
component = components.get_component(node.name)
54-
assets.register(component)
52+
components.get_component(node.name)
5553

5654
if not _has_nodelist(node) or node.nodelist is None:
5755
return
5856

5957
for child in node.nodelist:
6058
if isinstance(child, BirdNode):
61-
component = components.get_component(child.name)
62-
assets.register(component)
59+
components.get_component(child.name)
6360

6461
if isinstance(child, ExtendsNode):
6562
parent_template = child.get_parent(context)

src/django_bird/staticfiles.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from dataclasses import field
54
from enum import Enum
65
from pathlib import Path
7-
from typing import TYPE_CHECKING
8-
9-
from django.template.backends.django import Template as DjangoTemplate
106

117
from ._typing import override
128

13-
if TYPE_CHECKING:
14-
from django_bird.components import Component
15-
169

1710
class AssetType(Enum):
1811
CSS = "css"
@@ -45,35 +38,3 @@ def from_path(cls, path: Path) -> Asset:
4538
case _:
4639
raise ValueError(f"Unknown asset type for path: {path}")
4740
return cls(path=path, type=asset_type)
48-
49-
50-
def get_template_assets(template: DjangoTemplate):
51-
assets: set[Asset] = set()
52-
template_path = Path(template.template.origin.name)
53-
for asset_type in AssetType:
54-
asset_path = template_path.with_suffix(asset_type.ext)
55-
if asset_path.exists():
56-
asset = Asset.from_path(asset_path)
57-
assets.add(asset)
58-
return frozenset(assets)
59-
60-
61-
@dataclass
62-
class AssetRegistry:
63-
components: set[Component] = field(default_factory=set)
64-
65-
def clear(self) -> None:
66-
"""Clear the component cache. Mainly useful for testing."""
67-
self.components.clear()
68-
69-
def register(self, component: Component) -> None:
70-
self.components.add(component)
71-
72-
def get_assets(self, asset_type: AssetType) -> list[Asset]:
73-
assets: list[Asset] = []
74-
for component in self.components:
75-
assets.extend(a for a in component.assets if a.type == asset_type)
76-
return assets
77-
78-
79-
assets = AssetRegistry()

src/django_bird/templatetags/tags/asset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
from django_bird._typing import TagBits
1010
from django_bird._typing import override
11+
from django_bird.components import components
1112
from django_bird.staticfiles import Asset
1213
from django_bird.staticfiles import AssetType
13-
from django_bird.staticfiles import assets
1414

1515
CSS_TAG = "bird:css"
1616
JS_TAG = "bird:js"
@@ -48,7 +48,7 @@ def __init__(self, asset_type: AssetType):
4848

4949
@override
5050
def render(self, context: Context) -> str:
51-
component_assets = assets.get_assets(self.asset_type)
51+
component_assets = components.get_assets(self.asset_type)
5252
return self._render_assets(component_assets)
5353

5454
def _render_assets(self, assets: list[Asset]) -> str:

tests/conftest.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,3 @@ def clear_components_registry():
170170
components.clear()
171171
yield
172172
components.clear()
173-
174-
175-
@pytest.fixture(autouse=True)
176-
def clear_assets_registry():
177-
from django_bird.staticfiles import assets
178-
179-
assets.clear()
180-
yield
181-
assets.clear()

tests/test_components.py

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from pathlib import Path
4+
35
import pytest
46
from django.template.backends.django import Template
57
from django.template.exceptions import TemplateDoesNotExist
@@ -18,7 +20,7 @@ def test_from_name_basic(self, create_bird_template):
1820
comp = Component.from_name("button")
1921

2022
assert comp.name == "button"
21-
assert comp.assets == set()
23+
assert comp.assets == frozenset()
2224
assert isinstance(comp.template, Template)
2325
assert comp.render({}) == "<button>Click me</button>"
2426

@@ -38,30 +40,50 @@ def test_from_name_with_assets(self, create_template, create_bird_template):
3840
assert Asset(js_file, AssetType.JS) in comp.assets
3941

4042
@pytest.mark.parametrize(
41-
"asset_suffix,asset_content,expected_asset_type",
43+
"suffix,content,expected_type",
4244
[
4345
(".css", "button { color: red; }", AssetType.CSS),
4446
(".js", "console.log('loaded');", AssetType.JS),
4547
],
4648
)
4749
def test_from_name_with_partial_assets(
48-
self,
49-
asset_suffix,
50-
asset_content,
51-
expected_asset_type,
52-
create_template,
53-
create_bird_template,
50+
self, suffix, content, expected_type, create_template, create_bird_template
5451
):
5552
template_file = create_bird_template("button", "<button>Click me</button>")
5653
create_template(template_file)
5754

58-
file = template_file.with_suffix(asset_suffix)
59-
file.write_text(asset_content)
55+
file = template_file.with_suffix(suffix)
56+
file.write_text(content)
6057

6158
comp = Component.from_name("button")
6259

6360
assert len(comp.assets) == 1
64-
assert Asset(file, expected_asset_type) in comp.assets
61+
assert Asset(file, expected_type) in comp.assets
62+
63+
def test_from_name_no_assets(self, create_template, create_bird_template):
64+
template_file = create_bird_template("button", "<button>Click me</button>")
65+
create_template(template_file)
66+
67+
comp = Component.from_name("button")
68+
69+
assert len(comp.assets) == 0
70+
71+
def test_from_name_custom_component_dir(
72+
self, create_template, create_bird_template
73+
):
74+
template_file = create_bird_template(
75+
name="button", content="<button>", sub_dir="components"
76+
)
77+
78+
css_file = template_file.with_suffix(".css")
79+
css_file.write_text("button { color: red; }")
80+
81+
create_template(template_file)
82+
83+
comp = Component.from_name("components/button")
84+
85+
assert len(comp.assets) == 1
86+
assert Asset(css_file, AssetType.CSS) in comp.assets
6587

6688

6789
class TestComponentRegistry:
@@ -104,13 +126,97 @@ def test_component_not_found(self, registry):
104126
def test_cache_with_debug(self, registry, create_bird_template):
105127
create_bird_template(name="button", content="<button>Click me</button>")
106128

107-
assert len(registry._cache) == 0
129+
assert len(registry._components) == 0
108130

109131
with override_settings(DEBUG=True):
110132
registry.get_component("button")
111133

112-
assert len(registry._cache) == 0
134+
assert len(registry._components) == 0
113135

114136
registry.get_component("button")
115137

116-
assert len(registry._cache) == 1
138+
assert len(registry._components) == 1
139+
140+
def test_get_assets_by_type(
141+
self, registry, create_bird_template, create_bird_asset
142+
):
143+
template_file = create_bird_template("test", "<div>Test</div>")
144+
css_asset = create_bird_asset(template_file, ".test { color: red; }", "css")
145+
js_asset = create_bird_asset(template_file, "console.log('test');", "js")
146+
147+
registry.get_component(
148+
"test"
149+
) # This now registers the component and its assets
150+
151+
css_assets = registry.get_assets(AssetType.CSS)
152+
js_assets = registry.get_assets(AssetType.JS)
153+
154+
assert len(css_assets) == 1
155+
assert len(js_assets) == 1
156+
assert Asset(Path(css_asset), AssetType.CSS) in css_assets
157+
assert Asset(Path(js_asset), AssetType.JS) in js_assets
158+
159+
def test_multiple_components_same_asset_names(
160+
self, registry, create_bird_template, create_bird_asset
161+
):
162+
template1 = create_bird_template("comp1", "<div>One</div>", sub_dir="first")
163+
template2 = create_bird_template("comp2", "<div>Two</div>", sub_dir="second")
164+
165+
css1 = create_bird_asset(template1, ".one { color: red; }", "css")
166+
css2 = create_bird_asset(template2, ".two { color: blue; }", "css")
167+
168+
registry.get_component("first/comp1")
169+
registry.get_component("second/comp2")
170+
171+
css_assets = registry.get_assets(AssetType.CSS)
172+
assert len(css_assets) == 2
173+
174+
asset_paths = {str(asset.path) for asset in css_assets}
175+
assert str(css1) in asset_paths
176+
assert str(css2) in asset_paths
177+
178+
def test_template_inheritance_assets(
179+
self,
180+
registry,
181+
create_bird_template,
182+
create_bird_asset,
183+
create_template,
184+
templates_dir,
185+
):
186+
parent = create_bird_template("parent", "<div>Parent</div>")
187+
child = create_bird_template("child", "<div>Child</div>")
188+
189+
parent_css = create_bird_asset(parent, ".parent { color: red; }", "css")
190+
child_css = create_bird_asset(child, ".child { color: blue; }", "css")
191+
192+
base_path = templates_dir / "base.html"
193+
base_path.write_text("""
194+
{% bird parent %}Parent Content{% endbird %}
195+
{% block content %}{% endblock %}
196+
""")
197+
198+
child_path = templates_dir / "child.html"
199+
child_path.write_text("""
200+
{% extends 'base.html' %}
201+
{% block content %}
202+
{% bird child %}Child Content{% endbird %}
203+
{% endblock %}
204+
""")
205+
206+
# Pre-load the components so they're in the registry
207+
registry.get_component("parent")
208+
registry.get_component("child")
209+
210+
template = create_template(child_path)
211+
template.render({})
212+
213+
css_assets = registry.get_assets(AssetType.CSS)
214+
asset_paths = {str(asset.path) for asset in css_assets}
215+
216+
assert str(parent_css) in asset_paths, "Parent CSS not found in assets"
217+
assert str(child_css) in asset_paths, "Child CSS not found in assets"
218+
219+
def test_empty_registry(self, registry):
220+
assert len(registry._components) == 0
221+
assert len(registry.get_assets(AssetType.CSS)) == 0
222+
assert len(registry.get_assets(AssetType.JS)) == 0

tests/test_loader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
from django.template.engine import Engine
99
from django.template.loader import get_template
1010

11+
from django_bird.components import components
1112
from django_bird.loader import BIRD_TAG_PATTERN
1213
from django_bird.loader import BirdLoader
13-
from django_bird.staticfiles import assets
1414
from django_bird.templatetags.tags.bird import BirdNode
1515

1616

@@ -86,4 +86,4 @@ def test_scan_for_components(
8686

8787
loader._scan_for_components(node, context)
8888

89-
assert len(assets.components) == expected_count
89+
assert len(components._components) == expected_count

0 commit comments

Comments
 (0)