diff --git a/htmltools/_core.py b/htmltools/_core.py index e334f6e..ed4adbc 100644 --- a/htmltools/_core.py +++ b/htmltools/_core.py @@ -22,7 +22,13 @@ if sys.version_info >= (3, 8): from typing import TypedDict, SupportsIndex, Protocol, runtime_checkable, Literal else: - from typing_extensions import TypedDict, SupportsIndex, Protocol, runtime_checkable, Literal + from typing_extensions import ( + TypedDict, + SupportsIndex, + Protocol, + runtime_checkable, + Literal, + ) from packaging.version import Version @@ -32,6 +38,7 @@ _package_dir, # type: ignore _html_escape, # type: ignore _flatten, # type: ignore + hash_deterministic, ) __all__ = ( @@ -793,7 +800,19 @@ def _hoist_head_content( # Put at beginning of head, and other hoisted tags at the # end. This matters only if the tag starts out with some children. head.insert(0, Tag("meta", charset="utf-8")) + + # Add some metadata about the dependencies so that shiny.js' renderDependency + # logic knows not to re-render them. deps = x.get_dependencies() + if len(deps) > 0: + head.append( + Tag( + "script", + ";".join([d.name + "[" + str(d.version) + "]" for d in deps]), + type="application/html-dependencies", + ) + ) + head.extend( [ d.as_html_tags(lib_prefix=lib_prefix, include_version=include_version) @@ -1113,6 +1132,14 @@ def head_content(*args: TagChildArg) -> HTMLDependency: *args The content to place in the ````. + Note + ---- + If the same content, ``x``, is included in a document multiple times via + ``head_content(x)``, ``x`` will only appear once in the final HTML document's + ````. More often than not, this is desirable behavior, but if you need the + same content included multiple times, you can add some irrelevant/empty tags (e.g., + ``TagList(x, Tag("meta"))``) to make sure ``x`` is included multiple times. + Example ------- >>> from htmltools import * @@ -1132,7 +1159,7 @@ def head_content(*args: TagChildArg) -> HTMLDependency: head = TagList(*args) head_str = head.get_html_string() # Create unique ID to use as name - name = "headcontent_{:x}".format(abs(hash(head_str))) + name = "headcontent_" + hash_deterministic(head_str) return HTMLDependency(name=name, version="0.0", head=head) diff --git a/htmltools/_util.py b/htmltools/_util.py index 1564644..1569bd9 100644 --- a/htmltools/_util.py +++ b/htmltools/_util.py @@ -1,6 +1,7 @@ +import hashlib +import importlib import os import re -import importlib import tempfile from typing import ( Any, @@ -127,6 +128,13 @@ def _package_dir(package: str) -> str: return os.path.dirname(pkg_file) +def hash_deterministic(s: str) -> str: + """ + Returns a deterministic hash of the given string. + """ + return hashlib.sha1(s.encode('utf-8')).hexdigest() + + class _HttpServerInfo(NamedTuple): port: int thread: Thread diff --git a/tests/snapshots/snap_test_deps.py b/tests/snapshots/snap_test_deps.py index 6989a20..0ac3ae7 100644 --- a/tests/snapshots/snap_test_deps.py +++ b/tests/snapshots/snap_test_deps.py @@ -11,6 +11,7 @@ + @@ -23,6 +24,7 @@ + @@ -35,6 +37,7 @@ + @@ -49,6 +52,7 @@ + @@ -61,6 +65,7 @@ + diff --git a/tests/test_deps.py b/tests/test_deps.py index a00caf8..79d4c76 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -1,6 +1,3 @@ -import os -from tempfile import TemporaryDirectory -from typing import Union, Optional import textwrap from htmltools import * @@ -32,6 +29,7 @@ def test_dep_resolution(): + @@ -46,6 +44,7 @@ def test_dep_resolution(): + @@ -93,6 +92,7 @@ def test_append_deps(): + @@ -138,6 +138,7 @@ def fake_dep(**kwargs): + diff --git a/tests/test_html_document.py b/tests/test_html_document.py index 249bec1..fc58047 100644 --- a/tests/test_html_document.py +++ b/tests/test_html_document.py @@ -30,6 +30,7 @@ def test_html_document_html_input(): Title + abcd @@ -49,6 +50,7 @@ def test_html_document_html_input(): + abcd @@ -68,6 +70,7 @@ def test_html_document_html_input(): + abcd @@ -96,6 +99,7 @@ def test_html_document_head_hoisting(): + diff --git a/tests/test_jsx_tags.py b/tests/test_jsx_tags.py index a148654..bb88ff8 100644 --- a/tests/test_jsx_tags.py +++ b/tests/test_jsx_tags.py @@ -15,6 +15,7 @@ def test_jsx_tags(): + @@ -33,7 +34,7 @@ def test_jsx_tags(): """ - % (react_ver, react_dom_ver) + % (react_ver, react_dom_ver, react_ver, react_dom_ver) ) # Only the "top-level" tag gets wrapped in @@ -64,7 +66,7 @@ def test_jsx_tags(): """ - % (react_ver, react_dom_ver) + % (react_ver, react_dom_ver, react_ver, react_dom_ver) ) x = Foo( diff --git a/tests/test_tags.py b/tests/test_tags.py index 535774d..ed258e0 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -235,6 +235,7 @@ def test_html_save(): + @@ -253,6 +254,7 @@ def test_html_save(): +