You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/05/08 12:55:49 UTC

(tooling-trusted-releases) branch cle-typed-data-model created (now 361e15e7)

This is an automated email from the ASF dual-hosted git repository.

alitheg pushed a change to branch cle-typed-data-model
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


      at 361e15e7 #912/#914 - move CLE functions into dedicated data model

This branch includes the following new commits:

     new 361e15e7 #912/#914 - move CLE functions into dedicated data model

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org


(tooling-trusted-releases) 01/01: #912/#914 - move CLE functions into dedicated data model

Posted by ar...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alitheg pushed a commit to branch cle-typed-data-model
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit 361e15e703595b8cb52794fcde56c506dc6c9a54
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Fri May 8 13:55:31 2026 +0100

    #912/#914 - move CLE functions into dedicated data model
---
 atr/cle.py             | 238 +++++++++++++++++++++++++++----------------------
 atr/cle_lib.py         | 192 +++++++++++++++++++++++++++++++++++++++
 tests/unit/test_cle.py |  30 ++++---
 3 files changed, 341 insertions(+), 119 deletions(-)

diff --git a/atr/cle.py b/atr/cle.py
index bf5b2195..a6f5e719 100644
--- a/atr/cle.py
+++ b/atr/cle.py
@@ -15,10 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 
-"""ECMA-428 Common Lifecycle Enumeration document generator.
+"""ECMA-428 CLE document generator for ATR.
 
-Reads from `LifecycleEvent` rows and renders to ECMA-428 wire format.
-Internal event names map to spec event types via `_SPEC_TYPE`:
+Reads from `LifecycleEvent` rows and renders to ECMA-428 format
+via the `atr.cle_lib` data model. ATR-specific concerns (PURL identifier,
+Apache license, support policy id, cycle-name semver bounds) live here;
+the spec-level data model lives in `cle_lib`.
+
+Internal `LifecycleEventType` values map to spec event types as:
 
     release  -> released
     archive  -> endOfDistribution
@@ -30,33 +34,28 @@ Internal event names map to spec event types via `_SPEC_TYPE`:
 Per ECMA-428 section 7.9, a `withdrawn` event retracts the *publication*
 of a previously-emitted event - typically a correction. The withdrawn
 event itself and the targeted event both remain in the document; the
