diff --git a/config.json b/config.json index d13fa81..c51769f 100644 --- a/config.json +++ b/config.json @@ -1,3 +1,3 @@ { - "EvaluationFunctionName": "" + "EvaluationFunctionName": "compareMusic" } diff --git a/docs/dev.md b/docs/dev.md index 7659400..c40fc0f 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -1,29 +1,96 @@ -# YourFunctionName -*Brief description of what this evaluation function does, from the developer perspective* +# compareMusic + +Automated formative feedback on music practice. Compares a student's MIDI performance against a reference MIDI and generates formatve, note-level feedback covering pitch accuracy, timing, and note duration. ## Inputs -*Specific input parameters which can be supplied when the `eval` command is supplied to this function.* +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `gap_penalty` | float | `6` | Alignment cost for a missing or extra note. Increase this if the function incorrectly splits one wrong note into a "missing + extra" pair. | +| `timing_relative_threshold` | float | `0.20` | Timing tolerance as a fraction of the inter-onset interval (IOI). `0.20` means up to 20% of the interval between consecutive notes. | +| `duration_relative_threshold` | float | `0.25` | Duration tolerance as a fraction of the reference note's duration. `0.25` means up to 25%. | +| `global_slow_threshold` | float | `1.15` | If the student's overall tempo scale exceeds this value, the overview reports "your tempo is slower than the reference". | +| `global_fast_threshold` | float | `0.85` | If the student's overall tempo scale falls below this value, the overview reports "your tempo is faster than the reference". | + +Both `response` and `answer` (i.e. reference) must be a JSON object with a `notes` array: + +```json +{ + "notes": [ + {"pitch": 60, "start": 0.00, "duration": 0.50}, + {"pitch": 62, "start": 0.60, "duration": 0.50} + ] +} +``` + +where `pitch` is an integer representing MIDI note number (e.g. middle C = 60), `start` is float representing note onset time in seconds, and `duration` is float in seconds. ## Outputs -*Output schema/values for this function* -## Examples -*List of example inputs and outputs for this function, each under a different sub-heading* +| Field | Type | Description | +|-------|------|-------------| +| `is_correct` | bool | `true` only when there are no missing notes, no extra notes, all pitches correct, all timing within threshold, and all durations within threshold | +| `feedback` | string | Human-readable feedback string | + +The feedback string is divided into two sections: + +**Overview** — overall tempo judgement, and counts of pitch errors, missing notes, and extra notes. + +**Detail** — note-by-note breakdown of every specific issue found. -### Simple Evaluation +## Examples + +### Perfect performance + +```python +response = { + "notes": [ + {"pitch": 60, "start": 0.00, "duration": 0.50}, + {"pitch": 62, "start": 0.60, "duration": 0.50} + ] +} +answer = { + "notes": [ + {"pitch": 60, "start": 0.00, "duration": 0.50}, + {"pitch": 62, "start": 0.60, "duration": 0.50} + ] +} +params = {} +``` + ```python { - "example": { - "Something": "something" - } + "is_correct": True, + "feedback": "Overview: \nTiming: your overall tempo is within an acceptable range. Good job! ...\n\nGreat performance! No further issues found." } ``` - + +### Wrong pitch and missing note + +```python +response = { + "notes": [ + {"pitch": 60, "start": 0.00, "duration": 0.50}, + {"pitch": 63, "start": 0.60, "duration": 0.50}, + {"pitch": 64, "start": 1.35, "duration": 0.50}, + {"pitch": 65, "start": 1.80, "duration": 0.70} + ] +} +answer = { + "notes": [ + {"pitch": 60, "start": 0.00, "duration": 0.50}, + {"pitch": 62, "start": 0.60, "duration": 0.50}, + {"pitch": 64, "start": 1.20, "duration": 0.50}, + {"pitch": 65, "start": 1.80, "duration": 0.50}, + {"pitch": 67, "start": 2.50, "duration": 0.50} + ] +} +params = {} +``` + ```python { - "example": { - "Something": "something" - } + "is_correct": False, + "feedback": "Overview: \nTiming: your overall tempo is within an acceptable range. ...\nThere is 1 note played with the wrong pitch.\nThere is 1 note you missed from the reference.\nThere are no extra notes. Good job!\n\nDetail: \nNote 5 (pitch 67) is missing in your performance.\nNote 2: wrong pitch -- expected 62, played 63 (1 semitone(s) off)." } ``` \ No newline at end of file diff --git a/docs/user.md b/docs/user.md index 108f533..e088035 100644 --- a/docs/user.md +++ b/docs/user.md @@ -1,3 +1,42 @@ -# YourFunctionName +# compareMusic + +`compareMusic` automatically evaluates a student's MIDI music performance against a reference MIDI and returns structured, formative feedback on pitch accuracy, timing, and note duration. + +## What the student sees + +Feedback is returned in two parts: + +**Overview** — a summary of overall tempo, and counts of pitch errors, missing notes, and extra notes. + +**Detail** — a note-level feedback of every specific issue, including which notes were missed, which had the wrong pitch, and which were played noticeably early, late, or with an incorrect duration. The function separates **global tempo** (playing consistently faster or slower throughout) from **local timing errors** (a single note noticeably early or late relative to surrounding notes), which means student who plays the whole piece at 80% speed will receive one global tempo comment instead of repetitive comments on every note. + +## Setting up a question + +Set the **Answer** field to a JSON object representing the reference MIDI performance, e.g.: + +```json +{ + "notes": [ + {"pitch": 60, "start": 0.00, "duration": 0.50}, + {"pitch": 62, "start": 0.60, "duration": 0.50}, + {"pitch": 64, "start": 1.20, "duration": 0.50} + ] +} +``` + +where `pitch` is an integer representing MIDI note number (e.g. middle C = 60), `start` is float representing note onset time in seconds, and `duration` is float in seconds. + +The student's **Response** must be in the same format. + +## Adjusting strictness + +All parameters are adjustable. If not set, the defaults below are used. + +| Parameter | Default | What it controls | +|-----------|---------|-----------------| +| `timing_relative_threshold` | `0.20` | How much timing deviation is acceptable, as a fraction of the gap between consecutive notes. Lower = stricter. | +| `duration_relative_threshold` | `0.25` | How much duration deviation is acceptable, as a fraction of the reference note's duration. Lower = stricter. | +| `gap_penalty` | `6` | Controls note alignment. Increase this if the function incorrectly reports a wrong note as "missing + extra". | +| `global_slow_threshold` | `1.15` | Overall tempo more than 15% slower than reference triggers a "too slow" comment. | +| `global_fast_threshold` | `0.85` | Overall tempo more than 15% faster than reference triggers a "too fast" comment. | -Teacher-facing documentation for this function. \ No newline at end of file diff --git a/evaluation_function/compare_MIDI.py b/evaluation_function/compare_MIDI.py new file mode 100644 index 0000000..363b98e --- /dev/null +++ b/evaluation_function/compare_MIDI.py @@ -0,0 +1,715 @@ +""" +compare_MIDI.py +================ +Core MIDI evaluation pipeline for the compareMusic evaluation function. + +Pipeline overview (called in order by compare_performance_ED): + Step 0 -- normalize_start_times (make first note start at t = 0.0) + Step 1 -- note_alignment_ED (edit-distance alignment) + Step 2 -- estimate_global_timing (linear regression for tempo drift) + estimate_global_duration_scale + Step 3 -- note_level_feedback (per-note pitch / timing / duration check) + Step 4 -- compute_stats (summary counts) + Step 5 -- generate_feedback_message (human-readable text) +""" + + +import numpy as np + +# Default thresholds / parameters +# Teachers can override any of these via the params dict in evaluation_function. +# ------------------------------------------------------------------------------ +# Gap penalty: cost of leaving a note unaligned (insertion/deletion) +DEFAULT_GAP_PENALTY = 6 + +# Timing: |response_start - predicted_start| / IOI must be below this. +# e.g. 0.20 means the start can be off by up to 20% of the inter-onset interval. +TIMING_RELATIVE_THRESHOLD = 0.20 + +# Duration: |response_dur / ref_dur - 1| must be below this. +# e.g. 0.25 means the student's duration can be off by up to 25% of the reference. +DURATION_RELATIVE_THRESHOLD = 0.25 + +# Thresholds that trigger a global tempo comment in the overview. +GLOBAL_SLOW_THRESHOLD = 1.15 # timing_scale > 1.15 -> "overall too slow" +GLOBAL_FAST_THRESHOLD = 0.85 # timing_scale < 0.85 -> "overall too fast" + + +# Step 0 - make first note start at t = 0.0 +# ------------------------------------------------------------------------------ +def normalize_start_times(notes): + """ + Shift all notes so that the first note starts at t=0. + + Args: + notes: list of note dicts, each with at least a "start" key. + + Returns: + A new list of note dicts (copies, not the original objects), with + every "start" value shifted so notes[0]["start"] == 0. Returns an + empty list unchanged if notes is empty. + """ + if not notes: + return [] + + first_start = notes[0]["start"] + + shifted_notes = [] + for note in notes: + # Create a copy of the note dict with the "start" time shifted + note_copy = { + "pitch": note["pitch"], + "start": note["start"] - first_start, + "duration": note["duration"], + } + shifted_notes.append(note_copy) + + return shifted_notes + + +# Step 1 -- edit-distance alignment to identify missing/extra notes and pitch errors +# ------------------------------------------------------------------------------ +def compute_cost(note1, note2): + """ + Cost of aligning (replacing) one note with another, based on pitch. + + cost = 0: pitches are identical (a 'match'). + cost > 0: different pitches (a 'replacement') + + Args: + note1: dict with keys "pitch" (int), "start" (float), "duration" (float) + note2: dict with keys "pitch" (int), "start" (float), "duration" (float) + + Returns: + int: cost value >= 0 (lower means more similar pitch) + """ + return int(abs(note1["pitch"] - note2["pitch"])) + + +def note_alignment_ED(response_notes, ref_notes, gap_penalty=DEFAULT_GAP_PENALTY): + """ + Align notes using edit distance (ED). + The ED allows for insertions and deletions, which can be useful for + evaluating musical practice containing missing/extra notes. + + Args: + response_notes: The student's response MIDI notes to evaluate + ref_notes: The reference MIDI note + gap_penalty: cost of leaving a note unaligned (insertion/deletion) + + Returns: + operations: list of transformation ops dicts, in order from first note to last: + {'type': 'match' or 'replacement' or 'missing' or 'extra', + 'response_idx': int or None, + 'reference_idx': int or None, + 'cost': int} + D: accumulated cost matrix, shape (N+1, M+1) + """ + # the rows of D correspond to response notes + N = len(response_notes) + # the columns of D correspond to reference notes + M = len(ref_notes) + + # Build the accumulated cost matrix D of size (N+1 x M+1) + D = np.zeros((N + 1, M + 1), dtype=int) + + # Boundary conditions: aligning against an empty sequence means every note + # is unaligned, so the cost is n (or m) times the gap penalty. + for n in range(1, N + 1): + D[n, 0] = n * gap_penalty # n extra response notes + for m in range(1, M + 1): + D[0, m] = m * gap_penalty # m missing ref notes + + # Recursion (accumulated cost / score matrix D): + for n in range(1, N + 1): + for m in range(1, M + 1): + replace_cost = compute_cost(response_notes[n-1], ref_notes[m-1]) + D[n, m] = min( + D[n-1, m-1] + replace_cost, # diagonal: match or replacement + D[n-1, m] + gap_penalty, # vertical: extra note response[n-1] + D[n, m-1] + gap_penalty, # horizontal: missing response for ref[m-1] + ) + + # Backtrack and classify each transformation op based on movement direction in D + operations = [] + n, m = N, M + while n > 0 or m > 0: + # Boundary conditions: at the top row, only horizontal moves possible + if n == 0: + # Missing response for ref[m-1] (deletion) + operations.append({ + "type": "missing", + "response_idx": None, + "reference_idx": m - 1, + "cost": gap_penalty, + }) + m -= 1 + # At the leftmost column, only vertical moves possible + elif m == 0: + # Extra note response[n-1] (insertion) + operations.append({ + "type": "extra", + "response_idx": n - 1, + "reference_idx": None, + "cost": gap_penalty, + }) + n -= 1 + # For all other cases, we can move in any direction (diagonal, vertical, horizontal) + else: + replace_cost = compute_cost(response_notes[n - 1], ref_notes[m - 1]) + diag = D[n - 1, m - 1] + replace_cost # diagonal: match or replacement + up = D[n - 1, m] + gap_penalty # vertical: extra note response[n-1] + left = D[n, m - 1] + gap_penalty # horizontal: missing response for ref[m-1] + min_cost = min(diag, up, left) # find the minimum cost step + # classify the transformation ops based on the minimum cost step + if min_cost == diag: # Diagonal -> two notes are aligned (match or replacement) + operations.append({ + "type": "match" if replace_cost == 0 else "replacement", + "response_idx": n - 1, + "reference_idx": m - 1, + "cost": replace_cost, + }) + n, m = n - 1, m - 1 + elif min_cost == up: # Vertical -> response[n-1] is extra (insertion) + operations.append({ + "type": "extra", + "response_idx": n - 1, + "reference_idx": None, + "cost": gap_penalty, + }) + n -= 1 + else: # Horizontal -> response is missing for ref[m-1] (deletion) + operations.append({ + "type": "missing", + "response_idx": None, + "reference_idx": m - 1, + "cost": gap_penalty, + }) + m -= 1 + + operations.reverse() # Reverse to get ops in order from first note to last + return operations, D + + +# Step 2 -- estimate_global_timing and estimate_global_duration_scale +# ------------------------------------------------------------------------------ +def estimate_global_timing(operations, response_notes, ref_notes): + """ + Estimate the student's overall tempo relative to the reference, by fitting + a straight line through the matched note start times: + response_start ≈ scale * ref_start + offset + where: + scale > 1 means the student is playing slower overall + scale < 1 means the student is playing faster overall + offset captures any constant time shift + + Args: + operations: list of operation dicts (match/replacement/missing/extra) + response_notes: list of note dicts from response + ref_notes: list of note dicts from reference + + Returns: + scale: float, estimated tempo ratio (1.0 = same speed as reference) + offset: float (seconds), estimated constant time shift + """ + # Collect (ref_start, response_start) pairs from matched/replaced notes only. + # Missing/extra notes have no pair, so they cannot contribute to the fit. + ref_starts = [] + response_starts = [] + for op in operations: + if op["type"] in ("match", "replacement"): + ref_starts.append(ref_notes[op["reference_idx"]]["start"]) + response_starts.append(response_notes[op["response_idx"]]["start"]) + + # Not enough points for fitting a meaningful line — assume no drift in tempo. + if len(ref_starts) < 3: + return 1.0, 0.0 + + x = np.array(ref_starts, dtype=float) + y = np.array(response_starts, dtype=float) + + # Least-squares line fit: y = scale * x + offset + scale, offset = np.polyfit(x, y, 1) + + return float(scale), float(offset) + +def estimate_global_duration_scale(operations, response_notes, ref_notes): + """ + Estimate the student's overall note-length scale relative to the reference, + by fitting a line through the origin: + response_duration ≈ duration_scale * ref_duration + where: + duration_scale > 1 means notes are held longer overall + duration_scale < 1 means notes are held shorter overall + + Args: + operations: output of note_alignment_ED() + response_notes: list of student note dicts + ref_notes: list of reference note dicts + + Returns: + duration_scale (float): estimated duration ratio (1.0 = same as reference) + """ + ref_durations = [] + response_durations = [] + for op in operations: + if op["type"] in ("match", "replacement"): + ref_durations.append(ref_notes[op["reference_idx"]]["duration"]) + response_durations.append(response_notes[op["response_idx"]]["duration"]) + + if len(ref_durations) < 3: + return 1.0 + + x = np.array(ref_durations, dtype=float) + y = np.array(response_durations, dtype=float) + + # Least-squares fit through the origin: y = scale * x + # Closed-form solution: scale = sum(x*y) / sum(x*x) + duration_scale = float(np.sum(x * y) / np.sum(x * x)) + + return duration_scale + + +# Step 3 -- note_level_feedback +# ------------------------------------------------------------------------------ +def note_level_feedback(operations, response_notes, ref_notes, + timing_scale=1.0, timing_offset=0.0, duration_scale=1.0, + timing_relative_threshold=TIMING_RELATIVE_THRESHOLD, + duration_relative_threshold=DURATION_RELATIVE_THRESHOLD): + """ + Analyse each aligned note pair (or missing/extra event) and return a list + of note result dicts. + + Args: + operations: list of op dicts (match/replacement/missing/extra) + response_notes: list of note dicts from response + ref_notes: list of note dicts from reference + timing_scale: float, estimated tempo ratio (1.0 = same speed as reference) + timing_offset: float (seconds), estimated constant time shift + duration_scale: float, estimated overall duration ratio + timing_relative_threshold: float, relative tolerance for timing correctness + duration_relative_threshold: float, relative tolerance for duration correctness + + Returns: + note_level_results : list of dicts, each dict contains: + "reference_index" -> int (1-based) or None if operation_type = extra + "response_index" -> int (1-based) or None if operation_type = missing + "operation_type" -> str: "match", "replacement", "missing", or "extra" + "pitch_correct" -> bool + "pitch_diff" -> int (semitones) or None if operation_type = missing/extra + "timing_correct" -> bool + "timing_abs_diff" -> float (seconds) or None if operation_type = missing/extra + “timing_relative_diff” -> float (seconds) or None if operation_type = missing/extra + "duration_correct" -> bool + "duration_abs_diff"-> float (seconds) or None if operation_type = missing/extra + "duration_relative_diff" -> float (seconds) or None if operation_type = missing/extra + """ + # Compute IOI for each reference note: ioi[m] = ref_notes[m]["start"] - ref_notes[m-1]["start"] + # floor at 0.05s to avoid division by zero issues + ref_ioi = [None] * len(ref_notes) + for m in range(1, len(ref_notes)): + interval = ref_notes[m]["start"] - ref_notes[m - 1]["start"] + ref_ioi[m] = max(interval, 0.05) + + note_level_results = [] + + for op in operations: + res_idx = op["response_idx"] + ref_idx = op["reference_idx"] + op_type = op["type"] + + # Missing/extra notes: no pitch/timing/duration comparison is possible, + # so all the numeric fields are set to None. + if op_type in ("missing", "extra"): + note_level_results.append({ + "reference_index": (ref_idx + 1) if ref_idx is not None else None, + "response_index": (res_idx + 1) if res_idx is not None else None, + "operation_type": op_type, + "pitch_correct": False, + "pitch_diff": None, + "timing_correct": False, + "timing_abs_diff": None, + "timing_relative_diff": None, + "duration_correct": False, + "duration_abs_diff": None, + "duration_relative_diff": None, + }) + else: + # Matched (aligned) note pair + res_note = response_notes[res_idx] + ref_note = ref_notes[ref_idx] + + ## Pitch + pitch_diff = int(abs(res_note["pitch"] - ref_note["pitch"])) + pitch_correct = (pitch_diff == 0) + + # Timing — residual after removing the global tempo trend + predicted_start = timing_scale * ref_note["start"] + timing_offset + timing_abs_diff = abs(res_note["start"] - predicted_start) + if ref_idx == 0: + # First note will start at 0, so no difference. + timing_relative_diff = None + timing_correct = True + else: + ioi = ref_ioi[ref_idx] + timing_relative_diff = timing_abs_diff / ioi + timing_correct = (timing_relative_diff <= timing_relative_threshold) + + # Duration — residual after removing the global duration-scale trend + predicted_duration = duration_scale * ref_note["duration"] + duration_abs_diff = res_note["duration"] - predicted_duration + ref_dur = max(ref_note["duration"], 0.05) # floor at 0.05s to avoid division by zero issues + duration_relative_diff = duration_abs_diff / ref_dur + duration_correct = (abs(duration_relative_diff) <= duration_relative_threshold) + + note_level_results.append({ + "reference_index": ref_idx + 1, + "response_index": res_idx + 1, + "operation_type": op_type, + "pitch_correct": pitch_correct, + "pitch_diff": pitch_diff, + "timing_correct": timing_correct, + "timing_abs_diff": timing_abs_diff, + "timing_relative_diff": timing_relative_diff, + "duration_correct": duration_correct, + "duration_abs_diff": duration_abs_diff, + "duration_relative_diff": duration_relative_diff, + }) + + return note_level_results + + +# Step 4 -- compute_stats +# ------------------------------------------------------------------------------ +def compute_stats(note_details, ref_notes, timing_scale=1.0, + timing_offset=0.0, duration_scale=1.0): + """ + Compute summary counts and correctness booleans from note-level feedback. + + Args: + note_details: list of dicts, output of note_level_feedback() + ref_notes: list of reference note dicts + timing_scale: float, from estimate_global_timing() + timing_offset: float, from estimate_global_timing() + duration_scale: float, from estimate_global_duration_scale() + + Returns: + stats: dict with keys: + "pitch_all_correct" -> bool + "timing_all_correct" -> bool + "duration_all_correct" -> bool + "total_notes_in_reference" -> int + "total_notes_missing" -> int + "total_notes_extra" -> int + "total_notes_wrong_pitch" -> int + "total_notes_wrong_timing" -> int + "total_notes_wrong_duration" -> int + "total_notes_correct" -> int + "timing_scale" -> float + "timing_offset" -> float + "duration_scale" -> float + """ + paired = [n for n in note_details + if n["operation_type"] in ("match", "replacement")] + + stats = { + "pitch_all_correct": all(n["pitch_correct"] for n in paired), + "timing_all_correct": all(n["timing_correct"] for n in paired), + "duration_all_correct": all(n["duration_correct"] for n in paired), + "total_notes_in_reference": len(ref_notes), + "total_notes_missing": sum(1 for n in note_details if n["operation_type"] == "missing"), + "total_notes_extra": sum(1 for n in note_details if n["operation_type"] == "extra"), + "total_notes_wrong_pitch": sum(1 for n in paired if not n["pitch_correct"]), + "total_notes_wrong_timing": sum(1 for n in paired if not n["timing_correct"]), + "total_notes_wrong_duration": sum(1 for n in paired if not n["duration_correct"]), + "total_notes_correct": sum(1 for n in paired + if n["pitch_correct"] and n["timing_correct"] and n["duration_correct"] + ), + "timing_scale": timing_scale, + "timing_offset": timing_offset, + "duration_scale": duration_scale, + } + + return stats + + +# Step 5 -- generate_feedback_message +# ------------------------------------------------------------------------------ +def generate_feedback_message(note_details, response_notes, ref_notes, stats, + global_slow_threshold=GLOBAL_SLOW_THRESHOLD, + global_fast_threshold=GLOBAL_FAST_THRESHOLD): + """ + Generate human-readable feedback messages for the student. + + Part 1 - Overview: summary of timing trend, duration trend, and total counts + of each error type (pitch / missing / extra). + Part 2 - Detail: indicate exactly which notes have which problems. + + Args: + note_details: list of dicts, output of note_level_feedback() + response_notes: list of student note dicts + ref_notes: list of reference note dicts + stats: dict, output of compute_stats() + global_slow_threshold: timing_scale above this triggers "too slow" message + global_fast_threshold: timing_scale below this triggers "too fast" message + + Returns: + feedback_message (str) + """ + paired = [] + for n in note_details: + if n["operation_type"] in ("match", "replacement"): + paired.append(n) + + timing_scale = stats["timing_scale"] + timing_offset = stats["timing_offset"] + duration_scale = stats["duration_scale"] + + overview_messages = [] + detail_messages = [] + + # ---------- Part 1: Overview ---------- + # Tempo: acceptable / too slow / too fast --- + timing_pct = abs(timing_scale - 1.0) * 100 + duration_pct = abs(duration_scale - 1.0) * 100 + timing_direction = "behind" if timing_scale > 1.0 else "ahead of" + duration_direction = "longer" if duration_scale > 1.0 else "shorter" + + if timing_scale > global_slow_threshold: + overview_messages.append( + f"Overall, your tempo is slower than the reference " + f"(timing is about {timing_pct:.0f}% {timing_direction} the reference in general while " + f"notes are held about {duration_pct:.0f}% {duration_direction} than the reference). " + f"No worries! You will get better when you practice more to get more familiar with it!" + ) + elif timing_scale < global_fast_threshold: + overview_messages.append( + f"Overall, your tempo is faster than the reference " + f"(timing is about {timing_pct:.0f}% {timing_direction} the reference in general while " + f"notes are held about {duration_pct:.0f}% {duration_direction} than the reference). " + f"Don't rush even if you are confident in your performance." + f"Slow down and give each note its full value." + ) + else: + overview_messages.append( + f"Timing: your overall tempo is within an acceptable range. Good job! " + f"The timing is about {timing_pct:.0f}% {timing_direction} the reference in general while " + f"notes are held about {duration_pct:.0f}% {duration_direction} than the reference." + ) + + # Wrong pitch counts + if stats["total_notes_wrong_pitch"] > 0: + s = "is" if stats["total_notes_wrong_pitch"] == 1 else "are" + note_word = "note" if stats["total_notes_wrong_pitch"] == 1 else "notes" + overview_messages.append( + f"There {s} {stats['total_notes_wrong_pitch']} {note_word} played with the wrong pitch." + ) + else: + overview_messages.append("There are no pitch errors. Well done!") + # Missing counts + if stats["total_notes_missing"] > 0: + s = "is" if stats["total_notes_missing"] == 1 else "are" + note_word = "note" if stats["total_notes_missing"] == 1 else "notes" + overview_messages.append( + f"There {s} {stats['total_notes_missing']} {note_word} you missed from the reference." + ) + else: + overview_messages.append("There are no missing notes. Great!") + # Extra counts + if stats["total_notes_extra"] > 0: + s = "is" if stats["total_notes_extra"] == 1 else "are" + note_word = "note" if stats["total_notes_extra"] == 1 else "notes" + overview_messages.append( + f"There {s} {stats['total_notes_extra']} extra {note_word} played during practice. " + f"You may need to adjust your fingering or hand position to avoid extra notes." + ) + else: + overview_messages.append("There are no extra notes. Good job!") + + # ---------- Part 2: Detail ---------- + # Missing / extra notes + for n in note_details: + if n["operation_type"] == "missing": + ref_zero_based = n["reference_index"] - 1 + pitch = ref_notes[ref_zero_based]["pitch"] + detail_messages.append( + f"Note {n['reference_index']} (pitch {pitch}) is missing in your performance." + ) + elif n["operation_type"] == "extra": + res_zero_based = n["response_index"] - 1 + extra = response_notes[res_zero_based] + detail_messages.append( + f"Extra note played: pitch {extra['pitch']} at t={extra['start']:.2f}s ") + + # Pitch errors + for n in paired: + if not n["pitch_correct"]: + ref_zero_based = n["reference_index"] - 1 + res_zero_based = n["response_index"] - 1 + ref_p = ref_notes[ref_zero_based]["pitch"] + res_p = response_notes[res_zero_based]["pitch"] + detail_messages.append( + f"Note {n['reference_index']}: wrong pitch — " + f"expected {ref_p}, played {res_p} " + f"({n['pitch_diff']} semitone(s) off)." + ) + + # Local timing errors - these are residuals after removing the global timing trend + for n in paired: + if not n["timing_correct"]: + detail_messages.append( + f"Note {n['reference_index']}: timing is off by {n['timing_abs_diff']:.2f}s " + f"({n['timing_relative_diff'] * 100:.0f}% of the expected note interval), " + f"after accounting for the overall tempo trend." + ) + + # Local duration errors — these are residuals after removing the global duration trend + for n in paired: + if not n["duration_correct"]: + direction = "longer" if n["duration_abs_diff"] > 0 else "shorter" + ref_zero_based = n["reference_index"] - 1 + ref_dur = ref_notes[ref_zero_based]["duration"] + duration_pct = abs(n["duration_relative_diff"]) * 100 + detail_messages.append( + f"Note {n['reference_index']}: duration is {abs(n['duration_abs_diff']):.2f}s " + f"{direction} than the reference (i.e. " + f"{duration_pct:.0f}% off) after accounting for the overall duration trend " + ) + + all_messages = ["Overview: "] + overview_messages + + if detail_messages: + all_messages = all_messages + ["", "Detail: "] + detail_messages + else: + all_messages = all_messages + ["", "Great performance! No further issues found."] + + return "\n".join(all_messages) + + +# FeedbackResult class +# ------------------------------------------------------------------------------ +class FeedbackResult: + """ + Container for all outputs of compare_performance_ED(). + Using a class (instead of returning a tuple) makes unit tests much clearer: + result = compare_performance_ED(response, reference) + assert result.is_correct == False + assert result.stats["total_notes_missing"] == 1 + assert "missing" in result.feedback_message + + Attributes + ---------- + is_correct : bool + True only if every note is perfectly matched on pitch, timing, and duration. + stats : dict + Aggregate counts — see compute_stats() for the full key list. + note_details : list of dicts + Per-note analysis, one dict per alignment operation. + Each dict has the keys described in note_level_feedback(). + feedback_message : str + Human-readable feedback string, ready to display to the student. + see generate_feedback_message() for details. + operations : list of dicts + Raw alignment operations from note_alignment_ED(). + Kept here so visualisation helpers (plot_cost_matrix etc.) can use them. + D : numpy.ndarray + Accumulated cost matrix from the alignment step. + """ + + def __init__(self, is_correct, stats, note_details, + feedback_message, operations, D): + self.is_correct = is_correct + self.stats = stats + self.note_details = note_details + self.feedback_message = feedback_message + self.operations = operations + self.D = D + + def __repr__(self): + return ( + "FeedbackResult(is_correct=" + str(self.is_correct) + ", " + "stats=" + str(self.stats) + ")" + ) + + +# Pipeline +# ------------------------------------------------------------------------------ +def compare_performance_ED(responseMIDI, refMIDI, + gap_penalty=DEFAULT_GAP_PENALTY, + timing_relative_threshold=TIMING_RELATIVE_THRESHOLD, + duration_relative_threshold=DURATION_RELATIVE_THRESHOLD, + global_slow_threshold=GLOBAL_SLOW_THRESHOLD, + global_fast_threshold=GLOBAL_FAST_THRESHOLD): + """ + Full pipeline: normalisation -> alignment -> estimate global trends + -> note-level evaluation -> summary statistics -> feedback. + + Args: + responseMIDI: student MIDI dict with key "notes" + refMIDI: reference MIDI dict with key "notes" + gap_penalty: cost of an unaligned note + timing_relative_threshold: see note_level_feedback() + duration_relative_threshold: see note_level_feedback() + global_slow_threshold: see generate_feedback_message() + global_fast_threshold: see generate_feedback_message() + + Returns: + FeedbackResult object containing all analysis results + """ + # Step 0: Normalise start times + response_notes = normalize_start_times(responseMIDI["notes"]) + ref_notes = normalize_start_times(refMIDI["notes"]) + + # Step 1: Align notes using edit distance + operations, D = note_alignment_ED(response_notes, ref_notes, gap_penalty) + + # Step 2: Estimate the overall tempo trend + timing_scale, timing_offset = estimate_global_timing( + operations, response_notes, ref_notes + ) + duration_scale = estimate_global_duration_scale( + operations, response_notes, ref_notes + ) + + # Step 3: Note-level evaluation + note_details = note_level_feedback( + operations, response_notes, ref_notes, + timing_scale=timing_scale, + timing_offset=timing_offset, + duration_scale=duration_scale, + timing_relative_threshold=timing_relative_threshold, + duration_relative_threshold=duration_relative_threshold, + ) + + # Step 4: Compute summary statistics + stats = compute_stats( + note_details, ref_notes, + timing_scale=timing_scale, + timing_offset=timing_offset, + duration_scale=duration_scale, + ) + + # Step 5: Generate the human-readable feedback text + feedback_message = generate_feedback_message( + note_details, response_notes, ref_notes, stats, + global_slow_threshold=global_slow_threshold, + global_fast_threshold=global_fast_threshold, + ) + + # Step 6: Overall pass/fail judgement + is_correct = ( + stats["total_notes_missing"] == 0 + and stats["total_notes_extra"] == 0 + and stats["pitch_all_correct"] + and stats["timing_all_correct"] + and stats["duration_all_correct"] + ) + + return FeedbackResult( + is_correct=is_correct, + stats=stats, + note_details=note_details, + feedback_message=feedback_message, + operations=operations, + D=D, + ) \ No newline at end of file diff --git a/evaluation_function/evaluation.py b/evaluation_function/evaluation.py index 61ecaa3..176fdaa 100755 --- a/evaluation_function/evaluation.py +++ b/evaluation_function/evaluation.py @@ -1,6 +1,25 @@ +""" +evaluation.py +============= +Lambda Feedback platform calls evaluation_function(response, answer, params) +and expects a dict back with at least "is_correct" and "feedback" keys. +All evaluation logic is in compare_music.py, this file is for the platform interface. +""" + + from typing import Any from lf_toolkit.evaluation import Result, Params +from .compare_MIDI import ( + compare_performance_ED, + DEFAULT_GAP_PENALTY, + TIMING_RELATIVE_THRESHOLD, + DURATION_RELATIVE_THRESHOLD, + GLOBAL_SLOW_THRESHOLD, + GLOBAL_FAST_THRESHOLD, +) + + def evaluation_function( response: Any, answer: Any, @@ -12,7 +31,7 @@ def evaluation_function( The handler function passes three arguments to evaluation_function(): - `response` which are the answers provided by the student. - - `answer` which are the correct answers to compare against. + - `answer` which are the correct answers to compare against.i.e. reference - `params` which are any extra parameters that may be useful, e.g., error tolerances. @@ -28,7 +47,28 @@ def evaluation_function( return types and that evaluation_function() is the main function used to output the evaluation response. """ + if params is None: + params = {} + + result = compare_performance_ED( + response, + answer, + gap_penalty=params.get("gap_penalty", DEFAULT_GAP_PENALTY), + timing_relative_threshold=params.get( + "timing_relative_threshold", TIMING_RELATIVE_THRESHOLD + ), + duration_relative_threshold=params.get( + "duration_relative_threshold", DURATION_RELATIVE_THRESHOLD + ), + global_slow_threshold=params.get( + "global_slow_threshold", GLOBAL_SLOW_THRESHOLD + ), + global_fast_threshold=params.get( + "global_fast_threshold", GLOBAL_FAST_THRESHOLD + ), + ) - return Result( - is_correct=response == answer - ) \ No newline at end of file + return { + "is_correct": result.is_correct, + "feedback": result.feedback_message, + } \ No newline at end of file diff --git a/evaluation_function/evaluation_test.py b/evaluation_function/evaluation_test.py index 7a5c5bd..73f5eaa 100755 --- a/evaluation_function/evaluation_test.py +++ b/evaluation_function/evaluation_test.py @@ -1,30 +1,266 @@ +""" +evaluation_tests.py +=================== +Unit tests for the compareMusic evaluation function. + Read the docs on how to use unittest here: + https://docs.python.org/3/library/unittest.html +Run locally with: python -m pytest evaluation_test.py -v + +Sections +-------- +1. Helper: make_midi +2. Tests for normalize_start_times +3. Tests for note_alignment_ED +4. Tests for estimate_global_timing and estimate_global_duration_scale +5. Tests for note_level_feedback and compute_stats +6. Tests for evaluation_function (Lambda Feedback integration) +7. Tests for parameter overrides +""" + + import unittest +import json +from .compare_MIDI import ( + normalize_start_times, + compute_cost, + note_alignment_ED, + estimate_global_timing, + estimate_global_duration_scale, + compare_performance_ED, + DEFAULT_GAP_PENALTY, + TIMING_RELATIVE_THRESHOLD, + DURATION_RELATIVE_THRESHOLD, + GLOBAL_SLOW_THRESHOLD, + GLOBAL_FAST_THRESHOLD, +) +from .evaluation import evaluation_function -from .evaluation import Params, evaluation_function +# 1. Helper: make MIDI notes for testing +# ------------------------------------------------------------------------------ +def make_midi(pitches, starts, durations): + notes = [] + for i in range(len(pitches)): + notes.append({ + "pitch": pitches[i], + "start": starts[i], + "duration": durations[i], + }) + return {"notes": notes} -class TestEvaluationFunction(unittest.TestCase): - """ - TestCase Class used to test the algorithm. - --- - Tests are used here to check that the algorithm written - is working as it should. - It's best practise to write these tests first to get a - kind of 'specification' for how your algorithm should - work, and you should run these tests before committing - your code to AWS. +# 2. Tests for normalize_start_times +# ------------------------------------------------------------------------------ +class TestNormalizeStartTimes: - Read the docs on how to use unittest here: - https://docs.python.org/3/library/unittest.html + def test_first_note_starts_at_zero(self): + notes = make_midi([60, 62], [1.0, 1.5], [0.5, 0.5])["notes"] + result = normalize_start_times(notes) + assert result[0]["start"] == 0.0 + + def test_relative_gaps_preserved(self): + notes = make_midi([60, 62], [1.0, 1.6], [0.5, 0.5])["notes"] + result = normalize_start_times(notes) + assert abs(result[1]["start"] - 0.6) < 0.0001 + + def test_pitch_and_duration_unchanged(self): + notes = make_midi([64], [2.0], [0.8])["notes"] + result = normalize_start_times(notes) + assert result[0]["pitch"] == 64 + assert result[0]["duration"] == 0.8 - Use evaluation_function() to check your algorithm works - as it should. - """ - def test_evaluation(self): - response, answer, params = "Hello, World", "Hello, World", Params() +# 3. Tests for note_alignment_ED +# ------------------------------------------------------------------------------ +class TestNoteAlignmentED: + + def test_perfect_match_all_match_ops(self): + ref = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + res = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + types = [op["type"] for op in operations] + assert all(t == "match" for t in types) + + def test_missing_note_detected(self): + # pitch 62 missing in response + ref = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + res = make_midi([60, 64], [0, 1.0], [0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + types = [op["type"] for op in operations] + assert "missing" in types + + def test_extra_note_detected(self): + # extra pitch 62 in response + ref = make_midi([60, 64], [0, 1.0], [0.4, 0.4]) + res = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + types = [op["type"] for op in operations] + assert "extra" in types + + def test_wrong_pitch_is_replacement(self): + ref = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + res = make_midi([60, 65], [0, 0.5], [0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + replacements = [op for op in operations if op["type"] == "replacement"] + assert len(replacements) == 1 + + def test_ops_are_in_forward_order(self): + ref = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + res = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + ref_indices = [op["reference_idx"] for op in operations if op["reference_idx"] is not None] + assert ref_indices == sorted(ref_indices) + + def test_cost_matrix_shape(self): + ref = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + res = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + assert D.shape == (len(res["notes"]) + 1, len(ref["notes"]) + 1) + + +# 4. Tests for estimate_global_timing and estimate_global_duration_scale +# ------------------------------------------------------------------------------ +class TestGlobalEstimations: + + def test_perfect_timing_scale_is_one(self): + ref = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + res = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + scale, offset = estimate_global_timing(operations, res["notes"], ref["notes"]) + assert abs(scale - 1.0) < 0.01 + + def test_slower_playing_scale_greater_than_one(self): + ref = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + res = make_midi([60, 62, 64, 65], [0, 0.6, 1.2, 1.8], [0.4] * 4) # 20% slower + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + scale, offset = estimate_global_timing(operations, res["notes"], ref["notes"]) + assert scale > 1.0 + + def test_perfect_duration_scale_is_one(self): + ref = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + res = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + dur_scale = estimate_global_duration_scale(operations, res["notes"], ref["notes"]) + assert abs(dur_scale - 1.0) < 0.01 + + def test_fewer_than_3_matched_returns_defaults(self): + # Only 2 notes -- should return (1.0, 0.0) + ref = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + res = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + operations, D = note_alignment_ED(res["notes"], ref["notes"]) + scale, offset = estimate_global_timing(operations, res["notes"], ref["notes"]) + dur_scale = estimate_global_duration_scale(operations, res["notes"], ref["notes"]) + assert scale == 1.0 + assert offset == 0.0 + assert dur_scale == 1.0 + + +# 5. Tests for note_level_feedback and compute_stats +# ------------------------------------------------------------------------------ +class TestComparePerformanceED: + + def test_consistent_tempo_not_flagged_per_note(self): + """ + A student playing consistently 20% slower should NOT get + note-level timing warnings -- only a global tempo comment. + """ + ref = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + res = make_midi([60, 62, 64, 65], [0, 0.6, 1.2, 1.8], [0.4] * 4) + result = compare_performance_ED(res, ref) + for n in result.note_details: + if n["operation_type"] in ("match", "replacement"): + assert n["timing_correct"] == True + + def test_single_late_note_flagged(self): + """ + One note that is very late compared to the rest should be flagged + after the global trend is removed. + """ + ref = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + res = make_midi([60, 62, 64, 65], [0, 0.5, 1.8, 1.5], [0.4] * 4) # note 3 very late + result = compare_performance_ED(res, ref) + flagged = [n for n in result.note_details if not n["timing_correct"]] + assert len(flagged) > 0 + + def test_pitch_error_recorded_correctly(self): + ref = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + res = make_midi([60, 65], [0, 0.5], [0.4, 0.4]) # 3 semitones off + result = compare_performance_ED(res, ref) + replacements = [n for n in result.note_details if n["operation_type"] == "replacement"] + assert len(replacements) == 1 + assert replacements[0]["pitch_diff"] == 3 + assert replacements[0]["pitch_correct"] == False + + def test_all_correct_stats(self): + midi = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + result = compare_performance_ED(midi, midi) + assert result.stats["total_notes_missing"] == 0 + assert result.stats["total_notes_extra"] == 0 + assert result.stats["total_notes_wrong_pitch"] == 0 + assert result.stats["pitch_all_correct"] == True + + def test_missing_note_counted(self): + ref = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + res = make_midi([60, 64], [0, 1.0], [0.4, 0.4]) + result = compare_performance_ED(res, ref) + assert result.stats["total_notes_missing"] == 1 + + def test_extra_note_counted(self): + ref = make_midi([60, 64], [0, 1.0], [0.4, 0.4]) + res = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + result = compare_performance_ED(res, ref) + assert result.stats["total_notes_extra"] == 1 + + def test_total_notes_in_reference(self): + ref = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + res = make_midi([60, 62, 64], [0, 0.5, 1.0], [0.4, 0.4, 0.4]) + result = compare_performance_ED(res, ref) + assert result.stats["total_notes_in_reference"] == 3 + + +# 6. Tests for evaluation_function (Lambda Feedback integration) +# ------------------------------------------------------------------------------ +class TestEvaluationFunction: + """ + the core logic is already covered by TestComparePerformanceED above. + simple checks here to ensure the interface is working as expected. + """ + def test_perfect_performance_is_correct(self): + midi = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + result = evaluation_function(midi, midi, {}) + assert result["is_correct"] == True + + def test_pitch_error_is_not_correct(self): + ref = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + res = make_midi([60, 65], [0, 0.5], [0.4, 0.4]) + result = evaluation_function(res, ref, {}) + assert result["is_correct"] == False - result = evaluation_function(response, answer, params).to_dict() - self.assertEqual(result.get("is_correct"), True) - self.assertFalse(result.get("feedback", False)) +# 7. Tests for parameter overrides +# ------------------------------------------------------------------------------ +class TestParamOverrides: + + def test_tight_timing_threshold_triggers_warning(self): + """ + A very strict timing threshold should flag a note that is slightly late. + Note 3 is late while others are on time, so the residual is detectable. + """ + ref = make_midi([60, 62, 64, 65], [0, 0.5, 1.0, 1.5], [0.4] * 4) + res = make_midi([60, 62, 64, 65], [0, 0.5, 1.3, 1.5], [0.4] * 4) + result = compare_performance_ED( + res, ref, timing_relative_threshold=0.01 + ) + assert result.stats["total_notes_wrong_timing"] > 0 + + def test_custom_gap_penalty_passed_through(self): + # Note 2 is 3 semitones off (62 -> 65), so replacement cost = 3. + # A deletion + insertion each cost gap_penalty once, total = 2 * gap_penalty. + # gap_penalty=1 -> gap cost = 2*1 = 2 < 3 -> aligner prefers gap -> missing > 0 + # gap_penalty=6 -> gap cost = 2*6 = 12 > 3 -> aligner prefers replacement -> missing == 0 + # So the two assertions together prove that gap_penalty is being passed through. + ref = make_midi([60, 62], [0, 0.5], [0.4, 0.4]) + res = make_midi([60, 65], [0, 0.5], [0.4, 0.4]) # 3 semitones off + result_lenient = compare_performance_ED(res, ref, gap_penalty=1) + assert result_lenient.stats["total_notes_missing"] > 0 + result_default = compare_performance_ED(res, ref) + assert result_default.stats["total_notes_missing"] == 0 \ No newline at end of file diff --git a/notebooks/Note_alignment_and_MIDI_evaluation.ipynb b/notebooks/Note_alignment_and_MIDI_evaluation.ipynb new file mode 100644 index 0000000..7e0a8dd --- /dev/null +++ b/notebooks/Note_alignment_and_MIDI_evaluation.ipynb @@ -0,0 +1,305 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "id": "96c2775c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "from evaluation_function.compare_MIDI import compare_performance_ED\n", + "import numpy as np\n", + "from typing import Any\n", + "from lf_toolkit.evaluation import Result, Params\n", + "\n", + "cwd = os.getcwd()\n", + "\n", + "cwd = os.getcwd()\n", + "dir = os.path.dirname(cwd)\n", + "reference_path = os.path.join(dir, \"data\", \"referenceMIDI.json\")\n", + "response_path = os.path.join(dir, \"data\", \"responseMIDI.json\")\n", + "\n", + "with open(reference_path) as f1:\n", + " reference = json.load(f1)\n", + "\n", + "with open(response_path) as f2:\n", + " response = json.load(f2)\n" + ] + }, + { + "cell_type": "markdown", + "id": "8d3bba8e", + "metadata": {}, + "source": [ + "# Note Alignment techniques" + ] + }, + { + "cell_type": "markdown", + "id": "0b6a19f0", + "metadata": {}, + "source": [ + "The goal of the note alignment is to find if the student played any missing or extra notes, and which note is missing/extra.\n", + "\n", + "Based on the findings during the project plan phase, Dynamic Time Warping (DTW) is commonly used for alignment. The algorithm can be found in this book (Chpater 3.2 Dynamic Time Warping):\n", + "M. Müller, Fundamentals of Music Processing. Cham: Springer International Publishing, 2021, ISBN: 9783030698072. DOI:https://doi.org/10.1007/978-3-030-69808-9.\n", + "\n", + "In general, this DTW algorithm finds an optimal possibly nonlinear alignment between response MIDI sequence to reference MIDI sequence.\n", + "\n", + "Basic approach:\n", + "- Evaluating the local cost measure for each pair of elements in the response(X) and reference(Y) sequences. \n", + "- Dynamic programming to find an alignment path between X and Y having minimal overall cost, i.e. DTW distance. The algorithm computes a cumulative distance path, the timestamps of the target MIDI are warped so they perfectly align with the anchor points of the reference MIDI.\n", + "\n", + "However, this basic approach will not correctly handle the missing note case as expected, because it allows a note to match with multiple notes, and each note must be paired. Let's say, there is a note missing in the response, this algorithm tends to match a response note with two reference note, instead of reporting the missing problem." + ] + }, + { + "cell_type": "markdown", + "id": "adbaf01b", + "metadata": {}, + "source": [ + "Another note alignment for music comparison method is based on Edit Distance (ED), see https://www.math.univ-toulouse.fr/~mongeau/music.pdf, where the reference note sequence and the student's response sequence are aligned using dynamic programming. Instead of using a constant replacement cost as in the classic ED algroithm, here absolute pitch difference (measured in MIDI semitones) between two notes is used, so that matched note pairs have zero cost while larger pitch deviations have proportionally higher penalties.\n", + "\n", + "Insertion and deletion operations are represented by a fixed gap penalty, corresponding to extra notes played by the student and missing notes from the reference sequence, respectively. The gap penalty is empirically set to 6. This value is chosen to encourage the alignment algorithm to interpret most pitch deviations as replacement (wrong pitches) rather than decomposing them into separate insertion and deletion operations. Such behaviour is more consistent with common music teaching practice, where a note played at the correct temporal position but with an incorrect pitch is typically regarded as a wrong note instead of a missing note accompanied by an extra note.\n", + "\n", + "After constructing the accumulated cost matrix, backtracking is performed to recover the optimal alignment path. Each aligned pair is classified as one of four operation types: match, replacement, missing, or extra. These structured operations are subsequently used to generate formative feedback for the learner." + ] + }, + { + "cell_type": "markdown", + "id": "453b33d9", + "metadata": {}, + "source": [ + "Proportional tolerances are used instead of fixed absolute thresholds, because a fixed tolerance (e.g. +/-0.5 s) is unfair: it is too tolerant for long notes and overly strict for short notes" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "27e6eeb8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overview: \n", + "Timing: your overall tempo is within an acceptable range. Good job! The timing is about 3% behind the reference in general while notes are held about 10% longer than the reference.\n", + "There is 1 note played with the wrong pitch.\n", + "There is 1 note you missed from the reference.\n", + "There are no extra notes. Good job!\n", + "\n", + "Detail: \n", + "Note 5 (pitch 67) is missing in your performance.\n", + "Note 2: wrong pitch — expected 62, played 63 (1 semitone(s) off).\n", + "Note 4: duration is 0.15s longer than the reference (i.e. 30% off) after accounting for the overall duration trend \n" + ] + } + ], + "source": [ + "result = compare_performance_ED(response, reference)\n", + "print(result.feedback_message)" + ] + }, + { + "cell_type": "markdown", + "id": "f3765faf", + "metadata": {}, + "source": [ + "### Visualisation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8636c4c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvEAAAJOCAYAAAA+pFhBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAg5xJREFUeJzt3Qd0VFUXhuFv0obeRZAmYEFEFBELqKACYqGrKCgoVizYu/72imJFsYEoiEpHURSxoaIgYAdUmoDSe5u0+699wsQkUpJMkmnvs9bInGk5uROTfffss4/P8zxPAAAAAKJGQrgnAAAAAKBgCOIBAACAKEMQDwAAAEQZgngAAAAgyhDEAwAAAFGGIB4AAACIMgTxAAAAQJQhiAcAAACiDEE8AAAAEGUI4gFEtEaNGumss85SpGnbtq2OOuqocE8jbkTqzwEAhAtBPBCh7rvvPvl8PjVv3jzcU4la+++/v84999xwT0Oe5+ndd9/V6aefrurVq8vv96tOnTpq06aNBg8erM2bN0fE93/ssce6n7natWsrMzPzP/cvWrRICQkJ7jE33XRTicypuAS/1+ClfPnyatCggbp27ao333xTaWlp4Z4iAOwRQTwQgSyAGjp0qAumZs+e7S6ITqmpqerSpYv69Omj448/XtOnT3dBu/3buXNn3X777XryyScVKcqWLavly5drypQp/7nv9ddfV5kyZcIyr3nz5mn06NFF+pq1atVyJ1h2Wblypd5//30dd9xx6t+/vzt5tuMAAJGKIB6IQB9//LGWLFmiN954QzVq1NArr7wS7imhkG6++Wa99957mjBhgu644w41bNhQKSkp7gTt+uuvdydolp2OFDavFi1auIA9Jwt0hw0bFrMlLXZy0rhxY91yyy36+uuv3acO3bt3D/e0AGC3COKBCGRB+2GHHaaTTjpJl1xyid566y1t27Ztl48dNWqUTjjhBFWoUEFVqlRxJRuzZs3K92PS09NdOcFdd931n9e2cg8rO8jJTiouvPBCzZgxw2UtLfg54ogj9NVXX7n7LcPcsmVLd/uBBx7ogtecCvr1dsUeFyyDSExMdHPq2bOnFi9enP0Yu89OhN55553sx9o8c7ISF5urZZ/tYq87bdq0XI+xsgoLvi1ra485+eSTXVY4P1atWqUXX3xRZ555ptq3b7/Lx9SvX98dz5yfwlhm3gJKK7upWrWqC5zzfs0PPvjAvaf2flauXNldHz9+fIG+/92x+dhrbdy4Mfu2Tz/91L1ezrkW9XsS/NmaOXOmTjzxRJUuXVq33XbbLmviJ0+e7L6OvTc52c+bve4DDzygwrJjf9lll+m7777TZ599VujXAYDiRBAPRBgL/Cxze+WVV7rx5Zdfrq1bt7qAc1d18+edd54LoH766SeXPbRSgKeffrpAjymoZcuWacCAAe6Tgr/++ssFPRaoWgD/6KOPulKgpUuXuiC/R48e+vvvv1WUPv/88+wyCDu5sTIICw5tDjt27HCPsfvq1avnvn7wsT/88EP2azz00EMuyOzUqZP+/PNPLVy4UMccc4xOOeWUXIG8nUQ988wzevzxx933Yf9ee+212rRp017naYGvnQScdtpp+f7errjiCheY2tdYsWKFvvzyS/evndz88ccf7jE//vijK8Vp3bq15s+f7461zctqudeuXZuv739P7OfFHv/2229n32bvqS3kbdKkSbG9J8GfLfuZfeGFF7RgwQL3nuxKhw4ddOedd7qfN/taxh5vZUt2vHd1klgQwZOuL774IqTXAYBi4wGIKI899phXvnx5b/Pmzdm3denSxWvZsmWuxy1YsMBLTEz0rrzyyt2+Vn4ek5aW5tmvgjvvvPM/97Vu3do75phjct227777uvmtW7cu+7bly5e717D71qxZk337ihUrPJ/P576nwn69gw8+2Ovevbu3Nz/88IN73U8++ST7tnr16nk9evT4z2MXL17sJSUleddee+1/7jvhhBO8Vq1aueu//PKLe80HHngg12N+/vln9301b958j3N69NFH3fPff/99Lz9+/fVX9/hbbrkl1+0rV670ypQpk/29PP/88+5xa9eu3ePr7e773x079na8zTnnnOMde+yx7vrGjRu90qVLe4MGDfJWr17tvvaNN95YpO+JsZ8fv9/vvt+8dvVzkJGR4bVr186rVKmSO3aHH364V7du3Vw/g3v6XmvVqrXb+4Pv/aWXXrrX1wKAcCATD0SY1157Tb1791a5cuWyb7Os/DfffKPffvst+7aPPvpIGRkZLpu8O/l5TGFYVthKOIL2228/V6pjGXkr/wjad9993eMsy12ULPts2WL7uklJSbnKMiyrvjdWimFlPWefffZ/7rNM/LfffusWpFom3Vi2PifLRltte1ELfr1u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hrR9e1YpTd7FhcHb7H57XIwH8o8//rhmzpxZYs+LBwMGDNBPP/0U7mkAABAxmfjiusQDgvhgCU337llB+t4WE9r99jh7/M7a5cJYvny57rnnHvXq1UvXX3+9Zs2ale/nPvroo7keXxyB8zfffOPmGMrzoiWgHzhwoObMmVPsr2vHZuXKlUX+dQAAQPwhiDdWA79t294D+CB7nD3+jTcKddB/+eUXNW3aVEuWLFHbtm3dlsCtW7fWyJEj8711/T///JM9PuGEE1SrVi1FmkidV17Tp0932zNHy+sCABDtyMSHLuYXtu6VZdVtEWthPPts1mLXAn5sc+2116pv376uvCKoefPmuvTSS9W5c2fXSeehhx5Sy5Yt9emnn2rVqlXq06ePG0+ePNll4S0b//rrr+umm27StGnT3Nbn++23n3teixYtXKBvXXmuuuoqValSxWWF09PTdfPNN6tBgwbuaw4aNEifffaZUlJS1LhxY1199dVuUeXe5Pd5OedlC1SffvppzZ8/X6effrrmzp2rTp066bDDDnNzPv744933aln8888/X23atHGvYcfoyCOPdAHx33//7Y5R5cqV9fzzz7vOQ/3791f9+vWzv+bo0aP1xRdfyO/364ILLtDhhx+e/TrHHXecu89OgHr06OFOMqZMmaIffvhBTz31lN566y1dc801Ovroo3N9H/bcY445xj13zZo16tmzpxubl19+2X2fycnJatSokS677DJ3LHb1usYWrNr3m3MOAAAABRV/mfijjspanBq87LdfVheagm6wY4+359nzc76evf4eBAIBFwxaEJ/TWWed5fqCB8tPLDC8+OKLXVedhg0b6owzztBvv/2mAw880GW3Lfg799xzVadOnVwlLPY8C6pr1Kih2rVrq0OHDi7wtWA2MTFRZ599dvbXtEDUXqNjx44uQLbgOT/y+7yc8+rXr58++OADF6yPHTvWldoES0tszpdffrmqVavmTgq6dOniTlyCr2EnKvvuu687GejWrZsL3C34t42g7OQm6P7779d7772nVq1auRMVC7b/+OOP7Nexk6eqVau6YNvuW716tTu29rp2gtS9e3d3zHb1fdgxtfnZCYMdw3nz5rn7jjrqKPe80047zQXmdqzN7l7XAvi8cwAAIN6QiQ9d/GXirbyhELXee3y9AvY9twyyBYR5WXC3cePG7LEF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lK6/XrrzTmnMGOnUU8M9WxTCxo0btXDhQqWlpal+/frax07QEBUyMjK0aNEidz0xMdG9fzllZmZq5cqVLuO07777un8R2WrWrKk6deq463///beWLVuW6/5y5cq593nHjh36888/5dknpIh4Bx54oCpXrqyffvrJvXd5VatWTc2bN9e0adO0beffWxQvMvGhI4iP9AD+jDOyymg8T29KulrSIZLmSxrheTrdHrd9e9bjJk0ikI8yEydO1Msvv6z9999fZcqU0TnnnEMQH0VSU1P17bffuhOw1atX65Zbbsm+z4L3sWPHuuDegoaUlBT17t3bvc+IXIcccog6dOjgAvnPPvvM/f8Z1LJlS91xxx3upLtSpUpKT09X//79tcVKHBGRqlSpohEjRqhBgwbatGmTateurTPPPFOzZs3K9bghQ4aoffv2OvzwwzV/vv2FBSIfQXykshIZy8BbAJ+ZqTRJFh6Mk3SypLGSbpaygvjMTCkhIevxljWqVCncs0c+WJZv8ODBevbZZ3XAAQdwzKJQ6dKldf7552vNmjUuCMjJsnlnnXWWOymzbO2bb77pAocTTjghbPPF3n366afu0q9fv//cd8YZZ+i1117TuHH2m1h66aWX1KxZM5e9RWSy7PvAgQM1ZcoUN37ooYd09913q0uXLtmPueyyyzRjxgwdd9xxYZxp/CETH8U18VdffbVGjx6dPZ4zZ446d+7MR5NBw4ZlldBYgC7pF/uB3xnAG/v189fO8hrHHmePf+ONknwbEQLL8h177LGqWLGifv75Z7J5MSZnaZT9sSpbtqySk5PDPS2E4IsvvlDr1q3diVinTp3cpyu//vorxzSCLViwIDuANytWrNB2+/R6p3r16qlnz5565JFHwjRDIAqDePto6/PPP3fXLUtlH0neeeed7o+d1Zn+8ccf7qPquGTZ9+eey3XTSqvVzPPGWXjwT97nPvts1vMR8f755x+tW7dON9xwg8vo2R+S2bNnh3taKAaLFy/W8uXL3Uf1iF7z5s1zn7706NHDXX744Qe3pgXRwUpprr/+eg0YMCD7Nvs09JprrnFxB8KTiS+uSzwIWznN8ccfr7feestdHzp0qI488kgdffTRrsbQ7lu7dq2rJZ0+fbr7OCyvQCDgLkFW6xYzrI3kggW5bipnH8/neZiNy+e8wYJ3e966dVntJxHRLBjY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9fvAnHjiiXo3jGvdCOKjyFNPPaWTTz5Zl1xySbingsLYvl0Jf/4pC9MtiLdfD+9L+kTSTXke6svMlJeQ4Epvtv/+u7RzAylENgsAcgb0nuepZs2aYZ0TCubHH390lzPOOOM/91mwbr+H7QQtMTFRd955p0uo/P333xzmCPbggw+6f6dOnfqfDH3jxo111llnuex827ZtddVVV7kL4isTf9XO9/zCCy9Uv379XNVDXnbyfvrpp7sTvyBLqsZlTfyVV16pUaNGZY9nzZqljh07uj96+C/7WPfnn392v2zmzJnjsvKILkkjRrhAPr+/WiyQ17ZtSnrrrWKeGYrDxx9/rPbBBcyICfPnz1e5cuVc4NemTRtt2LDhP+U2iB5W5mafpgTLa+zvqn0KYydoiC+vvfaa+1TNTuz2pEyZMu7EPXipXLmy4jKIP/DAA/X555+76xa49+/fX/fYAsC0NH377bfusqszoXi1YsUKVwN/11136c0339S1117rMkKIEp5X6D7xSS++yI6vUcYys4sWLVKrVq3CPRUUsVq1aum4447TMccc44J6+5uF6GQJMauHv+yyy7Kz8MZKbxBfO7YmJOQvHB4zZoz2339/tWjRQrfffnvY11CELYg//vjjXaBuXn31VR199NHuDGjjxo267rrrdNppp7ksx+7YmbPVnua8xLLSpUu7j3LsY56BAwe6M8ZPPvmEjHy0WLtWCQsXZte855c93p6ndeuKbWooeh999JFat27tFkEittgnopa1e/zxx9W0aVM1atQo3FNCIa1bt84F8JUqVXL/v44cOdIFf7EeT8STTXnixLyLmgvCgvcBAwa42Ov+++/XhAkT1K5dO2VkZOTr+XbSb1UVBb0vIoP4Zs2aacGCBVq+fLkGDx6cXbO2zz77uOB+bxmsRx55xH0UFrzUqVNHsczqaoNdEYz90ilfvnzYzwKRP9YHPhQ+3ueoYb/Qrfb21FNPDfdUUMRy1r/aJ8WWaLKP1xG97BMzOyG7++67te++++qPP/4IKdBDZGXi69SpkytWtNixsKz64fzzz3cLXi3RPHr0aBevWulkfli1iZ0AFPS+iFzYmpSU5GrPunTpogceeEBlbaObArCPMXIuIrMzrFgO5O0Pha2wtx9A+9c+BrRjmHf1NCKT28gpDzt3t3XuP9rP785FrvUl7appqFeuXInME6Gb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UvKttyr5vvsUGDFCme3ahXv6yKcNGzZo7ty5uW7z+/068sgjOYZRxH6npqSkuOu2K2ZaWlqu+6tWrarKlSu72uQtW7aEaZbxJ5J2bC1OF198sSudueKKK/TKK6+4Wngzbty4kF+bID7C/4D88MMPbgVzxYoVde6552bf9/PPP7t+o/aLZ+3atapVq5bOPvtsJSTw4Uq0WLhwob788svs8bx589ShQweC+CjhHXCAdowfnxXIb98uX0aGvpU0WdL3km6zrbztj4mV19jjt2+Xv3t3BcaMIZCPEva79ZNPPske20m3nWgTxEcX68Ftya5DDjlETzzxhHtfg3r06OESZPZ31mqTJ02apBkzZoR1vogt9erVcyeHloE/7LDDXLxmZTRFgSA+gtkvFAvcZ8+e7bIHOSUlJalfv34uK2QZ+eeee85ldu2HBdGhdevW7mLsPezdu7fatm0b7mmhALz993c17xamW97nip2Xf0+3/2U95r2EBPl79dJ2+/+5UiWOdYSzjNndd9+dPb7lllvUvn37sM4JBRfswX3ffff9575ff/1V77zzTnawbxvvEMSXjHjJxD/88MO69NJLNWTIEN1+++3q37+/LrjgAldJEaqwpW0vv/zy7P9xjO1cdcYZZ8jbmbXCnllGwQJ4Yx8T2qrm4MeFiD5fffWVCxisZyyiR9KIEXYG5gL4/LBAXtu2Kemtt4p5ZihqVtr4119/qWXLlhzcGPLLL7/ogAMOcLtl2uY7c+bMCfeUEIPq16+vBx54QIsXL9bw4cNd0tUy84888ohrL2kbQEVVEG9B6BdffOGuZ2Zm6tprr3XfoDXAt4DGmuFbdhJ7ZyUZFvwRAEavjz76iAxftPE8JQ0eXKinJr34Il1rovD/0TZt2ig5OTncU0ERsxIHC+CtPDW4Dg3FLx6701jC9fTTT9fYsWNded7555+voUOHqlGjRoXatTVsQXyrVq303XffuetW6G/ZDasztLPg2267zX30cOihh7q68F2xAH/Tpk25LvHo66+/1pIlS9S9e/dwTwWFZH807D0kwxdl1q5VwsKF2TXv+WWPt+dp3bpimxqKvqezrUGilCY22QLDl19+WePHj1fPnj3DPR3EierVq+vmm2926+GszaQF8lFTE9+sWTO3sM8+UrAgPrjAz35JBn9R2ope++as/U5e9hHErurb4ont7mUf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R3Szukx7P4Os9hbRyz6utzZ2QdZ20hbAInpZ/bRdrITRTtBs3QNig5W8+f1+t+4BiBYE8QAAAECUoSYeAAAAiDIE8QAAAECUIYgHAAAAogxBPAAAABBlCOIBAACAKEN/LADYhblz57pLpUqV3J4MAABEEoJ4ADFh/vz5mjNnjrtuO23WrFnT9dovX758gV/rqaee0oMPPqiTTjpJTZs2JYgHAEQc+sQDiAlPPPGE7rzzTnXt2tXtaDxv3jwtW7ZMb7zxhs4888wCvdYhhxyi/v37q1+/fsU2XwAAQkEmHkDMsKz722+/nT2+4IILdPHFF2vlypW5Hrd+/Xp9++23bsfNZs2aqVq1au72rVu36r333tPy5cvdSYC9VosWLdSwYcM9Pi+44+7kyZPVrVs3t1PrggULdNxxx2m//fZz9//555/65ZdftO++++rII490u0MG2e02xxNOOEE//vijVq9erebNm7vH5rVw4UL9/PPPbqdmex2fz5fr/j19HQBA7CCIBxCzTjvtNA0fPlwrVqxQjRo13G3Dhg3Ttdde64LkxMREzZgxQ88884z69Omjbdu2afz48QoEApo5c6YLrC1QtyB+T88zixcv1nnnnaczzjhDS5YsUePGjdWgQQNVr15dl1xyiT744AMdc8wx7tMBO1mYMGGCy/ib0aNH680331SFChVUtWpVNw8L1CdNmqQTTzzRPSYtLc29zpgxY9zJgZ00VKlSRe+//76Sk5OVnp6+168DAIghHgDEgAEDBnhVq1bNddv999/v+f1+b9u2bW78008/eWXLlvVmzpyZ/ZjPPvvMK126tLd06dLs2+x1Ro4cmT3Oz/PmzJnj2a/USy65xMvMzMx+3MMPP+wdccQR3qZNm7Jvu+6667xWrVplj++55x7P5/N5n376afZtF154oXfyySdnj++77z6vWrVq3h9//JF928cff+xt3bo1318HABA7yMQDiBmpqamuBCZYEz9w4EDdddddKl26tLvfst1169Z1WfNFixZZEsPdnpKSounTp+vss8/e5esW5HnXXHNNrhKXoUOHqmXLlvroo4/c8+xi2XZ73o4dO1SqVCn3uEaNGrmFtEFt2rRxNf5B9knAFVdcoQMOOCD7tnbt2hX46wAAYgNBPICYYWUwVg5jpSWzZs1yAa8FvkEWhFuJiZWv5NShQwdVrFhxt69bkOdZV5y8z7WSnLzPtcDfymaCwbWVxuRktewWfAf99ddfOuigg/Y4x/x8HQBAbCCIBxCTC1stoD/llFPUo0cPTZ061d1mNee1atXKtfg1PwryvLwLTe25Xbp00S233KJQWL/6tWvX7nGORfF1AADRgR1bAcQky2S/9NJL+uKLL7Kz05Y5/+6777L7yQdZ15nt27fv9rUK+7zgc4cMGeJKfXKyDjgF0b59e40YMcKVCgXZ4lZb8FqUXwcAEB3IxAOIWYceeqjrHnPHHXe4LHX37t1deYntwGp94K3O/ddff9XEiRP1zTffZNfO51XY55nHHnvMtY60jjE2F2tP+dVXX7ng3zrH5NcjjzyiVq1aqXXr1q4LjgXw9smAtby07jRF9XUAANGBTDyAmGALQ61He17333+/awtpWXQrdRk5cqRbJGqlKbbos169evr+++9dK8ggex27PSg/z6tcubIr3cnbl93KcKz3u7V//Omnn/THH3+4k4Jx48ZlP6ZJkyZq27ZtrufZ6+f8fuzEwV6nU6dOrr2llQt9+OGHKlOmTL6/DgAgdrBjKwAAABBlyMQDAAAAUYYgHgAAAIgyBPEAAABAlCGIBwAAAKIMQTwAAAAQZQjiAQAAgChDEA8AAABEGYJ4AAAAIMoQxAMAAABRhiAeAAAAiDIE8QAAAECUIYgHAAAAFF3+D4NdNY7FAW5xAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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qOC9+uqr7ud169Z5LVq08MqWLet+3rRpk+78vdmzZ8d/f8yYMV7FihWLtH/Ra8855xy3L+nbt69Xu3Zt93jlypVetWrVvF69esVfP3bsWG/WrFlF2vfWnHXWWa7djz322D98R1GQ7Oxs937qS4+xbTru6tat6475qVOnenvssYf3zjvv8Lal4bEX9vaF0XHHHef95z//iV8Patas6S1YsCDZzUpJYT/+wt6+sJk/f75XunRpb9y4ce7+rk2bNl6HDh28zZs3J7tpKSnsx1/Y2xc2zz//vLfPPvu4vor6CeojDBkyJNnNSllhPv7C3Lawat26tde5c2f3eODAgV6NGjW8OXPmJLtZKSk7zY6/7c5oKlmypMv66dKli33//fd27bXXWpUqVaxXr17ueT2naXTKCtIUOH3Vr1/fTUP7448/3POqbXT++ee715cuXdquv/76Iv/9wvbvU4ZRZmame3zcccfZ/PnzXYRVv79+/Xq77bbb4q9Vdpb2V9R9F0T/H1KhQoV/+I4CO+66665zx/fdd99tbdq0sUcffdTat2/vnjvmmGPcl6Z5AlH0+OOPW79+/dzU5p49e9rnn3/uMl01RVujb61bt7ZHHnkk2c0Edrl9993XZW3onqhjx46utqSynIoVK2ZLliyxK664wmWcJ2Z1AFFx2WWXWcWKFePXCWXC6jxJzADU7Iivvvoqqe0EkuGxxx5zNZofeughu+uuu+yzzz6z/fbbz8aNGxfve+hLmeWIllgEZjuVKlXKTcvRl+jAOumkk9wUNNVD0vS37t275/qd5s2bu7pMqp1UqVKlXM/l/TmvxOkNhe2/WrVq7mcFs3wKOKkTri9NkatcubK7icqrqPvOS/WqvvjiC/ddAThN/dOUOmBXUQBYRek1hU6daqV2+5544gl7++23SfVGZB1xxBHu+tSjRw83rbROnTpuuz7rdT3QuXP77be7hS3+/e9/J7u5wC51yy23uA7DkUceaV9++WV8kE5T6jRQpwE0DSrqPKpRowb/OogM1WJVEPaiiy6y+++/35XjSKT7LdWDTZyaDUTFIYccYieffLJ169bNJkyYYAceeKDbvmLFCvf4qquucj9z3Yie7Q40rVmzxgWa/BsR0Q28fv7f//7nDjoFht59913bbbfd4q9RplD58uVdoGfu3LnuQ1mZUKIb/3jDMjNdkMivmSQzZ86MPy5s/4XR76tYt9qgToVof8py2p59q2i4gktvvPGGnXrqqa7AuUY7VAdKFyhgV5gzZ46rD6aRBBXsT6TRBBXFV7AUiCLV6Js+fbobYNACFD51HHQ9k4kTJ+a67gBRoME1DVDUrFkz37XjqaeecoMYolqUXEMQNerXKItJg8d5zw/VwFT/RH0gIIo0iK0+vGb15A22Krik/geiabunzin9TQW8H3zwQVf8WgfZGWec4YJGyuRRAe4OHTq4VNKXX37ZFZS87777XMqpaMSsadOm1q5dO3v//ffdjYxuYBKdcMIJbgTho48+ctMdNNXBV9j+C6PfP+ecc9z0opdeesm1/7TTTnP/X/903xs3bnSF0C6//HIXZJKHH37YdWQS2wzs7BshHbNK7X7rrbfciocKpAIwd5256aab7NNPP3Xf9eVnyfpBJk2N1sqhujYAUaHzQFPjNEVOKwhrQO3JJ5+MP68gkzI51Mk+/PDDXSkBICoUSFJ/RMXx1d/xV9v26T5f04WAKPrkk0/cIIXunZQRrszwxCLg6o/o/FG204YNG5LaVqRQoKlhw4b23//+19W2UHX51157zY4++mg3GqzV50QfxjrgVAejf//+lpGR4W7yffrA1nS0gQMHuqr12k8izfds1KiRCzKpw6ygT7NmzeLPF7Z/vVZzqn1ZWVnu7+l1ovoDt956q/sdBZpUn0BBpqLsO5Eyn5RBorofvjJlyrjRc2U0LVq0aHvfZqDIN0IKMqmzoA98nTcK4upDH4g6Xas0EPDBBx9YgwYN3Oe+rjmvv/56/DXqZKtmoD77C5vGDaQTrRasTD+dH7pP0r2MBhF1Tvh0zij4NHr06FydbCCdaeqPBpn/9a9/uYFvTQNS0FXTTOXnn392nWp/qhAQJaNGjXIlOtQP1iCE+tEqP6MEDr8fPnjwYBeI1UqN1MCMngxVBLeQ0LxOdZZJy0biFM1y5cq5xzouEmtuIYcK8mtpUT+jTvSzLgK6QfKnuCrLSe9j165deftS/NgLe/vCRIMaKnZ82GGHxbf500hbtGjhMplUk0nnR+JrkLrHX9jbFxaaJqpArOprJpYKeO+999zAm7KYNBDn1yxTJ1sLTJx11llJbHX4hf34C3v7wkKdYw1KaMaGTx1p1WPVOaCArDI2VL9swYIF7j3VILNmbSA1j78wty1sNONor732ckEm3w8//OCuK8piSqTkDvVJ8iaVIL2PPwJNCLV0O+GSSRlOkydPdrU4VIdMFwik7rEX9valEi1o4a9AJ5dcckm8eCVS8/gLe/tSLeNJwVpRp2LEiBG8nyl+/IW9falUskBf8vTTT7uMWRXMZ+Xp1D3+wty2VKPFJRSI1aqMypBVKZrGjRsnu1mhtibNjr8dWnUuaPpg1somAIKnQpb+/Gi/sCuA2Kpaqr/h23vvvXlbgP+nKXMqJbBp0yZW0gUS6FrhXy8UhFUHkSATEKMZFccee6wLltStW9cVC0e0hCrQdNBBB7miYgCCl5jaCiBHnTp13BeAgmnqKYCt4xwBcqtVq5b7QnRtdzFwAAAAAAAAIFHkA02rV692Bf20et72Uj11zUM95ZRT7I477nA/d+jQwRYuXLjd+wQAAAAAAEg1oZo6lwwKEKkAbJkyZbZ7H1phQlX0n3jiCatRo4ZlZGRYw4YNrVu3bjZgwIBA2wsUthSvlqjWqiht2rSxgw8+ONfzWpp6/PjxVrVqVTd3unLlyryhiIyZM2e6FbZKlSplZ599dq5VtrSy0GeffeaKVh5zzDF29NFHJ7WtwK6kATKdG1ow4oADDnCrbOleJq9hw4a5VYWuu+46dx0BomDVqlXu2NcKpa1bt861Omnv3r1t/fr1uV5/yy23xAv6Aulu9uzZ9umnn1qJEiXcCqX+tUH9Ea2KnVerVq3s+OOPT0JLsatFOqNp48aN1rdvX7v88st3aD9ffvml67SrY6/VvOTiiy+21157zZYtWxZQa4FtmzBhgiu2N3ToUHczdOmll8ZXCvILHmtZaq2QomNTS1eTdYeoeOihh1zwSJ3kGTNmuKV3/c/nhx9+2H1+T5w40d0wnXzyyXb99dcnu8nALlvl5sQTT7QuXbrY77//bm+88Ya7h8lL541WY7zvvvvcYAYQBQq+1q9f315++WX7888/3cIROkcSA00LFixIahuBZFGShVaSmzRpkv3888/uWrJ48eICX6t7Ll0/dM1BNEQ6o0nLWasSfuLIxPbQiVOvXr1c26pXr+72+84779iVV165gy0FCg+annPOOW5lIE3flL///tt+/fXXeLbGgw8+6IKizZs3ty1btliLFi2sR48e9txzz/H2Iq0pi0+B1u+++84FWEVB1uLFi7vHGlm76aabLDMzdkls166du1nq2rWry1IF0n1F0r/++st1qP3s7unTp+d6ja4nnTp1ctcM7mkQFZs3b3alMDRw98ADD7htun+aNWtWrtfp+WbNmiWplUByKLh02223uXusI444wm1TkEmZTaLMWH35HnnkEVccXIN5iIZIB5o0jcg/MXz333+/CxJp+oSeb9u2rbu5eumll9y0CnXoNfJ92WWXubTySy65xMaOHWtTp051aYO33nqrnXbaaW5fRx11lI0ZM4abMux0OjaVxXTBBRfYU0895TrM6ij7AVA9X61aNRdkkmLFitm5557rMjmAdDdo0CA79dRT3ee3Mpu0DPWZZ54ZX4ZaU+US+VPq1MkA0n3KnO5v+vTp46Y5KDNDwdi8HQENVGi7pg0BUaH7e2W56p7/2WefdeeLpv1olexE77//vuts77///q7fULJkyaS1GdhVdO3Q+aA+h/oTe+65pwssVapUKd9rde7079/frrjiCtcHQTRE+l967ty5+Uarf/rpJ7vxxhtt/vz5LkCkrA+NVKjzrovHeeedZ08//bQb6ZZrrrnGdebVqe/evbs1atQovi/tW38D2BW1Z1QPQAXpdVP0zTffuDphb731lnv+t99+s7333jvX7+hnTaMDonB+6LzQDY6m/Kh2njoKmiaUl0arVV9PHWqWq0a601Sg5cuXu3qVr7/+uhuNVjkB1TDzabqpgrV6DRDFeystGqT+gc6FI4880gYPHhx/jfoDeo0G+5Qd2KBBA/cYiML5ocEJJWQsWbLEBZ4OPPBA14cuaBaRtitoi+iIdEaTivepKGxexx13nD3++OPx9PFXXnnFdciVESLqoGhanLKfdMGpUqWK7bfffi4olUgp6Duymh1QVMq8UGdBtZf8kWgFQJVh53cY8hZ21egCEJXzQx1qBf7VIdCxr3pNGoFTfQGftl999dVuyulXX32V1DYDu4KftadspSFDhrjHKvRdp04dl82hzGx1IjTAVrFiRbfgBBCl80OrU2vwQbVY/XNFZQp0Xog/8CzKmG3atKmrQ6MasEC6nx+LFi1y5Tl0ffD70L169cpXlkM/K2Dr96URDZEONKkqfkE3TcoE8f3444+ujofmaCfatGmT67RodZatUcd/9913D7jVQH5+5oU6BT4FQe+++253rGplxbzZS6pRkzfLCUjX80M1A/xVgBR01bTpX375JdcNk6ZCaxq0gkx8diMK9thj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8kl755134q/xPM8GDRrkMn7+/e9/24ABA9w237p16+zee+91wambbrrJRo4caaeccop7bvPmzdaiRQtbuHBhrr95+umnF3n/eu2oUaPstttuc4/1N5YtW5br91966SXr2LGj+38YMWJEkfed6KSTTrIrrrjCvXbTpk02c+ZMu/32212WU+XKlXfkbQaKnF04f/78fNvnzZtnK1eu5F1EpP3222+5PvvF/6zWtQaIKh3/Og80eJho+fLltmDBgqS1CwiDv/76y+bOnZtvu84NnSMAgJ0QaKpWrZrL5vnkk09yBWAqVKgQf3zppZe6qWMKJCnj6emnn7auXbvGnz/zzDPtq6++cq/bd999XbBnzJgx7jntU0EiBaMSP/DHjh1b5P3rtRdddJHVrl3brr76aps4caJddtll8ef1XK9evezEE0+0s88+2wXKfvzxxyLtOy/tR9PoFGC64IIL7LrrrnOBMmBX0HnTuHHjXNt0LDds2NCys7P5R0CkdevWzX02J3r44YfddaF48eJJaxeQbMWKFbMmTZrYl19+Gd+2ZcsWO/nkk23YsGFJbRuQbJMmTbJDDjkk14DE7Nmz3TYCTQCwDd4Oev31171atWp5lStX9lq1auX16NHDW7p0qXtu2rRpXmZmprd48eL463/66SevRIkS3vr1672JEyd6pUqV8pYsWRJ//sEHH/TKli3rHm/atEnRK2/27Nnx58eMGeNVrFixSPsXvbZ///7x58eOHeuVK1fOPZ4yZYpXvHhx79dff40/v2XLFm/t2rVF2ndBpk+f7pUpU8Zr3Lixt3Hjxu1+XxGTnZ3tjgF96TG2TueR3qdZs2bFj+WmTZt6PXv2dD9/8skn7ivxeEfqHnthb1/YDBgwwDvooIPiP8+dO9crX768N3nyZPfzsmXL3PVh9erVSWxl6gj78Rf29oVNmzZtvHvvvTf+8zPPPOM1aNDA+/vvv93P8+fP9yZMmBD/Gal9/IW9fWGi90f9ge+//z6+rXXr1l7Xrl1zvW706NG5+gzY9nvK8Qekv0zbQarNpK9ff/3VJk+e7OoS9evXz2VS6EsjxR06dMg3XUFpqErV3m+//axq1arx544++ugi/+3C9n/AAQe4n/3vUqVKFZfdocwjTcPbZ599XLaTLyMjw8qUKVPkfed10EEHua+zzjrLSpQoUeT/F2BH6TyqX7++ff3111a3bl178cUXbdGiRXbzzTe755944gmX6q0svm1l5gHp6Nhjj3XTmzWNtFKlStalSxc3Zfqwww5zWRv6uXr16m766fjx43NdF4AonB+jR492j//880+7++67XRkE3QdpkRdle2dlZbn7I72O+xtERdmyZd11QvdWhx9+uL3xxhs2ffp0e/fdd+Ov+eabb6xNmzY2ePBgV2oDAGC2w4EmnwJG+jrttNOsXLlyrkB4xYoV3Y1J9+7d871+7733dqmnmgqXKLGWjNK5FfhRUMi3Zs2a+OPC9u/TPgqy2267ubo2ShHX30pU1H0DYess6GZI9cgUTHrhhResdOnS7rlPP/3UnnnmGabRIZIUhNVnvjoEWhl13Lhx7hok6kzPmDHDfebfcsst9uGHH7rAExCla8cjjzzi7oc0OHHCCSdYq1at3HP777+//fzzz+5eqmXLlq6TrSnZQNTurbS4j2q9PvTQQ66vkzgNWyUzAAAB1WjSjfnAgQNdlo9PN+8KDNWoUcN9MJcqVcoVI1atIn0pY2nKlCnuA7pp06a2evVqNzogGzZscCNn8cYVK2Y1a9aM12zSfpWl4Sts/4XR39fv6wLh00i2Oh87um8gGXRMT5gwwdWi0cibMusAxAYc/Do0N9xwg/Xo0cMFnkSBWQWZ5H//+587d4AoOeqoo2zt2rX23HPPuUymxx57LP6cMjXUydbiJsoIr1OnTlLbCiTr3kqLF9WqVStXUOn99993QdnE+rQAgACKgWt0WN9VhLhBgwaucPadd95pxx13nMsK0g2LimQr2+mII45wgSONJvvT2LSS2+WXX+5GxzRqtvvuu+f6Gz179nSdgiOPPNLd3CQWbS1s/4XR72vFPN1Y6cKh9isLRO3a0X0DyaAAqaZ9vvzyy26qA4Dc58fjjz/uPt+vuuqqfG+NBjq0KIWuX0AUpwfpfkur9Op+J9Grr77qrim6TytZsmTS2gkk69rxyy+/uPIgWhjInynhD4AXdD0BgKjboalzGg3u37+/q/2iektagU5BmcSMH40CKPPJX2L9wAMPdHP8fVpl7pRTTrFZs2a531V69vDhw+PPa9RAK5/o91V3RlRbqaj71760MoRPnQitcucHrJo3b+4uHvryp1f4F5DC9r01zz//fK66U8CuUq9ePZeJp5seHa8AcqgjrY6BVhPNu9KcOhDTpk1zNQaBqJ4fS5Ysybc6ozKd/GzzK6+80j7++GOyZREpKpmhQeh27dpZo0aN4ttVk0n1/D7//HPXV1CpAk0v1WsBIOoCqdGkKQeJH7x5aQqcgkhbU758+XzLsidS0CYxcNOsWbMi7z/va9VWTYNLpADZ1op7F9b2gmzr/wXYmZShp/OpoNpio0aNcoHTdevWuWCrboaAKFGnQIMbGmBIpFpmCjBpGrVqmSlrwx/YAKJA9TJVFF/nQd4BtbPPPju+MIquHdddd12SWgkkhwatN2/ebA8++GCu7SqKrwFy/2vOnDmuGDiBJgAIsBg4gOTRKLQKtKqIqzIMNTUoL6V3//HHH/GONYEmRIVWRVUASYtUJGbE+pYtW+bqCvrTTc8//3wCTYiEjRs3ug7y/fff7+pQKqiUV58+fax3796uHqcWlDj00EOT0lZgV9O14aeffrLrr7/eDUTkLe+hVXz1JSoFokFrCuUDQEyG53mehciqVatcwW1qZMBfZdCfiqkipKojgfw01UGdaN3w3HjjjbxFETj2wt6+MFGNP71XqvmnWhtI/+Mv7O0LCy3goulwKoCv2jOVK1dOdpPSQtiPv7C3LywUgH3vvfdcRp9quCIYHH9ANIQu0AQk4mKEZAn7sRf29iG9hf34C3v7kN7CfvyFvX1Ibxx/QDTs0KpzRaElcaO2Uptid6NHj3bfAQAAAAAAomKnBprGjh1rnTt3dqtgRYlWrevVq5crzAwAAAAAABAVOzXQ1K1bN1ecWIGXqNH/d0ErfwE7i1ZE6dGjh1tqVyvPqaCxCln6Zs2aZa1atXLL72qp3kcffZR/DETG77//bhdccIHttttuVr16dVfY1c861dQRXa+02pzOnaZNm7rVtYCo+OKLL6xJkyZuxTnVaxozZkyB1xi9Rvd0M2fOTEo7gV1N1wldL3R90HRDrSrnL6wilSpVcudE4tfixYv5hwIQecV25io/EyZMsHbt2kXyTVaHfsWKFQXerAE7w7XXXmtvvfWWDR061N0E/etf/7IPP/zQPafVgk4//XTbb7/9bOHChTZkyBBXGPnVV1/lHwNpT4Gk5s2bW8mSJe2HH36wGTNmuM/nuXPnuudV7FUrNSq4pPND58ppp51mixYtSnbTgZ1u5MiRdtZZZ9nll1/uArJvvPGGvf322/lep852QSuaAuns1ltvdav2vvzyy/bnn3/axRdf7K4ZiXSvr4CU/7Xnnnsmrb0AkPbFwJ999ll7/fXX3fQ5n5YI1WiZlpHWcroaOa5Vq5YbJZszZ45bZrd+/fpWokSJXPuaPXu2rV692g466CCXjeHTvg855BD3oa7fr1evnttnog0bNriRN41CqJOdmF3l/762KdtDbalatWqR/rYU1m4tE1y3bl178MEHd/DdjC4KBhbNL7/84o7/yZMnF7j0tJZ2P/PMM10ASqNvftbdt99+SzA0RY+9sLcvTB577DF7/vnnXYCpePHihb5en+n6vP/vf/9rJ5544i5pY6oJ+/EX9vaFibKUtNKvAklbM3XqVGvfvr29++677hqjc0lLuSM1j7+wty8sFHjdd999Xd1VZboWRPdUw4cPt2bNmu3y9qUqjj8gGnZaRtP333/vgjN5lwm96qqr7MADD3TL6WoUbfz48S4Y07ZtWzvvvPNcZ1kjzv7FT0tRayT66quvtjp16thHH30U359GnbUf/Z1LL73UTYdIrIukD34FtTp27Oj2ccQRR9i8efNy/b5qSOmmSfvfZ5994hkehf3tbbXb16BBA/c+ADubzqVq1aq5DCZNDdpjjz2sU6dOtnz5cvf8lClT3PHqB5lE54O2A+lOmUqNGzd2WX4a7NAUiMcff7zA1+qz/6mnnnLXkyOPPHKXtxXY1R0+DTgoU0n3Zgo4KPA0bty4+GuUEavrydNPP51vMA9IZwowZWVluYWNdt99dzcYrbIEymxKdMYZZ7jXafB60KBBSWsvAEQi0LR06VKrXLlyvu0//vijy6DQh7Zu+jXX+Y477nAZThoxu+GGG+yiiy5yr/3888/dPOf58+e7G6Hp06fnW8FOz2uanjrM/fv3d6nfS5YscbVpLrzwQpdNpP0qwFSzZk0X6MqblaRskO+++851PO65555C/7YynLbVbp86/GoLsLPpfFO2kkaZlYX3zTffuEw+/3hftWpVvikPCjrpWGZ1RETh/NAghKYH6fELL7xg9957b66po9qu7FZ1pB944AEbOHAg04SQ9jSFdMuWLTZgwACXha77ntatW7upo34dGg0SakCuTZs2yW4usEvpuqDBBw0ua1aGBpQ1vVqBV9/KlSvdoJ7OF/ULrrnmGncuAUDU7bRAk6YdaNpaXkq93muvvdxjfXCro6tMC2Vk6EtT0KZNm+Y+sJWWrQ9w1ZxRwEYdZQWn8tal8ae0aZRBqcAaiVMgS52GK664wj2XmZnppgqp4KWmRfiUCaXnRKnjCizppmtbf7uwdvvWrVvnRs+BnU2dYwWMevfubVWqVHEFwXXDo6w+Hc8VKlSwv/76K9fv6PjW70WxWD+iRce5ChyrtoYyNlRDT4MFw4YNi79GI9U6h/TZrswNXav0WQ+kMz9DSYMSDRs2dD9rIRNdN0aNGuU61oMHD7Y+ffoku6lA0u6tNPigjCbNfNCAtKZV6x4/72vVD9F15rXXXuNfC0DkxSIsO4E6uqpflJeyfBJHCpRRlHd1Nk1X0wiCgj260dfIgKa/KeCjkTVNefMlTgXyf1YHWsEjfyUInzKs9PfUkVBnXBLnpet3dHPlB5q29rcLa7emMIlGPVT3CdjZ1EHISzdH/vF/2GGHuQwOBZv8zCZl8Wk7EIXzI+8qcjo/ihXLP9aiwQp95quukwYRtlaXA0gHuh4UdJ/inx+TJk2y3377Lde9m2ia3V133eUWlQCieG/FIB0AFMLbSYYPH+7VqlUr17YOHTp4d911V/znKVOmeKVLl/aWLVuW63WrVq3K9d33wQcfeBUrVoz/rMc9e/aM/7xkyRKvVKlS3tdff+1NnDjRy8zM9H7//ff48wMGDPCqVq2a6/fHjBkT/3nGjBkqjO5t2rRpm3+7sHb7mjRp4j333HOFvFPYluzsbPdvoi89RsE2b97sNWzY0Lvgggu8pUuXenPnzvWOOuoo97Ns3LjRq1OnjnfZZZe557/66it3PA8ZMoS3NEWPvbC3L0xmzpzprg2DBg3y1qxZ433xxRde2bJlvbfeess9f/PNN3uffPKJt3LlSm/58uVe//79vZIlS+a6PiC1jr+wty9MHn74YW/ffff1fvjhB3cfc88993hVqlRx91R56dqi91T3S0jd4y/s7QuTpk2beu3bt/f+/PNP77fffvOOP/5478wzz3TPDRs2zOvVq5c3b948d+4MHTrU9Q+4t9o2jj8gGnZaRpPm+Cu7RzWZCloFS7S9Q4cOblWfG2+80Y2YafTsk08+sQkTJrjlQ7XM7rnnnusykF566SVXlDvRk08+6bKSNCKnGktHH320HXPMMe45TY9QTY7bbrvNZSTdeeeddt999xWp/dv624W1W1TnQCuA+cvLAzuTRp4/+OADVxtAK6QohVurzD3yyCPuea2IqGL2mh6hAvk6pjUarcwNIN1pavP777/vrgVa3EHniM4NTZ8TLSqh64OmPYgWmNDnP6sIIQpuueUWl+l9yimnuO+NGjVyK5XmXYUXiKJ33nnHlenQTA0V/Fb9MmW8+n0d3eu3bNnS9TO00IRW3ebeCgDMMhRt2llvRLdu3VyBPNW7EE09Uyf3sssui79Gf15zmUeMGOGmvGklrC5dusSnxGm7nldxbwV4VHTbn5qm17z44os2duxYmzVrlktxVV0afzqcakQpEKV6TdqmGkvt2rWL/21Ng+vVq5dbJUIWLFjgCnqrjpOWwN7W3y6s3aqVoyLjKrCJ7ccSqEiWsB97YW8f0lvYj7+wtw/pLezHX9jbh/TG8QdEw04NNGlkTCu/qc7RziiKraCOih2HbdRZb6kyoTTisffeeye7OSmNixE49jg3ED5h/2wOe/uQ3sJ+/IW9fUhvHH9ANOy0qXOi6TuarhA1KhCoaRcAAAAAAABRkn/JnRSiTCZ/BS0AAAAAAACkcUbTzqZpcwAAAAAAAAiHlM5oAgAAAAAAQHgQaAIAAAAAAEAgCDQBAAAAAAAgEASaAAAAAAAAEAgCTQAAAAAAAAgEgSYAAAAAAAAEgkATAAAAAAAAAkGgCQAAAAAAAIEg0AQAAAAAAIBAEGgCAAAAAABAIDKD2Q2w861Zs4a3GbtMKh1vqdRWpIdUOuZSqa1ID6l0zKVSW5EeOOaAaCDQhJRRrVq1ZDcBCCXODYDzA+D6AQAIC6bOAQAAAAAAIBAZnud5wewKCJ4Oz7Vr1/LWIqmysrIsIyMjVP8KnBsIC84PgPMDSJfrB4BgEGgCAAAAAABAIJg6BwAAAAAAgEAQaAIAAAAAAEAgCDQBAAAAAAAgEASaAAAAAAAAEAgCTQAAAAAAAAgEgSYAAAAAAAAEgkATAAAAAAAAAkGgCQAAAAAAAIEg0AQAAAAAAIBAEGgCAAAAAABAIAg0AQAAAAAAIBAEmgAAAAAAABAIAk0AAAAAAAAIBIEmAAAAAAAABIJAEwAAAAAAAAJBoAkAAAAAAACBINAEAAAAAACAQBBoAgAAAAAAQCAINAEAAAAAACAQBJoAAAAAAAAQCAJNAAAAAAAACASBJgAAAAAAAASCQBMAAAAAAAACQaAJAAAAAAAAgSDQBAAAAAAAgEAQaAIAAAAAAEAgCDQBAAAAAAAgEASaAAAAAAAAEAgCTQAAAAAAAAgEgSYAAAAAAAAEgkATAAAAAAAAAkGgCQAAAAAAAIEg0AQAAAAAAIBAEGgCAAAAAABAIAg0AQAAAAAAIBAEmgAAAAAAABAIAk0AAAAAAAAIBIEmAAAAAAAABIJAEwAAAAAAAAJBoAkAAAAAAACBINAEAAAAAACAQBBoAgAAAAAAQCAINAEAAAAAACAQBJoAAAAAAAAQCAJNAAAAAAAACASBJgAAAAAAAASCQBMAAAAAAAACQaAJAAAAAAAAgSDQBAAAAAAAgEAQaAIAAAAAAEAgCDQBAAAAAAAgEASaAAAAAAAAEAgCTQAAAAAAAAgEgSYAAAAAAAAEgkATAAAAAAAAAkGgCQAAAAAAAIEg0AQAAAAAAIBAEGgCAAAAAACABeH/ALW1dmc+yM6DAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# use ChatGPT to help with the code for the cost matrix and alignment arrows, wrote the code myself\n", + "import matplotlib.pyplot as plt\n", + "\n", + "def plot_cost_matrix(D, operations, response_notes, ref_notes):\n", + " fig, ax = plt.subplots(figsize=(8, 6))\n", + " D_display = D[1:, 1:]\n", + " im = ax.imshow(D_display, origin=\"upper\", aspect=\"auto\", cmap=\"gray_r\")\n", + " plt.colorbar(im, ax=ax, label=\"Accumulated cost\")\n", + " for i in range(D_display.shape[0]):\n", + " for j in range(D_display.shape[1]):\n", + " val = D_display[i, j]\n", + " ax.text(j, i, f\"{val:.0f}\",\n", + " ha=\"center\", va=\"center\", fontsize=8,\n", + " color=\"white\" if val > D_display.max() * 0.5 else \"black\")\n", + " path_rows, path_cols = [], []\n", + " for op in operations:\n", + " if op[\"type\"] in (\"match\", \"replacement\"):\n", + " path_rows.append(op[\"response_idx\"])\n", + " path_cols.append(op[\"reference_idx\"])\n", + " elif op[\"type\"] == \"extra\":\n", + " path_rows.append(op[\"response_idx\"])\n", + " path_cols.append(path_cols[-1] if path_cols else 0)\n", + " elif op[\"type\"] == \"missing\":\n", + " path_rows.append(path_rows[-1] if path_rows else 0)\n", + " path_cols.append(op[\"reference_idx\"])\n", + " ax.plot(path_cols, path_rows, \"ro-\", markersize=10, linewidth=2,\n", + " label=\"Optimal alignment path\")\n", + " ref_labels = [f\"$x_{j}$\" for j in range(len(ref_notes))]\n", + " response_labels = [f\"$y_{i}$\" for i in range(len(response_notes))]\n", + " ax.set_xticks(range(len(ref_labels)))\n", + " ax.set_xticklabels(ref_labels, fontsize=8)\n", + " ax.set_yticks(range(len(response_labels)))\n", + " ax.set_yticklabels(response_labels, fontsize=8)\n", + " ax.set_xlabel(\"Reference\")\n", + " ax.set_ylabel(\"Response\")\n", + " ax.set_title(\"Accumulated Cost Matrix D\")\n", + " ax.legend(loc=\"upper left\", fontsize=8)\n", + " plt.tight_layout()\n", + " plt.show()\n", + "\n", + "\n", + "def plot_alignment_arrows(operations, response_notes, ref_notes):\n", + " fig, ax = plt.subplots(figsize=(12, 3))\n", + " N = len(response_notes)\n", + " M = len(ref_notes)\n", + " for j, note in enumerate(ref_notes):\n", + " ax.add_patch(plt.Rectangle((j - 0.4, 0.75), 0.8, 0.5,\n", + " fill=False, edgecolor=\"black\", linewidth=1.5))\n", + " ax.text(j, 1.0, f\"$x_{{{j+1}}}$\\n{note['pitch']}\",\n", + " ha=\"center\", va=\"center\", fontsize=9)\n", + " for i, note in enumerate(response_notes):\n", + " ax.add_patch(plt.Rectangle((i - 0.4, -0.25), 0.8, 0.5,\n", + " fill=False, edgecolor=\"black\", linewidth=1.5))\n", + " ax.text(i, 0.0, f\"$y_{{{i+1}}}$\\n{note['pitch']}\",\n", + " ha=\"center\", va=\"center\", fontsize=9)\n", + " for op in operations:\n", + " if op[\"type\"] in (\"match\", \"replacement\"):\n", + " colour = \"green\" if op[\"type\"] == \"match\" else \"red\"\n", + " ax.annotate(\"\",\n", + " xy=(op[\"response_idx\"], 0.25),\n", + " xytext=(op[\"reference_idx\"], 0.75),\n", + " arrowprops=dict(arrowstyle=\"<->\", color=colour,\n", + " lw=1.5, mutation_scale=12),\n", + " )\n", + " elif op[\"type\"] == \"missing\":\n", + " j = op[\"reference_idx\"]\n", + " ax.text(j, 1.45, \"missing\", ha=\"center\", va=\"center\",\n", + " fontsize=12, color=\"red\", fontweight=\"bold\")\n", + " elif op[\"type\"] == \"extra\":\n", + " i = op[\"response_idx\"]\n", + " ax.text(i, -0.45, \"extra\", ha=\"center\", va=\"center\",\n", + " fontsize=12, color=\"red\", fontweight=\"bold\")\n", + " ax.text(-0.8, 1.0, \"Sequence X\\n(ref)\", ha=\"right\", va=\"center\", fontsize=9)\n", + " ax.text(-0.8, 0.0, \"Sequence Y\\n(response)\", ha=\"right\", va=\"center\", fontsize=9)\n", + " ax.set_xlim(-1.2, max(N, M) - 0.4)\n", + " ax.set_ylim(-0.7, 1.7)\n", + " ax.axis(\"off\")\n", + " plt.tight_layout()\n", + " plt.show()\n", + "\n", + "result = compare_performance_ED(response, reference)\n", + "plot_cost_matrix(result.D, result.operations, response[\"notes\"], reference[\"notes\"])\n", + "plot_alignment_arrows(result.operations, response[\"notes\"], reference[\"notes\"])" + ] + }, + { + "cell_type": "markdown", + "id": "9e5a7185", + "metadata": {}, + "source": [ + "### Summary" + ] + }, + { + "cell_type": "markdown", + "id": "9ea553da", + "metadata": {}, + "source": [ + "Step 0: Normalise the start times\n", + "- Adjust the start time of the first note to t=0 for both the reference and the response MIDI. This action will help eliminate the time gap between student start recording their practice and start playing the first note (if the gap exists).\n", + "\n", + "Step 1: Align notes using edit distance\n", + "- The purpose of note alignment here is to identify if there is any missing/extra notes. Unlike standard DTW where off-diagonal moves has no cost and every note must be aligned to another, the Edit-distance approach allows a note to be explicitly left unaligned at the cost of gap_penalty, i.e. allows for insertions (extra notes) and deletions (missing notes). Each aligned pair or unmatched element is classified into one of the four operation types according to the moving direction during backtracking:\n", + " - diagonal = match (identical pitch)/replacement (wrong pitch)\n", + " - vertical = extra (additional note played)\n", + " - horizontal = missing (note not played)\n", + " \n", + "Step 2: Estimate the overall tempo trend\n", + "- It is common for students to play at a slower tempo by setting the metronome to a slower pace during practice. Then after becoming more and more familiar with it, they may speed up unconsciously especially for the easier parts. Therefore, to avoid prompting the tempo problem for every single note in these cases, the global drift problem must be separated from local deviations. \n", + "- `estimate_global_timing` fits a linear regression over all matched note pairs: response_start ≈ scale × ref_start + offset, where scale represents the student's overall tempo relative to the reference (greater than 1.0 indicates playing slower; less than 1.0 indicates playing faster), and offset captures any constant time shift.\n", + "- `estimate_global_duration_scale` fits a least-squares regression, this regression line passes through the origin because note duration has no meaningful constant offset term: response_duration ≈ duration_scale × ref_duration, where duration_scale represents the general holding time for notes relative to the reference (greater than 1.0 indicates longer holding; less than 1.0 indicates shorter holding time)\n", + "- To ensure the fitting method is statistically and musically meaningful, if the amount of matched pairs available is smaller than three, the functions will assume there is no global tempo trend. \n", + "- The slow/fast decision mainly depends on the scale for timing.\n", + " \n", + "Step 3: note-level evaluation for matched pairs\n", + "\n", + "Pitch:\n", + "— A note is considered correct if and only if the pitch matches exactly (pitch_diff == 0). \n", + "- The absolute semitone difference is recorded for feedback.\n", + "Timing:\n", + "— The expected start time is predicted from the global trend line, then the relative difference in start time is calculated by ∣response_start − predicted_start∣ / inter-onset interval (IOI) of the reference note, \n", + "a note is considered correct if this relative difference is within a threshold.\n", + "- In the feedback, the amount of difference will be reported instead of directly indicate the correctness, so that the students will not be discourage to include their expressiveness in certain parts of the practice.\n", + "- The first note (ref_idx == 0) is always marked as timing-correct, since normalisation in Step 0 guarantees both sequences start at t = 0.\n", + "Duration \n", + "— The expected duration is predicted from the global duration scale, then the relative difference in duration is calculated by ∣response_duration − predicted_duration∣ / ref_duration, a note is considered correct if this relative difference is within a threshold.\n", + "\n", + "Step 4: Compute summary statistics\n", + "- Summary counts, including total notes missing, extra, wrong pitch, wrong timing, and wrong duration, as well as boolean flags indicating whether all paired notes are correct on each dimension. The global trend parameters (timing_scale, timing_offset, duration_scale) are also included. \n", + "\n", + "Step 5: Generate the human-readable feedback messages from those statistics and note-level feedback\n", + "- Overview provides a summary of the student's overall performance:\n", + " - Tempo judgement based on timing_scale: explicitly states whether the overall tempo is acceptable, too slow, or too fast, along with the duration_scale as supplementary context\n", + " - Total count of pitch errors, missing notes, and extra notes\n", + "- Detail provides actionable, note-level feedback for each issue identified:\n", + " - Missing/extra notes: identifies the specific note and pitch\n", + " - Pitch errors: states the expected and played pitch, and the semitone difference\n", + " - Local timing anomalies: reports the absolute and relative deviation after the global trend has been removed\n", + " - Local duration anomalies: reports the deviation direction and magnitude after the global duration scale has been removed" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "compareMusic", + "language": "python", + "name": "comparemusic" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/poetry.lock b/poetry.lock index 9f71f49..f62ad4e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,26 +1,37 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] [[package]] name = "anyio" -version = "4.4.0" +version = "4.6.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + {file = "anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a"}, + {file = "anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb"}, ] [package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] +trio = ["trio (>=0.26.1)"] [[package]] name = "attrs" @@ -28,18 +39,385 @@ version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] + +[[package]] +name = "backports-tarfile" +version = "1.2.0" +description = "Backport of CPython tarfile module" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\"" +files = [ + {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, + {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] + +[[package]] +name = "boto3" +version = "1.43.29" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "boto3-1.43.29-py3-none-any.whl", hash = "sha256:77c27ada27cdbf619a3bbc41fa9e991caef818d3a2988cf92ea722e107d90108"}, + {file = "boto3-1.43.29.tar.gz", hash = "sha256:354006c512cdb87ef8214a095f2ade961c8145734475cd7a7e6b39260ff5494a"}, +] + +[package.dependencies] +botocore = ">=1.43.29,<1.44.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.18.0,<0.19.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.43.29" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "botocore-1.43.29-py3-none-any.whl", hash = "sha256:5d62f2a03ed279a50207ca2824e009313df15f082b6bb591a095a4f04c7faef3"}, + {file = "botocore-1.43.29.tar.gz", hash = "sha256:dce39d33b707aa162aa3820975f99d7f8f746d46576169fb42ce4f2b3b56b261"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<2.2.0 || >2.2.0,<3" + +[package.extras] +crt = ["awscrt (==0.32.2)"] + +[[package]] +name = "build" +version = "1.5.0" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f"}, + {file = "build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +packaging = ">=24.0" +pyproject_hooks = "*" + +[package.extras] +keyring = ["keyring"] +uv = ["uv (>=0.1.18)"] +virtualenv = ["virtualenv (>=20.17) ; python_version >= \"3.10\" and python_version < \"3.14\"", "virtualenv (>=20.31) ; python_version >= \"3.14\""] + +[[package]] +name = "cachecontrol" +version = "0.14.4" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b"}, + {file = "cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1"}, +] + +[package.dependencies] +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2,<2.0.0" +requests = ">=2.16.0" + +[package.extras] +dev = ["cachecontrol[filecache,redis]", "cheroot (>=11.1.2)", "cherrypy", "codespell", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "certifi" +version = "2026.5.20" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, +] + +[[package]] +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\"" +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, +] + +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, +] + +[[package]] +name = "cleo" +version = "2.1.0" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +optional = false +python-versions = ">=3.7,<4.0" +groups = ["main"] +files = [ + {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, + {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=3.0.0,<4.0.0" [[package]] name = "colorama" @@ -47,24 +425,222 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "os_name == \"nt\" or sys_platform == \"win32\"", dev = "sys_platform == \"win32\""} [[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" optional = false -python-versions = ">=3.7" +python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, ] +[[package]] +name = "cryptography" +version = "49.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.9" +groups = ["main"] +markers = "sys_platform == \"linux\"" +files = [ + {file = "cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68"}, + {file = "cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9"}, + {file = "cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f"}, + {file = "cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459"}, + {file = "cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e"}, + {file = "cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866"}, + {file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8"}, + {file = "cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3"}, + {file = "cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27"}, + {file = "cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61"}, + {file = "cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8"}, + {file = "cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36"}, + {file = "cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e"}, + {file = "cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b"}, + {file = "cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001"}, + {file = "cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b"}, + {file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838"}, + {file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5"}, + {file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615"}, + {file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6"}, + {file = "cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6"}, + {file = "cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493"}, +] + +[package.dependencies] +cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +ssh = ["bcrypt (>=3.1.5)"] + +[[package]] +name = "distlib" +version = "0.4.3" +description = "Distribution utilities" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b"}, + {file = "distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed"}, +] + +[[package]] +name = "dotenv" +version = "0.9.9" +description = "Deprecated package" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9"}, +] + +[package.dependencies] +python-dotenv = "*" + +[[package]] +name = "dulwich" +version = "0.22.8" +description = "Python Git Library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, + {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, + {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8318bc0921d42e3e69f03716f983a301b5ee4c8dc23c7f2c5bbb28581257a9"}, + {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7a0f96a2a87f3b4f7feae79d2ac6b94107d6b7d827ac08f2f331b88c8f597a1"}, + {file = "dulwich-0.22.8-cp310-cp310-win32.whl", hash = "sha256:432a37b25733202897b8d67cdd641688444d980167c356ef4e4dd15a17a39a24"}, + {file = "dulwich-0.22.8-cp310-cp310-win_amd64.whl", hash = "sha256:f3a15e58dac8b8a76073ddca34e014f66f3672a5540a99d49ef6a9c09ab21285"}, + {file = "dulwich-0.22.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0852edc51cff4f4f62976bdaa1d82f6ef248356c681c764c0feb699bc17d5782"}, + {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:826aae8b64ac1a12321d6b272fc13934d8f62804fda2bc6ae46f93f4380798eb"}, + {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ae726f923057d36cdbb9f4fb7da0d0903751435934648b13f1b851f0e38ea1"}, + {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6987d753227f55cf75ba29a8dab69d1d83308ce483d7a8c6d223086f7a42e125"}, + {file = "dulwich-0.22.8-cp311-cp311-win32.whl", hash = "sha256:7757b4a2aad64c6f1920082fc1fccf4da25c3923a0ae7b242c08d06861dae6e1"}, + {file = "dulwich-0.22.8-cp311-cp311-win_amd64.whl", hash = "sha256:12b243b7e912011c7225dc67480c313ac8d2990744789b876016fb593f6f3e19"}, + {file = "dulwich-0.22.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d81697f74f50f008bb221ab5045595f8a3b87c0de2c86aa55be42ba97421f3cd"}, + {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bff1da8e2e6a607c3cb45f5c2e652739589fe891245e1d5b770330cdecbde41"}, + {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9969099e15b939d3936f8bee8459eaef7ef5a86cd6173393a17fe28ca3d38aff"}, + {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:017152c51b9a613f0698db28c67cf3e0a89392d28050dbf4f4ac3f657ea4c0dc"}, + {file = "dulwich-0.22.8-cp312-cp312-win32.whl", hash = "sha256:ee70e8bb8798b503f81b53f7a103cb869c8e89141db9005909f79ab1506e26e9"}, + {file = "dulwich-0.22.8-cp312-cp312-win_amd64.whl", hash = "sha256:dc89c6f14dcdcbfee200b0557c59ae243835e42720be143526d834d0e53ed3af"}, + {file = "dulwich-0.22.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbade3342376be1cd2409539fe1b901d2d57a531106bbae204da921ef4456a74"}, + {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71420ffb6deebc59b2ce875e63d814509f9c1dc89c76db962d547aebf15670c7"}, + {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a626adbfac44646a125618266a24133763bdc992bf8bd0702910d67e6b994443"}, + {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f1476c9c4e4ede95714d06c4831883a26680e37b040b8b6230f506e5ba39f51"}, + {file = "dulwich-0.22.8-cp313-cp313-win32.whl", hash = "sha256:b2b31913932bb5bd41658dd398b33b1a2d4d34825123ad54e40912cfdfe60003"}, + {file = "dulwich-0.22.8-cp313-cp313-win_amd64.whl", hash = "sha256:7a44e5a61a7989aca1e301d39cfb62ad2f8853368682f524d6e878b4115d823d"}, + {file = "dulwich-0.22.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9cd0c67fb44a38358b9fcabee948bf11044ef6ce7a129e50962f54c176d084e"}, + {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b79b94726c3f4a9e5a830c649376fd0963236e73142a4290bac6bc9fc9cb120"}, + {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16bbe483d663944972e22d64e1f191201123c3b5580fbdaac6a4f66bfaa4fc11"}, + {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e02d403af23d93dc1f96eb2408e25efd50046e38590a88c86fa4002adc9849b0"}, + {file = "dulwich-0.22.8-cp39-cp39-win32.whl", hash = "sha256:8bdd9543a77fb01be704377f5e634b71f955fec64caa4a493dc3bfb98e3a986e"}, + {file = "dulwich-0.22.8-cp39-cp39-win_amd64.whl", hash = "sha256:3b6757c6b3ba98212b854a766a4157b9cb79a06f4e1b06b46dec4bd834945b8e"}, + {file = "dulwich-0.22.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7bb18fa09daa1586c1040b3e2777d38d4212a5cdbe47d384ba66a1ac336fcc4c"}, + {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2fda8e87907ed304d4a5962aea0338366144df0df60f950b8f7f125871707f"}, + {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1748cd573a0aee4d530bc223a23ccb8bb5b319645931a37bd1cfb68933b720c1"}, + {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a631b2309feb9a9631eabd896612ba36532e3ffedccace57f183bb868d7afc06"}, + {file = "dulwich-0.22.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:00e7d9a3d324f9e0a1b27880eec0e8e276ff76519621b66c1a429ca9eb3f5a8d"}, + {file = "dulwich-0.22.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f8aa3de93201f9e3e40198725389aa9554a4ee3318a865f96a8e9bc9080f0b25"}, + {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e8da9dd8135884975f5be0563ede02179240250e11f11942801ae31ac293f37"}, + {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc5ce2435fb3abdf76f1acabe48f2e4b3f7428232cadaef9daaf50ea7fa30ee"}, + {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982b21cc3100d959232cadb3da0a478bd549814dd937104ea50f43694ec27153"}, + {file = "dulwich-0.22.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6bde2b13a05cc0ec2ecd4597a99896663544c40af1466121f4d046119b874ce3"}, + {file = "dulwich-0.22.8-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6d446cb7d272a151934ad4b48ba691f32486d5267cf2de04ee3b5e05fc865326"}, + {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f6338e6cf95cd76a0191b3637dc3caed1f988ae84d8e75f876d5cd75a8dd81a"}, + {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e004fc532ea262f2d5f375068101ca4792becb9d4aa663b050f5ac31fda0bb5c"}, + {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bfdbc6fa477dee00d04e22d43a51571cd820cfaaaa886f0f155b8e29b3e3d45"}, + {file = "dulwich-0.22.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ae900c8e573f79d714c1d22b02cdadd50b64286dd7203028f0200f82089e4950"}, + {file = "dulwich-0.22.8-py3-none-any.whl", hash = "sha256:ffc7a02e62b72884de58baaa3b898b7f6427893e79b1289ffa075092efe59181"}, + {file = "dulwich-0.22.8.tar.gz", hash = "sha256:701547310415de300269331abe29cb5717aa1ea377af826bf513d0adfb1c209b"}, +] + +[package.dependencies] +urllib3 = ">=1.25" + [package.extras] -test = ["pytest (>=6)"] +dev = ["mypy (==1.15.0)", "ruff (==0.9.7)"] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, + {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "filelock" +version = "3.29.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767"}, + {file = "filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a"}, +] + +[[package]] +name = "findpython" +version = "0.6.3" +description = "A utility to find python versions on your system" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, + {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, +] + +[package.dependencies] +packaging = ">=20" [[package]] name = "flake8" @@ -72,6 +648,7 @@ version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, @@ -82,12 +659,72 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -96,23 +733,156 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "importlib-metadata" +version = "9.0.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\"" +files = [ + {file = "importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7"}, + {file = "importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=3.4)"] +perf = ["ipython"] +test = ["packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] + [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "installer" +version = "0.7.0" +description = "A library for installing Python wheels." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jaraco-context" +version = "6.1.2" +description = "Useful decorators and context managers" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535"}, + {file = "jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3"}, +] + +[package.dependencies] +"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=3.4)"] +test = ["jaraco.test (>=5.6.0)", "portend", "pytest (>=6,!=8.1.*)"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] + +[[package]] +name = "jaraco-functools" +version = "4.5.0" +description = "Functools like those found in stdlib" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4"}, + {file = "jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03"}, +] + +[package.dependencies] +more_itertools = "*" + +[package.extras] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=3.4)"] +test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] + +[[package]] +name = "jeepney" +version = "0.9.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "sys_platform == \"linux\"" +files = [ + {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, + {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, +] + +[package.extras] +test = ["async-timeout ; python_version < \"3.11\"", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["trio"] + +[[package]] +name = "jmespath" +version = "1.1.0" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64"}, + {file = "jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d"}, +] + [[package]] name = "jsonrpcserver" version = "5.0.9" description = "Process JSON-RPC requests" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "jsonrpcserver-5.0.9.tar.gz", hash = "sha256:a71fb2cfa18541c80935f60987f92755d94d74141248c7438847b96eee5c4482"}, ] @@ -130,6 +900,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -137,7 +908,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -151,6 +922,7 @@ version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, @@ -159,32 +931,70 @@ files = [ [package.dependencies] referencing = ">=0.31.0" +[[package]] +name = "keyring" +version = "25.7.0" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f"}, + {file = "keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b"}, +] + +[package.dependencies] +importlib_metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +"jaraco.context" = "*" +"jaraco.functools" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +completion = ["shtab (>=1.1.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=3.4)"] +test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] +type = ["pygobject-stubs", "pytest-mypy (>=1.0.1)", "shtab", "types-pywin32"] + [[package]] name = "lf-toolkit" version = "0.0.1" description = "" optional = false -python-versions = "^3.9" +python-versions = "^3.11" +groups = ["main"] files = [] develop = false [package.dependencies] -anyio = "4.4.0" -jsonrpcserver = "5.0.9" +anyio = "4.6.0" +boto3 = "^1.42.36" +dotenv = "^0.9.9" +jsonrpcserver = ">=5.0.9" +pillow = "^12.1.0" +poetry-plugin-export = "^1.9.0" +pydantic = "^2.0" +pytest-asyncio = "^1.2.0" pywin32 = {version = "^306", optional = true, markers = "sys_platform == \"win32\""} -sympy = "1.12" +requests = "^2.32.5" +sympy = ">=1.12,<2.0" ujson = "5.10.0" [package.extras] -http = ["fastapi (>=0.111.0,<0.112.0)"] -ipc = ["pywin32 (>=306,<307)"] -parsing = ["antlr4-python3-runtime (==4.13.1)", "lark (==1.1.9)", "latex2sympy @ git+https://github.com/purdue-tlt/latex2sympy.git@1.11.2"] +http = ["fastapi (>=0.115.0,<0.116.0)"] +ipc = ["pywin32 (>=306,<307) ; sys_platform == \"win32\""] +parsing = ["antlr4-python3-runtime (==4.13.2)", "lark (==1.2.2)", "latex2sympy @ git+https://github.com/purdue-tlt/latex2sympy.git@1.12.0"] [package.source] type = "git" url = "https://github.com/lambda-feedback/toolkit-python.git" reference = "main" -resolved_reference = "19704127e57f68fcad22cd9757c8239ca7d88ca2" +resolved_reference = "713f13fec11a1d81668fefc3199942f604ba1505" [[package]] name = "mccabe" @@ -192,17 +1002,31 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "more-itertools" +version = "11.1.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192"}, + {file = "more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d"}, +] + [[package]] name = "mpmath" version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -211,15 +1035,174 @@ files = [ [package.extras] develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] +gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] tests = ["pytest (>=4.6)"] +[[package]] +name = "msgpack" +version = "1.2.0" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "msgpack-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ed8c9495a0f12d17a2b4b69e23f895b88f26aabe40911c86594d3fbddecfff08"}, + {file = "msgpack-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7384859c90b45a28a4b31aa50b49cca84504c9f27df459cea6e072627650dcb"}, + {file = "msgpack-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63b35e8e65f04ff7ad5c9c70885da587c74f51e4b4eb3db624eac6d250e8cf59"}, + {file = "msgpack-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004c5a02acd3eca4e15e1ae7b461c32e3711105a28b1ad78be2f6facff4c523"}, + {file = "msgpack-1.2.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e2032dacb0a973fcbf7bd088415a369dae31c5af40e199d234806be22e86765"}, + {file = "msgpack-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1feb100651fbe4b39826207cb20af065dfbfbfa43b1bafd7eaa2252abf7acfd"}, + {file = "msgpack-1.2.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:82487709d4c597d252311a65370220675fb1cc859e7da9269a3060c03ac02cf6"}, + {file = "msgpack-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0268c67a74f5f913f545a0fdbbfaa3f6ebcf23b4c3209bb99704a2ea87e13f90"}, + {file = "msgpack-1.2.0-cp310-cp310-win32.whl", hash = "sha256:7df87173b0e13ddd134919731f13525dbbf75204145597decf1cb86887ebb492"}, + {file = "msgpack-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6371edb47788fbfd8a22016f9a97b5616dd9849bc50abcbb8e82d38f71efa096"}, + {file = "msgpack-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec35cd3f127f50806aa10c3f74bf27b749f13ddf1d2217964ada8f38042d1653"}, + {file = "msgpack-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:317eb298297121bfad9173d748124a04a36af27b6ac39c2bbc1db1ce57608dcf"}, + {file = "msgpack-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50fe6434de89073273026dd032a62e8b63f8857a261d7a2df5b07c9e72f3a8f7"}, + {file = "msgpack-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106c6d333ff3d4eda075b7d4b9695d1752c5bcc635e40d0dbaf4e276c9ed80e1"}, + {file = "msgpack-1.2.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:67055a611e871cb1bd0acb732f2e9f64ca8155ca0bba1d0a5bb362e7209e5541"}, + {file = "msgpack-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ceec7f8e633d5a4b4a32b0416bef90ee3cd1017ea36247f705e523072e576119"}, + {file = "msgpack-1.2.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7ec5851160a3c2c0f77d68ddec620318cd8e7d88d94f9c058190e8ce0dfa1d31"}, + {file = "msgpack-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd7140f7b09dbe1984a0dff3189375d840247e3e4cf4ac45c5a499b3b599c8d2"}, + {file = "msgpack-1.2.0-cp311-cp311-win32.whl", hash = "sha256:cbfd54018d386da0951c7a2be13de0f58559d251313e613b2155e52ed1cbd8f1"}, + {file = "msgpack-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:653373c4614c31463ba486a67776e4bb396af289921bd5353e209534b71467fa"}, + {file = "msgpack-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:7a260aea1e5e7d6c7f1d9284c7360d29021627b61dc4dd7df144b81210810537"}, + {file = "msgpack-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2d6047ccd11a12c96a69f2bfe026471abef67334c3d0494a93e5310e45140a2"}, + {file = "msgpack-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0347e3ac0dfee99086d3b68fe959da3f5f657c0019ddbaeaaa259a85f8603422"}, + {file = "msgpack-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25552ff1f2ff3dc8333e27eabb94f702da5929ed0e07969688194a3e9f12e151"}, + {file = "msgpack-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0d94420d9d52c56568159a69200af7e45eadb29615fa9d09fada140de1c38c7"}, + {file = "msgpack-1.2.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d16e1f2db4a9eebc07b7cc91898d71e710f2eed8358711a605fee802caff8923"}, + {file = "msgpack-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9cb2e700e85f1e27bbb5c9de6cc1c9a4bc5ac64d5404bdcbcb37a0dc7a947a3"}, + {file = "msgpack-1.2.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:717d0b166dd176a5f786aeafff081f6439680acf5af193eb63e6266c12b04d3d"}, + {file = "msgpack-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e87c7a21654d18111eb1a89bd5c42baba42e61887365d9e89585e112b4203f9e"}, + {file = "msgpack-1.2.0-cp312-cp312-win32.whl", hash = "sha256:967e0c891f5f23ab65762f2e5dc95922759c79f1ef99ef4c7e1fdd863e0d0af9"}, + {file = "msgpack-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:6c23e33cee28dcffa112ae205661da4636fd7b06bd9ad1559a890623b92d060b"}, + {file = "msgpack-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:6eeb771571f63f68045433b1a35c0256b946f31ed62f006997e40b8ad8b735af"}, + {file = "msgpack-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a1d30df1f302f2b7a7404afbac2ab76d510036c34cf34dffb01f704a7288e45"}, + {file = "msgpack-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:581e317112260d8ca488d490cad9290a5682276f309c41c7de237a85ed8799c8"}, + {file = "msgpack-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6827d12eacc16873eba62408a1b7bbe8ecfb4a8f7ed78a631ae9bae6ad43cf2"}, + {file = "msgpack-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a186027e4279efa4c8bf06ce30605498d7d0d3af0fba0b9799dce85a3fd4a93c"}, + {file = "msgpack-1.2.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a96142c14a11cf1a509e8b9aaf72858a3b742b7613e095ce646913e88ce7bd99"}, + {file = "msgpack-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50c220579b68a6085b95408b2eaa486b259520f55d8e363ddc9b5d7ba5a6ac6d"}, + {file = "msgpack-1.2.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4dcb9d12ab100ecacdfaaf37a3d72fe8392eacc7054afc1916b12d1b747c8446"}, + {file = "msgpack-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a804727188ab0ebb237fadb303b743f04925a69d8c3247292d1e33e679767c15"}, + {file = "msgpack-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1a1ac6ae1fe23298f79380e7b144c8a454e5d05616b0096584f353ba2d750114"}, + {file = "msgpack-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c3c80949d79578f9dc85fd9fb91edfe6694e8a729cd5744634d59d8455fdde3"}, + {file = "msgpack-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fcf8f76fa587c2395fd0057c7232dbf071241f9ad280b235adb7ab585289989e"}, + {file = "msgpack-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f854fa1a8b55d75d82ef9a905d9cdbeffdf7897c088f6020bd221867da5e56a5"}, + {file = "msgpack-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e90df581f80f53b372d5d9d9349078d729851a3a0d0bd74f53ccb598d01e45b8"}, + {file = "msgpack-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b276ed50d8ac75d1f134a433ae79af8557d0fa25ee5b4737da533dfc2ce382e8"}, + {file = "msgpack-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:544d972459c92aa32e63b800d07c2d9cf2734a3be29cee3a0b478a622850e9f5"}, + {file = "msgpack-1.2.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a070147cc2cf6b8a891734e0f5c8fe8f70ed8739ab30ba140b058005a6e86af4"}, + {file = "msgpack-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7685e23b0f51745a751629c31713fbefdef8896b31b2bb38299dfa4ae6c0740c"}, + {file = "msgpack-1.2.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b9204daeee8d91a7ae5acf2d2a8e3983be9a3025f38aa21bfaefbd7eea84a7dc"}, + {file = "msgpack-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bfc057248609742ebbabf6bcd27fea4fd99c4980584e613c168c9b002318298f"}, + {file = "msgpack-1.2.0-cp314-cp314-win32.whl", hash = "sha256:a3faa7edf2388337ae849239878e92f0298b4dab4488e4f1834062f9d0c410c9"}, + {file = "msgpack-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:1a3effc392a57744e4681e55d05f97d5ee7b598747d718340a9b4b8a970c40e1"}, + {file = "msgpack-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:56a318f7df6bec7b40928d6b0519961f20a510d8baabf6baa393a70444588f0a"}, + {file = "msgpack-1.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:afa4a65ab2097795e771a74a3a81ea49534aaeba874eaf426a3332268e045ae6"}, + {file = "msgpack-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:409550770632bb28daa70a11d0ed5763f7db38f40b06f7db9f11dd2794d01102"}, + {file = "msgpack-1.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf47e3cd11ce044965a9736a322afdd390b31ed602d1c1b10211d1a841f1d587"}, + {file = "msgpack-1.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:204bc9f5d6e59c1718c0a4a84fc8ff71b5b4562faac257c1a68bca611ecf9b72"}, + {file = "msgpack-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:610154307b27267266368bc1d1c7bb8aeb71da7be9356d403cb2442d9e6399f5"}, + {file = "msgpack-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6799f157bb63e79f11e2e590cfdb28423fc18dd60c270c3914b5b4586ae36f7e"}, + {file = "msgpack-1.2.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:72bd844902cf0a5ac3af2ef742f253cd0b1e5bcd184f49b4fb9a6a1f7bf305e8"}, + {file = "msgpack-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3c0bd450f78d0d81722c80da6cdbf674a856967870a9db2f6c4debc4d8b3c67c"}, + {file = "msgpack-1.2.0-cp314-cp314t-win32.whl", hash = "sha256:378caf74c4c718dfc17590ce68a6d710ed398ff6fcf08237de23b77755730b55"}, + {file = "msgpack-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:553b42598165c4dd3235994fd6e4b0dfb1ce5f3fd33d94ba9609442643015f38"}, + {file = "msgpack-1.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2825bb1da548d214ab8a810906b7dd69a10f3838b615a2cc46e5172d3cb44f6e"}, + {file = "msgpack-1.2.0.tar.gz", hash = "sha256:8e17af38197bf58e7e819041678f6178f4491493f5b8c8580414f40f7c2c3c41"}, +] + +[[package]] +name = "numpy" +version = "2.4.6" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4"}, + {file = "numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d"}, + {file = "numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8"}, + {file = "numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538"}, + {file = "numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47"}, + {file = "numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93"}, + {file = "numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8"}, + {file = "numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6"}, + {file = "numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8"}, + {file = "numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147"}, + {file = "numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577"}, + {file = "numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1"}, + {file = "numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb"}, + {file = "numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41"}, + {file = "numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698"}, + {file = "numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f"}, + {file = "numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853"}, + {file = "numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a"}, + {file = "numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2"}, + {file = "numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45"}, + {file = "numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751"}, + {file = "numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8"}, + {file = "numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0"}, + {file = "numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb"}, + {file = "numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f"}, + {file = "numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3"}, + {file = "numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b"}, + {file = "numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089"}, + {file = "numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a"}, + {file = "numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605"}, + {file = "numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91"}, + {file = "numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359"}, + {file = "numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778"}, + {file = "numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1"}, + {file = "numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe"}, + {file = "numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997"}, + {file = "numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20"}, + {file = "numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d"}, + {file = "numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67"}, + {file = "numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd"}, + {file = "numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab"}, + {file = "numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75"}, + {file = "numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd"}, + {file = "numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079"}, + {file = "numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7"}, + {file = "numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5"}, + {file = "numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096"}, + {file = "numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b"}, + {file = "numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8"}, + {file = "numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402"}, + {file = "numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb"}, + {file = "numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1"}, + {file = "numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261"}, + {file = "numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6"}, + {file = "numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a"}, + {file = "numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e"}, + {file = "numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e"}, + {file = "numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43"}, + {file = "numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e"}, + {file = "numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895"}, + {file = "numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4"}, + {file = "numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063"}, + {file = "numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627"}, + {file = "numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02"}, + {file = "numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73"}, + {file = "numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda"}, +] + [[package]] name = "oslash" version = "0.6.3" description = "OSlash (Ø) for Python 3.8+" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "OSlash-0.6.3-py3-none-any.whl", hash = "sha256:89b978443b7db3ac2666106bdc3680add3c886a6d8fcdd02fd062af86d29494f"}, {file = "OSlash-0.6.3.tar.gz", hash = "sha256:868aeb58a656f2ed3b73d9dd6abe387b20b74fc9413d3e8653b615b15bf728f3"}, @@ -234,17 +1217,176 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pbs-installer" +version = "2025.12.17" +description = "Installer for Python Build Standalone" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pbs_installer-2025.12.17-py3-none-any.whl", hash = "sha256:1a899ac5af9ca4c59a7a7944ec3fcf7ad7e40d5684b12eadcfbeee7c59d44123"}, + {file = "pbs_installer-2025.12.17.tar.gz", hash = "sha256:cf32043fadd168c17a1b18c1c3f801090281bd5c9ce101e2deb7e0e51c8279dd"}, +] + +[package.dependencies] +httpx = {version = ">=0.27.0,<1", optional = true, markers = "extra == \"download\""} +zstandard = {version = ">=0.21.0", optional = true, markers = "extra == \"install\""} + +[package.extras] +all = ["pbs-installer[download,install]"] +download = ["httpx (>=0.27.0,<1)"] +install = ["zstandard (>=0.21.0)"] + +[[package]] +name = "pillow" +version = "12.2.0" +description = "Python Imaging Library (fork)" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f"}, + {file = "pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c"}, + {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3"}, + {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa"}, + {file = "pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032"}, + {file = "pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5"}, + {file = "pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024"}, + {file = "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab"}, + {file = "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176"}, + {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b"}, + {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909"}, + {file = "pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808"}, + {file = "pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60"}, + {file = "pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe"}, + {file = "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5"}, + {file = "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780"}, + {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5"}, + {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5"}, + {file = "pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940"}, + {file = "pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5"}, + {file = "pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c"}, + {file = "pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795"}, + {file = "pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3"}, + {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9"}, + {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795"}, + {file = "pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e"}, + {file = "pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b"}, + {file = "pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06"}, + {file = "pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b"}, + {file = "pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4"}, + {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4"}, + {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea"}, + {file = "pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24"}, + {file = "pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98"}, + {file = "pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295"}, + {file = "pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed"}, + {file = "pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286"}, + {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50"}, + {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104"}, + {file = "pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7"}, + {file = "pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150"}, + {file = "pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1"}, + {file = "pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463"}, + {file = "pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e"}, + {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06"}, + {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43"}, + {file = "pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354"}, + {file = "pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1"}, + {file = "pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e"}, + {file = "pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +xmp = ["defusedxml"] + +[[package]] +name = "pkginfo" +version = "1.12.1.2" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, + {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + +[[package]] +name = "platformdirs" +version = "4.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a"}, + {file = "platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7"}, +] + [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -254,34 +1396,260 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "poetry" +version = "2.1.4" +description = "Python dependency management and packaging made easy." +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "poetry-2.1.4-py3-none-any.whl", hash = "sha256:0019b64d33fed9184a332f7fad60ca47aace4d6a0e9c635cdea21b76e96f32ce"}, + {file = "poetry-2.1.4.tar.gz", hash = "sha256:bed4af5fc87fb145258ac5b1dae77de2cd7082ec494e3b2f66bca0f477cbfc5c"}, +] + +[package.dependencies] +build = ">=1.2.1,<2.0.0" +cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]} +cleo = ">=2.1.0,<3.0.0" +dulwich = ">=0.22.6,<0.23.0" +fastjsonschema = ">=2.18.0,<3.0.0" +findpython = ">=0.6.2,<0.7.0" +installer = ">=0.7.0,<0.8.0" +keyring = ">=25.1.0,<26.0.0" +packaging = ">=24.0" +pbs-installer = {version = ">=2025.1.6,<2026.0.0", extras = ["download", "install"]} +pkginfo = ">=1.12,<2.0" +platformdirs = ">=3.0.0,<5" +poetry-core = "2.1.3" +pyproject-hooks = ">=1.0.0,<2.0.0" +requests = ">=2.26,<3.0" +requests-toolbelt = ">=1.0.0,<2.0.0" +shellingham = ">=1.5,<2.0" +tomlkit = ">=0.11.4,<1.0.0" +trove-classifiers = ">=2022.5.19" +virtualenv = ">=20.26.6,<20.33.0" +xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "2.1.3" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, + {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, +] + +[[package]] +name = "poetry-plugin-export" +version = "1.10.0" +description = "Poetry plugin to export the dependencies to various formats" +optional = false +python-versions = "<4.0,>=3.10" +groups = ["main"] +files = [ + {file = "poetry_plugin_export-1.10.0-py3-none-any.whl", hash = "sha256:fb9b61332718fb91c8d9399edb00fd73cf99de37506adf617fbdf55079bab223"}, + {file = "poetry_plugin_export-1.10.0.tar.gz", hash = "sha256:26ef9df924cd874a825d92d6bc01a5a869a4a28d2f2ebba61d3b5b19c60120f0"}, +] + +[package.dependencies] +poetry = ">=2.1.0,<3.0.0" +poetry-core = ">=2.1.0,<3.0.0" +tomlkit = ">=0.11.4,<1.0.0" + [[package]] name = "pycodestyle" version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] +[[package]] +name = "pycparser" +version = "3.0" +description = "C parser in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "(sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"" +files = [ + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, +] + +[[package]] +name = "pydantic" +version = "2.11.10" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, + {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pyflakes" version = "3.2.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, + {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, +] + [[package]] name = "pytest" version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, @@ -289,21 +1657,71 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, +] + +[package.dependencies] +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.2.2" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, + {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pywin32" version = "306" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, @@ -321,12 +1739,122 @@ files = [ {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.5" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "rapidfuzz-3.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:071d96b957a33b9296b9284b6350a0fb6d030b154a04efd7c15e56b98b79a517"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667f40fe9c81ad129b198d236881b00dd9e8314d9cc72d03c3e16bdfe5879051"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9fff308486bbd2c8c24f25e8e152c7594d3fe8db265a2d6a1ce24d58671127f"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dfa552338f51aec280f17b02d28bace1e162d1a84ccd80e3339a57f98aedb56b"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:068b3e965ca9d9ee4debe40001ae7c3938ba646308afd33cf0c66618147db65c"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88b7d31ff1cc5e9bc0e4406e6b1fa00b6d37163d50bb58091e9b976ff1129faa"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eacb434410b8d9ca99a8d42352ef085cf423e3c76c1f0b86be2fcba3bff2952c"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:649712823f3abcdc48427147a5384fac15623ba435d0013959b52e6462521397"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-win32.whl", hash = "sha256:13cb79c23ef5516e4c4e3830877be8b19aa75203636be1163d690d37803f6504"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:f2073495a7f9b75e57e600747ac09510d67683fd64d3228e009740b7ef88f9fe"}, + {file = "rapidfuzz-3.14.5-cp310-cp310-win_arm64.whl", hash = "sha256:8166efddea49fdbc61185559f47593239e4794fd7c9044dd5a789d1a90af852d"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e251126d48615e1f02b4a178f2cd0cd4f0332b8a019c01a2e10480f7552554b4"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ab449c9abd0d4e1f8145dce0798a4c822a1a1933d613c764a641bea88b8bdab"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb2829fedd672dd7107267189dabe2bbe07972801d636014417c6861eb89e358"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d50e5861872935fece391351cbb5ba21d1bced277cf5e1143d207a0a35f1925"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:7092a216728f80c960bd6b3807275d1ee318b168986bd5dc523349581d4890b8"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9669753caef7fdc6529f6adcc5883ed98d65976445d9322e7dbdb6b697feee13"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:823b1b9d9230809d8edcc18872770764bfe8ef4357995e16744047c8ccf0e489"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f0b2af76b7e7060c09e1a0dfa9410eb19369cbe6164509bff2ef94094b54d2b6"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-win32.whl", hash = "sha256:c5801a89604c65ab4cc9e91b23bc4076d0ca80efd8c976fb63843d7879a85d7f"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:d7ca16637c0ede8243f84074044bd0b2335a0341421f8227c85756de2d18c819"}, + {file = "rapidfuzz-3.14.5-cp311-cp311-win_arm64.whl", hash = "sha256:8c90cdf8516d9057e502aa6003cea71cf5ec27cc44699ca52412b502a04761bb"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d3378f471ef440473a396ce2f8e97ee12f89a78b495540e0a5617bbfe895638"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e910eebca9fd0eba245c0555e764597e8a0cccb673a92da2dc2397050725f48"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01550fe5f60fd176aa66b7611289d46dc4aa4b1b904874c7b6d1d54e581c5ec1"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48bee0b91bebfaec41e1081e351000659ab7570cc4598d617aa04d5bf827f9e6"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:7e580cb04ad849ae9b786fa21383c6b994b6e6c1444ad1cb9f22392759d72741"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09d6c9ba091854f07817055d795d604179c12a8f308ba4c7d56f3719dfea1646"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1e989f86113be66574113b9c7bdf4793f3f863d248e47d911b355e05ca6b6b10"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ebd1a18e2e47bc0b292a07e6ed9c3642f8aaa672d12253885f599b50807a4f9"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-win32.whl", hash = "sha256:9981d38a703b86f0e315a3cd229fd1906fe1d91c989ed121fb975b3c849f89f5"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:d8375e3da319593389727c3187ccaf3e0e84199accc530866b8e0f2b79af05e9"}, + {file = "rapidfuzz-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:478b59bb018a6780d73f33e38d0b3ec5e968a6c1ed42876b993dd456b7aa20e8"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebd8fd343bf8492a1e60bcb6dc99f90f74f65d98d8241a6b3e1fed225b76ecd6"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6737b35d5af7479c5bf9710f7b17edd9d2c43128d974d25fb4ea653e42c64609"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b002c7994cc9f2bc9d9856f0fbaee6e8072c983873846c92f25cefba5b2a925f"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17a34330cd2a538c1ce5d400b61ba358c5b72c654b928ff87b362e88f8b864c7"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:95d937e74c1a7a1287dfb03b62a827be08ede10a155cf1af73bbf47f2b73ee6e"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:46b92a9970dcc34f0096901c792644094cab49554ac3547f35e3aebbdf0a3610"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e012177c8e8a8a0754ae0d6027d63042aa5ff036d9f40f07cb3466a6082e21b8"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a2ae6f53f99c9a0eca7a0afc5b4e45fc73bc1dd4ac74c00509031d76df80ed98"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-win32.whl", hash = "sha256:4a60f0057231188e3bd30216f7b4e0f279b11fa4ec818bb6c1d9f014d1562fbc"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:11bfc2ed8fbe4ab86bd516fadefab126f90e6dcadffa761739fcb304707dfd35"}, + {file = "rapidfuzz-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:b486b5218808f6f4dc471b114b1054e63553db69705c97da0271f47bd706aedd"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39ef8658aaf67d51667e7bdaf7096f432333377d8302ac43c70b5df8a4cf89b8"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ad37a0be705b544af6296da8edddc260d10a8ae5462530fc9991f66498bb1f9"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d45e06f60729e07d9b20c205f7e5cff90b6ef2584e852eecf46e045aea69627d"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52da10236aa6212de71b9e170bace65b64b129c0dea7fc243d6c9ce976f5074"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:440d30faaf682ca496170a7f0cc5453ec942e3e079f0fd802c9a7f938dfb50a3"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:56227a61fd3d17b0cd9793132431f3a3d07c8654be96794ba9f89fe0fc8b2d09"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2e83cd2e25bb4edd97b689d9979d9c3acccdaaf26ceac08212ceece202febcfa"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:af3b859726cd3374287e405e14b9634563c078c5531a4f62375508addebddad1"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-win32.whl", hash = "sha256:8ce1d850b3c0178440efde9e884d98421b5e87ff925f364d6d79e23910d7593f"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c84af70bcf34e99aee894e46a0f1ac77f17d0ef828179c387407642e2466d28a"}, + {file = "rapidfuzz-3.14.5-cp313-cp313t-win_arm64.whl", hash = "sha256:aac0ad28c686a5e72b81668b906c030ee28050b244544b8af68e12fb32543895"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1a31cc6d7d03e7318a0974c038959c59e19c752b81115f2e9138b3331cd64d45"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0298d357e2bc59d572da4db0bc631009b6f8f6c9bc8c11e99a12b833f16b6575"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59b3dba758661a318995655435c6ab20a04ade79fa51e75bc8dc107cac8df280"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4900143d82071bdda533b00300c40b14b963ff826b3642cc463b6dd0f036585e"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:feedf219672eef83ea6be6f3bb093bba396a8560fc75be85ba225f082903df0a"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419e4397a36e2665ec992d8d64c20ba4b2a42500c76ecadeca78a4f19cb9cc32"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:97131ab2be39043054ee28d99e09efe316e6d53449b7e962dfcf3c2de8b2b246"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:593c00dac4e30231c35bf3b4f1da8ec0998762e9e94425586a5d636fcd57f9d0"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-win32.whl", hash = "sha256:0084b687b02b4e569b46d8d6d4ad25659528e6081cd6d067ca453a69035f07e4"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:5dfa89d78f22cd773054caff44827b846161a29f2dcf7e78b8f90d086621e502"}, + {file = "rapidfuzz-3.14.5-cp314-cp314-win_arm64.whl", hash = "sha256:67f3f9d2b444268ab53e47d31bab89954888d23c04c6789f2c727e51fe4b1d13"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:77eac0526899b3c3ad1454bb2b03cdb491d67358ec8ef0c9c48bd61b632b431d"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b9c6bd754d11f6e78ac54e3d86b4b11dc1ba2f13e5fc958899574532897f5a99"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:738c96944d076deeaff70e92b65696ab4f7ecb8081d7791c5403a3257dfaf8ff"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4c1bca487a17fe4226b4ffb2d30e799d2b274d692cffa76bd0746f56235fca3"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:af6a90a4ed2a48fa1a2d17e9d824e6c7c950bea5bad0b707c77fd55751e6bfef"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bf5018938208d4597b2e679a4f8cff9fd252f1df53583130ae56281a21801b64"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c0919d1f89ddf91129906705723118ea09754171e4116f5a5dbc667c7bc9b261"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:93d8da883a35116d6813432177f35e570db5b0a5e30ecb0cbd7cb39c815735df"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-win32.whl", hash = "sha256:0f23e37019ec07712d58976b1ab2b889f8649a7f7c2f626a2f34ea9139e79279"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7d5ca9c7832e6879a707296d1463685f7c243a27846227044504741640caec66"}, + {file = "rapidfuzz-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3e91dcd2549b8f8d843f98ba03a17e01f3d8b72ce942adbbb6761bc58ffce813"}, + {file = "rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:578e6051f6d5e6200c259b47a103cf06bb875ab5814d17333fc0b5c290b22f4c"}, + {file = "rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbf1b8bb2695415b347f3727da1addca2acb82c9b97ac86bebf8b1bead1eb12d"}, + {file = "rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4a8f5cc84c7ad6bffa0e9947b33eb343ad66e6b53e94fe54378a5508c5ed53"}, + {file = "rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c6d85283629646fa87acc22c66b30ea9d4de7f6fdf887daa2e30fa041829b5"}, + {file = "rapidfuzz-3.14.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:dfef96543ced67d9513a422755db422ae1dc34dade0a1485e0b43e7342ed3ebf"}, + {file = "rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e"}, +] + +[package.extras] +all = ["numpy"] + [[package]] name = "referencing" version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, @@ -336,12 +1864,50 @@ files = [ attrs = ">=22.2.0" rpds-py = ">=0.7.0" +[[package]] +name = "requests" +version = "2.34.2" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, + {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, +] + +[package.dependencies] +certifi = ">=2023.5.7" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.26,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "rpds-py" version = "0.20.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, @@ -448,12 +2014,72 @@ files = [ {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, ] +[[package]] +name = "s3transfer" +version = "0.18.0" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "s3transfer-0.18.0-py3-none-any.whl", hash = "sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6"}, + {file = "s3transfer-0.18.0.tar.gz", hash = "sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd"}, +] + +[package.dependencies] +botocore = ">=1.37.4,<2.0a0" + +[package.extras] +crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] + +[[package]] +name = "secretstorage" +version = "3.5.0" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "sys_platform == \"linux\"" +files = [ + {file = "secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137"}, + {file = "secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + [[package]] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -465,6 +2091,7 @@ version = "1.12" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, @@ -474,14 +2101,27 @@ files = [ mpmath = ">=0.19" [[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" +name = "tomlkit" +version = "0.15.0" +description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738"}, + {file = "tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3"}, +] + +[[package]] +name = "trove-classifiers" +version = "2026.6.1.19" +description = "Canonical source for classifiers on PyPI (pypi.org)." +optional = false +python-versions = "*" +groups = ["main"] files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "trove_classifiers-2026.6.1.19-py3-none-any.whl", hash = "sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3"}, + {file = "trove_classifiers-2026.6.1.19.tar.gz", hash = "sha256:c5132b4b61a829d11cfbd2d72e97f20a45ed6edb95e45c5efdeb5e00836b2745"}, ] [[package]] @@ -490,17 +2130,34 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "ujson" version = "5.10.0" description = "Ultra fast JSON encoder and decoder for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, @@ -582,7 +2239,246 @@ files = [ {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"}, ] +[[package]] +name = "urllib3" +version = "2.7.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, + {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, +] + +[package.extras] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] + +[[package]] +name = "virtualenv" +version = "20.32.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56"}, + {file = "virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "xattr" +version = "1.3.0" +description = "Python wrapper for extended filesystem attributes" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "sys_platform == \"darwin\"" +files = [ + {file = "xattr-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a80c4617e08670cdc3ba71f1dbb275c1627744c5c3641280879cb3bc95a07237"}, + {file = "xattr-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51cdaa359f5cd2861178ae01ea3647b56dbdfd98e724a8aa3c04f77123b78217"}, + {file = "xattr-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2fea070768d7d2d25797817bea93bf0a6fda6449e88cfee8bb3d75de9ed11c7b"}, + {file = "xattr-1.3.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69bca34be2d7a928389aff4e32f27857e1c62d04c91ec7c1519b1636870bd58f"}, + {file = "xattr-1.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05f8e068409742d246babba60cff8310b2c577745491f498b08bf068e0c867a3"}, + {file = "xattr-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bbd06987102bc11f5cbd08b15d1029832b862cf5bc61780573fc0828812f01ca"}, + {file = "xattr-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b8589744116d2c37928b771c50383cb281675cd6dcfd740abfab6883e3d4af85"}, + {file = "xattr-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:331a51bf8f20c27822f44054b0d760588462d3ed472d5e52ba135cf0bea510e8"}, + {file = "xattr-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:196360f068b74fa0132a8c6001ce1333f095364b8f43b6fd8cdaf2f18741ef89"}, + {file = "xattr-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:405d2e4911d37f2b9400fa501acd920fe0c97fe2b2ec252cb23df4b59c000811"}, + {file = "xattr-1.3.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ae3a66ae1effd40994f64defeeaa97da369406485e60bfb421f2d781be3b75d"}, + {file = "xattr-1.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:69cd3bfe779f7ba87abe6473fdfa428460cf9e78aeb7e390cfd737b784edf1b5"}, + {file = "xattr-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c5742ca61761a99ae0c522f90a39d5fb8139280f27b254e3128482296d1df2db"}, + {file = "xattr-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a04ada131e9bdfd32db3ab1efa9f852646f4f7c9d6fde0596c3825c67161be3"}, + {file = "xattr-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd4e63614722d183e81842cb237fd1cc978d43384166f9fe22368bfcb187ebe5"}, + {file = "xattr-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:995843ef374af73e3370b0c107319611f3cdcdb6d151d629449efecad36be4c4"}, + {file = "xattr-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa23a25220e29d956cedf75746e3df6cc824cc1553326d6516479967c540e386"}, + {file = "xattr-1.3.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b4345387087fffcd28f709eb45aae113d911e1a1f4f0f70d46b43ba81e69ccdd"}, + {file = "xattr-1.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe92bb05eb849ab468fe13e942be0f8d7123f15d074f3aba5223fad0c4b484de"}, + {file = "xattr-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c42ef5bdac3febbe28d3db14d3a8a159d84ba5daca2b13deae6f9f1fc0d4092"}, + {file = "xattr-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5d66af6523332189108f34e966ca120ff816dfa077ca34b31e6263f8a236"}, + {file = "xattr-1.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:937d8c91f6f372788aff8cc0984c4be3f0928584839aaa15ff1c95d64562071c"}, + {file = "xattr-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e470b3f15e9c3e263662506ff26e73b3027e1c9beac2cbe9ab89cad9c70c0495"}, + {file = "xattr-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f2238b2a973fcbf5fefa1137db97c296d27f4721f7b7243a1fac51514565e9ec"}, + {file = "xattr-1.3.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f32bb00395371f4a3bed87080ae315b19171ba114e8a5aa403a2c8508998ce78"}, + {file = "xattr-1.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:78df56bfe3dd4912548561ed880225437d6d49ef082fe6ccd45670810fa53cfe"}, + {file = "xattr-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:864c34c14728f21c3ef89a9f276d75ae5e31dd34f48064e0d37e4bf0f671fc6e"}, + {file = "xattr-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1fd185b3f01121bd172c98b943f9341ca3b9ea6c6d3eb7fe7074723614d959ff"}, + {file = "xattr-1.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:630c85020282bd0bcb72c3d031491c4e91d7f29bb4c094ebdfb9db51375c5b07"}, + {file = "xattr-1.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:95f1e14a4d9ca160b4b78c527bf2bac6addbeb0fd9882c405fc0b5e3073a8752"}, + {file = "xattr-1.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88557c0769f64b1d014aada916c9630cfefa38b0be6c247eae20740d2d8f7b47"}, + {file = "xattr-1.3.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6992eb5da32c0a1375a9eeacfab15c66eebc8bd34be63ebd1eae80cc2f8bf03"}, + {file = "xattr-1.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da5954424099ca9d402933eaf6112c29ddde26e6da59b32f0bf5a4e35eec0b28"}, + {file = "xattr-1.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:726b4d0b66724759132cacdcd84a5b19e00b0cdf704f4c2cf96d0c08dc5eaeb5"}, + {file = "xattr-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:928c49ceb0c70fc04732e46fa236d7c8281bfc3db1b40875e5f548bb14d2668c"}, + {file = "xattr-1.3.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f3bef26fd2d5d7b17488f4cc4424a69894c5a8ed71dd5f657fbbf69f77f68a51"}, + {file = "xattr-1.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:64f1fb511f8463851e0d97294eb0e0fde54b059150da90582327fb43baa1bb92"}, + {file = "xattr-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1e6c216927b16fd4b72df655d5124b69b2a406cb3132b5231179021182f0f0d1"}, + {file = "xattr-1.3.0-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c0d9ab346cdd20539afddf2f9e123efee0fe8d54254d9fc580b4e2b4e6d77351"}, + {file = "xattr-1.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2c5e7ba0e893042deef4e8638db7a497680f587ac7bd6d68925f29af633dfa6b"}, + {file = "xattr-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e0dabb39596d8d7b83d6f9f7fa30be68cf15bfb135cb633e2aad9887d308a32"}, + {file = "xattr-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5eeaa944516b7507ec51456751334b4880e421de169bbd067c4f32242670d606"}, + {file = "xattr-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03712f84e056dcd23c36db03a1f45417a26eef2c73d47c2c7d425bf932601587"}, + {file = "xattr-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45f85233a51c71659969ce364abe6bd0c9048a302b7fcdbea675dc63071e47ff"}, + {file = "xattr-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fefcf20d040e79ec3bf6e7dc0fdcfd972f70f740d5a69ed67b20c699bb9cea"}, + {file = "xattr-1.3.0-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9e68a02adde8a5f8675be5e8edc837eb6fdbe214a6ee089956fae11d633c0e51"}, + {file = "xattr-1.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:50c12d92f5214b0416cf4b4fafcd02dca5434166657553b74b8ba6abc66cb4b4"}, + {file = "xattr-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c69999ed70411ac2859f1f8c918eb48a6fd2a71ef41dc03ee846f69e2200bb2"}, + {file = "xattr-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b3cf29da6840eb94b881eab692ae83b1421c9c15a0cd92ffb97a0696ceac8cac"}, + {file = "xattr-1.3.0.tar.gz", hash = "sha256:30439fabd7de0787b27e9a6e1d569c5959854cb322f64ce7380fedbfa5035036"}, +] + +[package.dependencies] +cffi = ">=1.16.0" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "zipp" +version = "4.1.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\"" +files = [ + {file = "zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f"}, + {file = "zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=3.4)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] + +[[package]] +name = "zstandard" +version = "0.25.0" +description = "Zstandard bindings for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, + {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0"}, + {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e"}, + {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74"}, + {file = "zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa"}, + {file = "zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e"}, + {file = "zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c"}, + {file = "zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6"}, + {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa"}, + {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7"}, + {file = "zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4"}, + {file = "zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2"}, + {file = "zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137"}, + {file = "zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b"}, + {file = "zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a"}, + {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512"}, + {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa"}, + {file = "zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd"}, + {file = "zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01"}, + {file = "zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9"}, + {file = "zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94"}, + {file = "zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551"}, + {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98"}, + {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf"}, + {file = "zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09"}, + {file = "zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5"}, + {file = "zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049"}, + {file = "zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3"}, + {file = "zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859"}, + {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c"}, + {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088"}, + {file = "zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12"}, + {file = "zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2"}, + {file = "zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d"}, + {file = "zstandard-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0"}, + {file = "zstandard-0.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362"}, + {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388"}, + {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27"}, + {file = "zstandard-0.25.0-cp39-cp39-win32.whl", hash = "sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649"}, + {file = "zstandard-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860"}, + {file = "zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b"}, +] + +[package.extras] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] + [metadata] -lock-version = "2.0" -python-versions = "^3.9" -content-hash = "c5477d016b68fbb728e7211abafd67e0f8d2a0a8c4e5d81c7c39f68105682c25" +lock-version = "2.1" +python-versions = "^3.11" +content-hash = "2ca77bf0f8acc4673958514a8043a5d543bd6029389a4130956cb33a2b21fc63" diff --git a/pyproject.toml b/pyproject.toml index 17de985..91c9adc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,11 +11,12 @@ evaluation_function = "evaluation_function.main:main" evaluation_function_dev = "evaluation_function.dev:dev" [tool.poetry.dependencies] -python = "^3.9" +python = "^3.11" typing_extensions = "^4.12.2" lf_toolkit = { git = "https://github.com/lambda-feedback/toolkit-python.git", branch = "main", extras = [ "ipc", ] } +numpy = "^2.4.6" [tool.poetry.group.dev.dependencies] pytest = "^8.2.2"