From ba40dc091b0be38f3d21b97ea5daefbbf6d32c5d Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Mon, 2 Feb 2026 19:46:25 +0100 Subject: [PATCH 1/6] Add serialization of missing Description The `` element in the XML configuration contains language-specific descriptions. It is used in the index pages of each product release. This PR adds the missing handling of this element. * Build `Description` model for `Manifest` * In `Deliverable`, create new methods (see below). They transform the `` element * `all_categories`: Return the categories of the deliverable. * `categories`: Return the categories from the product node. * `categories_from_root`: Return the categories from the root node. * For submodels of `Manifest`, add serialization method (e.g., for `lang` attribute) * Improve test cases * Update doc sources of auto API * Make `Category.rank` set automatically * Add translations to `Category.translations` --- changelog.d/155.feature.rst | 1 + .../models/deliverable/Deliverable.rst | 44 +++++++++ .../docbuild/models/manifest/Archive.rst | 6 ++ .../docbuild/models/manifest/Category.rst | 11 +++ .../models/manifest/CategoryTranslation.rst | 6 ++ .../docbuild/models/manifest/Description.rst | 17 ++++ src/docbuild/cli/cmd_metadata/metaprocess.py | 16 +++- src/docbuild/models/deliverable.py | 38 ++++++++ src/docbuild/models/manifest.py | 91 +++++++++++++++++- tests/models/test_deliverable.py | 75 ++++++++++++++- tests/models/test_manifest.py | 96 +++++++++++++++++++ 11 files changed, 392 insertions(+), 9 deletions(-) create mode 100644 changelog.d/155.feature.rst diff --git a/changelog.d/155.feature.rst b/changelog.d/155.feature.rst new file mode 100644 index 00000000..241a5500 --- /dev/null +++ b/changelog.d/155.feature.rst @@ -0,0 +1 @@ +Add serialization of missing :class:`~docserv.models.deliverable.Description`. diff --git a/docs/source/reference/_autoapi/docbuild/models/deliverable/Deliverable.rst b/docs/source/reference/_autoapi/docbuild/models/deliverable/Deliverable.rst index 8fa1cc6c..eda1bf9a 100644 --- a/docs/source/reference/_autoapi/docbuild/models/deliverable/Deliverable.rst +++ b/docs/source/reference/_autoapi/docbuild/models/deliverable/Deliverable.rst @@ -11,6 +11,50 @@ docbuild.models.deliverable.Deliverable deliverable. + .. py:property:: all_categories + :type: collections.abc.Generator[lxml.etree._Element, None, None] + + + Return the groups (formerly categories) of the deliverable. + + Yield all elements from the product and root node. + + :yield: The elements categories/category and category. + + + + .. py:property:: categories + :type: collections.abc.Generator[lxml.etree._Element, None, None] + + + Return the groups (formerly categories) from the product node. + + Yield all elements under the product were this deliverable belongs to. + + :yield: The elements categories/category and category. + + + + .. py:property:: categories_from_root + :type: collections.abc.Generator[lxml.etree._Element, None, None] + + + Return the groups (formerly categories) from the root node. + + Yield all elements under the root node. + + :yield: The elements categories/category and category. + + + + .. py:property:: desc + :type: collections.abc.Generator[lxml.etree._Element, None, None] + + + Return the from the product node. + + + .. py:property:: productid :type: str | None diff --git a/docs/source/reference/_autoapi/docbuild/models/manifest/Archive.rst b/docs/source/reference/_autoapi/docbuild/models/manifest/Archive.rst index d7eea114..48d5ac16 100644 --- a/docs/source/reference/_autoapi/docbuild/models/manifest/Archive.rst +++ b/docs/source/reference/_autoapi/docbuild/models/manifest/Archive.rst @@ -19,3 +19,9 @@ docbuild.models.manifest.Archive "zip": "/en-us/sles/16.0/sles-16.0-en-us.zip" } + + .. py:method:: serialize_lang(value: docbuild.models.language.LanguageCode, info: pydantic.SerializationInfo) -> str + + Serialize LanguageCode to a string like 'en-us'. + + diff --git a/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst b/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst index d9a58a3f..b85c5806 100644 --- a/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst +++ b/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst @@ -25,3 +25,14 @@ docbuild.models.manifest.Category ] } + + .. py:method:: from_xml_node(node: lxml.etree._Element) -> collections.abc.Generator[Self, None, None] + :classmethod: + + + Extract categories from a parent XML node. + + :param node: a node pointing to ```` + :yield: A :class:`Category` instance for each category found. + + diff --git a/docs/source/reference/_autoapi/docbuild/models/manifest/CategoryTranslation.rst b/docs/source/reference/_autoapi/docbuild/models/manifest/CategoryTranslation.rst index fc962eca..90c198ef 100644 --- a/docs/source/reference/_autoapi/docbuild/models/manifest/CategoryTranslation.rst +++ b/docs/source/reference/_autoapi/docbuild/models/manifest/CategoryTranslation.rst @@ -19,3 +19,9 @@ docbuild.models.manifest.CategoryTranslation "title": "About" } + + .. py:method:: serialize_lang(value: docbuild.models.language.LanguageCode, info: pydantic.SerializationInfo) -> str + + Serialize LanguageCode to a string like 'en-us'. + + diff --git a/docs/source/reference/_autoapi/docbuild/models/manifest/Description.rst b/docs/source/reference/_autoapi/docbuild/models/manifest/Description.rst index e7cac23b..941c0fef 100644 --- a/docs/source/reference/_autoapi/docbuild/models/manifest/Description.rst +++ b/docs/source/reference/_autoapi/docbuild/models/manifest/Description.rst @@ -19,3 +19,20 @@ docbuild.models.manifest.Description "description": "