-withdrawal references its target via the `eventId` field. ATR mostly uses it
-for corrections to previously-published cycle dates (see
+withdrawal references its target via the `eventId` field. ATR mostly uses
+it for corrections to previously-published cycle dates (see
 policy.edit_cycle_dates).
 
-We don't emit endOfMarketing, supersededBy, or componentRenamed.
+We don't use endOfMarketing, supersededBy, or componentRenamed.
 """
 
 from __future__ import annotations
 
-import datetime
 from typing import TYPE_CHECKING, Any, Final
 
+import atr.cle_lib as cle_lib
 import atr.models.sql as sql
 
 if TYPE_CHECKING:
+    import datetime
     from collections.abc import Iterable
 
-CLE_SCHEMA_URL: Final[str] = "https://ecma-tc54.github.io/ECMA-428/cle.v1.0.0.schema.json"
+CLE_SCHEMA_URL: Final[str] = cle_lib.CLE_SCHEMA_URL
 
-_SPEC_TYPE: Final[dict[sql.LifecycleEventType, str]] = {
-    sql.LifecycleEventType.RELEASE: "released",
-    sql.LifecycleEventType.ARCHIVE: "endOfDistribution",
-    sql.LifecycleEventType.EOD: "endOfDevelopment",
-    sql.LifecycleEventType.EOS: "endOfSupport",
-    sql.LifecycleEventType.EOL: "endOfLife",
-    sql.LifecycleEventType.WITHDRAW: "withdrawn",
-}
+_SUPPORT_DEFAULT_DESCRIPTION: Final[str] = "Apache project community support"
+_SUPPORT_DEFAULT_ID: Final[str] = "default"
 
 
 def project_document(
@@ -92,54 +91,45 @@ def release_document(
     return _document(project, list(events), [release], now=now)
 
 
-def _document(
-    project: sql.Project,
-    events: list[sql.LifecycleEvent],
-    releases: list[sql.Release],
-    *,
-    now: datetime.datetime,
-) -> dict[str, Any]:
-    rendered_events = _events(project, events, releases)
-    # Document is "as of" the latest event's publication. Falls back to `now`
-    # when no events are present so the field is always populated.
-    updated_at = max((e.published for e in events if e.id is not None), default=now)
-    doc: dict[str, Any] = {
-        "$schema": CLE_SCHEMA_URL,
-        "identifier": _identifier(project),
-        "updatedAt": _iso(updated_at),
-        "events": rendered_events,
-    }
-    # Patch in the support information - currently static
-    if any(e["type"] in {"endOfDevelopment", "endOfSupport"} for e in rendered_events):
-        doc["definitions"] = {
+def _definitions_for(
+    events: list[cle_lib.CleEvent],
+) -> dict[str, list[cle_lib.SupportDefinition]] | None:
+    """Build the document's `definitions.support` block, if any event needs it.
+
+    The default support policy is currently the only one ATR emits.
+    """
+    if any(isinstance(e, cle_lib.EndOfDevelopmentEvent | cle_lib.EndOfSupportEvent) for e in events):
+        return {
             "support": [
-                {"id": "default", "description": "Apache project community support"},
-            ]
+                cle_lib.SupportDefinition(
+                    id=_SUPPORT_DEFAULT_ID,
+                    description=_SUPPORT_DEFAULT_DESCRIPTION,
+                ),
+            ],
         }
-    return doc
+    return None
 
 
-def _events(
+def _document(
     project: sql.Project,
     events: list[sql.LifecycleEvent],
     releases: list[sql.Release],
-) -> list[dict[str, Any]]:
-    """Render lifecycle event rows to ECMA-428 events.
-
-    Spec id is the db id. Output is sorted descending by id per § 6.2.
-    """
+    *,
+    now: datetime.datetime,
+) -> dict[str, Any]:
     releases_by_key = {r.key: r for r in releases}
     releases_by_cycle = _releases_by_cycle(releases)
-
-    emit = [event for event in events if event.id is not None]
-    emit.sort(key=lambda e: e.id or 0, reverse=True)
-
-    rendered: list[dict[str, Any]] = []
-    for event in emit:
-        rendered_event = _render_event(project, event, releases_by_key, releases_by_cycle)
-        rendered_event["id"] = event.id
-        rendered.append(rendered_event)
-    return rendered
+    # Spec id is the db id, so events without one can't be rendered.
+    cle_events = [
+        _to_cle_event(project, event, releases_by_key, releases_by_cycle) for event in events if event.id is not None
+    ]
+    doc = cle_lib.CleDocument.from_events(
+        identifier=_identifier(project),
+        events=cle_events,
+        definitions=_definitions_for(cle_events),
+        now=now,
+    )
+    return doc.to_dict()
 
 
 def _identifier(project: sql.Project) -> str:
@@ -152,15 +142,6 @@ def _identifier(project: sql.Project) -> str:
     return f"pkg:apache/{project.key}"
 
 
-def _iso(value: datetime.datetime) -> str:
-    """Render a UTC datetime as ISO 8601 with a Z suffix."""
-    if value.tzinfo is None:
-        value = value.replace(tzinfo=datetime.UTC)
-    else:
-        value = value.astimezone(datetime.UTC)
-    return value.isoformat().replace("+00:00", "Z")
-
-
 def _release_for(event: sql.LifecycleEvent, releases_by_key: dict[str, sql.Release]) -> sql.Release:
     """Resolve the release a version-scoped event refers to.
 
@@ -182,49 +163,6 @@ def _releases_by_cycle(releases: list[sql.Release]) -> dict[str, list[sql.Releas
     return grouped
 
 
-def _render_event(
-    project: sql.Project,
-    event: sql.LifecycleEvent,
-    releases_by_key: dict[str, sql.Release],
-    releases_by_cycle: dict[str, list[sql.Release]],
-) -> dict[str, Any]:
-    """Render one LifecycleEvent to its CLE spec dict shape. id is set by the caller."""
-    rendered: dict[str, Any] = {
-        "type": _SPEC_TYPE[event.event],
-        "effective": _iso(event.effective),
-        "published": _iso(event.published),
-    }
-
-    if event.event is sql.LifecycleEventType.RELEASE:
-        release = _release_for(event, releases_by_key)
-        rendered["version"] = release.version
-        rendered["license"] = "Apache-2.0"
-    elif event.event is sql.LifecycleEventType.ARCHIVE:
-        release = _release_for(event, releases_by_key)
-        rendered["versions"] = [{"range": _vers_literal(project, release.version)}]
-    elif event.event in (
-        sql.LifecycleEventType.EOD,
-        sql.LifecycleEventType.EOS,
-        sql.LifecycleEventType.EOL,
-    ):
-        if event.cycle_key is None:
-            raise ValueError(f"{event.event} event requires cycle_key")
-        cycle_name = event.cycle_key.removeprefix(f"{project.key}-")
-        cycle_releases = releases_by_cycle.get(event.cycle_key, [])
-        rendered["versions"] = [{"range": _vers_for_cycle(project, cycle_name, cycle_releases)}]
-        if event.event in (sql.LifecycleEventType.EOD, sql.LifecycleEventType.EOS):
-            rendered["supportId"] = "default"
-    elif event.event is sql.LifecycleEventType.WITHDRAW:
-        if event.target_event_id is None:
-            raise ValueError("withdraw event requires target_event_id")
-        rendered["eventId"] = event.target_event_id
-
-    if event.reference_urls:
-        rendered["references"] = list(event.reference_urls)
-
-    return rendered
-
-
 def _semver_bounds_for_cycle_name(name: str) -> tuple[str, str] | None:
     """Derive (floor, ceiling) semver bounds from a cycle name prefix.
 
@@ -254,6 +192,92 @@ def _semver_bounds_for_cycle_name(name: str) -> tuple[str, str] | None:
     return floor, ceiling
 
 
+def _to_cle_event(
+    project: sql.Project,
+    event: sql.LifecycleEvent,
+    releases_by_key: dict[str, sql.Release],
+    releases_by_cycle: dict[str, list[sql.Release]],
+) -> cle_lib.CleEvent:
+    """Convert a `sql.LifecycleEvent` row to a typed `cle_lib` event.
+
+    VERS ranges are resolved here from cycle membership and project version
+    method; `cle_lib` treats them as opaque strings.
+    """
+    if event.id is None:
+        raise ValueError("LifecycleEvent.id is required for CLE rendering")
+    references = list(event.reference_urls or [])
+
+    if event.event is sql.LifecycleEventType.RELEASE:
+        release = _release_for(event, releases_by_key)
+        return cle_lib.ReleasedEvent(
+            id=event.id,
+            effective=event.effective,
+            published=event.published,
+            references=references,
+            version=release.version,
+            license="Apache-2.0",
+        )
+
+    if event.event is sql.LifecycleEventType.ARCHIVE:
+        release = _release_for(event, releases_by_key)
+        return cle_lib.EndOfDistributionEvent(
+            id=event.id,
+            effective=event.effective,
+            published=event.published,
+            references=references,
+            versions=[_vers_literal(project, release.version)],
+        )
+
+    if event.event in (
+        sql.LifecycleEventType.EOD,
+        sql.LifecycleEventType.EOS,
+        sql.LifecycleEventType.EOL,
+    ):
+        if event.cycle_key is None:
+            raise ValueError(f"{event.event} event requires cycle_key")
+        cycle_name = event.cycle_key.removeprefix(f"{project.key}-")
+        cycle_releases = releases_by_cycle.get(event.cycle_key, [])
+        versions = [_vers_for_cycle(project, cycle_name, cycle_releases)]
+        if event.event is sql.LifecycleEventType.EOD:
+            return cle_lib.EndOfDevelopmentEvent(
+                id=event.id,
+                effective=event.effective,
+                published=event.published,
+                references=references,
+                versions=versions,
+                support_id=_SUPPORT_DEFAULT_ID,
+            )
+        if event.event is sql.LifecycleEventType.EOS:
+            return cle_lib.EndOfSupportEvent(
+                id=event.id,
+                effective=event.effective,
+                published=event.published,
+                references=references,
+                versions=versions,
+                support_id=_SUPPORT_DEFAULT_ID,
+            )
+        return cle_lib.EndOfLifeEvent(
+            id=event.id,
+            effective=event.effective,
+            published=event.published,
+            references=references,
+            versions=versions,
+        )
+
+    if event.event is sql.LifecycleEventType.WITHDRAW:
+        if event.target_event_id is None:
+            raise ValueError("withdraw event requires target_event_id")
+        return cle_lib.WithdrawnEvent(
+            id=event.id,
+            effective=event.effective,
+            published=event.published,
+            references=references,
+            event_id=event.target_event_id,
+        )
+
+    raise ValueError(f"unsupported lifecycle event type: {event.event}")
+
+
 def _vers_for_cycle(project: sql.Project, cycle_name: str, cycle_releases: list[sql.Release]) -> str:
     """Render a VERS range covering a cycle.
 
diff --git a/atr/cle_lib.py b/atr/cle_lib.py
new file mode 100644
index 00000000..8025e9f0
--- /dev/null
+++ b/atr/cle_lib.py
@@ -0,0 +1,192 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""ECMA-428 Common Lifecycle Enumeration data model.
+
+Typed Pydantic models for CLE documents and events, plus a dict
+renderer. Has no ATR-specific dependencies; Apache-specific concerns
+(PURL identifier, license string, support policy id) live in the adapter
+at `atr/cle.py`.
+
+VERS ranges (ECMA-427) are held as opaque strings here. Resolving cycle
+names to ranges or literal versions is an adapter responsibility.
+"""
+
+from __future__ import annotations
+
+import datetime
+from typing import TYPE_CHECKING, Annotated, Any, Final, Literal
+
+import pydantic
+
+if TYPE_CHECKING:
+    from collections.abc import Iterable
+
+CLE_SCHEMA_URL: Final[str] = "https://ecma-tc54.github.io/ECMA-428/cle.v1.0.0.schema.json"
+
+
+class _EventBase(pydantic.BaseModel):
+    """Fields shared by every CLE event type."""
+
+    id: int
+    effective: datetime.datetime
+    published: datetime.datetime
+    references: list[str] = pydantic.Field(default_factory=list)
+
+
+class EndOfDevelopmentEvent(_EventBase):
+    type: Literal["endOfDevelopment"] = "endOfDevelopment"
+    versions: list[str]
+    support_id: str
+
+
+class EndOfDistributionEvent(_EventBase):
+    type: Literal["endOfDistribution"] = "endOfDistribution"
+    versions: list[str]
+
+
+class EndOfLifeEvent(_EventBase):
+    type: Literal["endOfLife"] = "endOfLife"
+    versions: list[str]
+
+
+class EndOfSupportEvent(_EventBase):
+    type: Literal["endOfSupport"] = "endOfSupport"
+    versions: list[str]
+    support_id: str
+
+
+class ReleasedEvent(_EventBase):
+    type: Literal["released"] = "released"
+    version: str
+    license: str | None = None
+
+
+class WithdrawnEvent(_EventBase):
+    type: Literal["withdrawn"] = "withdrawn"
+    event_id: int
+    reason: str | None = None
+
+
+CleEvent = Annotated[
+    EndOfDevelopmentEvent
+    | EndOfDistributionEvent
+    | EndOfLifeEvent
+    | EndOfSupportEvent
+    | ReleasedEvent
+    | WithdrawnEvent,
+    pydantic.Field(discriminator="type"),
+]
+
+
+class SupportDefinition(pydantic.BaseModel):
+    id: str
+    description: str
+    url: str | None = None
+
+
+class CleDocument(pydantic.BaseModel):
+    """
+    A CLE document as defined by ECMA-428.
+
+    `identifier` may be a single PURL or an array of aliases for the same
+    component.
+    """
+
+    schema_url: str = CLE_SCHEMA_URL
+    identifier: str | list[str]
+    updated_at: datetime.datetime
+    events: list[CleEvent] = pydantic.Field(default_factory=list)
+    definitions: dict[str, list[SupportDefinition]] | None = None
+
+    @classmethod
+    def from_events(
+        cls,
+        *,
+        identifier: str | list[str],
+        events: Iterable[CleEvent],
+        definitions: dict[str, list[SupportDefinition]] | None = None,
+        now: datetime.datetime,
+    ) -> CleDocument:
+        """
+        Build a document from a flat iterable of events.
+
+        `updated_at` is taken from the latest `published` across the events,
+        falling back to `now` so the field is always populated.
+        """
+        event_list = list(events)
+        updated = max((e.published for e in event_list), default=now)
+        return cls(
+            identifier=identifier,
+            updated_at=updated,
+            events=event_list,
+            definitions=definitions,
+        )
+
+    def to_dict(self) -> dict[str, Any]:
+        """Render to a dict."""
+        # Per ECMA-428 § 6.2, events emit in descending id order.
+        sorted_events = sorted(self.events, key=lambda e: e.id, reverse=True)
+        doc: dict[str, Any] = {
+            "$schema": self.schema_url,
+            "identifier": self.identifier,
+            "updatedAt": _iso(self.updated_at),
+            "events": [event_to_dict(e) for e in sorted_events],
+        }
+        if self.definitions:
+            doc["definitions"] = {
+                key: [d.model_dump(exclude_none=True) for d in defs] for key, defs in self.definitions.items()
+            }
+        return doc
+
+
+def event_to_dict(event: CleEvent) -> dict[str, Any]:
+    """Render one event to a dict."""
+    rendered: dict[str, Any] = {
+        "id": event.id,
+        "type": event.type,
+        "effective": _iso(event.effective),
+        "published": _iso(event.published),
+    }
+
+    if isinstance(event, ReleasedEvent):
+        rendered["version"] = event.version
+        if event.license is not None:
+            rendered["license"] = event.license
+    elif isinstance(event, EndOfDevelopmentEvent | EndOfSupportEvent):
+        rendered["versions"] = [{"range": v} for v in event.versions]
+        rendered["supportId"] = event.support_id
+    elif isinstance(event, EndOfLifeEvent | EndOfDistributionEvent):
+        rendered["versions"] = [{"range": v} for v in event.versions]
+    elif isinstance(event, WithdrawnEvent):
+        rendered["eventId"] = event.event_id
+        if event.reason is not None:
+            rendered["reason"] = event.reason
+
+    if event.references:
+        rendered["references"] = list(event.references)
+
+    return rendered
+
+
+def _iso(value: datetime.datetime) -> str:
+    """Render a UTC datetime as ISO 8601 with a Z suffix."""
+    if value.tzinfo is None:
+        value = value.replace(tzinfo=datetime.UTC)
+    else:
+        value = value.astimezone(datetime.UTC)
+    return value.isoformat().replace("+00:00", "Z")
diff --git a/tests/unit/test_cle.py b/tests/unit/test_cle.py
index 99ff6540..1f8745c6 100644
--- a/tests/unit/test_cle.py
+++ b/tests/unit/test_cle.py
@@ -21,6 +21,7 @@ from types import SimpleNamespace
 import pytest
 
 import atr.cle as cle
+import atr.cle_lib as cle_lib
 import atr.models.sql as sql
 
 
@@ -33,7 +34,7 @@ def _event(
     cycle_key: str | None = None,
     reference_urls: list[str] | None = None,
     target_event_id: int | None = None,
-    row_id: int | None = None,
+    row_id: int | None = 1,
 ) -> SimpleNamespace:
     return SimpleNamespace(
         id=row_id,
@@ -47,6 +48,11 @@ def _event(
     )
 
 
+def _render(project, event, releases_by_key, releases_by_cycle):
+    typed = cle._to_cle_event(project, event, releases_by_key, releases_by_cycle)
+    return cle_lib.event_to_dict(typed)
+
+
 def test_identifier_renders_apache_purl():
     """
     Note: this may well be updated later as there's ongoing discussion of the right pURL to use.
@@ -65,7 +71,7 @@ def test_render_event_release_emits_version_and_separate_dates():
         version_key="foo-1.2.3",
         cycle_key="foo-default",
     )
-    rendered = cle._render_event(project, event, {"foo-1.2.3": rel}, {"foo-default": [rel]})
+    rendered = _render(project, event, {"foo-1.2.3": rel}, {"foo-default": [rel]})
     assert rendered["type"] == "released"
     assert rendered["effective"] == "2026-01-01T00:00:00Z"
     assert rendered["published"] == "2025-12-01T00:00:00Z"
@@ -80,7 +86,7 @@ def test_render_event_release_raises_when_version_key_missing():
         effective=datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC),
     )
     with pytest.raises(ValueError, match="requires version_key"):
