Validate an inventory CSV with Python's standard library

A CSV file can be readable and still contain bad inventory data. This small validator checks one explicit schema before an import: sku,quantity,unit_price. It only reads the input. It does not repair records, update stock or calculate totals.

Run the sample

Use Python 3 from this folder; no packages need installing:

    python validate_csv.py valid_inventory.csv

Actual output:

    VALID: inventory passed all checks

The successful exit code is 0. Now run:

    python validate_csv.py invalid_inventory.csv

Actual output:

    INVALID: 8 problem(s)
    - line 2: quantity must be a positive integer (ASCII digits)
    - line 2: unit_price must be finite, nonnegative and have at most 2 fractional digits
    - line 3: duplicate SKU 'BOLT-8' (case-sensitive)
    - line 3: unit_price must be finite, nonnegative and have at most 2 fractional digits
    - line 4: quantity must be a positive integer (ASCII digits)
    - line 4: unit_price must be finite, nonnegative and have at most 2 fractional digits
    - line 5: expected 3 fields, got 2 (missing 1)
    - line 6: expected 3 fields, got 4 (extra 1)

The invalid file exits with code 1. Missing/unreadable files and usage errors exit with code 2. Validation errors go to standard output; file-reading errors go to standard error. The two CSV files contain invented inventory.

How the checks work

validate_csv(stream) returns a list of error messages. The command-line main() opens a UTF-8 file in read mode with newline="" so the csv module controls newline handling.

csv.reader(..., strict=True) handles quoted commas and multiline fields. The first record must be exactly sku,quantity,unit_price, in that order and case. A UTF-8 byte-order mark is not removed. Each later record must have exactly three fields; a short or long record gets a field-count error and is skipped. Blank records fail too. A header-only file passes.

A set tracks nonblank SKUs. Comparison is verbatim and case-sensitive: Bolt and bolt are different; surrounding spaces and Unicode forms are not normalized. An empty or whitespace-only SKU is invalid. A well-shaped row still reserves its SKU even if its quantity or price is invalid.

Quantities must be ASCII decimal digits with at least one nonzero digit. Leading zeros are allowed. Signs, spaces, decimals and digit separators are rejected. No integer conversion is needed, so a long quantity does not encounter Python's integer-string conversion limit.

Prices use Decimal directly, avoiding binary floating-point conversion. The code checks is_finite() before comparing with zero, so NaN and infinity do not slip through. The Decimal exponent must be at least -2: 1.23 passes, while 1.230 fails. Nothing is rounded.

This is a value-and-scale check, not a strict price-format rule. Decimal also accepts scientific notation, signed zero, surrounding whitespace, underscores and Unicode decimal digits. For example, 1e2 and -0.00 pass. A system requiring plain ASCII currency text should add an explicit input-format rule before using this sample for an import.

Checks and limits

    python -m unittest -v

All 19 test methods passed locally on Python 3.14.3, Windows 11. Cases include malformed quoting, exact headers, missing/extra fields, duplicate SKUs, price boundaries and command-line exit codes. The command-line tests compare the sample files' bytes before and after validation.

Error locations use physical line numbers at the end of a record; a quoted multiline value spans more than one line. Row errors accumulate, but a bad header or malformed CSV stops further parsing. Python's strict CSV reader is not a complete RFC compliance checker; it can accept a quote inside an unquoted field. The default field-size limit applies (131072 characters here). The set of SKUs and accumulated errors use memory, and no maximum price, stock ceiling or inventory-catalog check is imposed.

Status: AI agents led the code, tests and manuscript production. This is synthetic, unpaid portfolio proof, not paid client work, a paid writing credit or a qualification claim. No service, account or network connection is used.
