* feat: v2.1.0 — fal platform utilities (async fan-out, cost tools, recovery) Six new nodes under FAL/Platform, built on fal's queue/request-id/pricing primitives: - Fal Submit + Fal Collect: queue jobs without blocking and collect results later — N generations run in parallel inside one graph - Fal Result by Request ID: re-fetch any past generation from fal's queue without re-generating or re-billing - Fal Cost Estimator: per-run cost report parsed from registry pricing before anything is queued - Fal Session Costs: in-memory ledger of every fal call this session (endpoint, duration, request id, est. cost) with running total - Fal Save Media from URL: persist expiring CDN results into the ComfyUI output directory Core: utils/pricing.py (pricing-text parser + registry lookup), utils/ledger.py (thread-safe session ledger), api.py gains submit_only, result_from_request_id, request-id capture via on_enqueue, and automatic ledger recording; any_endpoint helpers extracted for reuse. 16 new tests (61 total). * feat: v2.2.0 — persistent result cache, spend guard, URL passthrough Three fal-platform features on top of the 2.1 platform pack: - Persistent result cache (sqlite, [cache] config): identical fal calls are served free from disk across ComfyUI restarts; uploads are deduplicated by content hash so identical inputs upload once. Bypass per node via force_rerun. Cache hits are never blocked by the spend guard and never recorded as spend. - Spend Guard + billing ([spend_guard] config): preflight refuses to submit once the session's estimated spend reaches session_budget_usd or the account balance (fal Billing API) falls below min_balance_usd. New Fal Account Balance node; ledger reconciliation against the aggregated usage API (fal exposes no per-request billed amounts). - fal-CDN URL passthrough: every dynamic node's media inputs gain optional *_direct_url twins (URL sent directly, no download/re-upload; 1,405 twins across 992 nodes) and image-kind nodes output image_urls alongside IMAGE — fal→fal chains with zero local I/O. Review fixes: path traversal blocked in FalSaveMediaURL (confined to the output dir, atomic O_EXCL filename claim), FalAnyEndpoint force_rerun now bypasses the cache, cache lookup ordered before spend-guard preflight, result recovery no longer counts as new session spend. 14 new tests (76 total).
57 lines
1.4 KiB
Python
57 lines
1.4 KiB
Python
"""Unit tests for the session cost ledger."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import threading
|
|
|
|
import pytest
|
|
from conftest import PKG, _load_package
|
|
|
|
|
|
@pytest.fixture()
|
|
def ledger(pricing_mod=None):
|
|
_load_package()
|
|
mod = importlib.import_module(f"{PKG}.nodes.utils.ledger")
|
|
instance = mod.SessionLedger()
|
|
instance.reset()
|
|
yield instance
|
|
instance.reset()
|
|
|
|
|
|
def test_record_and_totals(ledger):
|
|
ledger.record("fal-ai/a", "req-1", 2.5, 0.10)
|
|
ledger.record("fal-ai/b", "req-2", 1.0, None)
|
|
entries = ledger.entries()
|
|
assert len(entries) == 2
|
|
assert ledger.total_cost() == pytest.approx(0.10)
|
|
assert ledger.unknown_cost_count() == 1
|
|
|
|
|
|
def test_report_mentions_calls(ledger):
|
|
ledger.record("fal-ai/kling-video/v3/pro/image-to-video", "abc123", 12.4, 0.35)
|
|
report = ledger.report()
|
|
assert "kling" in report
|
|
assert "abc123" in report
|
|
|
|
|
|
def test_reset(ledger):
|
|
ledger.record("fal-ai/a", None, 1.0, 0.5)
|
|
ledger.reset()
|
|
assert ledger.entries() == []
|
|
assert ledger.total_cost() == 0.0
|
|
|
|
|
|
def test_thread_safety(ledger):
|
|
def worker():
|
|
for _ in range(100):
|
|
ledger.record("fal-ai/t", None, 0.1, 0.01)
|
|
|
|
threads = [threading.Thread(target=worker) for _ in range(10)]
|
|
for t in threads:
|
|
t.start()
|
|
for t in threads:
|
|
t.join()
|
|
assert len(ledger.entries()) == 1000
|
|
assert ledger.total_cost() == pytest.approx(10.0)
|