Files
Gökay Aydoğan 75a4ce6324 Platform pack (#76)
* 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).
2026-07-02 14:54:07 +03:00

57 lines
1.7 KiB
Python

"""Unit tests for the registry pricing parser."""
from __future__ import annotations
import importlib
import pytest
from conftest import PKG, _load_package
@pytest.fixture(scope="session")
def pricing_mod():
_load_package()
return importlib.import_module(f"{PKG}.nodes.utils.pricing")
def test_run_ratio_wins_over_per_unit(pricing_mod):
text = (
"Your request will cost $0.08 per image. For $1.00, you can run "
"this model 12 times."
)
parsed = pricing_mod.PricingUtils.parse(text)
assert parsed["per_run"] == pytest.approx(1.0 / 12.0)
def test_per_unit_only(pricing_mod):
parsed = pricing_mod.PricingUtils.parse(
"Your request will cost $0.05 per second of video."
)
assert parsed["per_run"] is None
assert parsed["per_unit"] == pytest.approx(0.05)
assert "second" in parsed["unit"]
def test_per_image_implies_per_run(pricing_mod):
parsed = pricing_mod.PricingUtils.parse("Your request will cost $0.04 per image.")
assert parsed["per_run"] == pytest.approx(0.04)
def test_junk_never_raises(pricing_mod):
for junk in ("", "free during preview!!", "$", "per per per", None or ""):
parsed = pricing_mod.PricingUtils.parse(junk)
assert parsed["raw"] == junk
def test_estimate_against_real_registry(pricing_mod):
est = pricing_mod.PricingUtils.estimate("fal-ai/nano-banana-2/edit", 10)
assert est["runs"] == 10
report = pricing_mod.PricingUtils.format_report(est)
assert "fal-ai/nano-banana-2/edit" in report
def test_unknown_endpoint_safe(pricing_mod):
est = pricing_mod.PricingUtils.estimate("fal-ai/does-not-exist", 3)
report = pricing_mod.PricingUtils.format_report(est)
assert isinstance(report, str) and report