-        cle._render_event(project, event, {}, {})
+        _render(project, event, {}, {})
 
 
 def test_render_event_release_raises_when_release_unknown():
@@ -91,7 +97,7 @@ def test_render_event_release_raises_when_release_unknown():
         version_key="foo-1.0.0",
     )
     with pytest.raises(ValueError, match="references unknown release"):
-        cle._render_event(project, event, {}, {})
+        _render(project, event, {}, {})
 
 
 def test_render_event_archive_emits_versions_range():
@@ -103,7 +109,7 @@ def test_render_event_archive_emits_versions_range():
         version_key="foo-1.0.0",
         cycle_key="foo-default",
     )
-    rendered = cle._render_event(project, event, {"foo-1.0.0": rel}, {"foo-default": [rel]})
+    rendered = _render(project, event, {"foo-1.0.0": rel}, {"foo-default": [rel]})
     assert rendered["type"] == "endOfDistribution"
     assert rendered["versions"] == [{"range": "vers:generic/1.0.0"}]
 
@@ -119,7 +125,7 @@ def test_render_event_eod_uses_cycle_releases_for_range():
         effective=datetime.datetime(2026, 6, 1, tzinfo=datetime.UTC),
         cycle_key="foo-default",
     )
-    rendered = cle._render_event(project, event, {r.key: r for r in rels}, {"foo-default": rels})
+    rendered = _render(project, event, {r.key: r for r in rels}, {"foo-default": rels})
     assert rendered["type"] == "endOfDevelopment"
     assert rendered["versions"] == [{"range": "vers:generic/1.0.0|1.1.0"}]
     assert rendered["supportId"] == "default"
