mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
Prevent invalid flight queries from bootstrapping runtime
Validate flight-ticket CLI inputs before initializing the fast-flights runtime so bad dates, same-airport routes, and impossible ranges fail quickly without network or environment-dependent setup. Constraint: PR #224 adds a crawler-style skill whose helper must handle invalid user input conservatively before external provider access. Rejected: Add broad round-trip comparison support | outside the minimal merge-blocking correctness fix. Confidence: high Scope-risk: narrow Directive: Keep provider/runtime initialization after argparse help and local input validation. Tested: python3 -m py_compile flight-ticket-search/scripts/flight_ticket_search.py; CLI help and invalid-input smoke checks; ./scripts/validate-skills.sh; npm run typecheck; npm run lint; npm run test Not-tested: Live Google Flights search because local Homebrew Python has a pyexpat/libexpat linkage failure during dependency bootstrap. Co-authored-by: OmX <omx@oh-my-codex.dev>
This commit is contained in:
parent
7e89bc3647
commit
25db06795e
1 changed files with 62 additions and 3 deletions
|
|
@ -126,6 +126,13 @@ def nonnegative_int(value: str) -> int:
|
|||
return parsed
|
||||
|
||||
|
||||
def nonnegative_float(value: str) -> float:
|
||||
parsed = float(value)
|
||||
if parsed < 0:
|
||||
raise argparse.ArgumentTypeError("must be zero or a positive number")
|
||||
return parsed
|
||||
|
||||
|
||||
def iter_dates(start: date, end: date, step_days: int) -> Iterable[date]:
|
||||
d = start
|
||||
while d <= end:
|
||||
|
|
@ -167,13 +174,14 @@ def build_query_url(flight_data: list[Any], trip: str, adults: int, seat: str) -
|
|||
|
||||
|
||||
def make_flight_data(from_airport: str, to_airport: str, outbound: str, return_date: str | None = None) -> tuple[list[Any], str]:
|
||||
from fast_flights import FlightData
|
||||
|
||||
origin = validate_airport(from_airport, "from")
|
||||
dest = validate_airport(to_airport, "to")
|
||||
if origin == dest:
|
||||
raise SystemExit("from and to airports must be different")
|
||||
outbound_date = parse_date(outbound)
|
||||
|
||||
from fast_flights import FlightData
|
||||
|
||||
data = [FlightData(date=outbound_date.isoformat(), from_airport=origin, to_airport=dest)]
|
||||
if return_date:
|
||||
inbound_date = parse_date(return_date)
|
||||
|
|
@ -234,6 +242,56 @@ def summarize_result(res: Any, query_url: str, limit: int) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
|
||||
def validate_date_text(value: str, field: str) -> date:
|
||||
try:
|
||||
return parse_date(value)
|
||||
except ValueError as exc:
|
||||
raise SystemExit(f"{field} must be YYYY-MM-DD, got: {value!r}") from exc
|
||||
|
||||
|
||||
def validate_month_text(value: str) -> None:
|
||||
try:
|
||||
datetime.strptime(value + "-01", "%Y-%m-%d")
|
||||
except ValueError as exc:
|
||||
raise SystemExit(f"month must be YYYY-MM, got: {value!r}") from exc
|
||||
|
||||
|
||||
def validate_month_day_text(value: str) -> None:
|
||||
try:
|
||||
datetime.strptime("2000-" + value, "%Y-%m-%d")
|
||||
except ValueError as exc:
|
||||
raise SystemExit(f"month-day must be MM-DD, got: {value!r}") from exc
|
||||
|
||||
|
||||
def preflight_validate_args(args: argparse.Namespace) -> None:
|
||||
validate_airport(args.from_airport, "from")
|
||||
validate_airport(args.to_airport, "to")
|
||||
if args.from_airport.strip().upper() == args.to_airport.strip().upper():
|
||||
raise SystemExit("from and to airports must be different")
|
||||
|
||||
if args.command == "search":
|
||||
outbound = validate_date_text(args.date, "date")
|
||||
if args.return_date:
|
||||
inbound = validate_date_text(args.return_date, "return-date")
|
||||
if inbound < outbound:
|
||||
raise SystemExit("return-date must be on or after date")
|
||||
elif args.command == "compare-month":
|
||||
validate_month_text(args.month)
|
||||
elif args.command == "compare-range":
|
||||
start = validate_date_text(args.start_date, "start-date")
|
||||
end = validate_date_text(args.end_date, "end-date")
|
||||
if end < start:
|
||||
raise SystemExit("end-date must be on or after start-date")
|
||||
elif args.command == "compare-years":
|
||||
validate_month_day_text(args.month_day)
|
||||
try:
|
||||
years = [int(x) for x in re.split(r"[, ]+", args.years.strip()) if x]
|
||||
except ValueError as exc:
|
||||
raise SystemExit("years must be comma-separated numbers, e.g. 2026,2027") from exc
|
||||
if not years:
|
||||
raise SystemExit("years is required, e.g. 2026,2027")
|
||||
|
||||
def command_search(args: argparse.Namespace) -> dict[str, Any]:
|
||||
data, trip = make_flight_data(args.from_airport, args.to_airport, args.date, args.return_date)
|
||||
res = fetch_flights(data, trip, args.adults, args.seat)
|
||||
|
|
@ -385,7 +443,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
sp.add_argument("--adults", type=positive_int, default=1)
|
||||
sp.add_argument("--seat", choices=["economy", "premium-economy", "business", "first"], default="economy")
|
||||
sp.add_argument("--limit", type=positive_int, default=5)
|
||||
sp.add_argument("--sleep", type=float, default=1.5, help="seconds between comparison queries")
|
||||
sp.add_argument("--sleep", type=nonnegative_float, default=1.5, help="seconds between comparison queries")
|
||||
sp.add_argument("--format", choices=["json", "markdown"], default="markdown")
|
||||
|
||||
s = sub.add_parser("search", help="single one-way or round-trip search")
|
||||
|
|
@ -421,6 +479,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
def main() -> None:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
preflight_validate_args(args)
|
||||
ensure_runtime()
|
||||
if getattr(args, "max_dates", 0) == 0:
|
||||
args.max_dates = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue