diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py
index fabbceb1..f36393c3 100644
--- a/tests/unit/test_configuration.py
+++ b/tests/unit/test_configuration.py
@@ -18,11 +18,19 @@
class TestVT(unittest.TestCase):
def test_sanitize_tag_name(self):
self.assertEqual(VT.sanitize_tag_name("content-node"), "content_node")
- self.assertEqual(VT.sanitize_tag_name("search-engine"), "search_engine")
+ self.assertEqual(VT.sanitize_tag_name("from"), "from_")
def test_restore_tag_name(self):
- self.assertEqual(VT.restore_tag_name("content_node"), "content-node")
- self.assertEqual(VT.restore_tag_name("search_engine"), "search-engine")
+ self.assertEqual(vt("content_node").restore_tag_name(), "content-node")
+ self.assertEqual(vt("search_engine").restore_tag_name(), "search-engine")
+
+ def test_restore_with_underscores(self):
+ self.assertEqual(
+ vt("content_node", replace_underscores=False).tag, "content_node"
+ )
+ self.assertEqual(
+ vt("search_engine", replace_underscores=False).tag, "search_engine"
+ )
def test_attrmap(self):
self.assertEqual(attrmap("_max_memory"), "max-memory")
@@ -39,7 +47,7 @@ def test_single_tag(self):
self.assertEqual(str(xml_output), '')
def test_nested_tags(self):
- nested_tag = VT("content", (VT("document", ()),), {"attr": "value"})
+ nested_tag = vt("content", attr="value")(vt("document"))
xml_output = to_xml(nested_tag, indent=False)
# Expecting nested tags with proper newlines and indentation
expected_output = ''
@@ -705,5 +713,114 @@ def test_document_expiry(self):
self.assertTrue(compare_xml(self.xml_schema, str(generated_xml)))
+class TestUnderscoreAttributes(unittest.TestCase):
+ def setUp(self):
+ self.xml_schema = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <strong>
+ </strong>
+
+ ...
+
+
+
+
+ 1
+
+
+
+
+
+
+
+ 2
+ 1000
+ 500
+ 300
+
+
+
+"""
+
+ def test_valid_config_from_string(self):
+ self.assertTrue(validate_services(self.xml_schema))
+
+ def test_generate_schema(self):
+ generated = services(
+ container(
+ search(),
+ document_api(),
+ document_processing(),
+ clients(
+ client(
+ certificate(file="security/clients.pem"),
+ id="mtls",
+ permissions="read,write",
+ ),
+ client(
+ token(id="colpalidemo_write"),
+ id="token_write",
+ permissions="read,write",
+ ),
+ client(
+ token(id="colpalidemo_read"),
+ id="token_read",
+ permissions="read",
+ ),
+ ),
+ config(
+ vt("tag")(
+ vt("bold")(
+ vt("open", ""),
+ vt("close", ""),
+ ),
+ vt("separator", "..."),
+ ),
+ name="container.qr-searchers",
+ ),
+ id="colpalidemo_container",
+ version="1.0",
+ ),
+ content(
+ redundancy("1"),
+ documents(document(type="pdf_page", mode="index")),
+ nodes(node(distribution_key="0", hostalias="node1")),
+ config(
+ vt("max_matches", "2", replace_underscores=False),
+ vt("length", "1000"),
+ vt("surround_max", "500", replace_underscores=False),
+ vt("min_length", "300", replace_underscores=False),
+ name="vespa.config.search.summary.juniperrc",
+ ),
+ id="colpalidemo_content",
+ version="1.0",
+ ),
+ version="1.0",
+ )
+ generated_xml = generated.to_xml()
+ # Validate against relaxng
+ print(self.xml_schema)
+ print(generated_xml)
+ self.assertTrue(validate_services(str(generated_xml)))
+ self.assertTrue(compare_xml(self.xml_schema, str(generated_xml)))
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/vespa/configuration/services.py b/vespa/configuration/services.py
index 115d399c..59142f75 100644
--- a/vespa/configuration/services.py
+++ b/vespa/configuration/services.py
@@ -154,6 +154,8 @@
"maxsize",
"clients",
"client",
+ "certificate",
+ "token",
]
# Fail if any tag is duplicated. Provide feedback of which tags are duplicated.
diff --git a/vespa/configuration/vt.py b/vespa/configuration/vt.py
index 74ba1265..ac3db08f 100644
--- a/vespa/configuration/vt.py
+++ b/vespa/configuration/vt.py
@@ -12,6 +12,7 @@
"for": "for_",
"time": "time_",
"io": "io_",
+ "from": "from_",
}
restore_reserved = {v: k for k, v in replace_reserved.items()}
@@ -25,19 +26,29 @@ def sanitize_tag_name(tag: str) -> str:
replaced = tag.replace("-", "_")
return replace_reserved.get(replaced, replaced)
- @staticmethod
- def restore_tag_name(tag: str) -> str:
- "Restore sanitized tag names back to the original names for XML generation"
- return restore_reserved.get(tag, tag).replace("_", "-")
-
- def __init__(self, tag: str, cs: tuple, attrs: dict = None, void_=False, **kwargs):
+ def __init__(
+ self,
+ tag: str,
+ cs: tuple,
+ attrs: dict = None,
+ void_=False,
+ replace_underscores: bool = True,
+ **kwargs,
+ ):
assert isinstance(cs, tuple)
self.tag = self.sanitize_tag_name(tag) # Sanitize tag name
self.children, self.attrs = cs, attrs or {}
self.void_ = void_
+ self.replace_underscores = replace_underscores
def __setattr__(self, k, v):
- if k.startswith("__") or k in ("tag", "children", "attrs", "void_"):
+ if k.startswith("__") or k in (
+ "tag",
+ "children",
+ "attrs",
+ "void_",
+ "replace_underscores",
+ ):
return super().__setattr__(k, v)
self.attrs[k.lstrip("_").replace("_", "-")] = v
@@ -53,6 +64,15 @@ def list(self):
def get(self, k, default=None):
return self.attrs.get(k.lstrip("_").replace("_", "-"), default)
+ def restore_tag_name(
+ self,
+ ) -> str:
+ "Restore sanitized tag names back to the original names for XML generation"
+ restored = restore_reserved.get(self.tag, self.tag)
+ if self.replace_underscores:
+ return restored.replace("_", "-")
+ return restored
+
def __repr__(self):
return f"{self.tag}({self.children},{self.attrs})"
@@ -120,11 +140,17 @@ def vt(
void_: bool = False,
attrmap: callable = attrmap,
valmap: callable = valmap,
+ replace_underscores: bool = True,
**kw,
):
"Create a VT structure with `tag`, `children` and `attrs`"
# NB! fastcore.xml uses tag.lower() for tag names. This is not done here.
- return VT(tag, *_preproc(c, kw, attrmap=attrmap, valmap=valmap), void_=void_)
+ return VT(
+ tag,
+ *_preproc(c, kw, attrmap=attrmap, valmap=valmap),
+ void_=void_,
+ replace_underscores=replace_underscores,
+ )
# XML void tags (self-closing)
@@ -178,7 +204,8 @@ def _to_xml(elm, lvl, indent, do_escape):
return f"{esc_fn(str(elm).strip())}{nl if indent else ''}"
tag, cs, attrs = elm.list
- stag = VT.restore_tag_name(tag)
+
+ stag = elm.restore_tag_name()
# Prepare the attribute string only once
attr_str = ""