@@ -132,7 +138,7 @@ def test_render_event_eos_uses_wildcard_for_empty_cycle():
         effective=datetime.datetime(2026, 6, 1, tzinfo=datetime.UTC),
         cycle_key="foo-default",
     )
-    rendered = cle._render_event(project, event, {}, {})
+    rendered = _render(project, event, {}, {})
     assert rendered["type"] == "endOfSupport"
     assert rendered["versions"] == [{"range": "vers:generic/*"}]
     assert rendered["supportId"] == "default"
@@ -145,7 +151,7 @@ def test_render_event_eol_omits_support_id():
         effective=datetime.datetime(2027, 6, 1, tzinfo=datetime.UTC),
         cycle_key="foo-default",
     )
-    rendered = cle._render_event(project, event, {}, {})
+    rendered = _render(project, event, {}, {})
     assert rendered["type"] == "endOfLife"
     assert "supportId" not in rendered
 
@@ -157,7 +163,7 @@ def test_render_event_eol_raises_when_cycle_key_missing():
         effective=datetime.datetime(2026, 6, 1, tzinfo=datetime.UTC),
     )
     with pytest.raises(ValueError, match="requires cycle_key"):
-        cle._render_event(project, event, {}, {})
+        _render(project, event, {}, {})
 
 
 def test_render_event_includes_references_when_set():
