Source code for bootlace.endpoint

from collections.abc import Iterator
from collections.abc import Mapping
from typing import Any

import attrs
from flask import Blueprint
from flask import url_for

from bootlace.util import is_active_endpoint


@attrs.define(frozen=True, init=False)
class KeywordArguments(Mapping[str, Any]):
    """
    A mapping of keyword arguments for a URL endpoint,
    which is frozen and hashable for use as a key in a dictionary.
    """

    _arguments: frozenset[tuple[str, Any]]

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        arguments = frozenset(dict(*args, **kwargs).items())
        object.__setattr__(self, "_arguments", arguments)

    def as_dict(self) -> dict[str, Any]:
        return dict(self._arguments)

    def __getitem__(self, __key: str) -> Any:
        return self.as_dict()[__key]

    def __iter__(self) -> Iterator[str]:
        return iter((key for key, _ in self._arguments))

    def __len__(self) -> int:
        return len(self._arguments)

    def __repr__(self) -> str:
        return f"KeywordArguments({self.as_dict()!r})"


[docs] @attrs.define(frozen=True, repr=False, kw_only=True) class Endpoint: """An endpoint for a breadcrumb, as captured at registration""" #: The flask context, if any context: None | Blueprint = None #: The endpoint name name: str #: The keyword arguments for the endpoint used with url_for url_kwargs: KeywordArguments = attrs.field(factory=lambda: KeywordArguments(), converter=KeywordArguments) #: Whether to ignore the query string when checking for active status ignore_query: bool = True
[docs] @classmethod def from_name(cls, name: str, **kwargs: Any) -> "Endpoint": """Create an endpoint from a name and keyword arguments""" ignore_query = kwargs.pop("ignore_query", True) return cls(name=name, url_kwargs=KeywordArguments(**kwargs), ignore_query=ignore_query)
@property def full_name(self) -> str: """The full name of the endpoint""" if self.context is not None and "." not in self.name: return f"{self.context.name}.{self.name}" return self.name @property def blueprint(self) -> str | None: """The blueprint for the endpoint""" if "." in self.name: return ".".join(self.name.split(".")[:-1]) if self.context is not None: return self.context.name return None @property def url(self) -> str: """The URL for the endpoint""" return self.build()
[docs] def build(self, **kwds: Any) -> str: """Build the URL for the endpoint with additional keyword arguments""" if isinstance(self.context, Blueprint): name = f"{self.context.name}.{self.name}" return url_for(name, **self.url_kwargs, **kwds) return url_for(self.name, **self.url_kwargs, **kwds)
@property def active(self) -> bool: """Whether the endpoint is active""" return is_active_endpoint(self.full_name, self.url_kwargs, self.ignore_query) def __call__(self, **kwds: Any) -> str: return self.build(**kwds) def __repr__(self) -> str: parts = [] if self.context is not None: parts.append(f"{self.context.name:s}.{self.name:s}") else: parts.append(f"{self.name:s}") if self.url_kwargs: parts.append(f", {self.url_kwargs!r}") if not self.ignore_query: parts.append(", ignore_query=False") statement = ", ".join(parts) return f"Endpoint({statement})"
def convert_endpoint(endpoint: str | Endpoint) -> Endpoint: """Convert a string endpoint to an Endpoint object""" if isinstance(endpoint, str): return Endpoint.from_name(endpoint) return endpoint