Skip to content

Models & pagination

Bulletins

Search and lookup methods return Bulletin objects. The most specific model is chosen for each document: a known collection type selects a per-collection model, otherwise the bulletinFamily selects a family model, falling back to GenericBulletin. Per-collection models subclass their family model, which subclasses Bulletin, so family classes are a stable isinstance/annotation surface. Fields are accessed as attributes and are all optional (a missing field is None).

The family and per-collection models follow a base → family → type hierarchy — see Data models for every family and collection with its fields, descriptions and examples.

vulners._models.bulletin.Bulletin

Bases: VulnersModel

A single Vulners document; base for every bulletinFamily. Carries the fields present in every document. Family/type subclasses add their own; extra="allow" keeps any unmodelled field accessible.

vulners._models.bulletin.GenericBulletin

Bases: Bulletin

Fallback for any bulletinFamily without a dedicated model (forward-compat).

CVSS & nested objects

Value objects shared across families. Cvss specializes to Cvss2/Cvss3/Cvss4 by its version.

vulners._models.bulletin.Cvss

Bases: VulnersModel

CVSS score block; the base/fallback across scoring versions.

vulners._models.bulletin.Timestamps

Bases: VulnersModel

Lifecycle timestamps Vulners maintains for a document (ISO-8601 strings).

vulners._models.bulletin.Enchantments

Bases: VulnersModel

Vulners-computed enrichment layer over the raw document.

vulners._models.bulletin.EpssScore

Bases: VulnersModel

One EPSS (Exploit Prediction Scoring System) datapoint.

Package metadata

audit.metadata returns a PackageMetadata: a package's declared license (always a list), its version and the range the metadata covers. Use found to tell a package the registry does not know (empty range) apart from a known package with no recorded license.

vulners._models.audit.PackageMetadata

Bases: VulnersModel

License and version-range metadata for a single registry package.

Returned by :meth:vulners._resources._sync.audit.Audit.metadata. license is always a list — an empty list means a known package has no recorded license. Use :attr:found to tell that apart from a package name the registry does not know, which the endpoint answers (with HTTP 200) as an empty range.

name class-attribute instance-attribute

name: str | None = None

The package name echoed back by the registry.

version class-attribute instance-attribute

version: str | None = None

The queried package version.

range class-attribute instance-attribute

range: str | None = None

The version range this metadata covers; empty when the package name is unknown.

license class-attribute instance-attribute

license: list[str] | None = None

SPDX-style license identifiers; an empty list when the registry records none.

found property

found: bool

Whether the registry knows this package name.

The endpoint answers HTTP 200 even for an unknown name, returning an empty range; found is then False. A True value with an empty :attr:license means the package is known but has no recorded license.

Pagination

search.query returns a SearchPage (async: AsyncSearchPage). It knows its place in the result window and walks further pages when you iterate it.

vulners._pagination.SearchPage dataclass

SearchPage(data: list[T] = list(), total: int | None = None, offset: int = 0, limit: int = 0, fetch: SyncFetch[T] | None = None)

Bases: Generic[T]

One page of search results, aware of its place in the result window.

has_next_page

has_next_page() -> bool

Report whether another page can be fetched.

Returns:

Type Description
bool

True when a further page exists and its offset stays within the

bool

10 000-document search window; False otherwise, including when

bool

this page has no fetch callback or already reaches total.

Source code in src/vulners/_pagination.py
def has_next_page(self) -> bool:
    """Report whether another page can be fetched.

    Returns:
        ``True`` when a further page exists and its offset stays within the
        10 000-document search window; ``False`` otherwise, including when
        this page has no fetch callback or already reaches ``total``.
    """
    if self.fetch is None or _window_blocked(self.offset, self.limit):
        return False
    return not _short_page(len(self.data), self.limit, self.offset, self.total)

next_page

next_page() -> SearchPage[T]

Fetch the page after this one.

Returns:

Type Description
SearchPage[T]

The next :class:SearchPage, positioned limit documents further

SearchPage[T]

into the results.

Raises:

Type Description
SearchWindowExceeded

the next page would cross the 10 000-document result window; use the archive API to read further.

Source code in src/vulners/_pagination.py
def next_page(self) -> SearchPage[T]:
    """Fetch the page after this one.

    Returns:
        The next :class:`SearchPage`, positioned ``limit`` documents further
        into the results.

    Raises:
        SearchWindowExceeded: the next page would cross the 10 000-document
            result window; use the archive API to read further.
    """
    next_offset = self.offset + self.limit
    if _window_blocked(self.offset, self.limit):
        raise SearchWindowExceeded(
            f"cannot page past offset {SEARCH_WINDOW}: the search window is "
            "capped at 10000 documents. Use the archive API to retrieve more."
        )
    if self.fetch is None:
        raise RuntimeError("this page was not built with a fetch callback")
    return self.fetch(next_offset, self.limit)

vulners._pagination.AsyncSearchPage dataclass

AsyncSearchPage(data: list[T] = list(), total: int | None = None, offset: int = 0, limit: int = 0, fetch: AsyncFetch[T] | None = None)

Bases: Generic[T]

Async counterpart of :class:SearchPage.

has_next_page

has_next_page() -> bool

