Skip to content

Getting started

Install tunas and parse your first meet results file.

Install

pip install tunas

tunas requires Python 3.12 or newer. With uv:

uv add tunas

A first example

from tunas import read_cl2, Event

# read_cl2 yields one MeetArchive per source file (path, dir, list of paths, or file-like)
for archive in read_cl2("results/"):
    for meet in archive.meets:
        swimmer = next((s for s in meet.swimmers
                        if s.id_short == "ABC123456789"), None)
        if swimmer is None:
            continue
        for swim in swimmer.swims_in(Event.FLY_200_LCM):
            outcome = swim.time if swim.time is not None else swim.status.value
            print(f"{meet.name}: {swimmer.full_name} {swim.session.value} {outcome}")

Every tunas program parses input into self-contained Meet objects and traverses the resulting data graph.

Walkthrough

1. Parsing

read_cl2 parses a file path, directory, list of paths, or text stream, yielding a lazy iterator of MeetArchive objects — one per file. Each archive carries that file's meets and its own report.

Parsing is lenient by default. Malformed records are skipped or recovered, and recorded as warnings in the per-file report:

for archive in read_cl2("results/"):
    for warning in archive.report.warnings:
        print(f"{warning.source}:{warning.line_no}: {warning.reason}")

Each report aggregates running metrics for its file like files_read, meets_parsed, swimmers_parsed, and individual_swims_parsed:

print(archive.report.meets_parsed, "meets,", archive.report.individual_swims_parsed, "swims")

Pass strict=True to fail fast and raise ParseError on the first warning. For large datasets, the iterator is lazy — files are parsed one at a time as you consume archives, so peak memory stays flat regardless of corpus size. See parsing.md for more details.

2. Finding a Swimmer

Swimmers are grouped within each meet by member ID (id_short falling back to id_long):

swimmer = next((s for s in meet.swimmers if s.id_short == "ABC123456789"), None)

3. Querying Swims

swimmer.swims_in(event) returns all flat-start individual swims and relay legs for the given event, including prelims, finals, scratches, and disqualifications:

swims = swimmer.swims_in(Event.FLY_200_LCM)

Both IndividualSwim and RelaySwim share the uniform Swim interface (time, status, event, date, meet, swimmer).

4. Time Standards

USA Swimming motivational cuts (B through AAAA) are bundled locally for offline use:

from tunas import qualifies_for, all_qualified, standard_time, TimeStandard

# Fastest standard achieved (or None)
standard = qualifies_for(swim.time, swim.event, age=12, sex=swimmer.sex)

# All qualified standards, slowest first
standards = all_qualified(swim.time, swim.event, age=12, sex=swimmer.sex)

# Cutoff time for a specific standard (or None)
target = standard_time(TimeStandard.AAAA, swim.event, age=12, sex=swimmer.sex)

Ages are bucketed into single-year groups (10 & under, 11-12, 13-14, 15-16, 17-18). Lookups are defined for MALE and FEMALE only; Sex.MIXED raises ValueError.

What's in the box

Concept Class Where to read more
Reading a file read_cl2, read_hy3 parsing.md
A meet Meet models.md
A swimmer Swimmer models.md
A club Club models.md
One swimmer's swim Swim (base of the next two) models.md
An individual swim IndividualSwim models.md
A relay (+ its legs / alternates) Relay, RelaySwim models.md
Splits Split models.md
A time Time API reference
An event Event API reference
Strokes, courses, etc. Stroke, Course, ... API reference
Time standards qualifies_for, standard_time, TimeStandard API reference

Where to go next