"""CLI: provisions a new `Product` (a SowAI app consuming this service -- auto, crm, ...) and prints its PLAINTEXT API key exactly ONCE. Only the bcrypt hash of the key is ever persisted (`tenancy.models.Product`'s docstring) -- if the printed key is lost, the only recovery is provisioning a new Product (or, once key rotation exists, rotating it; not built yet). There is no HTTP endpoint for this on purpose (design spec, decision #5: API keys are per-product and provisioning one is an operator action, not self-service) -- this script is the ONLY writer of `products`. Run against the target DB via the app's own session config (DATABASE_URL env, same convention as `auto/backend/scripts/seed_dev_admin.py`): uv run python scripts/create_product.py --name auto """ import argparse import asyncio import secrets from fiscal_svc.core.db import async_session_maker from fiscal_svc.tenancy.service import create_product async def _main(name: str) -> None: # `secrets.token_urlsafe` (CSPRNG, not `random`) -- this string IS the # credential, same rigor as any password/token generated in this # codebase. 32 bytes of entropy before urlsafe-base64 encoding. api_key = secrets.token_urlsafe(32) async with async_session_maker() as session: product = await create_product(session, name=name, api_key=api_key) print(f"Product criado: {product.name} (id={product.id})") print() print("API key -- copie agora. Só o hash bcrypt fica no banco; esta é a") print("ÚNICA vez que o valor em claro é exibido:") print() print(api_key) def main() -> None: parser = argparse.ArgumentParser( description="Provisiona um novo Product (consumidor da API do sowai-fiscal-svc)." ) parser.add_argument("--name", required=True, help="Nome do produto SowAI (ex.: 'auto').") args = parser.parse_args() asyncio.run(_main(args.name)) if __name__ == "__main__": main()