The English description for a product.

" } + + .. py:method:: serialize_lang(value: docbuild.models.language.LanguageCode, info: pydantic.SerializationInfo) -> str + + Serialize LanguageCode to a string like 'en-us'. + + + + .. py:method:: from_xml_node(node: lxml.etree._Element) -> collections.abc.Generator[Self, None, None] + :classmethod: + + + Extract descriptions from a parent XML node. + + :param node: a node pointing to ```` + :yield: + + diff --git a/src/docbuild/cli/cmd_metadata/metaprocess.py b/src/docbuild/cli/cmd_metadata/metaprocess.py index 444fa8c4..c4e7108a 100644 --- a/src/docbuild/cli/cmd_metadata/metaprocess.py +++ b/src/docbuild/cli/cmd_metadata/metaprocess.py @@ -16,7 +16,7 @@ from ...constants import DEFAULT_DELIVERABLES from ...models.deliverable import Deliverable from ...models.doctype import Doctype -from ...models.manifest import Document, Manifest +from ...models.manifest import Category, Description, Document, Manifest from ...utils.contextmgr import PersistentOnErrorTemporaryDirectory, edit_json from ...utils.git import ManagedGitRepo from ..context import DocBuildContext @@ -338,25 +338,30 @@ def store_productdocset_json( for doctype, docset, files in collect_files_flat(doctypes, meta_cache_dir): # files: list[Path] + # TODO: Create a Deliverable object? product = doctype.product.value - stdout.print(f" > Processed group: {doctype} / {docset}") + stdout.print(f" ❱ Processed group: {doctype} / {docset}") # The XPath logic is encapsulated within the Doctype model productxpath = f"./{doctype.product_xpath_segment()}" productnode = stitchnode.find(productxpath) docsetxpath = f"./{doctype.docset_xpath_segment(docset)}" docsetnode = productnode.find(docsetxpath) + descriptions = Description.from_xml_node(productnode) + categories = Category.from_xml_node(productnode) manifest = Manifest( productname=productnode.find("name").text, acronym=product, version=docset, lifecycle=docsetnode.attrib.get("lifecycle") or "", + descriptions=descriptions, + categories=categories, # * hide-productname is False by default in the Manifest model - # * descriptions, categories, archives are empty lists by default + # * archives are empty lists by default ) for f in files: - stdout.print(f" {f}") + stdout.print(f" | {f.stem}") try: with (meta_cache_dir / f).open(encoding="utf-8") as fh: loaded_doc_data = json.load(fh) @@ -384,10 +389,13 @@ def store_productdocset_json( jsonfile = ( jsondir / f"{docset}.json" ) # e.g., /path/to/cache/product_id/docset_id.json + + # jsonfile.write_text(manifest.model_dump_json(indent=2, by_alias=True)) log.info( "Wrote merged metadata JSON for %s/%s => %s", product, docset, jsonfile ) + stdout.print(f" > Result: {jsonfile}") async def process( diff --git a/src/docbuild/models/deliverable.py b/src/docbuild/models/deliverable.py index 554acccc..cf563b25 100644 --- a/src/docbuild/models/deliverable.py +++ b/src/docbuild/models/deliverable.py @@ -1,5 +1,6 @@ """Module for defining the Deliverable model.""" +from collections.abc import Generator from dataclasses import dataclass, field from functools import cached_property from pathlib import Path @@ -28,6 +29,43 @@ class Deliverable: _product_node: etree._Element | None = field(repr=False, default=None) _meta: Metadata | None = None + @cached_property + def all_categories(self) -> Generator[etree._Element, None, None]: + """Return the groups (formerly categories) of the deliverable. + + Yield all elements from the product and root node. + + :yield: The elements categories/category and category. + """ + yield from self.categories + yield from self.categories_from_root + + @cached_property + def categories(self) -> Generator[etree._Element, None, None]: + """Return the groups (formerly categories) from the product node. + + Yield all elements under the product were this deliverable belongs to. + + :yield: The elements categories/category and category. + """ + yield from self.product_node.xpath("categories/category|category") + + @cached_property + def categories_from_root(self) -> Generator[etree._Element, None, None]: + """Return the groups (formerly categories) from the root node. + + Yield all elements under the root node. + + :yield: The elements categories/category and category. + """ + root = self.product_node.getroottree().getroot() + yield from root.xpath("categories/category|category") + + @cached_property + def desc(self) -> Generator[etree._Element, None, None]: + """Return the from the product node.""" + yield from self.product_node.xpath("desc") + @cached_property def productid(self) -> str | None: """Return the product ID.""" diff --git a/src/docbuild/models/manifest.py b/src/docbuild/models/manifest.py index 82f61c67..3e304608 100644 --- a/src/docbuild/models/manifest.py +++ b/src/docbuild/models/manifest.py @@ -1,14 +1,17 @@ """Pydantic models for the metadata manifest structure.""" +from collections.abc import Generator from datetime import date -from typing import Self +from typing import ClassVar, Self +from lxml import etree from pydantic import ( BaseModel, Field, SerializationInfo, field_serializer, field_validator, + # model_validator, ) from ..models.language import LanguageCode @@ -31,6 +34,36 @@ class Description(BaseModel): default: bool description: str + @field_serializer("lang") + def serialize_lang(self: Self, value: LanguageCode, info: SerializationInfo) -> str: + """Serialize LanguageCode to a string like 'en-us'.""" + return str(value) + + @classmethod + def from_xml_node( + cls: type[Self], node: etree._Element + ) -> Generator[Self, None, None]: + """Extract descriptions from a parent XML node. + + :param node: a node pointing to ```` + :yield: + """ + for n in node.xpath("desc"): + text = "".join( + f"<{child.tag}>{ + ' '.join( + x.strip() + for t in child.itertext() + for x in t.splitlines() + if x.strip() + ) + }" + for child in n.iterchildren() + if child.tag != "title" + ) + + yield cls(**{"default": False, **n.attrib}, description=text) + class CategoryTranslation(BaseModel): """Represents a translation for a category title. @@ -45,9 +78,14 @@ class CategoryTranslation(BaseModel): """ lang: LanguageCode - default: bool + default: bool = Field(default=False) title: str + @field_serializer("lang") + def serialize_lang(self: Self, value: LanguageCode, info: SerializationInfo) -> str: + """Serialize LanguageCode to a string like 'en-us'.""" + return str(value) + class Category(BaseModel): """Represents a category for a product/docset. @@ -67,9 +105,51 @@ class Category(BaseModel): } """ - id: str = Field(alias="categoryId") + _current_rank: ClassVar[int] = 0 + + @staticmethod + def _increment_rank() -> int: + """Increments the counter and returns the next value.""" + Category._current_rank += 1 + return Category._current_rank + + id: str = Field(serialization_alias="categoryId") + # Automatically called. Depends on the order of the XML element. + rank: int = Field(default_factory=_increment_rank) translations: list[CategoryTranslation] = Field(default_factory=list) + # @model_validator(mode="before") + # @classmethod + # def prevent_manual_rank(cls, data: Any) -> Any: + # """Prevent setting the rank attribute as it is set automatically.""" + # # If 'rank' is in the input data, it means the user tried to set it manually + # if isinstance(data, dict) and "rank" in data: + # raise ValueError( + # "The 'rank' field is automatic and cannot be set manually." + # ) + # return data + + @classmethod + def from_xml_node( + cls: type[Self], node: etree._Element + ) -> Generator[Self, None, None]: + """Extract categories from a parent XML node. + + :param node: a node pointing to ```` + :yield: A :class:`Category` instance for each category found. + """ + for cat in node.xpath("category|categories/category"): + langs = cat.xpath("language") + translations = [ + CategoryTranslation( + lang=lng.attrib.get("lang", "en-us"), + default=lng.attrib.get("default", False), + title=lng.attrib.get("title", ""), + ) + for lng in langs + ] + yield cls(id=cat.attrib.get("categoryid", ""), translations=translations) + class Archive(BaseModel): """Represents an archive (e.g., a ZIP file) for a product/docset. @@ -87,6 +167,11 @@ class Archive(BaseModel): default: bool zip: str + @field_serializer("lang") + def serialize_lang(self: Self, value: LanguageCode, info: SerializationInfo) -> str: + """Serialize LanguageCode to a string like 'en-us'.""" + return str(value) + class DocumentFormat(BaseModel): """Represents the available formats for a document. diff --git a/tests/models/test_deliverable.py b/tests/models/test_deliverable.py index 2fd527a9..f0051404 100644 --- a/tests/models/test_deliverable.py +++ b/tests/models/test_deliverable.py @@ -18,7 +18,11 @@ def node() -> etree._ElementTree: SUSE Linux Enterprise Server SLES - + + + + + 15 SP6 @@ -61,7 +65,12 @@ def node() -> etree._ElementTree: def get_deliverables(node, *, lang: str = "en-us"): """Get all deliverable elements from the XML node.""" yield from node.xpath( - f"/product/docset/builddocs/language[@lang={lang!r}]/deliverable", + ( + # Works for product nodes that is a root element and + # those that is under another element: + "(/product | /docservconfig/product)" + f"/docset/builddocs/language[@lang={lang!r}]/deliverable" + ), ) @@ -331,3 +340,65 @@ def test_deliverable_repr(firstnode: etree._Element): def test_deliverable_to_dict(firstnode: Deliverable): with pytest.raises(NotImplementedError): firstnode.to_dict() + + +def test_deliverable_group_from_product(node: etree._ElementTree): + deli = Deliverable(next(get_deliverables(node))) + assert len(list(deli.categories)) == 1 + + +def test_deliverable_group_from_root(): + doc = """ + + + + + + + + + + + maintenance/SLE15SP6 + + DC-SLES-administration + + + + + + + """ + node = etree.fromstring(doc, parser=None).getroottree() + deli = Deliverable(next(get_deliverables(node))) + assert len(list(deli.categories_from_root)) == 1 + + +def test_deliverable_all_groups(): + doc = """ + + + + + + + + + + + + + + maintenance/SLE15SP6 + + DC-SLES-administration + + + + + + + """ + node = etree.fromstring(doc, parser=None).getroottree() + deli = Deliverable(next(get_deliverables(node))) + assert len(list(deli.all_categories)) == 2 diff --git a/tests/models/test_manifest.py b/tests/models/test_manifest.py index ca958bd4..10adcedc 100644 --- a/tests/models/test_manifest.py +++ b/tests/models/test_manifest.py @@ -1,8 +1,13 @@ from datetime import date +from lxml import etree import pytest from docbuild.models.manifest import ( + Archive, + Category, + CategoryTranslation, + Description, Document, DocumentFormat, SingleDocument, @@ -119,3 +124,94 @@ def test_document_rank_coercion_and_serialization( serialized = doc.model_dump(by_alias=True) # rank has no alias, so its key is "rank" assert serialized["rank"] == expected_serialized + + +def test_description_serialize_lang() -> None: + """Test serialization of LanguageCode""" + desc = Description(lang="en-us", default=True, description="Test description") + serialized = desc.model_dump(by_alias=True) + assert serialized["lang"] == "en-us" + + +def test_category_translation_serialize_lang() -> None: + """Test serialization of LanguageCode in CategoryTranslation.""" + cat_trans = CategoryTranslation(lang="de-de", default=False, title="Test Titel") + serialized = cat_trans.model_dump() + assert serialized["lang"] == "de-de" + + +def test_category_from_xml_node() -> None: + """Test extraction of categories from an XML node.""" + doc = """ + + + + + + + + + + + + + + + """ + node = etree.fromstring(doc, parser=None) + # Reset class variable for predictable rank + Category._current_rank = 0 + models = list(Category.from_xml_node(node)) + + assert len(models) == 4 + + # Test first category + assert models[0].id == "cat1" + assert models[0].rank == 1 + assert len(models[0].translations) == 2 + assert models[0].translations[0].lang == "en-us" + assert models[0].translations[0].default is True + assert models[0].translations[0].title == "Category 1 EN" + + # Test category with missing categoryid attribute + assert models[3].id == "" + assert models[3].rank == 4 + assert models[3].translations[0].title == "No ID" + + +def test_category_rank() -> None: + # Just to be sure, we reset the current rank: + Category._current_rank = 0 + for idx, i in enumerate(["A", "B", "C"], 1): + cat = Category(id=i, translations=[]) + serizalized = cat.model_dump() + assert serizalized["rank"] == idx + + +def test_archive_serialize_lang() -> None: + """Test serialization of LanguageCode in Archive.""" + archive = Archive(lang="fr-fr", default=False, zip="test.zip") + serialized = archive.model_dump() + assert serialized["lang"] == "fr-fr" + + +def test_description_from_xml_node() -> None: + """Test extraction of descriptions from XML node""" + doc = """ + + Hello Title +

