Skip to content

lifecycle

beautyspot.lifecycle モジュールは、データの保持期間(Retention)の解析、および関数名に基づいたポリシーの解決を担います。 現在は 完全修飾名(module.qualname)を優先 して解決し、マッチしない場合のみ短い関数名にフォールバックします。

beautyspot.lifecycle

LifecyclePolicy

Manages data retention policies based on function names.

Source code in src/beautyspot/lifecycle.py
class LifecyclePolicy:
    """
    Manages data retention policies based on function names.
    """

    def __init__(
        self,
        rules: List[Rule],
        default_retention: Union[str, timedelta, int, float, None] = "30d",
    ):
        if isinstance(default_retention, _ForeverSentinel):
            raise ValidationError(
                "Retention.FOREVER cannot be used as default_retention. "
                "Use it on individual @spot.mark(retention=Retention.FOREVER) instead."
            )
        self.rules = rules
        self._default_retention = parse_retention(default_retention)

    def resolve(self, func_name: str) -> Optional[timedelta]:
        """
        Find the first matching rule for the given function name.
        Returns the retention timedelta, or the default retention if no match.
        """
        for rule in self.rules:
            if fnmatch.fnmatch(func_name, rule.pattern):
                return parse_retention(rule.retention)
        return self._default_retention

    def resolve_with_fallback(
        self, func_identifier: str, func_name: str
    ) -> Optional[timedelta]:
        """
        Resolve retention using the fully-qualified identifier first, then
        fall back to the short function name for backward compatibility.
        """
        for rule in self.rules:
            if fnmatch.fnmatch(func_identifier, rule.pattern):
                return parse_retention(rule.retention)

        for rule in self.rules:
            if fnmatch.fnmatch(func_name, rule.pattern):
                return parse_retention(rule.retention)

        return self._default_retention

    @classmethod
    def default(cls) -> "LifecyclePolicy":
        """Default policy: 30-day retention."""
        return cls(rules=[], default_retention="30d")

default() classmethod

Default policy: 30-day retention.

Source code in src/beautyspot/lifecycle.py
@classmethod
def default(cls) -> "LifecyclePolicy":
    """Default policy: 30-day retention."""
    return cls(rules=[], default_retention="30d")

resolve(func_name)

Find the first matching rule for the given function name. Returns the retention timedelta, or the default retention if no match.

Source code in src/beautyspot/lifecycle.py
def resolve(self, func_name: str) -> Optional[timedelta]:
    """
    Find the first matching rule for the given function name.
    Returns the retention timedelta, or the default retention if no match.
    """
    for rule in self.rules:
        if fnmatch.fnmatch(func_name, rule.pattern):
            return parse_retention(rule.retention)
    return self._default_retention

resolve_with_fallback(func_identifier, func_name)

Resolve retention using the fully-qualified identifier first, then fall back to the short function name for backward compatibility.

Source code in src/beautyspot/lifecycle.py
def resolve_with_fallback(
    self, func_identifier: str, func_name: str
) -> Optional[timedelta]:
    """
    Resolve retention using the fully-qualified identifier first, then
    fall back to the short function name for backward compatibility.
    """
    for rule in self.rules:
        if fnmatch.fnmatch(func_identifier, rule.pattern):
            return parse_retention(rule.retention)

    for rule in self.rules:
        if fnmatch.fnmatch(func_name, rule.pattern):
            return parse_retention(rule.retention)

    return self._default_retention

Retention

Constants for retention policies.

Attributes:

Name Type Description
INDEFINITE

ライフサイクルポリシーに委ねるデフォルト値 (None)。 ポリシーが設定されている場合はそのルールに従い、 未設定の場合は無期限保持となります。

FOREVER _ForeverSentinel

ライフサイクルポリシーを明示的にバイパスし、 このキャッシュエントリを常に無期限保持することを宣言します。 @spot.mark(retention=Retention.FOREVER) で使用します。

Source code in src/beautyspot/lifecycle.py
class Retention:
    """Constants for retention policies.

    Attributes:
        INDEFINITE: ライフサイクルポリシーに委ねるデフォルト値 (None)。
            ポリシーが設定されている場合はそのルールに従い、
            未設定の場合は無期限保持となります。
        FOREVER: ライフサイクルポリシーを明示的にバイパスし、
            このキャッシュエントリを常に無期限保持することを宣言します。
            ``@spot.mark(retention=Retention.FOREVER)`` で使用します。
    """

    INDEFINITE = None
    FOREVER: _ForeverSentinel = _FOREVER