@@ -170,7 +176,7 @@ def test_render_event_includes_references_when_set():
         cycle_key="foo-default",
         reference_urls=["https://lists.apache.org/thread/abc", "https://example.com/announce"],
     )
-    rendered = cle._render_event(project, event, {"foo-1.0.0": rel}, {"foo-default": [rel]})
+    rendered = _render(project, event, {"foo-1.0.0": rel}, {"foo-default": [rel]})
     assert rendered["references"] == [
         "https://lists.apache.org/thread/abc",
         "https://example.com/announce",
@@ -186,7 +192,7 @@ def test_render_event_omits_references_when_empty():
         version_key="foo-1.0.0",
         cycle_key="foo-default",
     )
-    rendered = cle._render_event(project, event, {"foo-1.0.0": rel}, {"foo-default": [rel]})
+    rendered = _render(project, event, {"foo-1.0.0": rel}, {"foo-default": [rel]})
     assert "references" not in rendered
 
 
@@ -238,7 +244,7 @@ def test_render_event_eol_for_semver_uses_derived_range():
         effective=datetime.datetime(2030, 1, 1, tzinfo=datetime.UTC),
         cycle_key="foo-2.x",
     )
-    rendered = cle._render_event(project, event, {}, {})
+    rendered = _render(project, event, {}, {})
     assert rendered["versions"] == [{"range": "vers:semver/>=2.0.0|<3.0.0"}]
 
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org