Bound the Dell warranty sync per run

The sync route asked Dell about every pending service tag in one request.
bulk_lookup sends 100 tags per HTTP call at a 15 second timeout, so a
freshly-onboarded site was dozens of sequential calls inside a single web
request. A gunicorn worker timeout or a proxy would plausibly kill it first,
leaving the user with a failure after minutes and no way to tell what landed.
F3 released the pooled connection before the vendor call, which stopped one
sync from taking the app down, but did not make the request itself survivable.

Extract the sync into a service taking a per-run tag limit. The route now caps
at 200 tags - two Dell calls - and returns how many are still pending so the
caller can run it again. A flask warranty sync-dell command does the same work
from the command line for a large first-run backfill.

The chunk is sampled at random rather than taken off a stable order. A serial
Dell does not recognize never gains a warranty and so stays pending forever;
under a stable order those dead tags would occupy every chunk and the sync
would never reach the rest of the fleet.

Nothing runs this on a schedule. The operation is used a few times a year, so
a cron would idle-poll the warranty tables tens of thousands of times a year
for nothing and log a configuration failure on every run at sites without Dell
credentials.

The two existing sync tests patched get_provider on the route module; the
provider is now resolved in the service, so they patch it there.
This commit is contained in:
benjamin.baker
2026-09-19 14:21:41 -04:00
parent 530f6a60f8
commit 32f4ccdd48
6 changed files with 353 additions and 103 deletions

View File

@@ -10,12 +10,14 @@ import logging
from pathlib import Path
from typing import List, Dict, Optional, Type
import click
from flask import Flask, Blueprint
from shopdb.plugins.base import BasePlugin, PluginMeta
from .api import warranty_bp
from .models import Warranty, WarrantyAsset
from .services import CLI_SYNC_LIMIT
logger = logging.getLogger(__name__)
@@ -182,5 +184,44 @@ class WarrantyPlugin(BasePlugin):
"""
return ['warranty.view']
def get_cli_commands(self) -> List:
"""Return CLI commands for this plugin."""
@click.group('warranty')
def warrantycli():
"""Warranty plugin commands."""
pass
@warrantycli.command('sync-dell')
@click.option('--limit', default=None, type=int,
help=f'Tags to look up this run (default {CLI_SYNC_LIMIT}).')
@click.option('--all', 'recheck_all', is_flag=True,
help='Re-check assets that already have a dated warranty.')
def syncdell(limit, recheck_all):
"""Look up Dell warranties for a bounded chunk of pending assets.
Manual counterpart to the Dell sync button, for a large first-run
backfill where repeated clicking would be tedious. Nothing runs this
on a schedule. Each run takes one Dell call by default; assets that
already hold a dated warranty are skipped, so runs are cheap and
safely repeatable until `remaining` reaches zero.
"""
from flask import current_app
from .services import run_dell_sync, CLI_SYNC_LIMIT as default_limit
from .services import ProviderNotConfigured, WarrantyLookupError
with current_app.app_context():
try:
summary = run_dell_sync(limit=limit or default_limit,
recheck_all=recheck_all)
except (ProviderNotConfigured, WarrantyLookupError) as exc:
raise click.ClickException(str(exc))
click.echo(
f"Dell sync: {summary['tags']} tag(s) checked, "
f"{summary['matched']} matched, {summary['created']} added, "
f"{summary['updated']} updated, {summary['remaining']} remaining.")
return [warrantycli]
def init_app(self, app: Flask, db_instance) -> None:
logger.info(f"Warranty plugin initialized (v{self.meta.version})")