Hello Description

+
+ + + +
+ """ + node = etree.fromstring(doc, parser=None).getroottree() + model = next(iter(Description.from_xml_node(node))) + serialized = model.model_dump(by_alias=True) + assert serialized == { + "lang": "en-us", + "default": True, + "description": "

Hello Description

", + } From 31c2391faf01a7abc3529a7d44f6081f8c58b4e1 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 17 Feb 2026 08:45:11 +0100 Subject: [PATCH 2/6] Apply suggestion from @sushant-suse Co-authored-by: Sushant Gaurav --- .../reference/_autoapi/docbuild/models/manifest/Category.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst b/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst index b85c5806..8f6b1ac7 100644 --- a/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst +++ b/docs/source/reference/_autoapi/docbuild/models/manifest/Category.rst @@ -32,7 +32,7 @@ docbuild.models.manifest.Category Extract categories from a parent XML node. - :param node: a node pointing to ```` + :param node: a node pointing to ````. :yield: A :class:`Category` instance for each category found. From bb8eda161bf695a075ca2ba935777be35b7ec84d Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 17 Feb 2026 08:56:48 +0100 Subject: [PATCH 3/6] Implement reset_rank class method to set rank to 0 --- src/docbuild/models/manifest.py | 14 ++++---------- tests/models/test_manifest.py | 4 ++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/docbuild/models/manifest.py b/src/docbuild/models/manifest.py index 3e304608..420833b5 100644 --- a/src/docbuild/models/manifest.py +++ b/src/docbuild/models/manifest.py @@ -118,16 +118,10 @@ def _increment_rank() -> int: rank: int = Field(default_factory=_increment_rank) translations: list[CategoryTranslation] = Field(default_factory=list) - # @model_validator(mode="before") - # @classmethod - # def prevent_manual_rank(cls, data: Any) -> Any: - # """Prevent setting the rank attribute as it is set automatically.""" - # # If 'rank' is in the input data, it means the user tried to set it manually - # if isinstance(data, dict) and "rank" in data: - # raise ValueError( - # "The 'rank' field is automatic and cannot be set manually." - # ) - # return data + @classmethod + def reset_rank(cls: type[Self]) -> None: + """Reset the rank counter.""" + cls._current_rank = 0 @classmethod def from_xml_node( diff --git a/tests/models/test_manifest.py b/tests/models/test_manifest.py index 10adcedc..d8a2f745 100644 --- a/tests/models/test_manifest.py +++ b/tests/models/test_manifest.py @@ -160,7 +160,7 @@ def test_category_from_xml_node() -> None: """ node = etree.fromstring(doc, parser=None) # Reset class variable for predictable rank - Category._current_rank = 0 + Category.reset_rank() models = list(Category.from_xml_node(node)) assert len(models) == 4 @@ -181,7 +181,7 @@ def test_category_from_xml_node() -> None: def test_category_rank() -> None: # Just to be sure, we reset the current rank: - Category._current_rank = 0 + Category.reset_rank() for idx, i in enumerate(["A", "B", "C"], 1): cat = Category(id=i, translations=[]) serizalized = cat.model_dump() From 9ce46937ca17dbb92dbc0494e1bb6c182f6e3ddd Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 18 Feb 2026 12:41:10 +0100 Subject: [PATCH 4/6] Use Iterable instead of list as type annotation --- src/docbuild/models/manifest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/docbuild/models/manifest.py b/src/docbuild/models/manifest.py index 420833b5..62bacbe3 100644 --- a/src/docbuild/models/manifest.py +++ b/src/docbuild/models/manifest.py @@ -1,6 +1,6 @@ """Pydantic models for the metadata manifest structure.""" -from collections.abc import Generator +from collections.abc import Iterable from datetime import date from typing import ClassVar, Self @@ -303,10 +303,10 @@ class Manifest(BaseModel): version: str lifecycle: str | LifecycleFlag = Field(default=LifecycleFlag.unknown) hide_productname: bool = Field(default=False, alias="hide-productname") - descriptions: list[Description] = Field(default_factory=list) - categories: list[Category] = Field(default_factory=list) - documents: list[Document] = Field(default_factory=list) - archives: list[Archive] = Field(default_factory=list) + descriptions: Iterable[Description] = Field(default_factory=list) + categories: Iterable[Category] = Field(default_factory=list) + documents: Iterable[Document] = Field(default_factory=list) + archives: Iterable[Archive] = Field(default_factory=list) if __name__ == "__main__": # pragma: nocover From 1cbd4eb03f05703f67f81c9c8686d91347e2ba91 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 18 Feb 2026 12:41:55 +0100 Subject: [PATCH 5/6] Reset the rank --- src/docbuild/cli/cmd_metadata/metaprocess.py | 4 ++++ src/docbuild/models/manifest.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/docbuild/cli/cmd_metadata/metaprocess.py b/src/docbuild/cli/cmd_metadata/metaprocess.py index c4e7108a..cc517b5d 100644 --- a/src/docbuild/cli/cmd_metadata/metaprocess.py +++ b/src/docbuild/cli/cmd_metadata/metaprocess.py @@ -347,6 +347,7 @@ def store_productdocset_json( docsetxpath = f"./{doctype.docset_xpath_segment(docset)}" docsetnode = productnode.find(docsetxpath) descriptions = Description.from_xml_node(productnode) + categories = Category.from_xml_node(productnode) manifest = Manifest( @@ -396,6 +397,9 @@ def store_productdocset_json( "Wrote merged metadata JSON for %s/%s => %s", product, docset, jsonfile ) stdout.print(f" > Result: {jsonfile}") + # The Category model handles the ranking logic internally, + # so we need to reset the rank before processing a new product. + Category.reset_rank() async def process( diff --git a/src/docbuild/models/manifest.py b/src/docbuild/models/manifest.py index 62bacbe3..ebc812bf 100644 --- a/src/docbuild/models/manifest.py +++ b/src/docbuild/models/manifest.py @@ -1,6 +1,6 @@ """Pydantic models for the metadata manifest structure.""" -from collections.abc import Iterable +from collections.abc import Generator, Iterable from datetime import date from typing import ClassVar, Self From dcd35117b94b209a4fe199b49af15a977ea8fee8 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 18 Feb 2026 13:59:47 +0100 Subject: [PATCH 6/6] Use list again --- src/docbuild/models/manifest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/docbuild/models/manifest.py b/src/docbuild/models/manifest.py index ebc812bf..420833b5 100644 --- a/src/docbuild/models/manifest.py +++ b/src/docbuild/models/manifest.py @@ -1,6 +1,6 @@ """Pydantic models for the metadata manifest structure.""" -from collections.abc import Generator, Iterable +from collections.abc import Generator from datetime import date from typing import ClassVar, Self @@ -303,10 +303,10 @@ class Manifest(BaseModel): version: str lifecycle: str | LifecycleFlag = Field(default=LifecycleFlag.unknown) hide_productname: bool = Field(default=False, alias="hide-productname") - descriptions: Iterable[Description] = Field(default_factory=list) - categories: Iterable[Category] = Field(default_factory=list) - documents: Iterable[Document] = Field(default_factory=list) - archives: Iterable[Archive] = Field(default_factory=list) + descriptions: list[Description] = Field(default_factory=list) + categories: list[Category] = Field(default_factory=list) + documents: list[Document] = Field(default_factory=list) + archives: list[Archive] = Field(default_factory=list) if __name__ == "__main__": # pragma: nocover