Rule dataclass

A rule defining retention policy based on function name pattern.

Source code in src/beautyspot/lifecycle.py
@dataclass
class Rule:
    """
    A rule defining retention policy based on function name pattern.
    """

    pattern: str
    retention: Union[str, timedelta, int, None]

parse_retention(value)

Helper function to normalize retention specification to timedelta. None means 'indefinite' (defers to lifecycle policy). int is treated as 'seconds'.

Note

_ForeverSentinel (Retention.FOREVER) は本関数では処理しません。 呼び出し元 (Spot._calculate_expires_at) で事前にチェックしてください。

Source code in src/beautyspot/lifecycle.py
def parse_retention(
    value: RetentionSpec
) -> Optional[timedelta]:
    """
    Helper function to normalize retention specification to timedelta.
    None means 'indefinite' (defers to lifecycle policy).
    int is treated as 'seconds'.

    Note:
        ``_ForeverSentinel`` (``Retention.FOREVER``) は本関数では処理しません。
        呼び出し元 (``Spot._calculate_expires_at``) で事前にチェックしてください。
    """
    if value is None:
        return None

    if isinstance(value, timedelta):
        if value.total_seconds() <= 0:
            raise ValidationError(f"Retention timedelta must be positive, got {value}.")
        return value

    if isinstance(value, (int, float)):
        if value <= 0:
            raise ValidationError(
                f"Retention must be a positive number (seconds), got {value}."
            )
        return timedelta(seconds=value)

    if isinstance(value, str):
        match = _TIME_PATTERN.match(value)
        if not match:
            raise ValidationError(
                f"Invalid retention format: '{value}'. Use format like '7d', '12h', '30m', '10s'."
            )

        amount, unit = int(match.group(1)), match.group(2)
        if amount <= 0:
            raise ValidationError(
                f"Retention duration must be positive, got '{value}'."
            )
        if unit == "d":
            return timedelta(days=amount)
        elif unit == "h":
            return timedelta(hours=amount)
        elif unit == "m":
            return timedelta(minutes=amount)
        elif unit == "s":
            return timedelta(seconds=amount)

    raise ValidationError(
        f"Retention must be str, int, float, or timedelta, got {type(value)}"
    )

主要な構成要素

1. Retention 解決ロジック (parse_retention)

ユーザーが指定した多様な形式(文字列、整数、timedelta)を、内部で扱う timedelta オブジェクトへと正規化します。

  • サポートされる形式:
  • 文字列: "7d" (日), "12h" (時間), "30m" (分) の形式。
  • 整数: 秒単位として扱われます。
  • timedelta: そのまま利用されます。
  • None: Retention.INDEFINITE (無期限) として扱われます。

2. Rule クラス

特定の関数名パターンに対して適用する保持期間を定義するデータクラスです。

属性 説明
pattern str fnmatch 形式のワイルドカードパターン(例: api_*
retention Union[str, int, timedelta, None] 適用する保持期間

3. LifecyclePolicy クラス

ルールの集合を管理し、実行時に特定の関数名に対してどのルールを適用するかを決定(Resolve)します。

  • 解決アルゴリズム: rules リストの 先頭から順に パターンマッチングを行い、最初にマッチしたルールの保持期間を返します。
  • 適用順序: 完全修飾名(module.qualname)→ 短い関数名(func_name)の順でマッチングします。
  • デフォルト挙動: マッチするルールがない場合、またはルールリストが空の場合は Retention.INDEFINITE(無期限)が返されます。

型定義と定数

Retention クラス

  • INDEFINITE: None(無期限保持を明示するためのエイリアス)。

注意事項

  • パターンマッチング: 内部で fnmatch.fnmatch を使用しているため、大文字小文字の区別は OS のファイルシステムルールに依存する可能性があります。一貫性を保つため、関数名の命名規則をプロジェクト内で統一することを推奨します。
  • バリデーション: parse_retention は、不正なフォーマット(例: "1year""10s" ※現状秒単位の文字指定は未サポート)が渡された場合、beautyspot.exceptions.ValidationError を送出します。