Report whether another page can be fetched.

Returns:

Type Description
bool

True when a further page exists and its offset stays within the

bool

10 000-document search window; False otherwise, including when

bool

this page has no fetch callback or already reaches total.

Source code in src/vulners/_pagination.py
def has_next_page(self) -> bool:
    """Report whether another page can be fetched.

    Returns:
        ``True`` when a further page exists and its offset stays within the
        10 000-document search window; ``False`` otherwise, including when
        this page has no fetch callback or already reaches ``total``.
    """
    if self.fetch is None or _window_blocked(self.offset, self.limit):
        return False
    return not _short_page(len(self.data), self.limit, self.offset, self.total)

next_page async

next_page() -> AsyncSearchPage[T]

Fetch the page after this one.

Returns:

Type Description
AsyncSearchPage[T]

The next :class:AsyncSearchPage, positioned limit documents

AsyncSearchPage[T]

further into the results.

Raises:

Type Description
SearchWindowExceeded

the next page would cross the 10 000-document result window; use the archive API to read further.

Source code in src/vulners/_pagination.py
async def next_page(self) -> AsyncSearchPage[T]:
    """Fetch the page after this one.

    Returns:
        The next :class:`AsyncSearchPage`, positioned ``limit`` documents
        further into the results.

    Raises:
        SearchWindowExceeded: the next page would cross the 10 000-document
            result window; use the archive API to read further.
    """
    next_offset = self.offset + self.limit
    if _window_blocked(self.offset, self.limit):
        raise SearchWindowExceeded(
            f"cannot page past offset {SEARCH_WINDOW}: the search window is "
            "capped at 10000 documents. Use the archive API to retrieve more."
        )
    if self.fetch is None:
        raise RuntimeError("this page was not built with a fetch callback")
    return await self.fetch(next_offset, self.limit)

Client configuration

The resolved, immutable configuration for a client instance is available as client.config.

vulners._config.ClientConfig dataclass

ClientConfig(api_key: SecretStr, base_url: URL, user_agent: str, timeout: Timeout = (lambda: DEFAULT_TIMEOUT)(), archive_timeout: Timeout = (lambda: ARCHIVE_TIMEOUT)(), max_retries: int = DEFAULT_MAX_RETRIES, connect_retries: int = DEFAULT_CONNECT_RETRIES, limits: Limits = (lambda: DEFAULT_LIMITS)(), max_rate_limit_wait: float = DEFAULT_MAX_RATE_LIMIT_WAIT, max_response_bytes: int | None = None, follow_redirects: bool = True, http2: bool = True, proxy: str | Proxy | None = None, verify: bool | str | SSLContext = True, trust_env: bool = True, before_request: tuple[Callable[..., Any], ...] = (), after_response: tuple[Callable[..., Any], ...] = (), on_error: tuple[Callable[..., Any], ...] = ())

Immutable resolved configuration for a client instance.

Copy-with-overrides via :meth:replace backs client.with_options(...).

timeout_for

timeout_for(profile: TimeoutProfile) -> Timeout

Select the timeout budget for a request profile.

Parameters:

Name Type Description Default
profile TimeoutProfile

"archive" for the extended read budget used by archive/bulk streaming downloads, or "default" for the standard per-request budget.

required

Returns:

Name Type Description
The Timeout

attr:archive_timeout for "archive", otherwise

Timeout

attr:timeout.

Source code in src/vulners/_config.py
def timeout_for(self, profile: TimeoutProfile) -> httpx.Timeout:
    """Select the timeout budget for a request profile.

    Args:
        profile: ``"archive"`` for the extended read budget used by
            archive/bulk streaming downloads, or ``"default"`` for the
            standard per-request budget.

    Returns:
        The :attr:`archive_timeout` for ``"archive"``, otherwise
        :attr:`timeout`.
    """
    return self.archive_timeout if profile == "archive" else self.timeout

replace

replace(**changes: object) -> ClientConfig

Return a copy of this config with selected fields overridden.

Backs client.with_options(...). The overrides are re-validated by __post_init__, so an invalid combination fails fast here rather than later inside httpx.

Parameters:

Name Type Description Default
**changes object

:class:ClientConfig field names mapped to new values; unspecified fields are carried over unchanged.

{}

Returns:

Type Description
ClientConfig

A new :class:ClientConfig; this instance is left unmodified.

Raises:

Type Description
ValueError

an override produces an invalid configuration (for example a negative max_retries or a non-HTTP base_url).

Source code in src/vulners/_config.py
def replace(self, **changes: object) -> ClientConfig:
    """Return a copy of this config with selected fields overridden.

    Backs ``client.with_options(...)``. The overrides are re-validated by
    ``__post_init__``, so an invalid combination fails fast here rather than
    later inside httpx.

    Args:
        **changes: :class:`ClientConfig` field names mapped to new values;
            unspecified fields are carried over unchanged.

    Returns:
        A new :class:`ClientConfig`; this instance is left unmodified.

    Raises:
        ValueError: an override produces an invalid configuration (for
            example a negative ``max_retries`` or a non-HTTP ``base_url``).
    """
    return dataclasses.replace(self, **changes)  # type: ignore[arg-type]