Commit fe88e2b
feat: Add vendor auto-discovery from file contents (#1195)
## Summary
- Add `vendor_from_file()` and `vendor_from_io()` public API functions
that auto-detect the correct `Vendor` for a file based on its contents
- Make `vendor_type` optional (`None` default) in `allotrope_from_file`,
`allotrope_from_io`, `allotrope_model_from_file`,
`allotrope_model_from_io` — when `None`, auto-discovers the vendor
- Add `AllotropeVendorNotFoundError` exception raised when no parser
matches
- Implement `sniff()` classmethods on all 53 parsers with lightweight
file-content checks (sheet names, header patterns, XML root tags, etc.)
- Add `discover_vendor_test.py` with 309 parametrized tests verifying
discovery against all existing test data files
- Add vendor auto-discovery guidance to CLAUDE.md for new parser
development
## Design
`discover_vendor()` in `parser_factory.py` runs a multi-pass detection
strategy:
1. **Filter by extension** — only parsers supporting the file's
extension are candidates
2. **Sniff pass** — call `sniff()` on each candidate, collect all
matches
3. **Single match** — return immediately
4. **Multiple matches** — try `create_data()` on each sniff match to
disambiguate (e.g., Gen5 vs Gen5Image both sniff True for `.txt`, but
only one parses successfully)
5. **No sniff matches** — try `create_data()` on all candidates as
brute-force fallback
6. **Sniff matched but parse failed** — trust the sniff result (parse
failure may be unrelated to discovery, e.g., encoding issues)
Each parser's `sniff()` is a lightweight classmethod: unique extensions
return `True`, shared extensions check distinctive content (header
columns, sheet names, XML root tags, etc.). UTF-16 LE/BE encoded files
are handled with BOM detection.
**Indistinguishable parser pairs** (same file format, either parser
works): Gen5/Gen5Image, xPONENT/IntelliFlex, VisionLite/GenesysOnBoard,
EchoCherryPick/EchoPlateReformat, QuantStudio/QuantStudioD&A,
MSDWorkbench/MethodicalMind.
## Test plan
- [x] All 1119 existing parser tests pass
- [x] 309 new discovery tests pass — one per test data file, verifying
correct vendor detection
- [x] Lint + mypy clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>1 parent 8009b8b commit fe88e2b
59 files changed
Lines changed: 1293 additions & 15 deletions
File tree
- src/allotropy
- parsers
- agilent_gen5_image
- agilent_gen5
- agilent_openlab_cds
- agilent_tapestation_analysis
- appbio_absolute_q
- appbio_quantstudio_designandanalysis
- appbio_quantstudio
- bd_biosciences_facsdiva
- beckman_coulter_biomek
- beckman_echo_cherry_pick
- beckman_echo_plate_reformat
- beckman_pharmspec
- beckman_vi_cell_blu
- beckman_vi_cell_xr
- benchling_chromeleon
- benchling_empower
- biorad_bioplex_manager
- bmg_labtech_smart_control
- bmg_mars
- cfxmaestro
- chemometec_nc_view
- chemometec_nucleoview
- ctl_immunospot
- cytiva_biacore_insight
- cytiva_biacore_t200_control
- cytiva_biacore_t200_evaluation
- cytiva_unicorn
- example_weyland_yutani
- flowjo
- luminex_intelliflex
- luminex_xponent
- mabtech_apex
- methodical_mind
- moldev_softmax_pro
- msd_workbench
- novabio_flex2
- perkin_elmer_envision
- qiacuity_dpcr
- revvity_kaleido
- revvity_matrix
- roche_cedex_bioht
- roche_cedex_hires
- tecan_magellan
- thermo_fisher_genesys30
- thermo_fisher_genesys_on_board
- thermo_fisher_nanodrop_8000
- thermo_fisher_nanodrop_eight
- thermo_fisher_nanodrop_one
- thermo_fisher_qubit4
- thermo_fisher_qubit_flex
- thermo_fisher_visionlite
- thermo_skanit
- unchained_labs_lunatic_stunner
- tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
72 | 91 | | |
73 | 92 | | |
74 | 93 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
36 | 40 | | |
37 | 41 | | |
38 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
296 | 298 | | |
297 | 299 | | |
298 | 300 | | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
299 | 335 | | |
300 | 336 | | |
301 | 337 | | |
| |||
Lines changed: 12 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
24 | 36 | | |
25 | 37 | | |
26 | 38 | | |
| |||
Lines changed: 12 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
29 | 41 | | |
30 | 42 | | |
31 | 43 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
28 | 32 | | |
29 | 33 | | |
30 | 34 | | |
| |||
Lines changed: 13 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
26 | 39 | | |
27 | 40 | | |
28 | 41 | | |
| |||
Lines changed: 31 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
3 | 5 | | |
4 | 6 | | |
5 | 7 | | |
| |||
26 | 28 | | |
27 | 29 | | |
28 | 30 | | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
29 | 60 | | |
30 | 61 | | |
31 | 62 | | |
| |||
Lines changed: 29 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | 4 | | |
3 | 5 | | |
| |||
31 | 33 | | |
32 | 34 | | |
33 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
34 | 63 | | |
35 | 64 | | |
36 | 65 | | |
| |||
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | 4 | | |
3 | 5 | | |
| |||
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
33 | 47 | | |
34 | 48 | | |
35 | 49 | | |
| |||
0 commit comments