From f7255806457e451381a761df81c2672128a700bb Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 09:58:35 +0000 Subject: [PATCH 01/16] style: lint with ruff --- biberplus/__init__.py | 6 +- biberplus/reducer/__init__.py | 2 +- biberplus/reducer/factor_reducer.py | 8 +- biberplus/reducer/pca_reducer.py | 48 +- biberplus/reducer/text_encoder.py | 34 +- biberplus/tagger/__init__.py | 10 +- biberplus/tagger/biber_plus_tagger.py | 1057 ++++++++++------- biberplus/tagger/biber_run_order.py | 192 +-- biberplus/tagger/constants.py | 203 ++-- biberplus/tagger/data_io.py | 14 +- biberplus/tagger/function_words_tagger.py | 12 +- biberplus/tagger/tag_frequencies.py | 87 +- biberplus/tagger/tag_helper.py | 81 +- biberplus/tagger/tagger.py | 50 +- biberplus/tagger/tagger_utils.py | 16 +- corpora_tagging/data_partitioner.py | 6 +- corpora_tagging/parallel_tagging.py | 14 +- experiments/pan/tag_pan_small.py | 34 +- experiments/performance/biber_plus.py | 52 +- experiments/performance/data_utils.py | 55 +- experiments/performance/neurobiber.py | 25 +- .../register_variation/tag_core_corpus.py | 42 +- modeling/biberta/collator.py | 47 +- modeling/biberta/main.py | 102 +- modeling/biberta/models.py | 309 +++-- modeling/build_datasets/combine_datasets.py | 24 +- modeling/build_datasets/contrastive/align.py | 22 +- .../contrastive/convert_to_datasets.py | 10 +- modeling/build_datasets/contrastive/main.py | 45 +- modeling/build_datasets/contrastive/merge.py | 94 +- .../build_datasets/contrastive/partition.py | 68 +- modeling/build_datasets/curate_dataset.py | 128 +- .../spanish_subreddit_sampling.py | 198 +-- modeling/contrastive_training/collator.py | 48 +- modeling/contrastive_training/evaluation.py | 20 +- modeling/contrastive_training/losses.py | 2 +- modeling/contrastive_training/main.py | 105 +- modeling/contrastive_training/models.py | 196 ++- modeling/model_utils.py | 45 +- modeling/neurobiber/downsample.py | 110 +- modeling/neurobiber/evaluate.py | 77 +- modeling/neurobiber/prepare_dataset.py | 386 +++--- modeling/neurobiber/tag_counts.py | 30 +- modeling/neurobiber/tagger.py | 198 ++- modeling/neurobiber/train_model.py | 96 +- modeling/neurobiber/train_utils.py | 116 +- modeling/polybiberta/collators.py | 22 +- modeling/polybiberta/main.py | 114 +- modeling/polybiberta/models.py | 178 ++- .../polybiberta/multilingual_trainer_utils.py | 52 +- modeling/tagger/downsample.py | 32 +- modeling/tagger/prepare_dataset.py | 386 +++--- modeling/tagger/train_model.py | 111 +- modeling/tagger/train_utils.py | 114 +- modeling/trainer_utils.py | 36 +- setup.py | 20 +- tests/tagger/test_additional_features.py | 91 +- tests/tagger/test_coordination.py | 40 +- tests/tagger/test_lexical_classes.py | 87 +- tests/tagger/test_lexical_specificity.py | 40 +- tests/tagger/test_modals.py | 51 +- tests/tagger/test_negations.py | 37 +- tests/tagger/test_nominal_forms.py | 60 +- tests/tagger/test_passives.py | 38 +- tests/tagger/test_place_time_adverbials.py | 82 +- tests/tagger/test_prep_adj_adverbs.py | 44 +- tests/tagger/test_pronouns_proverbs.py | 153 ++- tests/tagger/test_questions.py | 74 +- ...t_reduced_forms_dispreferred_structures.py | 61 +- tests/tagger/test_specialized_verbs.py | 54 +- tests/tagger/test_stative_forms.py | 57 +- tests/tagger/test_subordination_features.py | 189 +-- tests/tagger/test_tag_frequencies.py | 2 +- tests/tagger/test_tense_aspect_markers.py | 76 +- 74 files changed, 4123 insertions(+), 2702 deletions(-) diff --git a/biberplus/__init__.py b/biberplus/__init__.py index c34f807..43e5032 100644 --- a/biberplus/__init__.py +++ b/biberplus/__init__.py @@ -1,8 +1,8 @@ __version__ = "0.1.0" -__author__ = 'Kenan Alkiek' -__credits__ = 'University of Michigan - The Blablablab' +__author__ = "Kenan Alkiek" +__credits__ = "University of Michigan - The Blablablab" from . import tagger from . import reducer -__all__ = ['tagger', 'reducer'] +__all__ = ["tagger", "reducer"] diff --git a/biberplus/reducer/__init__.py b/biberplus/reducer/__init__.py index 12e1f5a..203b4be 100644 --- a/biberplus/reducer/__init__.py +++ b/biberplus/reducer/__init__.py @@ -1,4 +1,4 @@ from .pca_reducer import tags_pca from .text_encoder import encode_text -__all__ = ['tags_pca', 'encode_text'] \ No newline at end of file +__all__ = ["tags_pca", "encode_text"] diff --git a/biberplus/reducer/factor_reducer.py b/biberplus/reducer/factor_reducer.py index da1d8f0..a792823 100644 --- a/biberplus/reducer/factor_reducer.py +++ b/biberplus/reducer/factor_reducer.py @@ -2,15 +2,15 @@ from factor_analyzer import FactorAnalyzer -def tags_factor_analysis(frequencies_df, n_factors=3, name=None, rotation='promax'): +def tags_factor_analysis(frequencies_df, n_factors=3, name=None, rotation="promax"): fa = FactorAnalyzer(rotation=rotation, n_factors=n_factors) - X = frequencies_df.drop('tag', axis=1).values + X = frequencies_df.drop("tag", axis=1).values fa.fit(X) - columns = ['Factor{}'.format(i + 1) for i in range(n_factors)] + columns = ["Factor{}".format(i + 1) for i in range(n_factors)] loadings_df = pd.DataFrame(data=fa.loadings_, columns=columns) if name: - loadings_df['name'] = name + loadings_df["name"] = name return loadings_df, fa diff --git a/biberplus/reducer/pca_reducer.py b/biberplus/reducer/pca_reducer.py index ed2ec2a..6ea3e4c 100644 --- a/biberplus/reducer/pca_reducer.py +++ b/biberplus/reducer/pca_reducer.py @@ -8,51 +8,41 @@ def tags_pca(frequencies_df, components=2, name=None): pca = PCA(n_components=components) - X = frequencies_df.drop('tag', axis=1).values + X = frequencies_df.drop("tag", axis=1).values pca_features = pca.fit_transform(X) - columns = ['PC{}'.format(i + 1) for i in range(components)] + columns = ["PC{}".format(i + 1) for i in range(components)] pca_df = pd.DataFrame(data=pca_features, columns=columns) if name: - pca_df['name'] = name + pca_df["name"] = name return pca_df, pca.explained_variance_ def plot_pca_2d(df): sns.set() - sns.lmplot( - x='PC1', - y='PC2', - data=df, - hue='name', - fit_reg=False, - legend=True - ) - plt.title('2D PCA Graph') + sns.lmplot(x="PC1", y="PC2", data=df, hue="name", fit_reg=False, legend=True) + plt.title("2D PCA Graph") plt.show() def visualize_explained_variance(explained_variance): - plt.bar( - range(1, len(explained_variance) + 1), - explained_variance - ) + plt.bar(range(1, len(explained_variance) + 1), explained_variance) - plt.xlabel('PCA Feature') - plt.ylabel('Explained variance') - plt.title('Feature Explained Variance') + plt.xlabel("PCA Feature") + plt.ylabel("Explained variance") + plt.title("Feature Explained Variance") plt.show() def plot_explained_variance(pca): n_components = pca.n_components_ plt.figure(figsize=(10, 5)) - plt.bar(range(n_components), pca.explained_variance_, align='center') - plt.xticks(range(n_components), ['PC{}'.format(i + 1) for i in range(n_components)]) - plt.ylabel('Explained Variance') - plt.xlabel('Principal Components') - plt.title('Explained Variance of PCA Components') + plt.bar(range(n_components), pca.explained_variance_, align="center") + plt.xticks(range(n_components), ["PC{}".format(i + 1) for i in range(n_components)]) + plt.ylabel("Explained Variance") + plt.xlabel("Principal Components") + plt.title("Explained Variance of PCA Components") plt.show() @@ -60,10 +50,10 @@ def plot_cumulative_explained_variance(pca): n_components = pca.n_components_ explained_variance_ratio_cumsum = np.cumsum(pca.explained_variance_ratio_) plt.figure(figsize=(10, 5)) - plt.plot(range(n_components), explained_variance_ratio_cumsum, marker='o') - plt.xticks(range(n_components), ['PC{}'.format(i + 1) for i in range(n_components)]) - plt.ylabel('Cumulative Explained Variance Ratio') - plt.xlabel('Principal Components') - plt.title('Cumulative Explained Variance Ratio of PCA Components') + plt.plot(range(n_components), explained_variance_ratio_cumsum, marker="o") + plt.xticks(range(n_components), ["PC{}".format(i + 1) for i in range(n_components)]) + plt.ylabel("Cumulative Explained Variance Ratio") + plt.xlabel("Principal Components") + plt.title("Cumulative Explained Variance Ratio of PCA Components") plt.grid(True) plt.show() diff --git a/biberplus/reducer/text_encoder.py b/biberplus/reducer/text_encoder.py index d2aef8e..2de8979 100644 --- a/biberplus/reducer/text_encoder.py +++ b/biberplus/reducer/text_encoder.py @@ -7,18 +7,34 @@ def encode_text(config, text, round_to=10): encodings = {} biber_tags = BIBER_PLUS_TAGS + DOC_TAGS - binary_tags = ['BIN_' + tag for tag in biber_tags] + binary_tags = ["BIN_" + tag for tag in biber_tags] # Split the counts by type - if config['binary_tags']: - binary_frequencies = frequencies_df[frequencies_df['tag'].isin(binary_tags)] - encodings['binary'] = binary_frequencies[['mean', 'std']].to_numpy().flatten().round(round_to).tolist() + if config["binary_tags"]: + binary_frequencies = frequencies_df[frequencies_df["tag"].isin(binary_tags)] + encodings["binary"] = ( + binary_frequencies[["mean", "std"]] + .to_numpy() + .flatten() + .round(round_to) + .tolist() + ) - if config['function_words']: - fw_frequencies = frequencies_df[~frequencies_df['tag'].isin(biber_tags + binary_tags)] - encodings['function_words'] = fw_frequencies.drop('tag', axis=1).to_numpy().flatten().round(round_to).tolist() + if config["function_words"]: + fw_frequencies = frequencies_df[ + ~frequencies_df["tag"].isin(biber_tags + binary_tags) + ] + encodings["function_words"] = ( + fw_frequencies.drop("tag", axis=1) + .to_numpy() + .flatten() + .round(round_to) + .tolist() + ) - frequencies_df = frequencies_df[frequencies_df['tag'].isin(biber_tags)] - encodings['biber'] = frequencies_df.drop('tag', axis=1).to_numpy().flatten().round(round_to).tolist() + frequencies_df = frequencies_df[frequencies_df["tag"].isin(biber_tags)] + encodings["biber"] = ( + frequencies_df.drop("tag", axis=1).to_numpy().flatten().round(round_to).tolist() + ) return encodings diff --git a/biberplus/tagger/__init__.py b/biberplus/tagger/__init__.py index 278360b..eea0097 100644 --- a/biberplus/tagger/__init__.py +++ b/biberplus/tagger/__init__.py @@ -3,5 +3,11 @@ from .tagger import load_config, load_pipeline, tag_text from .tag_frequencies import calculate_tag_frequencies -__all__ = ['load_config', 'load_pipeline', 'tag_text', 'calculate_tag_frequencies', 'BiberPlusTagger', - 'FunctionWordsTagger'] +__all__ = [ + "load_config", + "load_pipeline", + "tag_text", + "calculate_tag_frequencies", + "BiberPlusTagger", + "FunctionWordsTagger", +] diff --git a/biberplus/tagger/biber_plus_tagger.py b/biberplus/tagger/biber_plus_tagger.py index f8dc6a4..c9830da 100644 --- a/biberplus/tagger/biber_plus_tagger.py +++ b/biberplus/tagger/biber_plus_tagger.py @@ -10,9 +10,9 @@ class BiberPlusTagger: def __init__(self, tagged_words, patterns_dict, ttr_n=400): """ - :param tagged_words: Words in the form of a dictionary - :param patterns_dict: Dictionary containing list of words for different patterns. - e.g. public verbs, downtoners, etc. + :param tagged_words: Words in the form of a dictionary + :param patterns_dict: Dictionary containing list of words for different patterns. + e.g. public verbs, downtoners, etc. """ self.tagged_words = tagged_words self.word_count = len(self.tagged_words) @@ -37,11 +37,15 @@ def get_tagging_methods(): """Get all methods that start with 'tag' and sort them by RUN_ORDER.""" attrs = (getattr(self, name) for name in dir(self)) methods = filter(inspect.ismethod, attrs) - tag_methods = [m for m in methods if m.__name__.startswith('tag')] - missing_methods = set([method.__name__ for method in tag_methods]) - set(RUN_ORDER) + tag_methods = [m for m in methods if m.__name__.startswith("tag")] + missing_methods = set([method.__name__ for method in tag_methods]) - set( + RUN_ORDER + ) if missing_methods: - raise ValueError(f"Missing methods in RUN_ORDER: {', '.join(missing_methods)}") + raise ValueError( + f"Missing methods in RUN_ORDER: {', '.join(missing_methods)}" + ) return sorted(tag_methods, key=lambda x: RUN_ORDER.index(x.__name__)) @@ -56,7 +60,7 @@ def get_tagging_methods(): for tag_method in tag_methods: tag = tag_method(tagged_word, previous_words, next_words) if tag: - tagged_word['tags'].append(tag) + tagged_word["tags"].append(tag) self.update_doc_level_stats(tagged_word) @@ -70,142 +74,158 @@ def update_doc_level_stats(self, word): if self.helper.is_adverb(word): self.adverb_count += 1 - self.word_lengths.append(len(word['text'])) + self.word_lengths.append(len(word["text"])) def get_previous_n_words(self, index, n): if index - n < 0: padding = [None] * (n - index) n -= len(padding) - previous_n_words = self.tagged_words[index - n: n][::-1] + previous_n_words = self.tagged_words[index - n : n][::-1] previous_n_words.extend(padding) return previous_n_words else: - return self.tagged_words[index - n: index][::-1] + return self.tagged_words[index - n : index][::-1] def get_next_n_words(self, index, n): - next_n_words = self.tagged_words[index + 1: index + n + 1] + next_n_words = self.tagged_words[index + 1 : index + n + 1] if len(next_n_words) < n: padding = [None] * (n - len(next_n_words)) next_n_words.extend(padding) return next_n_words def get_phrase(self, next_n_words): - curr_word_txt = self.tagged_words[self.current_index]['text'].lower() - next_n_text = [w['text'].lower() for w in next_n_words] + curr_word_txt = self.tagged_words[self.current_index]["text"].lower() + next_n_text = [w["text"].lower() for w in next_n_words] phrase = curr_word_txt + " " + " ".join(next_n_text) return phrase """ A) Tense and Aspect Markers """ def tag_vbd(self, word, previous_words, next_words): - """ Past tense POS """ - if word['xpos'] == 'VBD': - return 'VBD' + """Past tense POS""" + if word["xpos"] == "VBD": + return "VBD" def tag_peas(self, word, previous_words, next_words): - """ Perfect aspect - 1) HAVE + (ADV) + (ADV) + VBD/VBN - 2) HAVE + N/PRO + VBN/VBD - """ + """Perfect aspect + 1) HAVE + (ADV) + (ADV) + VBD/VBN + 2) HAVE + N/PRO + VBN/VBD + """ if not self.helper.is_have(word): return # Direct verb after HAVE: HAVE + VBD/VBN - if next_words[0] and next_words[0]['xpos'] in ['VBD', 'VBN']: + if next_words[0] and next_words[0]["xpos"] in ["VBD", "VBN"]: return "PEAS" # One intervening word: HAVE + (ADV/N/PRO) + VBD/VBN - if next_words[1] and next_words[1]['xpos'] in ['VBD', 'VBN']: - if self.helper.is_adverb(next_words[0]) or self.helper.is_noun(next_words[0]) or self.helper.is_pronoun( - next_words[0]): + if next_words[1] and next_words[1]["xpos"] in ["VBD", "VBN"]: + if ( + self.helper.is_adverb(next_words[0]) + or self.helper.is_noun(next_words[0]) + or self.helper.is_pronoun(next_words[0]) + ): return "PEAS" # Two intervening adverbs: HAVE + (ADV) + (ADV) + VBD/VBN - if next_words[2] and next_words[2]['xpos'] in ['VBD', 'VBN']: - if self.helper.is_adverb(next_words[0]) and self.helper.is_adverb(next_words[1]): + if next_words[2] and next_words[2]["xpos"] in ["VBD", "VBN"]: + if self.helper.is_adverb(next_words[0]) and self.helper.is_adverb( + next_words[1] + ): return "PEAS" def tag_vprt(self, word, previous_words, next_words): - """ Present tense: VBP or VBZ tag""" - if word['xpos'] in ['VBP', 'VBZ']: - return 'VPRT' + """Present tense: VBP or VBZ tag""" + if word["xpos"] in ["VBP", "VBZ"]: + return "VPRT" """ B) PLACE and TIME Adverbials """ def tag_place(self, word, previous_words, next_words): - """ Any item in the place adverbials list that is not a proper noun (NNP) """ - if word['text'].lower() in self.patterns['place_adverbials'] and not self.helper.is_proper_noun(word): - return 'PLACE' + """Any item in the place adverbials list that is not a proper noun (NNP)""" + if word["text"].lower() in self.patterns[ + "place_adverbials" + ] and not self.helper.is_proper_noun(word): + return "PLACE" def tag_time(self, word, previous_words, next_words): - """ Time adverbials with the exception: soon is not a time adverbial if it is followed by the word as """ - word_text = word['text'].lower() + """Time adverbials with the exception: soon is not a time adverbial if it is followed by the word as""" + word_text = word["text"].lower() - if word_text == 'soon' and next_words and next_words[0]['text'].lower() == 'as': + if word_text == "soon" and next_words and next_words[0]["text"].lower() == "as": return - if word_text in self.patterns['time_adverbials']: - return 'TIME' + if word_text in self.patterns["time_adverbials"]: + return "TIME" """ C) Pronouns and pro-verbs """ def tag_fpp1(self, word, previous_words, next_words): - """ Any item of this list: I, me, us, my, we, our, myself, ourselves and their contractions. - Tokenizer separates contractionas """ - if word['text'].lower() in self.patterns['first_person_pronouns']: - return 'FPP1' + """Any item of this list: I, me, us, my, we, our, myself, ourselves and their contractions. + Tokenizer separates contractionas""" + if word["text"].lower() in self.patterns["first_person_pronouns"]: + return "FPP1" def tag_spp2(self, word, previous_words, next_words): - """ Any item of this list: you, your, yourself, yourselves, thy, thee, thyself, thou """ - if word['text'].lower() in self.patterns['second_person_pronouns']: - return 'SPP2' + """Any item of this list: you, your, yourself, yourselves, thy, thee, thyself, thou""" + if word["text"].lower() in self.patterns["second_person_pronouns"]: + return "SPP2" def tag_tpp3(self, word, previous_words, next_words): - """ Any item of this list: she, he, they, her, him, them, his, their, himself, herself, themselves """ - if word['text'].lower() in self.patterns['third_person_pronouns']: - return 'TPP3' + """Any item of this list: she, he, they, her, him, them, his, their, himself, herself, themselves""" + if word["text"].lower() in self.patterns["third_person_pronouns"]: + return "TPP3" def tag_pit(self, word, previous_words, next_words): - """ Any pronoun it. Although not specified in Biber (1988), the present program also tags its and - itself as "Pronoun it". """ - if self.helper.is_pronoun(word) and word['text'].lower() in self.patterns['pronoun_it']: - return 'PIT' + """Any pronoun it. Although not specified in Biber (1988), the present program also tags its and + itself as "Pronoun it".""" + if ( + self.helper.is_pronoun(word) + and word["text"].lower() in self.patterns["pronoun_it"] + ): + return "PIT" def tag_inpr(self, word, previous_words, next_words): - """ Any item of this list: anybody, anyone, anything, everybody, everyone, everything, nobody, - none, nothing, nowhere, somebody, someone, something """ - if word['text'].lower() in self.patterns['indefinite_pronouns']: - return 'INPR' + """Any item of this list: anybody, anyone, anything, everybody, everyone, everything, nobody, + none, nothing, nowhere, somebody, someone, something""" + if word["text"].lower() in self.patterns["indefinite_pronouns"]: + return "INPR" def tag_demp(self, word, previous_words, next_words): - """ Tags as demonstrative pronouns the words: those, this, these, and that when they fit specific patterns. """ + """Tags as demonstrative pronouns the words: those, this, these, and that when they fit specific patterns.""" - if word['text'].lower() in self.patterns['demonstrative_pronouns']: - excluded_tags = {'TOBJ', 'TSUB', 'THAC', 'THVC'} + if word["text"].lower() in self.patterns["demonstrative_pronouns"]: + excluded_tags = {"TOBJ", "TSUB", "THAC", "THVC"} # If the word already has one of the excluded tags, do not process further - if any(tag in word['tags'] for tag in excluded_tags): + if any(tag in word["tags"] for tag in excluded_tags): return if next_words[0]: - following_word = next_words[0]['text'].lower() - following_tag = next_words[0]['xpos'] + following_word = next_words[0]["text"].lower() + following_tag = next_words[0]["xpos"] is_followed_by_verb = self.helper.is_any_verb(next_words[0]) is_followed_by_specifics = following_word in ["'s", "and"] is_followed_by_punct = self.helper.is_punctuation(next_words[0]) # Changed to check any tag starting with 'W' - is_followed_by_wh = following_tag.startswith('W') - - if any([is_followed_by_verb, is_followed_by_specifics, is_followed_by_punct, is_followed_by_wh]): + is_followed_by_wh = following_tag.startswith("W") + + if any( + [ + is_followed_by_verb, + is_followed_by_specifics, + is_followed_by_punct, + is_followed_by_wh, + ] + ): return "DEMP" - def tag_prod(self, word, previous_words, next_words): - """ Pro-verb do. Any form of DO that is used as main verb and, therefore, excluding DO when used as + """Pro-verb do. Any form of DO that is used as main verb and, therefore, excluding DO when used as auxiliary verb. The tagger tags as PROD any DO that is NOT in neither of the following patterns: (a) DO followed by a verb (any tag starting with V) or followed by adverbs (RB), negations and then - a verb (V); (b) DO preceded by a punctuation mark or a WH pronoun """ + a verb (V); (b) DO preceded by a punctuation mark or a WH pronoun""" if not self.helper.is_do(word): return # Exclude DO + Verb @@ -213,24 +233,34 @@ def tag_prod(self, word, previous_words, next_words): return # Exclude DO + Adverb + Verb - if next_words[1] and self.helper.is_adverb(next_words[0]) and self.helper.is_verb(next_words[1]): + if ( + next_words[1] + and self.helper.is_adverb(next_words[0]) + and self.helper.is_verb(next_words[1]) + ): return # Exclude PUNCT + DO and WHP + DO - if previous_words[0] and (self.helper.is_punctuation(previous_words[0]) or - previous_words[0]['xpos'] == 'WP'): + if previous_words[0] and ( + self.helper.is_punctuation(previous_words[0]) + or previous_words[0]["xpos"] == "WP" + ): return # Exclude DO tokens immediately preceded by any WH word (e.g., "How") - if previous_words[0] and previous_words[0]['xpos'].startswith('W'): + if previous_words[0] and previous_words[0]["xpos"].startswith("W"): return # Additional check for auxiliary DO at sentence start: # For example, in "Do you know the answer?" token "Do" is followed by a subject pronoun ("you") # and then a verb ("know"), so we should not tag it as PROD. - if (previous_words[0] is None and next_words[0] and - self.helper.is_subject_pronoun(next_words[0]) and next_words[1] and - self.helper.is_verb(next_words[1])): + if ( + previous_words[0] is None + and next_words[0] + and self.helper.is_subject_pronoun(next_words[0]) + and next_words[1] + and self.helper.is_verb(next_words[1]) + ): return return "PROD" @@ -238,58 +268,81 @@ def tag_prod(self, word, previous_words, next_words): """ D) Questions """ def tag_whqu(self, word, previous_words, next_words): - """ Direct WH-questions. Punctuation + WH word + auxiliary verb. Slightly modified to allow + """Direct WH-questions. Punctuation + WH word + auxiliary verb. Slightly modified to allow for intervening word between punctuation and WH word""" - if word['xpos'][0] == 'W' and next_words[0]: + if word["xpos"][0] == "W" and next_words[0]: # Check if next token is auxiliary or (if not) the token after is auxiliary - if self.helper.is_auxiliary(next_words[0]) or (len(next_words) > 1 and next_words[1] and self.helper.is_auxiliary(next_words[1])): - if self.helper.is_punctuation(previous_words[0]) or (previous_words[1] and self.helper.is_punctuation(previous_words[1])): + if self.helper.is_auxiliary(next_words[0]) or ( + len(next_words) > 1 + and next_words[1] + and self.helper.is_auxiliary(next_words[1]) + ): + if self.helper.is_punctuation(previous_words[0]) or ( + previous_words[1] and self.helper.is_punctuation(previous_words[1]) + ): return "WHQU" - """ E) Nominal Forms """ + def tag_nomz(self, word, previous_words, next_words): - suffixes = ('ity','tion','sion','ment','ness','tions','sions','ments','nesses') - if self.helper.is_noun(word) and word['text'].lower().endswith(suffixes): - if word['text'].lower() not in self.patterns['nominalizations_stop_list']: - return 'NOMZ' - + suffixes = ( + "ity", + "tion", + "sion", + "ment", + "ness", + "tions", + "sions", + "ments", + "nesses", + ) + if self.helper.is_noun(word) and word["text"].lower().endswith(suffixes): + if word["text"].lower() not in self.patterns["nominalizations_stop_list"]: + return "NOMZ" def tag_ger(self, word, previous_words, next_words): - # If it's recognized as a verb in gerund form (VBG) *or* recognized as a noun, + # If it's recognized as a verb in gerund form (VBG) *or* recognized as a noun, # and is 10+ letters ending in "ing" or "ings," then call it 'GER' if ( - (word['xpos'] == 'VBG' or self.helper.is_noun(word)) - and len(word['text']) >= 10 - and word['text'].lower().endswith(('ing','ings')) + (word["xpos"] == "VBG" or self.helper.is_noun(word)) + and len(word["text"]) >= 10 + and word["text"].lower().endswith(("ing", "ings")) ): - return 'GER' - + return "GER" def tag_nn(self, word, previous_words, next_words): - """ Total other nouns. Any noun not tagged as a nominalisation or a gerund. Plural nouns (NNS) and + """Total other nouns. Any noun not tagged as a nominalisation or a gerund. Plural nouns (NNS) and proper nouns (NNP and NNPS) tags are changed to NN and included in this count""" - if self.helper.is_noun(word) and 'NOMZ' not in word['tags'] and 'GER' not in word['tags']: + if ( + self.helper.is_noun(word) + and "NOMZ" not in word["tags"] + and "GER" not in word["tags"] + ): return "NN" """ F) Passives """ def tag_pass(self, word, previous_words, next_words): - """ Agentless passives are tagged for 2 patterns. First, any form BE + 1-2 optional RBs + (VBD|VBN). + """Agentless passives are tagged for 2 patterns. First, any form BE + 1-2 optional RBs + (VBD|VBN). Second any form BE + nominal form (noun|pronoun) + (VBN). Following original Biber which does not allow for intervening negation in this pattern""" if not self.helper.is_be(word): return + def is_past_verb(word): - return word and word['xpos'] in ['VBN', 'VBD'] + return word and word["xpos"] in ["VBN", "VBD"] # BE + VBN/VBD if is_past_verb(next_words[0]): return "PASS" # BE + be + VBN/VBD (handle progressive/passive constructions, e.g. "is being renovated") - if next_words[0] and self.helper.is_be(next_words[0]) and is_past_verb(next_words[1]): + if ( + next_words[0] + and self.helper.is_be(next_words[0]) + and is_past_verb(next_words[1]) + ): return "PASS" # BE + ADV + VBN/VBD @@ -297,26 +350,30 @@ def is_past_verb(word): return "PASS" # BE + N/PRO + VBN/VBD - if (self.helper.is_noun(next_words[0]) or self.helper.is_pronoun(next_words[0])) and is_past_verb( - next_words[1]): + if ( + self.helper.is_noun(next_words[0]) or self.helper.is_pronoun(next_words[0]) + ) and is_past_verb(next_words[1]): return "PASS" # BE + ADV + ADV + VBN/VBD - if self.helper.is_adverb(next_words[0]) and self.helper.is_adverb(next_words[1]) and is_past_verb( - next_words[2]): + if ( + self.helper.is_adverb(next_words[0]) + and self.helper.is_adverb(next_words[1]) + and is_past_verb(next_words[2]) + ): return "PASS" def tag_bypa(self, word, previous_words, next_words): - """ By-passives. PASS are found and the preposition by follows it""" - if 'PASS' in word['tags']: + """By-passives. PASS are found and the preposition by follows it""" + if "PASS" in word["tags"]: for next_word in next_words[:4]: # Check up to the next 4 words - if next_word and next_word['text'].lower() == 'by': - return 'BYPA' + if next_word and next_word["text"].lower() == "by": + return "BYPA" """ G) Stative Forms""" def tag_bema(self, word, previous_words, next_words): - """ Be as main verb (BEMA): BE followed by a (DT), (PRP$) or a (PIN) or an adjective (JJ). + """Be as main verb (BEMA): BE followed by a (DT), (PRP$) or a (PIN) or an adjective (JJ). Allows adverbs or negations to appear between the verb BE and the rest of the pattern. """ @@ -324,234 +381,354 @@ def tag_bema(self, word, previous_words, next_words): return None # If immediately preceded by an existential "there", do not tag BEMA. - if previous_words[0] and previous_words[0]['xpos'] == 'EX': + if previous_words[0] and previous_words[0]["xpos"] == "EX": return None # If the next word is a past-participle (indicative of a passive construction), do not tag BEMA. - if next_words[0] and next_words[0]['xpos'] in ['VBN', 'VBD']: + if next_words[0] and next_words[0]["xpos"] in ["VBN", "VBD"]: return None - valid_tags = ['DT', 'PRP$', 'JJ', 'JJR'] + valid_tags = ["DT", "PRP$", "JJ", "JJR"] # Directly follows BE - if next_words[0] and (next_words[0]['xpos'] in valid_tags or self.helper.is_preposition(next_words[0])): - return 'BEMA' + if next_words[0] and ( + next_words[0]["xpos"] in valid_tags + or self.helper.is_preposition(next_words[0]) + ): + return "BEMA" # Preceded by an adverb or negation - check next_words[0] then next_words[1] - if next_words[1] and next_words[0] and ( - self.helper.is_adverb(next_words[0]) or self.tag_xx0(next_words[0], None, None)): - if next_words[1]['xpos'] in valid_tags or self.helper.is_preposition(next_words[1]): - return 'BEMA' + if ( + next_words[1] + and next_words[0] + and ( + self.helper.is_adverb(next_words[0]) + or self.tag_xx0(next_words[0], None, None) + ) + ): + if next_words[1]["xpos"] in valid_tags or self.helper.is_preposition( + next_words[1] + ): + return "BEMA" def tag_ex(self, word, previous_words, next_words): - """ Existential there from the POS tags""" - if word['xpos'] == 'EX': - return 'EX' + """Existential there from the POS tags""" + if word["xpos"] == "EX": + return "EX" """ H) Subordination Features """ def tag_thvc(self, word, previous_words, next_words): - - if word['text'].lower() != 'that': + if word["text"].lower() != "that": return def is_rel_verb(curr_word): - txt = curr_word['text'].lower() - return txt in (self.patterns['public_verbs'] | self.patterns['private_verbs'] | - self.patterns['suasive_verbs']) + txt = curr_word["text"].lower() + return txt in ( + self.patterns["public_verbs"] + | self.patterns["private_verbs"] + | self.patterns["suasive_verbs"] + ) # Condition 1: Preceded by certain conjunctions or punctuation, followed by specific tags if previous_words[0] and next_words[0]: - prev_word = previous_words[0]['text'].lower() - following_tags = ['DT', 'CD', 'PRP', 'NNS', 'NNP'] - if prev_word in ['and', 'nor', 'but', 'or', 'also'] or self.helper.is_punctuation(previous_words[0]): - if next_words[0]['xpos'] in following_tags or self.helper.is_quantifier(next_words[0]): + prev_word = previous_words[0]["text"].lower() + following_tags = ["DT", "CD", "PRP", "NNS", "NNP"] + if prev_word in [ + "and", + "nor", + "but", + "or", + "also", + ] or self.helper.is_punctuation(previous_words[0]): + if next_words[0]["xpos"] in following_tags or self.helper.is_quantifier( + next_words[0] + ): return "THVC" # Condition 2: Preceded by specific verbs, followed by anything except verbs, punctuation or 'and' - if is_rel_verb(previous_words[0]) or 'SMP' in previous_words[0]['tags']: - exclusions = [self.helper.is_any_verb, self.helper.is_punctuation, lambda w: w['text'].lower() == 'and'] + if is_rel_verb(previous_words[0]) or "SMP" in previous_words[0]["tags"]: + exclusions = [ + self.helper.is_any_verb, + self.helper.is_punctuation, + lambda w: w["text"].lower() == "and", + ] if not any(func(next_words[0]) for func in exclusions): return "THVC" # Condition 3: Preceded by specific verbs and a preposition, and up to four non-noun words if self.helper.is_noun(previous_words[0]): - if previous_words[2] and is_rel_verb(previous_words[2]) and self.helper.is_preposition( - previous_words[1]): + if ( + previous_words[2] + and is_rel_verb(previous_words[2]) + and self.helper.is_preposition(previous_words[1]) + ): return "THVC" # Check for up to 4 intervening words that are not nouns for i in range(1, 5): - prev_n_words = self.get_previous_n_words(self.current_index, n=i + 3) - if prev_n_words[i + 2] and is_rel_verb(prev_n_words[0]) and self.helper.is_preposition( - prev_n_words[1]): - if not any(self.helper.is_noun(w) for w in prev_n_words[2:i + 1]): + prev_n_words = self.get_previous_n_words( + self.current_index, n=i + 3 + ) + if ( + prev_n_words[i + 2] + and is_rel_verb(prev_n_words[0]) + and self.helper.is_preposition(prev_n_words[1]) + ): + if not any( + self.helper.is_noun(w) for w in prev_n_words[2 : i + 1] + ): return "THVC" def tag_thac(self, word, previous_words, next_words): - """ That adjective complements. That preceded by an adjective (JJ or a predicative adjective, PRED).""" - if word['text'].lower() == 'that' and previous_words[0]: - if self.helper.is_adjective(previous_words[0]) or 'PRED' in previous_words[0]['tags']: + """That adjective complements. That preceded by an adjective (JJ or a predicative adjective, PRED).""" + if word["text"].lower() == "that" and previous_words[0]: + if ( + self.helper.is_adjective(previous_words[0]) + or "PRED" in previous_words[0]["tags"] + ): return "THAC" def tag_whcl(self, word, previous_words, next_words): - """ WH-clauses. any public, private or suasive verb followed by any WH word, followed by a word that is + """WH-clauses. any public, private or suasive verb followed by any WH word, followed by a word that is NOT an auxiliary (tag MD for modal verbs, or a form of DO, or a form of HAVE, or a form of BE).""" - verb_tags = {'PUBV', 'PRIV', 'SUAV'} + verb_tags = {"PUBV", "PRIV", "SUAV"} - if word['xpos'].startswith('W') and previous_words[0] and any(tag in previous_words[0]['tags'] for tag in verb_tags): + if ( + word["xpos"].startswith("W") + and previous_words[0] + and any(tag in previous_words[0]["tags"] for tag in verb_tags) + ): if next_words[0] and not self.helper.is_auxiliary(next_words[0]): return "WHCL" def tag_to(self, word, previous_words, next_words): - """ Infinitives: POS tag TO that are not a preposition """ - if word['xpos'] == 'TO': + """Infinitives: POS tag TO that are not a preposition""" + if word["xpos"] == "TO": # Prepositions are 'to's followed by - filter_preps = ['IN', 'CD', 'DT', 'JJ', 'PRP$', 'WP$', 'WDT', 'WP', 'WRB', - 'PDT', 'N', 'NNS', 'NP', 'NPs', 'PRP'] - if next_words[0] and next_words[0]['xpos'] not in filter_preps: - return 'TO' + filter_preps = [ + "IN", + "CD", + "DT", + "JJ", + "PRP$", + "WP$", + "WDT", + "WP", + "WRB", + "PDT", + "N", + "NNS", + "NP", + "NPs", + "PRP", + ] + if next_words[0] and next_words[0]["xpos"] not in filter_preps: + return "TO" def tag_presp(self, word, previous_words, next_words): - """ Present participial clause. - - A word with 'VBG' as xpos. - - Preceded by a punctuation mark. - - Followed by specific xpos values (e.g., PIN, DT, QUAN, etc.).""" + """Present participial clause. + - A word with 'VBG' as xpos. + - Preceded by a punctuation mark. + - Followed by specific xpos values (e.g., PIN, DT, QUAN, etc.).""" + + valid_next_tags = { + "PIN", + "DT", + "QUAN", + "CD", + "WH", + "WP", + "WP$", + "PRP", + "RB", + "WRB", + } - valid_next_tags = {'PIN', 'DT', 'QUAN', 'CD', 'WH', 'WP', 'WP$', 'PRP', 'RB', 'WRB'} + if ( + word["xpos"] == "VBG" + and previous_words[0] + and self.helper.is_punctuation(previous_words[0]) + ): + if next_words[0] and next_words[0]["xpos"] in valid_next_tags: + return "PRESP" - if word['xpos'] == 'VBG' and previous_words[0] and self.helper.is_punctuation(previous_words[0]): - if next_words[0] and next_words[0]['xpos'] in valid_next_tags: - return 'PRESP' def tag_pastp(self, word, previous_words, next_words): - """ Past partcipial clauses: punctuation followed by VBN -> PIN or RB - e.g. 'Built' in a single week, the house would stand for fifty years""" - if (word['xpos'] == 'VBN' and previous_words[0] and self.helper.is_punctuation(previous_words[0]) - and next_words[0] and ( - self.helper.is_adverb(next_words[0]) or self.tag_pin(next_words[0], None, None))): + """Past partcipial clauses: punctuation followed by VBN -> PIN or RB + e.g. 'Built' in a single week, the house would stand for fifty years""" + if ( + word["xpos"] == "VBN" + and previous_words[0] + and self.helper.is_punctuation(previous_words[0]) + and next_words[0] + and ( + self.helper.is_adverb(next_words[0]) + or self.tag_pin(next_words[0], None, None) + ) + ): return "PASTP" def tag_wzpast(self, word, previous_words, next_words): - """ Past participial WHIZ deletion relatives. Characterized by: - - A word with 'VBN' as xpos. - - Preceded by a noun or a quantifier pronoun. - - Followed by a preposition, an adverb, or a form of the verb "BE".""" - if (word['xpos'] == 'VBN' and previous_words[0] and - (self.helper.is_noun(previous_words[0]) or self.helper.is_quantifier_pronoun(previous_words[0])) and - next_words[0] and (self.helper.is_preposition(next_words[0]) or self.helper.is_adverb(next_words[0]) or - self.helper.is_be(next_words[0]))): + """Past participial WHIZ deletion relatives. Characterized by: + - A word with 'VBN' as xpos. + - Preceded by a noun or a quantifier pronoun. + - Followed by a preposition, an adverb, or a form of the verb "BE".""" + if ( + word["xpos"] == "VBN" + and previous_words[0] + and ( + self.helper.is_noun(previous_words[0]) + or self.helper.is_quantifier_pronoun(previous_words[0]) + ) + and next_words[0] + and ( + self.helper.is_preposition(next_words[0]) + or self.helper.is_adverb(next_words[0]) + or self.helper.is_be(next_words[0]) + ) + ): return "WZPAST" def tag_wzpres(self, word, previous_words, next_words): - """ Present participial WHIZ deletion relatives: VBG preceded by an NN - e.g. the 'causing' this decline' is """ - if word['xpos'] == 'VBG': - if previous_words[0] and previous_words[0]['xpos'].startswith('NN'): - return 'WZPRES' + """Present participial WHIZ deletion relatives: VBG preceded by an NN + e.g. the 'causing' this decline' is""" + if word["xpos"] == "VBG": + if previous_words[0] and previous_words[0]["xpos"].startswith("NN"): + return "WZPRES" def tag_tsub(self, word, previous_words, next_words): - """ That relative clauses on subject position. Criteria: - - The word is 'that'. - - Preceded by a noun. - - Followed by a verb or an auxiliary verb, possibly with an intervening adverb or negation.""" - if word['text'].lower() != 'that': + """That relative clauses on subject position. Criteria: + - The word is 'that'. + - Preceded by a noun. + - Followed by a verb or an auxiliary verb, possibly with an intervening adverb or negation.""" + if word["text"].lower() != "that": return None - if previous_words[0] and next_words[0] and self.helper.is_noun(previous_words[0]): + if ( + previous_words[0] + and next_words[0] + and self.helper.is_noun(previous_words[0]) + ): # Check if following word is a verb or auxiliary - if self.helper.is_verb(next_words[0]) or self.helper.is_auxiliary(next_words[0]): + if self.helper.is_verb(next_words[0]) or self.helper.is_auxiliary( + next_words[0] + ): return "TSUB" # Allow for intervening RB or XXO. # Make sure next_words[0] exists before passing it to tag_xx0. - if next_words[1] and next_words[0] and ( - self.tag_xx0(next_words[0], None, None) or self.helper.is_adverb(next_words[0]) + if ( + next_words[1] + and next_words[0] + and ( + self.tag_xx0(next_words[0], None, None) + or self.helper.is_adverb(next_words[0]) + ) ): if self.helper.is_any_verb(next_words[1]): return "TSUB" def tag_tobj(self, word, previous_words, next_words): - """ That relative clauses on object position. Criteria: - - The word is 'that'. - - Preceded by a noun. - - Followed by one of: determiner, subject form of a personal pronoun, - possessive pronoun, the pronoun 'it', an adjective, plural noun, - proper noun, or possessive noun. """ - if word['text'].lower() != 'that' or not previous_words[0] or not self.helper.is_noun(previous_words[0]): + """That relative clauses on object position. Criteria: + - The word is 'that'. + - Preceded by a noun. + - Followed by one of: determiner, subject form of a personal pronoun, + possessive pronoun, the pronoun 'it', an adjective, plural noun, + proper noun, or possessive noun.""" + if ( + word["text"].lower() != "that" + or not previous_words[0] + or not self.helper.is_noun(previous_words[0]) + ): return # Tags for the word following 'that' - allowed_following_tags = {'DT', 'CD', 'PRP', 'PRP$', 'NNS', 'NNP', 'JJ'} + allowed_following_tags = {"DT", "CD", "PRP", "PRP$", "NNS", "NNP", "JJ"} next_word = next_words[0] - if next_word and (next_word['xpos'] in allowed_following_tags or - self.helper.is_quantifier(next_word) or - self.tag_pit(next_word, previous_words[1:], next_words[1:]) or - self.helper.is_possesive_pronoun(next_word)): + if next_word and ( + next_word["xpos"] in allowed_following_tags + or self.helper.is_quantifier(next_word) + or self.tag_pit(next_word, previous_words[1:], next_words[1:]) + or self.helper.is_possesive_pronoun(next_word) + ): return "TOBJ" - def tag_whsub(self, word, previous_words, next_words): - """ WH relative clauses on subject position. Criteria: - - Starts with a WH pronoun. - - Preceded by a word that's NOT a form of ASK or TELL and a noun. - - Followed by a verb or an auxiliary verb, possibly with an intervening adverb or negation """ - if (word['xpos'][0] == 'W' and previous_words[1] - and previous_words[1]['text'].lower() not in self.patterns['ask_tell'] - and self.helper.is_noun(previous_words[0])): - + """WH relative clauses on subject position. Criteria: + - Starts with a WH pronoun. + - Preceded by a word that's NOT a form of ASK or TELL and a noun. + - Followed by a verb or an auxiliary verb, possibly with an intervening adverb or negation""" + if ( + word["xpos"][0] == "W" + and previous_words[1] + and previous_words[1]["text"].lower() not in self.patterns["ask_tell"] + and self.helper.is_noun(previous_words[0]) + ): # NOT ASK/TELL -> Noun -> WP -> (RB/XXO) -> Verb - if (next_words[0] and (self.helper.is_any_verb(next_words[0]) or - (next_words[1] and ( - self.helper.is_adverb(next_words[0]) or self.tag_xx0(next_words[0], None, - None)) - and self.helper.is_any_verb(next_words[1])))): + if next_words[0] and ( + self.helper.is_any_verb(next_words[0]) + or ( + next_words[1] + and ( + self.helper.is_adverb(next_words[0]) + or self.tag_xx0(next_words[0], None, None) + ) + and self.helper.is_any_verb(next_words[1]) + ) + ): return "WHSUB" def tag_whobj(self, word, previous_words, next_words): - """ WH relative clauses on object position. Criteria: - - Starts with a WH pronoun. - - Not preceded by a form of ASK or TELL and followed by a noun. - - Not followed by an adverb, a negation, a verb or an auxiliary verb. """ - if not word['xpos'].startswith('W'): + """WH relative clauses on object position. Criteria: + - Starts with a WH pronoun. + - Not preceded by a form of ASK or TELL and followed by a noun. + - Not followed by an adverb, a negation, a verb or an auxiliary verb.""" + if not word["xpos"].startswith("W"): return - if next_words[0] and not (self.helper.is_adverb(next_words[0]) or self.helper.is_any_verb(next_words[0]) - or self.tag_xx0(next_words[0], previous_words[1:], next_words[1:])): - if previous_words[2] and previous_words[2]['text'].lower() not in self.patterns['ask_tell'] \ - and self.helper.is_noun(previous_words[0]): + if next_words[0] and not ( + self.helper.is_adverb(next_words[0]) + or self.helper.is_any_verb(next_words[0]) + or self.tag_xx0(next_words[0], previous_words[1:], next_words[1:]) + ): + if ( + previous_words[2] + and previous_words[2]["text"].lower() not in self.patterns["ask_tell"] + and self.helper.is_noun(previous_words[0]) + ): return "WHOBJ" def tag_pire(self, word, previous_words, next_words): - """ Pied-piping relatives clauses. Any preposition (PIN) followed by whom, who, whose or which """ - if word['text'].lower() in ['whom', 'who', 'whose', 'which']: - if previous_words[0] and 'PIN' in previous_words[0]['tags']: + """Pied-piping relatives clauses. Any preposition (PIN) followed by whom, who, whose or which""" + if word["text"].lower() in ["whom", "who", "whose", "which"]: + if previous_words[0] and "PIN" in previous_words[0]["tags"]: return "PIRE" def tag_sere(self, word, previous_words, next_words): - """ Sentence relatives. Everytime a punctuation mark is followed by the word which """ - if word['text'].lower() == 'which': + """Sentence relatives. Everytime a punctuation mark is followed by the word which""" + if word["text"].lower() == "which": if previous_words[0] and self.helper.is_punctuation(previous_words[0]): - return 'SERE' + return "SERE" def tag_caus(self, word, previous_words, next_words): - """ Any occurrence of the word because """ - if word['text'].lower() == 'because': - return 'CAUS' + """Any occurrence of the word because""" + if word["text"].lower() == "because": + return "CAUS" def tag_conc(self, word, previous_words, next_words): - """ Any occurrence of the words although, though, tho """ - if word['text'].lower() in self.patterns['concessive_adverbial_subordinators']: - return 'CONC' + """Any occurrence of the words although, though, tho""" + if word["text"].lower() in self.patterns["concessive_adverbial_subordinators"]: + return "CONC" def tag_cond(self, word, previous_words, next_words): - """ Any occurrence of the words if or unless""" - if word['text'].lower() in self.patterns['conditional_adverbial_subordinators']: - return 'COND' + """Any occurrence of the words if or unless""" + if word["text"].lower() in self.patterns["conditional_adverbial_subordinators"]: + return "COND" def tag_osub(self, word, previous_words, next_words): - """ Other adverbial subordinators. Any occurrence of the OSUB words. For multi-word units only tag the first """ - if word['text'].lower() in self.patterns['other_adverbial_subordinators']: + """Other adverbial subordinators. Any occurrence of the OSUB words. For multi-word units only tag the first""" + if word["text"].lower() in self.patterns["other_adverbial_subordinators"]: return "OSUB" if not next_words: @@ -560,39 +737,42 @@ def tag_osub(self, word, previous_words, next_words): # 2 word case if next_words[0]: phrase = self.get_phrase(next_words[:1]) - if phrase in self.patterns['other_adverbial_subordinators']: + if phrase in self.patterns["other_adverbial_subordinators"]: return "OSUB" # 3 word case if next_words[1]: phrase = self.get_phrase(next_words[:2]) - if phrase in self.patterns['other_adverbial_subordinators']: + if phrase in self.patterns["other_adverbial_subordinators"]: return "OSUB" # So that and such that cases - if phrase in ['so that', 'such that']: + if phrase in ["so that", "such that"]: # Cannot be followed by a noun or adjective - if not (self.helper.is_noun(next_words[1]) or self.helper.is_adjective(next_words[1])): + if not ( + self.helper.is_noun(next_words[1]) + or self.helper.is_adjective(next_words[1]) + ): return "OSUB" """ I) Prepositional Phrases, Adjectives, and Adverbs""" def tag_pin(self, word, previous_words, next_words): - """ Total prepositional phrases """ + """Total prepositional phrases""" if self.helper.is_preposition(word): return "PIN" def tag_jj(self, word, previous_words, next_words): - """ Attributive adjectives """ - if word['xpos'] in ['JJ', 'JJR', 'JJS']: - return 'JJ' + """Attributive adjectives""" + if word["xpos"] in ["JJ", "JJR", "JJS"]: + return "JJ" def tag_pred(self, word, previous_words, next_words): - """ Predicative adjectives. Any form of BE followed by an adjective (JJ) followed by a word that is NOT + """Predicative adjectives. Any form of BE followed by an adjective (JJ) followed by a word that is NOT another adjective, an adverb (RB) or a noun (N). If any adverb or negation is intervening between the adjective and the word after it, the tag is still assigned. An adjective is tagged as predicative if it is - preceded by another predicative adjective followed by a phrasal coordinator e.g. the horse is big and fast """ + preceded by another predicative adjective followed by a phrasal coordinator e.g. the horse is big and fast""" if not self.helper.is_adjective(word): return None @@ -600,31 +780,40 @@ def tag_pred(self, word, previous_words, next_words): # Handle direct BE->ADJ case if previous_words[0] and self.helper.is_be(previous_words[0]): if next_words[0] and not ( - self.helper.is_adjective(next_words[0]) or - self.helper.is_adverb(next_words[0]) or - self.helper.is_noun(next_words[0]) + self.helper.is_adjective(next_words[0]) + or self.helper.is_adverb(next_words[0]) + or self.helper.is_noun(next_words[0]) ): return "PRED" # Allow for intervening negation/adverb. Ensure next_words[0] exists. - if len(next_words) > 1 and next_words[0] and ( - self.tag_xx0(next_words[0], None, None) or self.helper.is_adverb(next_words[0]) + if ( + len(next_words) > 1 + and next_words[0] + and ( + self.tag_xx0(next_words[0], None, None) + or self.helper.is_adverb(next_words[0]) + ) ): if next_words[1] and not ( - self.helper.is_adjective(next_words[1]) or - self.helper.is_noun(next_words[1]) or - self.helper.is_adverb(next_words[1]) + self.helper.is_adjective(next_words[1]) + or self.helper.is_noun(next_words[1]) + or self.helper.is_adverb(next_words[1]) ): return "PRED" # Handle phrasal coordinator case - if previous_words[1] and 'PRED' in previous_words[1]['tags'] and 'PHC' in previous_words[0]['tags']: + if ( + previous_words[1] + and "PRED" in previous_words[1]["tags"] + and "PHC" in previous_words[0]["tags"] + ): return "PRED" def tag_rb(self, word, previous_words, next_words): - """ Any adverb i.e. POS tags RB, RBS, RBR, WRB""" + """Any adverb i.e. POS tags RB, RBS, RBR, WRB""" if self.helper.is_adverb(word): - return 'RB' + return "RB" """ J) Lexical Specificity """ @@ -632,73 +821,80 @@ def compute_type_token_ratio(self): uniq_vocab = set() for i in range(self.ttr_n): - uniq_vocab.add(self.tagged_words[i]['text'].lower()) + uniq_vocab.add(self.tagged_words[i]["text"].lower()) self.ttr = len(uniq_vocab) / self.ttr_n """ K) Lexical Classes """ def tag_conj(self, word, previous_words, next_words): - """ Conjuncts find any item in the conjuncts list with preceding punctuation. + """Conjuncts find any item in the conjuncts list with preceding punctuation. Only the first word is tagged. """ - word_text = word['text'].lower() - if word_text in self.patterns['conjucts']: - return 'CONJ' + word_text = word["text"].lower() + if word_text in self.patterns["conjucts"]: + return "CONJ" # Specific words that should be tagged as CONJ when preceded by punctuation - punctuation_sensitive_conjuncts = ['altogether', 'rather'] - if previous_words and previous_words[0] and self.helper.is_punctuation(previous_words[0]) and word_text in punctuation_sensitive_conjuncts: - return 'CONJ' + punctuation_sensitive_conjuncts = ["altogether", "rather"] + if ( + previous_words + and previous_words[0] + and self.helper.is_punctuation(previous_words[0]) + and word_text in punctuation_sensitive_conjuncts + ): + return "CONJ" def tag_dwnt(self, word, previous_words, next_words): - """ Any instance of the words in the downtowners list """ - if word['text'].lower() in self.patterns['downtoners']: - return 'DWNT' + """Any instance of the words in the downtowners list""" + if word["text"].lower() in self.patterns["downtoners"]: + return "DWNT" def tag_amp(self, word, previous_words, next_words): - """ Any instance of the items in the amplifiers list """ - if word['text'].lower() in self.patterns['amplifiers']: - return 'AMP' + """Any instance of the items in the amplifiers list""" + if word["text"].lower() in self.patterns["amplifiers"]: + return "AMP" def tag_dpar(self, word, previous_words, next_words): - """ Discourse particle: the words well, now, anyhow, anyways preceded by a punctuation mark """ - if word['text'].lower() in self.patterns['discourse_particles']: + """Discourse particle: the words well, now, anyhow, anyways preceded by a punctuation mark""" + if word["text"].lower() in self.patterns["discourse_particles"]: if previous_words[0] and self.helper.is_punctuation(previous_words[0]): - return 'DPAR' + return "DPAR" def tag_hdg(self, word, previous_words, next_words): - """ Hedges. Any hedge token. In cases of multi-word units such as more or less, only the first word is + """Hedges. Any hedge token. In cases of multi-word units such as more or less, only the first word is tagged as HDG. For the terms sort of and kind of these two items must be preceded by a determiner (DT), a quantifier (QUAN), a cardinal number (CD), an adjective (JJ or PRED), a possessive pronouns (PRP$) or WH word (see entry on WH-questions) """ # One word hedges - if word['text'].lower() in self.patterns['hedges']: + if word["text"].lower() in self.patterns["hedges"]: return "HDG" # Two word hedges if next_words[0]: phrase = self.get_phrase(next_words[:1]) # Handle kind of / sort of case - if phrase in ['kind of', 'sort of']: - if previous_words[0] and (previous_words[0]['xpos'] in ['DT', 'CD', 'PRP$'] or - previous_words[0]['xpos'][0] == 'W' or - self.helper.is_quantifier(word) or self.helper.is_adjective( - previous_words[0])): + if phrase in ["kind of", "sort of"]: + if previous_words[0] and ( + previous_words[0]["xpos"] in ["DT", "CD", "PRP$"] + or previous_words[0]["xpos"][0] == "W" + or self.helper.is_quantifier(word) + or self.helper.is_adjective(previous_words[0]) + ): return "HDG" - elif phrase in self.patterns['hedges']: + elif phrase in self.patterns["hedges"]: return "HDG" # Three word hedges if next_words[1]: phrase = self.get_phrase(next_words[:2]) - if phrase in self.patterns['hedges']: + if phrase in self.patterns["hedges"]: return "HDG" def tag_emph(self, word, previous_words, next_words): - """ Emphatics. Tags words and phrases that convey emphasis. Criteria: + """Emphatics. Tags words and phrases that convey emphasis. Criteria: - Any word in the emphatics list. - Real+adjective, so+adjective. - Any form of DO followed by a verb. @@ -706,7 +902,7 @@ def tag_emph(self, word, previous_words, next_words): """ # Single word emphatics - if word['text'].lower() in self.patterns['emphatics']: + if word["text"].lower() in self.patterns["emphatics"]: return "EMPH" # Check for the availability of next word. @@ -714,7 +910,9 @@ def tag_emph(self, word, previous_words, next_words): return None # Two-word emphatics: real+adjective and so+adjective - if word['text'].lower() in ['real', 'so'] and self.helper.is_adjective(next_words[0]): + if word["text"].lower() in ["real", "so"] and self.helper.is_adjective( + next_words[0] + ): return "EMPH" # DO form followed by a verb @@ -723,15 +921,17 @@ def tag_emph(self, word, previous_words, next_words): # Other two-word emphatics in patterns phrase = self.get_phrase(next_words[:1]) - if phrase in self.patterns['emphatics']: + if phrase in self.patterns["emphatics"]: return "EMPH" def tag_demo(self, word, previous_words, next_words): - """ Demonstratives. words that, this, these, those have not been + """Demonstratives. words that, this, these, those have not been tagged as either DEMP, TOBJ, TSUB, THAC, or THVC""" - is_demonstrative = word['text'].lower() in self.patterns['demonstratives'] - has_invalid_tags = any(tag in word['tags'] for tag in ['DEMP', 'TOBJ', 'TSUB', 'THAC', 'THVC']) + is_demonstrative = word["text"].lower() in self.patterns["demonstratives"] + has_invalid_tags = any( + tag in word["tags"] for tag in ["DEMP", "TOBJ", "TSUB", "THAC", "THVC"] + ) if is_demonstrative and not has_invalid_tags: return "DEMO" @@ -739,98 +939,119 @@ def tag_demo(self, word, previous_words, next_words): """ L) Modals """ def tag_pomd(self, word, previous_words, next_words): - """ The possibility modals listed by Biber (1988): can, may, might, could """ - if word['text'].lower() in self.patterns['possibility_modals']: - return 'POMD' + """The possibility modals listed by Biber (1988): can, may, might, could""" + if word["text"].lower() in self.patterns["possibility_modals"]: + return "POMD" def tag_nemd(self, word, previous_words, next_words): - """ The necessity modals listed by Biber (1988): ought, should, must. """ - if word['text'].lower() in self.patterns['necessity_modals']: - return 'NEMD' + """The necessity modals listed by Biber (1988): ought, should, must.""" + if word["text"].lower() in self.patterns["necessity_modals"]: + return "NEMD" def tag_prmd(self, word, previous_words, next_words): - """ Predictive modals. will, would, shall and their contractions: 'd_MD, ll_MD, wo_MD, sha_MD""" - if word['text'].lower() in self.patterns['predictive_modals'] and word['xpos'] == 'MD': - return 'PRMD' + """Predictive modals. will, would, shall and their contractions: 'd_MD, ll_MD, wo_MD, sha_MD""" + if ( + word["text"].lower() in self.patterns["predictive_modals"] + and word["xpos"] == "MD" + ): + return "PRMD" """ M) Specialized Verb Classes """ def tag_pubv(self, word, previous_words, next_words): - """ Any item in the public verbs list """ - if word['text'].lower() in self.patterns['public_verbs']: - return 'PUBV' + """Any item in the public verbs list""" + if word["text"].lower() in self.patterns["public_verbs"]: + return "PUBV" def tag_priv(self, word, previous_words, next_words): - """ Any item in the private verbs list """ - if word['text'].lower() in self.patterns['private_verbs']: - return 'PRIV' + """Any item in the private verbs list""" + if word["text"].lower() in self.patterns["private_verbs"]: + return "PRIV" def tag_suav(self, word, previous_words, next_words): - """ Any item in the suasive verbs list """ - if word['text'].lower() in self.patterns['suasive_verbs']: - return 'SUAV' + """Any item in the suasive verbs list""" + if word["text"].lower() in self.patterns["suasive_verbs"]: + return "SUAV" def tag_smp(self, word, previous_words, next_words): - """ Any occurrence of the forms of the two verbs seem and appear """ - if word['text'].lower() in self.patterns['seem_appear']: - return 'SMP' + """Any occurrence of the forms of the two verbs seem and appear""" + if word["text"].lower() in self.patterns["seem_appear"]: + return "SMP" """ N) Reduced forms and dispreferred structures """ def tag_cont(self, word, previous_words, next_words): - text = word['text'].lower() + text = word["text"].lower() if "n't" in text: - return 'CONT' + return "CONT" elif text.startswith("'") and text not in {"'s"}: - return 'CONT' + return "CONT" def tag_thatd(self, word, previous_words, next_words): - """ Subordinator that deletion """ + """Subordinator that deletion""" - txt = word['text'].lower() - if txt not in (self.patterns['public_verbs'] | self.patterns['private_verbs'] | self.patterns['suasive_verbs']): + txt = word["text"].lower() + if txt not in ( + self.patterns["public_verbs"] + | self.patterns["private_verbs"] + | self.patterns["suasive_verbs"] + ): return if next_words[0] and ( - self.tag_demp(next_words[0], previous_words[1:], next_words[1:]) or next_words[0]['text'].lower() - in self.patterns['subject_pronouns']): + self.tag_demp(next_words[0], previous_words[1:], next_words[1:]) + or next_words[0]["text"].lower() in self.patterns["subject_pronouns"] + ): return "THATD" # PUBV|PRIV|SUAV + PRO|N + V|AUX if next_words[1]: - if self.helper.is_pronoun(next_words[0]) or self.helper.is_noun(next_words[0]): + if self.helper.is_pronoun(next_words[0]) or self.helper.is_noun( + next_words[0] + ): if self.helper.is_any_verb(next_words[1]): return "THATD" # PUBV|PRIV|SUAV + JJ|PRED|ADV|DT|QUAN|CD|PRP$ + N + V|AUXV if next_words[2]: next_word_2 = next_words[2] - if next_word_2['xpos'] in ['JJ', 'DT', 'CD', 'PRP$'] or self.helper.is_adverb(next_word_2) or \ - self.tag_pred(next_words[0], [word] + previous_words, next_words[1:]) or \ - self.helper.is_quantifier(next_words[0]): - if self.helper.is_noun(next_words[1]) and (self.helper.is_verb(next_word_2) or - self.helper.is_auxiliary(next_word_2)): + if ( + next_word_2["xpos"] in ["JJ", "DT", "CD", "PRP$"] + or self.helper.is_adverb(next_word_2) + or self.tag_pred(next_words[0], [word] + previous_words, next_words[1:]) + or self.helper.is_quantifier(next_words[0]) + ): + if self.helper.is_noun(next_words[1]) and ( + self.helper.is_verb(next_word_2) + or self.helper.is_auxiliary(next_word_2) + ): return "THATD" # PUBV|PRIV|SUAV + JJ|PRED|ADV|DT|QUAN|CD|PRP$ + (ADJ) + N + V|AUXV if next_words[3]: - if next_words[0]['xpos'] in ['JJ', 'DT', 'CD', 'PRP$'] or self.helper.is_adverb(next_words[0]) or \ - self.tag_pred(next_words[0], previous_words[1:], next_words[1:]) or \ - self.helper.is_quantifier(next_words[0]): - if self.helper.is_adjective(next_words[1]) and self.helper.is_noun(next_words[2]) \ - and self.helper.is_any_verb(next_words[3]): + if ( + next_words[0]["xpos"] in ["JJ", "DT", "CD", "PRP$"] + or self.helper.is_adverb(next_words[0]) + or self.tag_pred(next_words[0], previous_words[1:], next_words[1:]) + or self.helper.is_quantifier(next_words[0]) + ): + if ( + self.helper.is_adjective(next_words[1]) + and self.helper.is_noun(next_words[2]) + and self.helper.is_any_verb(next_words[3]) + ): return "THATD" def tag_stpr(self, word, previous_words, next_words): - """ Stranded preposition. Preposition followed by a punctuation mark. + """Stranded preposition. Preposition followed by a punctuation mark. Update from Biber: can't be the word besides. E.g. the candidates I was thinking 'of',""" - if self.helper.is_preposition(word) and word['text'].lower() != 'besides': + if self.helper.is_preposition(word) and word["text"].lower() != "besides": if next_words[0] and self.helper.is_punctuation(next_words[0]): return "STPR" def tag_spin(self, word, previous_words, next_words): - """ Split infinitives. Every time an infinitive marker to is followed by one or two adverbs and - a verb base form. e.g. he wants to convincingly prove that """ - if word['text'].lower() != 'to': + """Split infinitives. Every time an infinitive marker to is followed by one or two adverbs and + a verb base form. e.g. he wants to convincingly prove that""" + if word["text"].lower() != "to": return # Check if the next word is an adverb @@ -838,11 +1059,15 @@ def tag_spin(self, word, previous_words, next_words): return # TO + 1 adverb + VB - if next_words[1] and next_words[1]['xpos'] == 'VB': + if next_words[1] and next_words[1]["xpos"] == "VB": return "SPIN" # TO + 2 adverbs + VB - if next_words[2] and self.helper.is_adverb(next_words[1]) and next_words[2]['xpos'] == 'VB': + if ( + next_words[2] + and self.helper.is_adverb(next_words[1]) + and next_words[2]["xpos"] == "VB" + ): return "SPIN" def tag_spau(self, word, previous_words, next_words): @@ -855,84 +1080,110 @@ def tag_spau(self, word, previous_words, next_words): return # AUX + 1 adverb + VB - if next_words[1] and self.helper.is_adverb(next_words[0]) and next_words[1]['xpos'][:2] == 'VB': + if ( + next_words[1] + and self.helper.is_adverb(next_words[0]) + and next_words[1]["xpos"][:2] == "VB" + ): return "SPAU" # AUX + 2 adverbs + VB - if next_words[2] and self.helper.is_adverb(next_words[0]) and self.helper.is_adverb(next_words[1]) and \ - next_words[2]['xpos'][:2] == 'VB': + if ( + next_words[2] + and self.helper.is_adverb(next_words[0]) + and self.helper.is_adverb(next_words[1]) + and next_words[2]["xpos"][:2] == "VB" + ): return "SPAU" """ O) Coordination """ def tag_phc(self, word, previous_words, next_words): - """ Phrasal coordination. Any 'and' followed by the same tag if the tag is in (adverb, adjective, verb, noun)""" - if word['text'].lower() == 'and' and previous_words[0] and next_words[0]: - if previous_words[0]['upos'] == next_words[0]['upos']: - if self.helper.is_adverb(previous_words[0]) or self.helper.is_adjective(previous_words[0]) or \ - self.helper.is_verb(previous_words[0]) or \ - self.helper.is_noun(previous_words[0]) or self.helper.is_proper_noun(previous_words[0]): + """Phrasal coordination. Any 'and' followed by the same tag if the tag is in (adverb, adjective, verb, noun)""" + if word["text"].lower() == "and" and previous_words[0] and next_words[0]: + if previous_words[0]["upos"] == next_words[0]["upos"]: + if ( + self.helper.is_adverb(previous_words[0]) + or self.helper.is_adjective(previous_words[0]) + or self.helper.is_verb(previous_words[0]) + or self.helper.is_noun(previous_words[0]) + or self.helper.is_proper_noun(previous_words[0]) + ): return "PHC" def tag_andc(self, word, previous_words, next_words): - """ Independent clause coordination. Assigned to the word and when it is found in one of the following - patterns: - (1) preceded by a comma and followed by it, so, then, you, there + BE, or a demonstrative pronoun - (DEMP) or the subject forms of a personal pronouns; - (2) preceded by any punctuation; - (3) followed by a WH pronoun or any WH word, an adverbial subordinator (CAUS, CONC, COND, OSUB) or a - discourse particle (DPAR) or a conjunct (CONJ)""" - if word['text'].lower() != 'and': + """Independent clause coordination. Assigned to the word and when it is found in one of the following + patterns: + (1) preceded by a comma and followed by it, so, then, you, there + BE, or a demonstrative pronoun + (DEMP) or the subject forms of a personal pronouns; + (2) preceded by any punctuation; + (3) followed by a WH pronoun or any WH word, an adverbial subordinator (CAUS, CONC, COND, OSUB) or a + discourse particle (DPAR) or a conjunct (CONJ)""" + if word["text"].lower() != "and": return # Condition (3): Check if "and" is followed by trigger words or punctuation signals if next_words[0]: - triggers = set(['it', 'so', 'then', 'you']) - if self.patterns.get('demonstrative_pronouns'): - triggers.update([w.lower() for w in self.patterns['demonstrative_pronouns']]) - if self.patterns.get('subject_pronouns'): - triggers.update([w.lower() for w in self.patterns['subject_pronouns']]) - if next_words[0]['text'].lower() in triggers: + triggers = set(["it", "so", "then", "you"]) + if self.patterns.get("demonstrative_pronouns"): + triggers.update( + [w.lower() for w in self.patterns["demonstrative_pronouns"]] + ) + if self.patterns.get("subject_pronouns"): + triggers.update([w.lower() for w in self.patterns["subject_pronouns"]]) + if next_words[0]["text"].lower() in triggers: return "ANDC" - if next_words[0]['text'].lower() == 'there' and next_words[1] and self.helper.is_be(next_words[1]): + if ( + next_words[0]["text"].lower() == "there" + and next_words[1] + and self.helper.is_be(next_words[1]) + ): return "ANDC" - if next_words[0]['xpos'].startswith('W'): + if next_words[0]["xpos"].startswith("W"): return "ANDC" - if (self.tag_caus(next_words[0], previous_words[1:], next_words[1:]) or - self.tag_conc(next_words[0], previous_words[1:], next_words[1:]) or - self.tag_cond(next_words[0], previous_words[1:], next_words[1:]) or - self.tag_osub(next_words[0], previous_words[1:], next_words[1:]) or - self.tag_dpar(next_words[0], previous_words[1:], next_words[1:]) or - self.tag_conj(next_words[0], previous_words[1:], next_words[1:])): + if ( + self.tag_caus(next_words[0], previous_words[1:], next_words[1:]) + or self.tag_conc(next_words[0], previous_words[1:], next_words[1:]) + or self.tag_cond(next_words[0], previous_words[1:], next_words[1:]) + or self.tag_osub(next_words[0], previous_words[1:], next_words[1:]) + or self.tag_dpar(next_words[0], previous_words[1:], next_words[1:]) + or self.tag_conj(next_words[0], previous_words[1:], next_words[1:]) + ): return "ANDC" # Condition (2): If the token immediately before "and" is punctuation (or its text ends with a punctuation mark) if previous_words[0]: - prev_text = previous_words[0]['text'] - if self.helper.is_punctuation(previous_words[0]) or (prev_text and prev_text[-1] in [',', ';', ':']): + prev_text = previous_words[0]["text"] + if self.helper.is_punctuation(previous_words[0]) or ( + prev_text and prev_text[-1] in [",", ";", ":"] + ): return "ANDC" return """ P) Negation """ def tag_xx0(self, word, previous_words, next_words): - """ Analytic negation: word 'not' and to the item n't_RB""" + """Analytic negation: word 'not' and to the item n't_RB""" if not word: # or `if word is None:` return None - if word['text'].lower() in self.patterns['analytic_negation']: - return 'XX0' + if word["text"].lower() in self.patterns["analytic_negation"]: + return "XX0" def tag_syne(self, word, previous_words, next_words): - """ Synthetic negation: (no, neither, nor) followed by an adjective, noun, or proper noun""" - if not next_words[0] or word['text'].lower() not in self.patterns['synthetic_negations']: + """Synthetic negation: (no, neither, nor) followed by an adjective, noun, or proper noun""" + if ( + not next_words[0] + or word["text"].lower() not in self.patterns["synthetic_negations"] + ): return next_word_conditions = ( self.helper.is_adjective(next_words[0]), self.helper.is_noun(next_words[0]), - self.helper.is_proper_noun(next_words[0])) + self.helper.is_proper_noun(next_words[0]), + ) if any(next_word_conditions): - return 'SYNE' + return "SYNE" def tag_quan(self, word, previous_words, next_words): if self.helper.is_quantifier(word): @@ -947,11 +1198,13 @@ def tag_articles(self, word, previous_words, next_words): return "ART" def tag_auxillary_be(self, word, previous_words, next_words): - if word['text'].lower() in self.patterns['be'] and self.helper.is_auxiliary(word): + if word["text"].lower() in self.patterns["be"] and self.helper.is_auxiliary( + word + ): return "AUXB" def tag_capitalizations(self, word, previous_words, next_words): - if word['text'][0].isupper(): + if word["text"][0].isupper(): return "CAP" def tag_subordinating_conjunctions(self, word, previous_words, next_words): @@ -960,37 +1213,37 @@ def tag_subordinating_conjunctions(self, word, previous_words, next_words): def tag_coordinating_conjunctions(self, word, previous_words, next_words): if self.helper.is_coordinating_conjunction(word): - return 'CCONJ' + return "CCONJ" def tag_determiners(self, word, previous_words, next_words): if self.helper.is_determiner(word): - return 'DET' + return "DET" def tag_emoji(self, word, previous_words, next_words): - emoji_pattern = re.compile('[\U00010000-\U0010ffff]', flags=re.UNICODE) - if re.search(emoji_pattern, word['text']): - return 'EMOJ' + emoji_pattern = re.compile("[\U00010000-\U0010ffff]", flags=re.UNICODE) + if re.search(emoji_pattern, word["text"]): + return "EMOJ" def tag_emoticon(self, word, previous_words, next_words): if self.helper.is_punctuation(word): - emoticon_pattern = re.compile('[:;=](?:-)?[)DPp\/]') - if re.search(emoticon_pattern, word['text']): - return 'EMOT' + emoticon_pattern = re.compile("[:;=](?:-)?[)DPp\/]") + if re.search(emoticon_pattern, word["text"]): + return "EMOT" def tag_exclamation_mark(self, word, previous_words, next_words): - if word['text'] == '!': - return 'EXCL' + if word["text"] == "!": + return "EXCL" def tag_hashtag(self, word, previous_words, next_words): - if word['text'][0] == '#': - return 'HASH' + if word["text"][0] == "#": + return "HASH" def tag_infinitives(self, word, previous_words, next_words): if self.helper.is_infinitive(word): return "INF" def tag_interjection(self, word, previous_words, next_words): - if word['xpos'] == 'UH': + if word["xpos"] == "UH": return "UH" def tag_numeral(self, word, previous_words, next_words): @@ -998,33 +1251,33 @@ def tag_numeral(self, word, previous_words, next_words): return "NUM" def tag_laughter_acronyms(self, word, previous_word, next_words): - if word['text'].lower() in self.patterns['laughter_acronyms']: + if word["text"].lower() in self.patterns["laughter_acronyms"]: return "LAUGH" def tag_possessive_pronoun(self, word, previous_words, next_words): - standalone_possessives = {'mine', 'yours', 'his', 'hers', 'ours', 'theirs'} - if word['xpos'][:3] == 'PRP' or word['text'].lower() in standalone_possessives: - return 'PRP' + standalone_possessives = {"mine", "yours", "his", "hers", "ours", "theirs"} + if word["xpos"][:3] == "PRP" or word["text"].lower() in standalone_possessives: + return "PRP" def tag_preposition(self, word, previous_words, next_words): if self.helper.is_preposition(word): return "PREP" def tag_proper_noun(self, word, previous_words, next_words): - if word['xpos'][:3] == 'NNP': + if word["xpos"][:3] == "NNP": return "NNP" def tag_question_mark(self, word, previous_words, next_words): - if word['text'] == '?': - return 'QUES' + if word["text"] == "?": + return "QUES" def tag_quotation_mark(self, word, previous_words, next_words): - if word['text'] == "'" or word['text'] == '"': + if word["text"] == "'" or word["text"] == '"': return "QUOT" def tag_at(self, word, previous_words, next_words): - if word['text'][0] == '@': - return 'AT' + if word["text"][0] == "@": + return "AT" def tag_subject_pronouns(self, word, previous_words, next_words): if self.helper.is_subject_pronoun(word): @@ -1032,11 +1285,11 @@ def tag_subject_pronouns(self, word, previous_words, next_words): def tag_url(self, word, previous_words, next_words): url_pattern = "(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})" - if re.search(url_pattern, word['text']): + if re.search(url_pattern, word["text"]): return "URL" def tag_wh_word(self, word, previous_words, next_words): - if word['xpos'][0] == 'W': + if word["xpos"][0] == "W": return "WH" def tag_indefinite_article(self, word, previous_words, next_words): @@ -1056,10 +1309,10 @@ def tag_comparative(self, word, previous_words, next_words): return "CMADJ" def tag_superlative(self, word, previous_words, next_words): - """ Noun (subject) + verb + the + superlative adjective + noun (object) """ + """Noun (subject) + verb + the + superlative adjective + noun (object)""" if self.helper.is_superlative_adjective(word): return "SPADJ" def tag_non_pos(self, word, previous_words, next_words): if self.helper.is_non_pos(word): - return 'X' + return "X" diff --git a/biberplus/tagger/biber_run_order.py b/biberplus/tagger/biber_run_order.py index d26528c..ba737c3 100644 --- a/biberplus/tagger/biber_run_order.py +++ b/biberplus/tagger/biber_run_order.py @@ -3,100 +3,100 @@ """ RUN_ORDER = [ - 'tag_quan', - 'tag_qupr', - 'tag_amp', - 'tag_pass', - 'tag_xx0', - 'tag_jj', - 'tag_osub', - 'tag_bema', - 'tag_caus', - 'tag_conc', - 'tag_cond', - 'tag_cont', - 'tag_dpar', - 'tag_dwnt', - 'tag_ex', - 'tag_fpp1', - 'tag_ger', - 'tag_rb', - 'tag_pin', - 'tag_inpr', - 'tag_to', - 'tag_nemd', - 'tag_conj', - 'tag_pastp', - 'tag_vbd', - 'tag_phc', - 'tag_pire', - 'tag_place', - 'tag_pomd', - 'tag_prmd', - 'tag_wzpres', - 'tag_vprt', - 'tag_priv', - 'tag_pit', - 'tag_pubv', - 'tag_spp2', - 'tag_smp', - 'tag_sere', - 'tag_stpr', - 'tag_suav', - 'tag_syne', - 'tag_tpp3', - 'tag_time', - 'tag_nomz', - 'tag_bypa', - 'tag_pred', - 'tag_tobj', - 'tag_tsub', - 'tag_thvc', - 'tag_nn', - 'tag_whqu', - 'tag_emph', - 'tag_hdg', - 'tag_wzpast', - 'tag_thac', - 'tag_demp', - 'tag_demo', - 'tag_peas', - 'tag_presp', - 'tag_prod', - 'tag_spau', - 'tag_spin', - 'tag_thatd', - 'tag_whobj', - 'tag_whsub', - 'tag_whcl', - 'tag_andc', - 'tag_articles', - 'tag_auxillary_be', - 'tag_capitalizations', - 'tag_subordinating_conjunctions', - 'tag_coordinating_conjunctions', - 'tag_determiners', - 'tag_emoji', - 'tag_emoticon', - 'tag_exclamation_mark', - 'tag_hashtag', - 'tag_infinitives', - 'tag_interjection', - 'tag_numeral', - 'tag_laughter_acronyms', - 'tag_possessive_pronoun', - 'tag_preposition', - 'tag_proper_noun', - 'tag_question_mark', - 'tag_quotation_mark', - 'tag_at', - 'tag_subject_pronouns', - 'tag_url', - 'tag_wh_word', - 'tag_indefinite_article', - 'tag_accusative_case', - 'tag_progressive_aspect', - 'tag_comparative', - 'tag_superlative', - 'tag_non_pos' + "tag_quan", + "tag_qupr", + "tag_amp", + "tag_pass", + "tag_xx0", + "tag_jj", + "tag_osub", + "tag_bema", + "tag_caus", + "tag_conc", + "tag_cond", + "tag_cont", + "tag_dpar", + "tag_dwnt", + "tag_ex", + "tag_fpp1", + "tag_ger", + "tag_rb", + "tag_pin", + "tag_inpr", + "tag_to", + "tag_nemd", + "tag_conj", + "tag_pastp", + "tag_vbd", + "tag_phc", + "tag_pire", + "tag_place", + "tag_pomd", + "tag_prmd", + "tag_wzpres", + "tag_vprt", + "tag_priv", + "tag_pit", + "tag_pubv", + "tag_spp2", + "tag_smp", + "tag_sere", + "tag_stpr", + "tag_suav", + "tag_syne", + "tag_tpp3", + "tag_time", + "tag_nomz", + "tag_bypa", + "tag_pred", + "tag_tobj", + "tag_tsub", + "tag_thvc", + "tag_nn", + "tag_whqu", + "tag_emph", + "tag_hdg", + "tag_wzpast", + "tag_thac", + "tag_demp", + "tag_demo", + "tag_peas", + "tag_presp", + "tag_prod", + "tag_spau", + "tag_spin", + "tag_thatd", + "tag_whobj", + "tag_whsub", + "tag_whcl", + "tag_andc", + "tag_articles", + "tag_auxillary_be", + "tag_capitalizations", + "tag_subordinating_conjunctions", + "tag_coordinating_conjunctions", + "tag_determiners", + "tag_emoji", + "tag_emoticon", + "tag_exclamation_mark", + "tag_hashtag", + "tag_infinitives", + "tag_interjection", + "tag_numeral", + "tag_laughter_acronyms", + "tag_possessive_pronoun", + "tag_preposition", + "tag_proper_noun", + "tag_question_mark", + "tag_quotation_mark", + "tag_at", + "tag_subject_pronouns", + "tag_url", + "tag_wh_word", + "tag_indefinite_article", + "tag_accusative_case", + "tag_progressive_aspect", + "tag_comparative", + "tag_superlative", + "tag_non_pos", ] diff --git a/biberplus/tagger/constants.py b/biberplus/tagger/constants.py index 60768d9..c234fc6 100644 --- a/biberplus/tagger/constants.py +++ b/biberplus/tagger/constants.py @@ -1,110 +1,103 @@ BIBER_PLUS_TAGS = [ - 'QUAN', - 'QUPR', - 'AMP', - 'PASS', - 'XX0', - 'JJ', - 'BEMA', - 'CAUS', - 'CONC', - 'COND', - 'CONJ', - 'CONT', - 'DPAR', - 'DWNT', - 'EX', - 'FPP1', - 'GER', - 'RB', - 'PIN', - 'INPR', - 'TO', - 'NEMD', - 'OSUB', - 'PASTP', - 'VBD', - 'PHC', - 'PIRE', - 'PLACE', - 'POMD', - 'PRMD', - 'WZPRES', - 'VPRT', - 'PRIV', - 'PIT', - 'PUBV', - 'SPP2', - 'SMP', - 'SERE', - 'STPR', - 'SUAV', - 'SYNE', - 'TPP3', - 'TIME', - 'NOMZ', - 'BYPA', - 'PRED', - 'TOBJ', - 'TSUB', - 'THVC', - 'NN', - 'DEMP', - 'DEMO', - 'WHQU', - 'EMPH', - 'HDG', - 'WZPAST', - 'THAC', - 'PEAS', - 'ANDC', - 'PRESP', - 'PROD', - 'SPAU', - 'SPIN', - 'THATD', - 'WHOBJ', - 'WHSUB', - 'WHCL', - 'ART', - 'AUXB', - 'CAP', - 'SCONJ', - 'CCONJ', - 'DET', - 'EMOJ', - 'EMOT', - 'EXCL', - 'HASH', - 'INF', - 'UH', - 'NUM', - 'LAUGH', - 'PRP', - 'PREP', - 'NNP', - 'QUES', - 'QUOT', - 'AT', - 'SBJP', - 'URL', - 'WH', - 'INDA', - 'ACCU', - 'PGAS', - 'CMADJ', - 'SPADJ', - 'X' + "QUAN", + "QUPR", + "AMP", + "PASS", + "XX0", + "JJ", + "BEMA", + "CAUS", + "CONC", + "COND", + "CONJ", + "CONT", + "DPAR", + "DWNT", + "EX", + "FPP1", + "GER", + "RB", + "PIN", + "INPR", + "TO", + "NEMD", + "OSUB", + "PASTP", + "VBD", + "PHC", + "PIRE", + "PLACE", + "POMD", + "PRMD", + "WZPRES", + "VPRT", + "PRIV", + "PIT", + "PUBV", + "SPP2", + "SMP", + "SERE", + "STPR", + "SUAV", + "SYNE", + "TPP3", + "TIME", + "NOMZ", + "BYPA", + "PRED", + "TOBJ", + "TSUB", + "THVC", + "NN", + "DEMP", + "DEMO", + "WHQU", + "EMPH", + "HDG", + "WZPAST", + "THAC", + "PEAS", + "ANDC", + "PRESP", + "PROD", + "SPAU", + "SPIN", + "THATD", + "WHOBJ", + "WHSUB", + "WHCL", + "ART", + "AUXB", + "CAP", + "SCONJ", + "CCONJ", + "DET", + "EMOJ", + "EMOT", + "EXCL", + "HASH", + "INF", + "UH", + "NUM", + "LAUGH", + "PRP", + "PREP", + "NNP", + "QUES", + "QUOT", + "AT", + "SBJP", + "URL", + "WH", + "INDA", + "ACCU", + "PGAS", + "CMADJ", + "SPADJ", + "X", ] -DOC_TAGS = [ - 'AWL', - 'TTR', - 'RB' -] +DOC_TAGS = ["AWL", "TTR", "RB"] # Keep track of all the acronyms in one spot -MDA_ACRONYMS = { - 'XXO': 'Analytic Negation', - 'SYNE': 'Synthetic Negation' -} +MDA_ACRONYMS = {"XXO": "Analytic Negation", "SYNE": "Synthetic Negation"} diff --git a/biberplus/tagger/data_io.py b/biberplus/tagger/data_io.py index d558a20..10fa786 100644 --- a/biberplus/tagger/data_io.py +++ b/biberplus/tagger/data_io.py @@ -8,13 +8,13 @@ def simple_split_batching(text: str, token_batch_size: int, show_progress: bool): - """ Split on spaces and count the number of tokens. As simple as it gets """ - tokens = text.split(' ') + """Split on spaces and count the number of tokens. As simple as it gets""" + tokens = text.split(" ") iterator = range(0, len(tokens), token_batch_size) batch_count = ceil(len(tokens) / token_batch_size) for i in tqdm(iterator, total=batch_count, disable=not show_progress): - yield " ".join(tokens[i:i + token_batch_size]) + yield " ".join(tokens[i : i + token_batch_size]) def spacy_tokenize_batching(text: str, token_batch_size: int, use_gpu: bool): @@ -40,10 +40,10 @@ def spacy_tokenize_batching_lists(text_lists: list, token_batch_size: int): def read_directory_of_text_files(dir_path): # Ensure directory path ends in a slash - if dir_path[-1] != '/': - dir_path += '/' + if dir_path[-1] != "/": + dir_path += "/" - txt_paths = glob(dir_path + '*.txt') + txt_paths = glob(dir_path + "*.txt") assert len(txt_paths) > 0, "No text files found in the directory!" for txt_path in txt_paths: @@ -51,5 +51,5 @@ def read_directory_of_text_files(dir_path): def read_from_file(txt_path): - with open(txt_path, 'r') as f: + with open(txt_path, "r") as f: return f.read() diff --git a/biberplus/tagger/function_words_tagger.py b/biberplus/tagger/function_words_tagger.py index f84fe35..7c7cdf4 100644 --- a/biberplus/tagger/function_words_tagger.py +++ b/biberplus/tagger/function_words_tagger.py @@ -9,12 +9,12 @@ def __init__(self, tagged_words, function_words): if function_words: self.function_words = set([w.lower().strip() for w in function_words]) else: - self.function_words = build_variable_dictionaries()['function_words'] + self.function_words = build_variable_dictionaries()["function_words"] def tag(self): for word in self.tagged_words: - if word['text'].lower() in self.function_words: - word['tags'].append(word['text'].lower()) + if word["text"].lower() in self.function_words: + word["tags"].append(word["text"].lower()) return self.tagged_words # def is_function_word(self, word): @@ -31,9 +31,11 @@ def tag_function_words(words, function_words=None, return_vector=False): if function_words: function_words = set([w.lower().strip() for w in function_words]) else: - function_words = build_variable_dictionaries()['function_words'] + function_words = build_variable_dictionaries()["function_words"] - function_word_counts = Counter(word.lower() for word in words if word.lower() in function_words) + function_word_counts = Counter( + word.lower() for word in words if word.lower() in function_words + ) # Sort into the original order of the function words variable if return_vector: diff --git a/biberplus/tagger/tag_frequencies.py b/biberplus/tagger/tag_frequencies.py index 7f2116a..f6b8100 100644 --- a/biberplus/tagger/tag_frequencies.py +++ b/biberplus/tagger/tag_frequencies.py @@ -1,6 +1,6 @@ -import warnings import functools import operator +import warnings from collections import defaultdict from math import ceil @@ -9,10 +9,13 @@ from biberplus.tagger import tag_text from biberplus.tagger.constants import BIBER_PLUS_TAGS -from biberplus.tagger.tagger_utils import load_config, load_pipeline, build_variable_dictionaries +from biberplus.tagger.tagger_utils import ( + build_variable_dictionaries, + load_config, + load_pipeline, +) - -warnings.filterwarnings('ignore', category=FutureWarning, message='.*swapaxes.*') +warnings.filterwarnings("ignore", category=FutureWarning, message=".*swapaxes.*") def calculate_tag_frequencies(text, pipeline=None, config=None): @@ -24,93 +27,113 @@ def calculate_tag_frequencies(text, pipeline=None, config=None): try: tagged_words = tag_text(text, pipeline, config) tagged_dataframe = pd.DataFrame(tagged_words) - tag_frequencies = count_tags_every_n_tokens(tagged_dataframe, tag_frequencies, tags, config) + tag_frequencies = count_tags_every_n_tokens( + tagged_dataframe, tag_frequencies, tags, config + ) return calculate_descriptive_stats(tag_frequencies) except Exception as e: print(text) print(e) + def calculate_descriptive_stats(tag_counts): rows = [] for tag, counts in tag_counts.items(): counts = np.array(counts) - rows.append({ - 'tag': tag, - 'mean': counts.mean(), - 'min_val': min(counts), - 'max_val': max(counts), - 'range': np.ptp(counts), - 'std': counts.std() - }) + rows.append( + { + "tag": tag, + "mean": counts.mean(), + "min_val": min(counts), + "max_val": max(counts), + "range": np.ptp(counts), + "std": counts.std(), + } + ) return pd.DataFrame(rows) def count_tags_every_n_tokens(tagged_df, tag_counts, tags, config): - num_batches = ceil(len(tagged_df) / config['token_normalization']) + num_batches = ceil(len(tagged_df) / config["token_normalization"]) for index, batch in enumerate(np.array_split(tagged_df, num_batches)): last_batch = index == num_batches - 1 # Ignore the last batch if it's too small, otherwise scale up tag frequencies - if last_batch and len(batch) <= config['drop_last_batch_pct'] * config['token_normalization']: + if ( + last_batch + and len(batch) + <= config["drop_last_batch_pct"] * config["token_normalization"] + ): break - weight = config['token_normalization'] / len(batch) if last_batch else 1.0 - tag_counts = update_tag_counts(batch, tag_counts, tags, tag_binary=config['binary_tags'], weight=weight) + weight = config["token_normalization"] / len(batch) if last_batch else 1.0 + tag_counts = update_tag_counts( + batch, tag_counts, tags, tag_binary=config["binary_tags"], weight=weight + ) return tag_counts -def update_tag_counts(tagged_df, tag_counts, tags, tag_binary, weight=1.): - curr_counts = pd.Series(functools.reduce(operator.iconcat, tagged_df.tags, []), - dtype=pd.StringDtype()).value_counts().to_dict() +def update_tag_counts(tagged_df, tag_counts, tags, tag_binary, weight=1.0): + curr_counts = ( + pd.Series( + functools.reduce(operator.iconcat, tagged_df.tags, []), + dtype=pd.StringDtype(), + ) + .value_counts() + .to_dict() + ) for tag in tags: count = round(curr_counts[tag] * weight) if tag in curr_counts else 0 tag_counts[tag].append(count) if tag_binary and tag in BIBER_PLUS_TAGS: - tag_name = 'BIN_' + tag if tag[:4] != 'BIN_' else tag + tag_name = "BIN_" + tag if tag[:4] != "BIN_" else tag tag_counts[tag_name].append(int(tag in curr_counts)) # Update document level tags - tag_counts['AWL'].append(calculate_mean_word_length(tagged_df)) + tag_counts["AWL"].append(calculate_mean_word_length(tagged_df)) # tag_counts['RB'].append(calculate_total_adverbs(tagged_df)) - tag_counts['TTR'].append(calculate_type_token_ratio(tagged_df)) + tag_counts["TTR"].append(calculate_type_token_ratio(tagged_df)) return tag_counts def calculate_total_adverbs(tagged_df): - return len(tagged_df[tagged_df['upos'] == 'ADV']) + return len(tagged_df[tagged_df["upos"] == "ADV"]) def calculate_mean_word_length(tagged_df): - return tagged_df['text'].apply(len).mean() + return tagged_df["text"].apply(len).mean() def calculate_type_token_ratio(tagged_df, first_n=400): if first_n: tagged_df = tagged_df.iloc[:first_n] - uniq_vocab = set(tagged_df['text'].unique()) + uniq_vocab = set(tagged_df["text"].unique()) return len(uniq_vocab) / len(tagged_df) def load_tags(config): tags = [] - if config['biber']: + if config["biber"]: tags.extend(BIBER_PLUS_TAGS) - if config['binary_tags']: - binary_tags = ['BIN_' + tag for tag in BIBER_PLUS_TAGS] + if config["binary_tags"]: + binary_tags = ["BIN_" + tag for tag in BIBER_PLUS_TAGS] tags.extend(binary_tags) - if config['function_words']: - fw = config['function_words_list'] if config['function_words_list'] else build_variable_dictionaries()[ - 'function_words'] + if config["function_words"]: + fw = ( + config["function_words_list"] + if config["function_words_list"] + else build_variable_dictionaries()["function_words"] + ) tags.extend(fw) return tags diff --git a/biberplus/tagger/tag_helper.py b/biberplus/tagger/tag_helper.py index 50423ec..034fc93 100644 --- a/biberplus/tagger/tag_helper.py +++ b/biberplus/tagger/tag_helper.py @@ -1,143 +1,146 @@ class TagHelper: - def __init__(self, patterns): self.patterns = patterns @staticmethod def is_adjective(word): - return word and word['upos'] == 'ADJ' + return word and word["upos"] == "ADJ" @staticmethod def is_adposition(word): - return word and word['upos'] == 'ADP' + return word and word["upos"] == "ADP" @staticmethod def is_adverb(word): - return word and (word['upos'] == 'ADV' or - word['xpos'] in ['RB', 'RBR', 'RBS', 'WRB']) + return word and ( + word["upos"] == "ADV" or word["xpos"] in ["RB", "RBR", "RBS", "WRB"] + ) @staticmethod def is_auxiliary(word): - return word and word['upos'] == 'AUX' + return word and word["upos"] == "AUX" @staticmethod def is_coordinating_conjunction(word): - return word and word['upos'] == 'CCONJ' + return word and word["upos"] == "CCONJ" @staticmethod def is_determiner(word): - return word and word['upos'] == 'DET' + return word and word["upos"] == "DET" @staticmethod def is_interjection(word): - return word and word['upos'] == 'INTJ' + return word and word["upos"] == "INTJ" @staticmethod def is_noun(word): - return word and word['upos'] == 'NOUN' + return word and word["upos"] == "NOUN" @staticmethod def is_numeral(word): - return word and word['upos'] == 'NUM' + return word and word["upos"] == "NUM" @staticmethod def is_particle(word): - return word and word['upos'] == 'PART' + return word and word["upos"] == "PART" @staticmethod def is_pronoun(word): - return word and word['upos'] == 'PRON' + return word and word["upos"] == "PRON" @staticmethod def is_proper_noun(word): - return word and word['upos'] == 'PROPN' + return word and word["upos"] == "PROPN" @staticmethod def is_punctuation(word): - return word and word['upos'] == 'PUNCT' + return word and word["upos"] == "PUNCT" @staticmethod def is_subordinating_conjunction(word): - return word and word['upos'] == 'SCONJ' + return word and word["upos"] == "SCONJ" @staticmethod def is_symbol(word): - return word and word['upos'] == 'SYM' + return word and word["upos"] == "SYM" @staticmethod def is_verb(word): - return word and word['upos'] == 'VERB' + return word and word["upos"] == "VERB" @staticmethod def is_any_noun(word): - return word and word['xpos'].startswith('NN') + return word and word["xpos"].startswith("NN") @staticmethod def is_any_verb(word): - return word['upos'] == 'VERB' or word['upos'] == 'AUX' + return word["upos"] == "VERB" or word["upos"] == "AUX" @staticmethod def is_past_tense(word): - return "Tense=Past" in word['feats'] + return "Tense=Past" in word["feats"] @staticmethod def is_indefinite_article(word): - return "Definite=Ind" in word['feats'] + return "Definite=Ind" in word["feats"] @staticmethod def is_infinitive(word): - return "VerbForm=Inf" in word['feats'] + return "VerbForm=Inf" in word["feats"] @staticmethod def is_possesive_pronoun(word): - return word['xpos'] == 'PRP$' or word['xpos'] == 'WP$' + return word["xpos"] == "PRP$" or word["xpos"] == "WP$" @staticmethod def is_article(word): - return "PronType=Art" in word['feats'] + return "PronType=Art" in word["feats"] @staticmethod def is_subject_pronoun(word): - return "PronType=Prs" in word['feats'] + return "PronType=Prs" in word["feats"] @staticmethod def is_accusative_case(word): - return "Case=Acc" in word['feats'] + return "Case=Acc" in word["feats"] @staticmethod def is_progressive_aspect(word): - return "Aspect=Prog" in word['feats'] + return "Aspect=Prog" in word["feats"] @staticmethod def is_comparative_adjective(word): - return "Degree=Cmp" in word['feats'] + return "Degree=Cmp" in word["feats"] @staticmethod def is_superlative_adjective(word): - return "Degree=Sup" in word['feats'] + return "Degree=Sup" in word["feats"] @staticmethod def is_non_pos(word): - return word['upos'] == 'X' + return word["upos"] == "X" def is_quantifier(self, word): - return word and word['text'].lower() in self.patterns['quantifiers'] + return word and word["text"].lower() in self.patterns["quantifiers"] def is_indefinite_pronoun(self, word): - return word and word['text'].lower() in self.patterns['indefinite_pronouns'] + return word and word["text"].lower() in self.patterns["indefinite_pronouns"] def is_quantifier_pronoun(self, word): - return word and word['text'].lower() in self.patterns['quantifier_pronouns'] + return word and word["text"].lower() in self.patterns["quantifier_pronouns"] def is_preposition(self, word): - return word and (word['upos'] == 'ADP' or word['xpos'] == 'IN' or - word['text'].lower() in self.patterns['prepositional_phrases']) + return word and ( + word["upos"] == "ADP" + or word["xpos"] == "IN" + or word["text"].lower() in self.patterns["prepositional_phrases"] + ) def is_be(self, word): - return word and word['text'].lower() in self.patterns['be'] + return word and word["text"].lower() in self.patterns["be"] def is_do(self, word): - return word and word['text'].lower() in self.patterns['do'] + return word and word["text"].lower() in self.patterns["do"] def is_have(self, word): - return word and word['text'].lower() in self.patterns['have'] + return word and word["text"].lower() in self.patterns["have"] diff --git a/biberplus/tagger/tagger.py b/biberplus/tagger/tagger.py index 7ee7fff..e129213 100644 --- a/biberplus/tagger/tagger.py +++ b/biberplus/tagger/tagger.py @@ -3,11 +3,15 @@ from tqdm import tqdm -sys.path.append('../..') +sys.path.append("../..") from biberplus.tagger.function_words_tagger import FunctionWordsTagger from biberplus.tagger.data_io import simple_split_batching -from biberplus.tagger.tagger_utils import build_variable_dictionaries, load_config, load_pipeline +from biberplus.tagger.tagger_utils import ( + build_variable_dictionaries, + load_config, + load_pipeline, +) from biberplus.tagger.biber_plus_tagger import BiberPlusTagger @@ -24,13 +28,15 @@ def tag_text(text, pipeline=None, config=None): all_tagged = [] # No need to batch / parallelize small texts - if len(text.split(' ')) < config['processing_size'] * 10: # Arbitrary cutoff + if len(text.split(" ")) < config["processing_size"] * 10: # Arbitrary cutoff return tag_batch(text, config, patterns_dict, pipeline) - if config['n_processes'] > 1: + if config["n_processes"] > 1: return tag_text_parallel(text, config) - for text_batch in simple_split_batching(text, config['processing_size'], config['show_progress']): + for text_batch in simple_split_batching( + text, config["processing_size"], config["show_progress"] + ): all_tagged.extend(tag_batch(text_batch, config, patterns_dict, pipeline)) return all_tagged @@ -42,14 +48,22 @@ def tag_text_parallel(text, config): # Split the text into batches process_args = [] - for text_batch in simple_split_batching(text, config['processing_size'], show_progress=False): + for text_batch in simple_split_batching( + text, config["processing_size"], show_progress=False + ): process_args.append((text_batch, config, patterns_dict, None)) all_tagged = [] - with Pool(config['n_processes']) as p: - for tagged_words in p.starmap(tag_batch, - tqdm(process_args, total=len(process_args), disable=not config['show_progress'])): + with Pool(config["n_processes"]) as p: + for tagged_words in p.starmap( + tag_batch, + tqdm( + process_args, + total=len(process_args), + disable=not config["show_progress"], + ), + ): all_tagged.extend(tagged_words) return all_tagged @@ -66,20 +80,22 @@ def tag_batch(text_batch, config, patterns_dict, pipeline=None): def tag_function_words(tagged_words, config): - if config['function_words']: - return FunctionWordsTagger(tagged_words, config['function_words_list']).tag() + if config["function_words"]: + return FunctionWordsTagger(tagged_words, config["function_words_list"]).tag() return tagged_words def tag_biber_and_binary(tagged_words, patterns_dict, config): - if config['biber'] or config['binary_tags']: + if config["biber"] or config["binary_tags"]: return BiberPlusTagger(tagged_words, patterns_dict).run_all() return tagged_words def word2dict(word): - return {'text': word.text, - 'upos': word.pos_, - 'xpos': word.tag_, - 'feats': word.morph if word.morph else "", - 'tags': []} + return { + "text": word.text, + "upos": word.pos_, + "xpos": word.tag_, + "feats": word.morph if word.morph else "", + "tags": [], + } diff --git a/biberplus/tagger/tagger_utils.py b/biberplus/tagger/tagger_utils.py index 0ea1c1d..add954a 100644 --- a/biberplus/tagger/tagger_utils.py +++ b/biberplus/tagger/tagger_utils.py @@ -6,14 +6,14 @@ def load_config(): - config_fp = Path(os.path.dirname(__file__)) / 'config.yaml' + config_fp = Path(os.path.dirname(__file__)) / "config.yaml" with open(config_fp) as f: return yaml.safe_load(f) def build_variable_dictionaries(): script_dir = Path(os.path.dirname(__file__)) - constant_files = script_dir.glob('constants/*.txt') + constant_files = script_dir.glob("constants/*.txt") variables_dict = {} for constant_file in constant_files: @@ -26,7 +26,7 @@ def build_variable_dictionaries(): def read_in_variables(txt_file): variables = [] - with open(txt_file, 'r') as f: + with open(txt_file, "r") as f: for line in f: var = line.strip() if var: @@ -35,14 +35,18 @@ def read_in_variables(txt_file): def load_pipeline(config): - if config['use_gpu']: + if config["use_gpu"]: spacy.require_gpu() - return spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner', 'textcat']) + return spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner", "textcat"] + ) def load_tokenizer(use_gpu=False): if use_gpu: spacy.require_gpu() - return spacy.load("en_core_web_sm", disable=['tagger', 'parser', 'lemmatizer', 'ner', 'textcat']) + return spacy.load( + "en_core_web_sm", disable=["tagger", "parser", "lemmatizer", "ner", "textcat"] + ) diff --git a/corpora_tagging/data_partitioner.py b/corpora_tagging/data_partitioner.py index ba6794d..4a5d61b 100644 --- a/corpora_tagging/data_partitioner.py +++ b/corpora_tagging/data_partitioner.py @@ -40,13 +40,13 @@ def save_partition(json_lines, output_directory, index): out = os.path.join(output_directory, f"partition-{index}.jsonl") logging.info(f"Saving {out}") - with jsonlines.open(out, mode='w') as writer: + with jsonlines.open(out, mode="w") as writer: writer.write_all(json_lines) def count_lines(input_file): """Count the number of lines in the file.""" - with open(input_file, 'rb') as f: + with open(input_file, "rb") as f: return sum(1 for _ in f) @@ -54,7 +54,7 @@ def join_tagged_files(input_directory, output_file): """Join all tagged files from the input directory into one output file.""" tagged_files = glob(os.path.join(input_directory, "*-tagged.jsonl")) - with jsonlines.open(output_file, mode='w') as writer: + with jsonlines.open(output_file, mode="w") as writer: for tagged_file in tqdm(tagged_files, desc="Merging tagged files"): with jsonlines.open(tagged_file) as reader: for obj in reader: diff --git a/corpora_tagging/parallel_tagging.py b/corpora_tagging/parallel_tagging.py index e61cb81..abbcfd7 100644 --- a/corpora_tagging/parallel_tagging.py +++ b/corpora_tagging/parallel_tagging.py @@ -7,7 +7,9 @@ from biberplus.tagger.tagger_utils import load_tokenizer -def tag_partitions(config, input_directory, output_directory, num_workers, default_niceness=20): +def tag_partitions( + config, input_directory, output_directory, num_workers, default_niceness=20 +): process_args = build_process_args(config, input_directory, output_directory) def set_niceness(): @@ -42,7 +44,7 @@ def build_process_args(config, input_directory, output_directory): process_args = [] for fp in partition_files: - fname = fp.rsplit('/', 1)[-1].replace('.jsonl', '') + '-tagged.jsonl' + fname = fp.rsplit("/", 1)[-1].replace(".jsonl", "") + "-tagged.jsonl" out = f"{output_directory}{fname}" process_args.append((config, fp, out)) @@ -51,10 +53,10 @@ def build_process_args(config, input_directory, output_directory): def tag_object(obj, config, tokenizer): try: - num_tokens = len(tokenizer(obj['fullText'])) - obj['num_tokens'] = num_tokens + num_tokens = len(tokenizer(obj["fullText"])) + obj["num_tokens"] = num_tokens if num_tokens >= 10: - obj['encodings'] = encode_text(text=obj['fullText'], config=config) + obj["encodings"] = encode_text(text=obj["fullText"], config=config) except Exception: pass @@ -62,5 +64,5 @@ def tag_object(obj, config, tokenizer): def append_chunk(output_file, tagged_objects): - with jsonlines.open(output_file, mode='a') as writer: + with jsonlines.open(output_file, mode="a") as writer: writer.write_all(tagged_objects) diff --git a/experiments/pan/tag_pan_small.py b/experiments/pan/tag_pan_small.py index 51666ff..ace8950 100644 --- a/experiments/pan/tag_pan_small.py +++ b/experiments/pan/tag_pan_small.py @@ -3,13 +3,13 @@ from tqdm import tqdm import os -sys.path.append('../..') +sys.path.append("../..") from modeling.neurobiber.tagger import load_model_and_tokenizer, get_predictions # Constants -input_directory = '/shared/3/datasets/PAN/pan20-av-training-small/' -output_directory = os.path.join(input_directory, 'tagged') +input_directory = "/shared/3/datasets/PAN/pan20-av-training-small/" +output_directory = os.path.join(input_directory, "tagged") # Create output directory if it doesn't exist os.makedirs(output_directory, exist_ok=True) @@ -18,27 +18,27 @@ model, tokenizer = load_model_and_tokenizer() # Read datasets -train = pd.read_json(input_directory + 'train.jsonl', lines=True) -dev = pd.read_json(input_directory + 'dev.jsonl', lines=True) -test = pd.read_json(input_directory + 'test.jsonl', lines=True) +train = pd.read_json(input_directory + "train.jsonl", lines=True) +dev = pd.read_json(input_directory + "dev.jsonl", lines=True) +test = pd.read_json(input_directory + "test.jsonl", lines=True) print(f"Dataset sizes - Train: {len(train)}, Dev: {len(dev)}, Test: {len(test)}") -for name, df in [('train', train), ('dev', dev), ('test', test)]: +for name, df in [("train", train), ("dev", dev), ("test", test)]: print(f"\nProcessing {name} set...") - + # Process first text in pair tqdm.pandas(desc=f"Tagging {name} texts (1/2)") - texts_1 = df['pair'].apply(lambda x: x[0]).tolist() + texts_1 = df["pair"].apply(lambda x: x[0]).tolist() predictions_1 = get_predictions(model, texts_1, tokenizer) - df['neural_biber_1'] = list(predictions_1) - + df["neural_biber_1"] = list(predictions_1) + # Process second text in pair tqdm.pandas(desc=f"Tagging {name} texts (2/2)") - texts_2 = df['pair'].apply(lambda x: x[1]).tolist() + texts_2 = df["pair"].apply(lambda x: x[1]).tolist() predictions_2 = get_predictions(model, texts_2, tokenizer) - df['neural_biber_2'] = list(predictions_2) - + df["neural_biber_2"] = list(predictions_2) + # Save processed dataset - output_path = os.path.join(output_directory, f'{name}_tagged.jsonl') - df.to_json(output_path, orient='records', lines=True) - print(f"Saved {name} set to {output_path}") \ No newline at end of file + output_path = os.path.join(output_directory, f"{name}_tagged.jsonl") + df.to_json(output_path, orient="records", lines=True) + print(f"Saved {name} set to {output_path}") diff --git a/experiments/performance/biber_plus.py b/experiments/performance/biber_plus.py index 858aa91..caeef92 100644 --- a/experiments/performance/biber_plus.py +++ b/experiments/performance/biber_plus.py @@ -2,61 +2,71 @@ import sys import time import json -import numpy as np +import numpy as np import pandas as pd -sys.path.append('/home/kalkiek/projects/biber-multidimensional-register-analysis/') +sys.path.append("/home/kalkiek/projects/biber-multidimensional-register-analysis/") from tqdm import tqdm from experiments.performance.data_utils import load_saved_dataset -from biberplus.tagger import load_config, load_pipeline, calculate_tag_frequencies, tag_text +from biberplus.tagger import ( + load_config, + load_pipeline, + calculate_tag_frequencies, + tag_text, +) + def process_texts(texts, pipeline, config): """Process all texts and return frequencies""" start_time = time.time() - + results = [] for text in tqdm(texts, desc="Processing texts"): # tags = tag_text(text, pipeline) if text: frequencies = calculate_tag_frequencies(text, pipeline, config) results.append(frequencies) - + end_time = time.time() processing_time = end_time - start_time - + return results, processing_time + def main(): # Load configuration config = load_config() - config.update({ - 'use_gpu': False, - 'n_processes': 4, - 'function_words': False, - 'token_normalization': 100, - }) + config.update( + { + "use_gpu": False, + "n_processes": 4, + "function_words": False, + "token_normalization": 100, + } + ) pipeline = load_pipeline(config) - + print("Configuration loaded:") print(json.dumps(config, indent=2)) - + # Load dataset texts = load_saved_dataset() print(f"\nLoaded {len(texts)} texts") - + # Process texts and measure time results, processing_time = process_texts(texts, pipeline, config) - + # Convert results to DataFrame df = pd.DataFrame(results) - + # Print performance metrics print(f"\nProcessing completed in {processing_time:.2f} seconds") - print(f"Average time per text: {processing_time/len(texts):.2f} seconds") - + print(f"Average time per text: {processing_time / len(texts):.2f} seconds") + # Optional: save results - df.to_csv('biber_results.csv', index=False) + df.to_csv("biber_results.csv", index=False) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/experiments/performance/data_utils.py b/experiments/performance/data_utils.py index a704ccc..b623856 100644 --- a/experiments/performance/data_utils.py +++ b/experiments/performance/data_utils.py @@ -2,22 +2,24 @@ import sys import time import json -import numpy as np +import numpy as np import pandas as pd import pickle from tqdm import tqdm + def load_samples(dataset_path, n_samples): """Load first n_samples from a corpus.jsonl file""" texts = [] - with open(os.path.join(dataset_path, "corpus.jsonl"), 'r') as f: + with open(os.path.join(dataset_path, "corpus.jsonl"), "r") as f: for i, line in enumerate(f): if i >= n_samples: break sample = json.loads(line) - texts.append(sample['fullText']) + texts.append(sample["fullText"]) return texts + def load_all_datasets(data_dir, datasets, n_samples): """Load samples from multiple datasets""" all_texts = [] @@ -27,48 +29,52 @@ def load_all_datasets(data_dir, datasets, n_samples): all_texts.extend(texts) return all_texts + def calculate_statistics(texts): """Calculate token length statistics for a list of texts""" token_lengths = [len(text.split()) for text in texts] stats = { - 'mean': np.mean(token_lengths), - 'min': np.min(token_lengths), - 'max': np.max(token_lengths), - 'median': np.median(token_lengths), - 'std': np.std(token_lengths), - 'total': sum(token_lengths) + "mean": np.mean(token_lengths), + "min": np.min(token_lengths), + "max": np.max(token_lengths), + "median": np.median(token_lengths), + "std": np.std(token_lengths), + "total": sum(token_lengths), } return stats -def save_dataset(texts, filename='dataset.pkl'): + +def save_dataset(texts, filename="dataset.pkl"): """Save the loaded texts to a pickle file""" - with open(filename, 'wb') as f: + with open(filename, "wb") as f: pickle.dump(texts, f) -def load_saved_dataset(filename='dataset.pkl'): + +def load_saved_dataset(filename="dataset.pkl"): """Load the saved texts from a pickle file""" - with open(filename, 'rb') as f: + with open(filename, "rb") as f: return pickle.load(f) + def main(): # Constants moved into main data_dir = "/shared/3/projects/hiatus/tagged_data" datasets = [ - 'amazon', - 'reddit', - 'gmane', - 'book3corpus', - 'ao3', - 'wiki', - 'wiki_discussions' + "amazon", + "reddit", + "gmane", + "book3corpus", + "ao3", + "wiki", + "wiki_discussions", ] n_samples = 1000 - + all_texts = load_all_datasets(data_dir, datasets, n_samples) - + # Save dataset for future use save_dataset(all_texts) - + # Calculate and print statistics stats = calculate_statistics(all_texts) print(f"Total tokens: {stats['total']:,}") @@ -79,5 +85,6 @@ def main(): print(f"Median: {stats['median']}") print(f"Std dev: {stats['std']:.2f}") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/experiments/performance/neurobiber.py b/experiments/performance/neurobiber.py index 4fcfeae..c424a5f 100644 --- a/experiments/performance/neurobiber.py +++ b/experiments/performance/neurobiber.py @@ -2,36 +2,43 @@ import sys import time -sys.path.append('/home/kalkiek/projects/biber-multidimensional-register-analysis/') +sys.path.append("/home/kalkiek/projects/biber-multidimensional-register-analysis/") from experiments.performance.data_utils import load_saved_dataset -from modeling.neurobiber.tagger import load_model_and_tokenizer, get_predictions, get_predictions_chunked_batch, predict_batch +from modeling.neurobiber.tagger import ( + load_model_and_tokenizer, + get_predictions, + get_predictions_chunked_batch, + predict_batch, +) def process_texts_neurobiber(texts, model, tokenizer, batch_size=128): start_time = time.time() - + # Use predict_batch which internally handles chunking predictions = predict_batch(model, tokenizer, texts) - + processing_time = time.time() - start_time return predictions, processing_time + def main(): # Load dataset texts = load_saved_dataset() print(f"\nLoaded {len(texts)} texts") - + # Load model and tokenizer model, tokenizer = load_model_and_tokenizer() print("Model and tokenizer loaded") - + # Process texts and measure time results, processing_time = process_texts_neurobiber(texts, model, tokenizer) - + # Print performance metrics print(f"\nProcessing completed in {processing_time:.3f} seconds") - print(f"Average time per text: {processing_time/len(texts):.3f} seconds") + print(f"Average time per text: {processing_time / len(texts):.3f} seconds") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/experiments/register_variation/tag_core_corpus.py b/experiments/register_variation/tag_core_corpus.py index fb33aca..5f4b1f9 100644 --- a/experiments/register_variation/tag_core_corpus.py +++ b/experiments/register_variation/tag_core_corpus.py @@ -5,14 +5,14 @@ import sys -sys.path.append('../..') +sys.path.append("../..") from modeling.neurobiber.tagger import load_model_and_tokenizer, predict_batch # Input/Output paths -INPUT_PATH = '/shared/3/projects/hiatus/core-corpus/train.tsv' +INPUT_PATH = "/shared/3/projects/hiatus/core-corpus/train.tsv" OUTPUT_DIR = os.path.dirname(INPUT_PATH) -OUTPUT_FILE = os.path.join(OUTPUT_DIR, 'neurobiber_train.jsonl') +OUTPUT_FILE = os.path.join(OUTPUT_DIR, "neurobiber_train.jsonl") # Load model and tokenizer model, tokenizer = load_model_and_tokenizer() @@ -21,59 +21,59 @@ # Read and sample data -df = pd.read_csv(INPUT_PATH, - sep='\t', - names=['register', 'doc_id', 'text']) +df = pd.read_csv(INPUT_PATH, sep="\t", names=["register", "doc_id", "text"]) -df = df.dropna(subset=['register']) +df = df.dropna(subset=["register"]) print(f"Processing {len(df)} documents...\n") + def validate_text(text): if not isinstance(text, str): return str(text) # Convert to string if possible return text + # Process in batches BATCH_SIZE = 128 processed_count = 0 error_count = 0 -with open(OUTPUT_FILE, 'w') as f: +with open(OUTPUT_FILE, "w") as f: for i in tqdm(range(0, len(df), BATCH_SIZE)): - batch = df.iloc[i:i+BATCH_SIZE] - + batch = df.iloc[i : i + BATCH_SIZE] + try: - texts = [validate_text(text) for text in batch['text'].tolist()] - + texts = [validate_text(text) for text in batch["text"].tolist()] + # Add length check for empty texts if not any(texts): print(f"Skipping batch at index {i} - all empty texts") continue - + predictions = predict_batch(model, tokenizer, texts) - + # Verify predictions shape matches batch size if len(predictions) != len(batch): print(f"Warning: Prediction count mismatch at batch {i}") continue - + # Write results for batch_idx, row in enumerate(batch.itertuples()): try: json_obj = { - 'doc_id': row.doc_id, - 'text': row.text, - 'register': row.register.split(), - 'neural_tags': predictions[batch_idx].tolist() + "doc_id": row.doc_id, + "text": row.text, + "register": row.register.split(), + "neural_tags": predictions[batch_idx].tolist(), } - f.write(json.dumps(json_obj) + '\n') + f.write(json.dumps(json_obj) + "\n") processed_count += 1 except Exception as e: error_count += 1 print(f"Error processing row {row.doc_id}: {str(e)}") continue - + except Exception as e: error_count += 1 print(f"Error processing batch starting at index {i}: {str(e)}") diff --git a/modeling/biberta/collator.py b/modeling/biberta/collator.py index 4c8c296..490d49a 100644 --- a/modeling/biberta/collator.py +++ b/modeling/biberta/collator.py @@ -15,7 +15,7 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): text_sents = self._encode_text(features, "text") batch = self._prepare_batch(text_sents) - encodings = [feature['text_biberPlus'] for feature in features] + encodings = [feature["text_biberPlus"] for feature in features] # If special token mask has been preprocessed, pop it from the dict. special_tokens_mask = batch.pop("special_tokens_mask", None) @@ -27,8 +27,14 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): return batch, encodings def _encode_text(self, features, feature_name): - return [{'input_ids': self.tokenizer(feature[feature_name])['input_ids'][:self.max_length]} for feature in - features] + return [ + { + "input_ids": self.tokenizer(feature[feature_name])["input_ids"][ + : self.max_length + ] + } + for feature in features + ] def _prepare_batch(self, sents): return self.tokenizer.pad( @@ -54,8 +60,8 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): query_batch = self._prepare_batch(query_sents) candidate_batch = self._prepare_batch(candidate_sents) - query_encodings = [feature['query_biberPlus'] for feature in features] - candidate_encodings = [feature['candidate_biberPlus'] for feature in features] + query_encodings = [feature["query_biberPlus"] for feature in features] + candidate_encodings = [feature["candidate_biberPlus"] for feature in features] # If special token mask has been preprocessed, pop it from the dict. special_tokens_mask = query_batch.pop("special_tokens_mask", None) @@ -65,21 +71,36 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): query_batch["input_ids"], special_tokens_mask=special_tokens_mask ) - candidate_batch["input_ids"], candidate_batch["labels"] = self.torch_mask_tokens( - candidate_batch["input_ids"], special_tokens_mask=special_tokens_mask + candidate_batch["input_ids"], candidate_batch["labels"] = ( + self.torch_mask_tokens( + candidate_batch["input_ids"], special_tokens_mask=special_tokens_mask + ) ) if self.evaluate: - query_authors = [feature['query_authorID'] for feature in features] - target_authors = [feature['candidate_authorID'] for feature in features] - - return query_batch, candidate_batch, query_encodings, candidate_encodings, query_authors, target_authors + query_authors = [feature["query_authorID"] for feature in features] + target_authors = [feature["candidate_authorID"] for feature in features] + + return ( + query_batch, + candidate_batch, + query_encodings, + candidate_encodings, + query_authors, + target_authors, + ) else: return query_batch, candidate_batch, query_encodings, candidate_encodings def _encode_text(self, features, feature_name): - return [{'input_ids': self.tokenizer(feature[feature_name])['input_ids'][:self.max_length]} for feature in - features] + return [ + { + "input_ids": self.tokenizer(feature[feature_name])["input_ids"][ + : self.max_length + ] + } + for feature in features + ] def _prepare_batch(self, sents): return self.tokenizer.pad( diff --git a/modeling/biberta/main.py b/modeling/biberta/main.py index 12f4195..a5857d7 100644 --- a/modeling/biberta/main.py +++ b/modeling/biberta/main.py @@ -3,7 +3,7 @@ import wandb -sys.path.append('../../..') +sys.path.append("../../..") from src.custom_training.mlm_finetuning.models import StyleMLMPairs, StyleMLM @@ -11,57 +11,73 @@ def argument_parser(): parser = argparse.ArgumentParser() - parser.add_argument('--train_data', default='/shared/3/projects/hiatus/tagged_data/mlm_finetuning/train/', type=str) - parser.add_argument('--dev_data', default='/shared/3/projects/hiatus/tagged_data/mlm_finetuning/dev/', type=str) - parser.add_argument('--out_dir', type=str, default='/shared/3/projects/hiatus/models/style-mlm/') - parser.add_argument('--pretrained_model', default='roberta-base', type=str) - parser.add_argument('--style_dimensions', default=64, type=int) - parser.add_argument('--text_pairs', default=False, type=bool) - parser.add_argument('--resume', action='store_true') - parser.add_argument('--tokenizer', default=None, type=str) - parser.add_argument('--epochs', default=5, type=int) - parser.add_argument('--learning_rate', default=5e-5, type=float) - parser.add_argument('--grad_norm', default=1.0, type=float) - parser.add_argument('--batch_size', default=128, type=int) - parser.add_argument('--eval_batch_size', default=128, type=int) - parser.add_argument('--grad_acc', default=1, type=int) - parser.add_argument('--device', default='cuda', type=str) - parser.add_argument('--saving_step', default=200, type=int) - parser.add_argument('--weight_decay', default=1e-2, type=float) - parser.add_argument('--max_length', default=350, type=int) - parser.add_argument('--gradient_checkpointing', default=False, type=bool) - parser.add_argument('--num_warmup_steps', default=1000, type=int) - parser.add_argument('--num_training_samples', default=-1, type=int) - parser.add_argument('--num_eval_samples', default=-1, type=int) - parser.add_argument('--evaluate', action='store_true') + parser.add_argument( + "--train_data", + default="/shared/3/projects/hiatus/tagged_data/mlm_finetuning/train/", + type=str, + ) + parser.add_argument( + "--dev_data", + default="/shared/3/projects/hiatus/tagged_data/mlm_finetuning/dev/", + type=str, + ) + parser.add_argument( + "--out_dir", type=str, default="/shared/3/projects/hiatus/models/style-mlm/" + ) + parser.add_argument("--pretrained_model", default="roberta-base", type=str) + parser.add_argument("--style_dimensions", default=64, type=int) + parser.add_argument("--text_pairs", default=False, type=bool) + parser.add_argument("--resume", action="store_true") + parser.add_argument("--tokenizer", default=None, type=str) + parser.add_argument("--epochs", default=5, type=int) + parser.add_argument("--learning_rate", default=5e-5, type=float) + parser.add_argument("--grad_norm", default=1.0, type=float) + parser.add_argument("--batch_size", default=128, type=int) + parser.add_argument("--eval_batch_size", default=128, type=int) + parser.add_argument("--grad_acc", default=1, type=int) + parser.add_argument("--device", default="cuda", type=str) + parser.add_argument("--saving_step", default=200, type=int) + parser.add_argument("--weight_decay", default=1e-2, type=float) + parser.add_argument("--max_length", default=350, type=int) + parser.add_argument("--gradient_checkpointing", default=False, type=bool) + parser.add_argument("--num_warmup_steps", default=1000, type=int) + parser.add_argument("--num_training_samples", default=-1, type=int) + parser.add_argument("--num_eval_samples", default=-1, type=int) + parser.add_argument("--evaluate", action="store_true") # the following arguments are only relevant if you hope to log results in wandb - parser.add_argument('--wandb', action='store_true') - parser.add_argument('--entity', default="sadiri-michigan", type=str) - parser.add_argument('--project_name', default='style-mlm', type=str) - parser.add_argument('--run_name', default='V1', type=str) - parser.add_argument('--code_dir', default='src/custom_training/mlm_finetuning', type=str) + parser.add_argument("--wandb", action="store_true") + parser.add_argument("--entity", default="sadiri-michigan", type=str) + parser.add_argument("--project_name", default="style-mlm", type=str) + parser.add_argument("--run_name", default="V1", type=str) + parser.add_argument( + "--code_dir", default="src/custom_training/mlm_finetuning", type=str + ) return parser.parse_args() def setup_wandb(args): - wandb.init(project=args.project_name, - entity="sadiri-michigan", - resume=args.resume, - settings=wandb.Settings(code_dir=args.code_dir), - config={"epochs": args.epochs, - "batch_size": args.batch_size, - "eval_batch_size": args.eval_batch_size, - "max_length": args.max_length, - "saving_step": args.saving_step, - "learning_rate": args.learning_rate, - "pretrained_model": args.pretrained_model, - "gradient_accumulation": args.grad_acc}) + wandb.init( + project=args.project_name, + entity="sadiri-michigan", + resume=args.resume, + settings=wandb.Settings(code_dir=args.code_dir), + config={ + "epochs": args.epochs, + "batch_size": args.batch_size, + "eval_batch_size": args.eval_batch_size, + "max_length": args.max_length, + "saving_step": args.saving_step, + "learning_rate": args.learning_rate, + "pretrained_model": args.pretrained_model, + "gradient_accumulation": args.grad_acc, + }, + ) wandb.run.name = args.run_name return wandb -if __name__ == '__main__': +if __name__ == "__main__": args = argument_parser() wb = setup_wandb(args) if args.wandb else None @@ -73,4 +89,4 @@ def setup_wandb(args): model.train_model() model.evaluate() - model.save_model(step=str(-1), version='last') + model.save_model(step=str(-1), version="last") diff --git a/modeling/biberta/models.py b/modeling/biberta/models.py index fd1d496..10ff334 100644 --- a/modeling/biberta/models.py +++ b/modeling/biberta/models.py @@ -9,46 +9,69 @@ from transformers import AutoModelForMaskedLM from transformers import get_cosine_schedule_with_warmup -sys.path.append('../..') +sys.path.append("../..") from src.custom_training.model_utils import load_model, calculate_perplexity from src.custom_training.trainer_utils import get_dataloaders class StyleMLM(nn.Module): - def __init__(self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None): + def __init__( + self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None + ): super(StyleMLM, self).__init__() self.args = args self.style_dimensions = args.style_dimensions self.adversarial_loss_weight = adversarial_loss_weight self.biber_plus_size = biber_plus_size self.device = args.device - self.train_loader, self.eval_loader = get_dataloaders(args, mlm=True, pairs=False, biber=True) + self.train_loader, self.eval_loader = get_dataloaders( + args, mlm=True, pairs=False, biber=True + ) self.wandb = wandb def init_model(self): - """ Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" + """Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" self.accelerator = Accelerator(gradient_accumulation_steps=self.args.grad_acc) self.device = self.accelerator.device if self.args.resume: - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', 'last') + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", "last" + ) self.model, optimizer, scheduler = load_model(checkpoint_dir) else: - self.model = AutoModelForMaskedLM.from_pretrained(self.args.pretrained_model).to(self.device) - - optimizer = AdamW(self.model.parameters(), lr=self.args.learning_rate, weight_decay=self.args.weight_decay) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=self.args.num_warmup_steps, - num_training_steps=len(self.train_loader) * self.args.epochs, - num_cycles=1) - - self.style_ll = nn.Linear(in_features=self.style_dimensions, out_features=self.biber_plus_size, - device=self.device) - self.not_style_ll = nn.Linear(in_features=self.model.config.hidden_size - self.style_dimensions, - out_features=self.biber_plus_size, device=self.device) + self.model = AutoModelForMaskedLM.from_pretrained( + self.args.pretrained_model + ).to(self.device) + + optimizer = AdamW( + self.model.parameters(), + lr=self.args.learning_rate, + weight_decay=self.args.weight_decay, + ) + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=self.args.num_warmup_steps, + num_training_steps=len(self.train_loader) * self.args.epochs, + num_cycles=1, + ) + + self.style_ll = nn.Linear( + in_features=self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) + self.not_style_ll = nn.Linear( + in_features=self.model.config.hidden_size - self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) self.cls_dropout = nn.Dropout(p=0.2) - self.model, self.optimizer, self.train_loader, self.scheduler = self.accelerator.prepare( - self.model, optimizer, self.train_loader, scheduler + self.model, self.optimizer, self.train_loader, self.scheduler = ( + self.accelerator.prepare( + self.model, optimizer, self.train_loader, scheduler + ) ) def train_model(self): @@ -56,13 +79,22 @@ def train_model(self): self.init_model() self.model.train() - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 - best_eval_metric = float('inf') + running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = ( + 0, + 0, + 0, + 0, + ) + best_eval_metric = float("inf") for epoch in range(args.epochs): - for i, (batch, biber_encoding) in tqdm(enumerate(self.train_loader), total=len(self.train_loader)): + for i, (batch, biber_encoding) in tqdm( + enumerate(self.train_loader), total=len(self.train_loader) + ): with self.accelerator.accumulate(self.model): - loss, mlm_loss, style_loss, adversarial_loss = self.train_step(batch, biber_encoding) + loss, mlm_loss, style_loss, adversarial_loss = self.train_step( + batch, biber_encoding + ) running_loss += loss.item() running_mlm_loss += mlm_loss.item() running_style_loss += style_loss.item() @@ -74,20 +106,26 @@ def train_model(self): "Train Overall Loss": running_loss / args.grad_acc, "Train MLM Loss": running_mlm_loss / args.grad_acc, "Train Style Loss": running_style_loss / args.grad_acc, - "Train Adversarial Training Loss": running_adversarial_loss / args.grad_acc + "Train Adversarial Training Loss": running_adversarial_loss + / args.grad_acc, } self.wandb.log(train_log) print(train_log) - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 + ( + running_loss, + running_mlm_loss, + running_style_loss, + running_adversarial_loss, + ) = 0, 0, 0, 0 if i % (args.saving_step * args.grad_acc) == 0 and i > 0: if args.evaluate: eval_style_loss = self.evaluate() if eval_style_loss < best_eval_metric: best_eval_metric = eval_style_loss - self.save_model(step=str(i), version='best') + self.save_model(step=str(i), version="best") else: - self.save_model(step=str(i), version='last') + self.save_model(step=str(i), version="last") self.accelerator.end_training() @@ -110,29 +148,43 @@ def train_step(self, batch, biber_encoding): def biber_style_loss(self, cls, biber_encoding): biber_encoding = torch.FloatTensor(biber_encoding).to(self.device) - style_dimensions = cls[:, :self.style_dimensions] + style_dimensions = cls[:, : self.style_dimensions] style_output = self.style_ll(style_dimensions) return nn.MSELoss()(style_output, biber_encoding) def biber_adversarial_loss(self, cls, biber_encoding): biber_encoding = torch.FloatTensor(biber_encoding).to(self.device) - not_style_dimensions = cls[:, self.style_dimensions:] + not_style_dimensions = cls[:, self.style_dimensions :] not_style_output = self.not_style_ll(not_style_dimensions) - adversarial_loss = (-1 * nn.MSELoss()(not_style_output, biber_encoding)) * self.adversarial_loss_weight + adversarial_loss = ( + -1 * nn.MSELoss()(not_style_output, biber_encoding) + ) * self.adversarial_loss_weight return adversarial_loss def evaluate(self): print("Evaluating the model...") self.model.eval() mlm_losses, style_losses, adversarial_losses = [], [], [] - for batch, biber_encoding in tqdm(self.eval_loader, total=len(self.eval_loader), position=0, leave=True): + for batch, biber_encoding in tqdm( + self.eval_loader, total=len(self.eval_loader), position=0, leave=True + ): with torch.no_grad(): outputs = self.model(**batch.to(self.device)) mlm_loss = outputs.loss - style_loss, adversarial_loss = self.get_biber_losses(batch, biber_encoding) - mlm_losses.append(self.accelerator.gather(mlm_loss.repeat(self.args.batch_size))) - style_losses.append(self.accelerator.gather(style_loss.repeat(self.args.batch_size))) - adversarial_losses.append(self.accelerator.gather(adversarial_loss.repeat(self.args.batch_size))) + style_loss, adversarial_loss = self.get_biber_losses( + batch, biber_encoding + ) + mlm_losses.append( + self.accelerator.gather(mlm_loss.repeat(self.args.batch_size)) + ) + style_losses.append( + self.accelerator.gather(style_loss.repeat(self.args.batch_size)) + ) + adversarial_losses.append( + self.accelerator.gather( + adversarial_loss.repeat(self.args.batch_size) + ) + ) mlm_losses = torch.cat(mlm_losses)[: len(self.eval_loader)] style_losses = torch.cat(style_losses)[: len(self.eval_loader)] @@ -143,7 +195,7 @@ def evaluate(self): "Eval Perplexity": perplexity, "Eval MLM Loss": torch.mean(mlm_losses).item(), "Eval Style Loss": torch.mean(style_losses).item(), - "Eval Adversarial Loss": torch.mean(adversarial_losses).item() + "Eval Adversarial Loss": torch.mean(adversarial_losses).item(), } print(eval_log) @@ -160,15 +212,17 @@ def get_biber_losses(self, batch, biber_encoding): return style_loss, adversarial_loss def _get_cls(self, outputs, training=True): - last_hidden_state = outputs['hidden_states'][-1] + last_hidden_state = outputs["hidden_states"][-1] cls_representation = last_hidden_state[:, 0, :] if training: return self.cls_dropout(cls_representation) return cls_representation - def save_model(self, step, version='last'): - """ Save model checkpoint """ - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', f'{version}') + def save_model(self, step, version="last"): + """Save model checkpoint""" + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", f"{version}" + ) os.makedirs(checkpoint_dir, exist_ok=True) # Save the LM @@ -177,48 +231,74 @@ def save_model(self, step, version='last'): unwrapped_model.save_pretrained(checkpoint_dir) # Save the optimizer and scheduler - torch.save({ - 'step': step, - 'optimizer_state_dict': self.optimizer.state_dict(), - 'scheduler_state_dict': self.scheduler.state_dict(), - }, os.path.join(checkpoint_dir, 'optimizer_and_scheduler.pt')) + torch.save( + { + "step": step, + "optimizer_state_dict": self.optimizer.state_dict(), + "scheduler_state_dict": self.scheduler.state_dict(), + }, + os.path.join(checkpoint_dir, "optimizer_and_scheduler.pt"), + ) print(f"Saved {version} model to {checkpoint_dir}") class StyleMLMPairs(nn.Module): - def __init__(self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None): + def __init__( + self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None + ): super(StyleMLMPairs, self).__init__() self.args = args self.style_dimensions = args.style_dimensions self.adversarial_loss_weight = adversarial_loss_weight self.biber_plus_size = biber_plus_size self.device = args.device - self.train_loader, self.eval_loader = get_dataloaders_style(args, mlm=True, pairs=True) + self.train_loader, self.eval_loader = get_dataloaders_style( + args, mlm=True, pairs=True + ) self.wandb = wandb def init_model(self): - """ Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" + """Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" self.accelerator = Accelerator(gradient_accumulation_steps=self.args.grad_acc) self.device = self.accelerator.device if self.args.resume: - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', 'last') + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", "last" + ) self.model, optimizer, scheduler = load_model(checkpoint_dir) else: - self.model = AutoModelForMaskedLM.from_pretrained(self.args.pretrained_model).to(self.device) - - optimizer = AdamW(self.model.parameters(), lr=self.args.learning_rate, weight_decay=self.args.weight_decay) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=self.args.num_warmup_steps, - num_training_steps=len(self.train_loader) * self.args.epochs, - num_cycles=1) - - self.style_ll = nn.Linear(in_features=self.style_dimensions, out_features=self.biber_plus_size, - device=self.device) - self.not_style_ll = nn.Linear(in_features=self.model.config.hidden_size - self.style_dimensions, - out_features=self.biber_plus_size, device=self.device) + self.model = AutoModelForMaskedLM.from_pretrained( + self.args.pretrained_model + ).to(self.device) + + optimizer = AdamW( + self.model.parameters(), + lr=self.args.learning_rate, + weight_decay=self.args.weight_decay, + ) + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=self.args.num_warmup_steps, + num_training_steps=len(self.train_loader) * self.args.epochs, + num_cycles=1, + ) + + self.style_ll = nn.Linear( + in_features=self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) + self.not_style_ll = nn.Linear( + in_features=self.model.config.hidden_size - self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) self.cls_dropout = nn.Dropout(p=0.2) - self.model, self.optimizer, self.train_loader, self.scheduler = self.accelerator.prepare( - self.model, optimizer, self.train_loader, scheduler + self.model, self.optimizer, self.train_loader, self.scheduler = ( + self.accelerator.prepare( + self.model, optimizer, self.train_loader, scheduler + ) ) def train_model(self): @@ -226,16 +306,27 @@ def train_model(self): self.init_model() self.model.train() - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 - best_eval_metric = float('inf') + running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = ( + 0, + 0, + 0, + 0, + ) + best_eval_metric = float("inf") index = 0 for epoch in range(args.epochs): - for (query_batch, candidate_batch, query_biber, candidate_biber) in tqdm(self.train_loader, - total=len(self.train_loader)): - for batch, biber_encoding in [(query_batch, query_biber), (candidate_batch, candidate_biber)]: + for query_batch, candidate_batch, query_biber, candidate_biber in tqdm( + self.train_loader, total=len(self.train_loader) + ): + for batch, biber_encoding in [ + (query_batch, query_biber), + (candidate_batch, candidate_biber), + ]: with self.accelerator.accumulate(self.model): - loss, mlm_loss, style_loss, adversarial_loss = self.train_step(batch, biber_encoding) + loss, mlm_loss, style_loss, adversarial_loss = self.train_step( + batch, biber_encoding + ) running_loss += loss.item() running_mlm_loss += mlm_loss.item() running_style_loss += style_loss.item() @@ -248,20 +339,26 @@ def train_model(self): "Train Overall Loss": running_loss / args.grad_acc, "Train MLM Loss": running_mlm_loss / args.grad_acc, "Train Style Loss": running_style_loss / args.grad_acc, - "Train Adversarial Training Loss": running_adversarial_loss / args.grad_acc + "Train Adversarial Training Loss": running_adversarial_loss + / args.grad_acc, } self.wandb.log(train_log) print(train_log) - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 + ( + running_loss, + running_mlm_loss, + running_style_loss, + running_adversarial_loss, + ) = 0, 0, 0, 0 if index % (args.saving_step * args.grad_acc) == 0 and index > 0: if args.evaluate: eval_style_loss = self.evaluate() if eval_style_loss < best_eval_metric: best_eval_metric = eval_style_loss - self.save_model(step=str(index), version='best') + self.save_model(step=str(index), version="best") else: - self.save_model(step=str(index), version='last') + self.save_model(step=str(index), version="last") self.accelerator.end_training() @@ -284,33 +381,54 @@ def train_step(self, batch, biber_encoding): def biber_style_loss(self, cls, biber_encoding): biber_encoding = torch.FloatTensor(biber_encoding).to(self.device) - style_dimensions = cls[:, :self.style_dimensions] + style_dimensions = cls[:, : self.style_dimensions] style_output = self.style_ll(style_dimensions) return nn.MSELoss()(style_output, biber_encoding) def biber_adversarial_loss(self, cls, biber_encoding): biber_encoding = torch.FloatTensor(biber_encoding).to(self.device) - not_style_dimensions = cls[:, self.style_dimensions:] + not_style_dimensions = cls[:, self.style_dimensions :] not_style_output = self.not_style_ll(not_style_dimensions) - adversarial_loss = (-1 * nn.MSELoss()(not_style_output, biber_encoding)) * self.adversarial_loss_weight + adversarial_loss = ( + -1 * nn.MSELoss()(not_style_output, biber_encoding) + ) * self.adversarial_loss_weight return adversarial_loss def evaluate(self): print("Evaluating the model...") self.model.eval() mlm_losses, style_losses, adversarial_losses = [], [], [] - for query_batch, candidate_batch, query_biber, candidate_biber, _, _, in tqdm(self.eval_loader, - total=len(self.eval_loader), - position=0, - leave=True): - for batch, biber_encoding in [(query_batch, query_biber), (candidate_batch, candidate_biber)]: + for ( + query_batch, + candidate_batch, + query_biber, + candidate_biber, + _, + _, + ) in tqdm( + self.eval_loader, total=len(self.eval_loader), position=0, leave=True + ): + for batch, biber_encoding in [ + (query_batch, query_biber), + (candidate_batch, candidate_biber), + ]: with torch.no_grad(): outputs = self.model(**batch.to(self.device)) mlm_loss = outputs.loss - style_loss, adversarial_loss = self.get_biber_losses(batch, biber_encoding) - mlm_losses.append(self.accelerator.gather(mlm_loss.repeat(self.args.batch_size))) - style_losses.append(self.accelerator.gather(style_loss.repeat(self.args.batch_size))) - adversarial_losses.append(self.accelerator.gather(adversarial_loss.repeat(self.args.batch_size))) + style_loss, adversarial_loss = self.get_biber_losses( + batch, biber_encoding + ) + mlm_losses.append( + self.accelerator.gather(mlm_loss.repeat(self.args.batch_size)) + ) + style_losses.append( + self.accelerator.gather(style_loss.repeat(self.args.batch_size)) + ) + adversarial_losses.append( + self.accelerator.gather( + adversarial_loss.repeat(self.args.batch_size) + ) + ) mlm_losses = torch.cat(mlm_losses)[: len(self.eval_loader)] style_losses = torch.cat(style_losses)[: len(self.eval_loader)] @@ -321,7 +439,7 @@ def evaluate(self): "Eval Perplexity": perplexity, "Eval MLM Loss": torch.mean(mlm_losses).item(), "Eval Style Loss": torch.mean(style_losses).item(), - "Eval Adversarial Loss": torch.mean(adversarial_losses).item() + "Eval Adversarial Loss": torch.mean(adversarial_losses).item(), } print(eval_log) @@ -338,15 +456,17 @@ def get_biber_losses(self, batch, biber_encoding): return style_loss, adversarial_loss def _get_cls(self, outputs, training=True): - last_hidden_state = outputs['hidden_states'][-1] + last_hidden_state = outputs["hidden_states"][-1] cls_representation = last_hidden_state[:, 0, :] if training: return self.cls_dropout(cls_representation) return cls_representation - def save_model(self, step, version='last'): - """ Save model checkpoint """ - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', f'{version}') + def save_model(self, step, version="last"): + """Save model checkpoint""" + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", f"{version}" + ) os.makedirs(checkpoint_dir, exist_ok=True) # Save the LM @@ -355,10 +475,13 @@ def save_model(self, step, version='last'): unwrapped_model.save_pretrained(checkpoint_dir) # Save the optimizer and scheduler - torch.save({ - 'step': step, - 'optimizer_state_dict': self.optimizer.state_dict(), - 'scheduler_state_dict': self.scheduler.state_dict(), - }, os.path.join(checkpoint_dir, 'optimizer_and_scheduler.pt')) + torch.save( + { + "step": step, + "optimizer_state_dict": self.optimizer.state_dict(), + "scheduler_state_dict": self.scheduler.state_dict(), + }, + os.path.join(checkpoint_dir, "optimizer_and_scheduler.pt"), + ) print(f"Saved {version} model to {checkpoint_dir}") diff --git a/modeling/build_datasets/combine_datasets.py b/modeling/build_datasets/combine_datasets.py index a3c07d6..1189729 100644 --- a/modeling/build_datasets/combine_datasets.py +++ b/modeling/build_datasets/combine_datasets.py @@ -5,12 +5,12 @@ def combine_and_save_datasets(split_files, out_path): datasets = [] for file_path in split_files: dataset = load_dataset("json", data_files=file_path) - datasets.append(dataset['train']) + datasets.append(dataset["train"]) combined = concatenate_datasets(datasets) combined = combined.shuffle() new_dataset = DatasetDict() - new_dataset['train'] = combined + new_dataset["train"] = combined new_dataset.save_to_disk(out_path) @@ -19,19 +19,19 @@ def load_split_files(input_directories, split): return [f"{input_directory}/{split}.jsonl" for input_directory in input_directories] -if __name__ == '__main__': +if __name__ == "__main__": input_directories = [ - '/shared/3/projects/hiatus/tagged_data/amazon/', - '/shared/3/projects/hiatus/tagged_data/reddit/', - '/shared/3/projects/hiatus/tagged_data/book3corpus/', - '/shared/3/projects/hiatus/tagged_data/wiki/', - '/shared/3/projects/hiatus/tagged_data/wiki_discussions/', - '/shared/3/projects/hiatus/tagged_data/realnews/', - '/shared/3/projects/hiatus/tagged_data/gmane/' + "/shared/3/projects/hiatus/tagged_data/amazon/", + "/shared/3/projects/hiatus/tagged_data/reddit/", + "/shared/3/projects/hiatus/tagged_data/book3corpus/", + "/shared/3/projects/hiatus/tagged_data/wiki/", + "/shared/3/projects/hiatus/tagged_data/wiki_discussions/", + "/shared/3/projects/hiatus/tagged_data/realnews/", + "/shared/3/projects/hiatus/tagged_data/gmane/", ] - for split in ['train', 'dev', 'test']: + for split in ["train", "dev", "test"]: print(f"Working on {split}") split_files = load_split_files(input_directories, split) - out_path = '/shared/3/projects/hiatus/tagged_data/sampled_dataset/' + out_path = "/shared/3/projects/hiatus/tagged_data/sampled_dataset/" combine_and_save_datasets(split_files, f"{out_path}{split}") diff --git a/modeling/build_datasets/contrastive/align.py b/modeling/build_datasets/contrastive/align.py index 39ac6f2..9345d51 100644 --- a/modeling/build_datasets/contrastive/align.py +++ b/modeling/build_datasets/contrastive/align.py @@ -2,17 +2,19 @@ def align(candidate_path, query_path): - candidates = pd.read_json(candidate_path, orient='records', lines=True) - queries = pd.read_json(query_path, orient='records', lines=True) + candidates = pd.read_json(candidate_path, orient="records", lines=True) + queries = pd.read_json(query_path, orient="records", lines=True) - candidates['authorIDs'] = candidates['authorIDs'].astype('str') - queries['authorIDs'] = queries['authorIDs'].astype('str') + candidates["authorIDs"] = candidates["authorIDs"].astype("str") + queries["authorIDs"] = queries["authorIDs"].astype("str") - candidates = candidates.sort_values(['authorIDs']) - queries = queries.sort_values(['authorIDs']) + candidates = candidates.sort_values(["authorIDs"]) + queries = queries.sort_values(["authorIDs"]) - assert set(queries['documentID']).intersection(set(candidates['documentID'])) == set() - assert queries['authorIDs'].tolist() == candidates['authorIDs'].tolist() + assert ( + set(queries["documentID"]).intersection(set(candidates["documentID"])) == set() + ) + assert queries["authorIDs"].tolist() == candidates["authorIDs"].tolist() - candidates.to_json(candidate_path, orient='records', lines=True) - queries.to_json(query_path, orient='records', lines=True) + candidates.to_json(candidate_path, orient="records", lines=True) + queries.to_json(query_path, orient="records", lines=True) diff --git a/modeling/build_datasets/contrastive/convert_to_datasets.py b/modeling/build_datasets/contrastive/convert_to_datasets.py index 0ad421d..29fce40 100644 --- a/modeling/build_datasets/contrastive/convert_to_datasets.py +++ b/modeling/build_datasets/contrastive/convert_to_datasets.py @@ -5,8 +5,8 @@ def create_datasets(datasets, folder): for dataset in datasets: - print(f'Creating {dataset} dataset...') - inpath = os.path.join(folder, f'{dataset}.jsonl') + print(f"Creating {dataset} dataset...") + inpath = os.path.join(folder, f"{dataset}.jsonl") outpath = os.path.join(folder, dataset) create_dataset(inpath, outpath) @@ -16,8 +16,8 @@ def create_dataset(inpath, outpath): dataset.save_to_disk(outpath) -if __name__ == '__main__': - folder = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/' - datasets = ['train', 'dev', 'test'] +if __name__ == "__main__": + folder = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/" + datasets = ["train", "dev", "test"] create_datasets(datasets, folder) diff --git a/modeling/build_datasets/contrastive/main.py b/modeling/build_datasets/contrastive/main.py index c635544..94a9cda 100644 --- a/modeling/build_datasets/contrastive/main.py +++ b/modeling/build_datasets/contrastive/main.py @@ -5,7 +5,7 @@ from merge import merge_datasets from partition import partition -sys.path.append('../../..') +sys.path.append("../../..") from src.build_datasets.contrastive.convert_to_datasets import create_dataset @@ -13,29 +13,44 @@ def build_dataset(input_file, output_path, nrows): partition(input_file, output_path, nrows) - for type in ['dev', 'test', 'train']: + for type in ["dev", "test", "train"]: build_data_split(type, output_path) def build_data_split(type, output_path): print(f"Aligning {type}") - align(os.path.join(output_path, f'{type}_candidates.jsonl'), os.path.join(output_path, f'{type}_queries.jsonl')) + align( + os.path.join(output_path, f"{type}_candidates.jsonl"), + os.path.join(output_path, f"{type}_queries.jsonl"), + ) print(f"Merging {type} data") merge_datasets( - [(os.path.join(output_path, f'{type}_queries.jsonl'), os.path.join(output_path, f'{type}_candidates.jsonl'))], - os.path.join(output_path, f'{type}.jsonl')) - - print(f'Creating {type} dataset...') - create_dataset(os.path.join(output_path, f'{type}.jsonl'), os.path.join(output_path, type)) - - -if __name__ == '__main__': + [ + ( + os.path.join(output_path, f"{type}_queries.jsonl"), + os.path.join(output_path, f"{type}_candidates.jsonl"), + ) + ], + os.path.join(output_path, f"{type}.jsonl"), + ) + + print(f"Creating {type} dataset...") + create_dataset( + os.path.join(output_path, f"{type}.jsonl"), os.path.join(output_path, type) + ) + + +if __name__ == "__main__": input_output_pairs = [ - ('/home/kalkiek/datasets/pan20-av-training-small/pan_small.jsonl', - '/home/kalkiek/datasets/pan20-av-training-small/'), - ('/home/kalkiek/datasets/pan20-av-training-large/pan_large.jsonl', - '/home/kalkiek/datasets/pan20-av-training-large/'), + ( + "/home/kalkiek/datasets/pan20-av-training-small/pan_small.jsonl", + "/home/kalkiek/datasets/pan20-av-training-small/", + ), + ( + "/home/kalkiek/datasets/pan20-av-training-large/pan_large.jsonl", + "/home/kalkiek/datasets/pan20-av-training-large/", + ), ] nrows = None # None to use the whole file. Samples from the first nrows diff --git a/modeling/build_datasets/contrastive/merge.py b/modeling/build_datasets/contrastive/merge.py index 74e1bda..f801c14 100644 --- a/modeling/build_datasets/contrastive/merge.py +++ b/modeling/build_datasets/contrastive/merge.py @@ -7,63 +7,87 @@ def merge_datasets(input_paths, output_path, biber=False): output_path = Path(output_path) - with output_path.open('w') as out: + with output_path.open("w") as out: for query_path, candidate_path in input_paths: query_path = Path(query_path) candidate_path = Path(candidate_path) try: - with query_path.open('r') as f1, candidate_path.open('r') as f2: + with query_path.open("r") as f1, candidate_path.open("r") as f2: for query, candidate in tqdm(zip(f1, f2)): query = json.loads(query) candidate = json.loads(candidate) - assert str(query['authorIDs']) == str(candidate['authorIDs']) + assert str(query["authorIDs"]) == str(candidate["authorIDs"]) line = { - 'query_id': query['documentID'], - 'query_authorID': query['authorIDs'], - 'query_text': query['fullText'], - 'candidate_id': candidate['documentID'], - 'candidate_authorID': candidate['authorIDs'], - 'candidate_text': candidate['fullText'] + "query_id": query["documentID"], + "query_authorID": query["authorIDs"], + "query_text": query["fullText"], + "candidate_id": candidate["documentID"], + "candidate_authorID": candidate["authorIDs"], + "candidate_text": candidate["fullText"], } - if biber and 'encodings' in query and 'encodings' in candidate \ - and query['encodings'] and candidate['encodings'] \ - and 'binary' in query['encodings'] and 'binary' in candidate['encodings']: - line.update({ - 'query_biberPlus': query['encodings']['binary'], - 'candidate_biberPlus': candidate['encodings']['binary'] - }) + if ( + biber + and "encodings" in query + and "encodings" in candidate + and query["encodings"] + and candidate["encodings"] + and "binary" in query["encodings"] + and "binary" in candidate["encodings"] + ): + line.update( + { + "query_biberPlus": query["encodings"]["binary"], + "candidate_biberPlus": candidate["encodings"][ + "binary" + ], + } + ) - out.write(json.dumps(line, ensure_ascii=False) + '\n') + out.write(json.dumps(line, ensure_ascii=False) + "\n") except Exception as e: - print(f"An error occurred while processing {query_path} and {candidate_path}: {e}") + print( + f"An error occurred while processing {query_path} and {candidate_path}: {e}" + ) if __name__ == "__main__": input_paths = [ - '/shared/3/projects/hiatus/Amazon', - '/shared/3/projects/hiatus/gmane', - '/shared/3/projects/hiatus/realnews', - '/shared/3/projects/hiatus/wiki_discussions', - '/shared/3/projects/hiatus/Reddit/data', - '/shared/3/projects/hiatus/BookCorpus', - '/shared/3/projects/hiatus/wiki' + "/shared/3/projects/hiatus/Amazon", + "/shared/3/projects/hiatus/gmane", + "/shared/3/projects/hiatus/realnews", + "/shared/3/projects/hiatus/wiki_discussions", + "/shared/3/projects/hiatus/Reddit/data", + "/shared/3/projects/hiatus/BookCorpus", + "/shared/3/projects/hiatus/wiki", ] - output_path = '/shared/3/projects/hiatus/pretraining/data' + output_path = "/shared/3/projects/hiatus/pretraining/data" - train_input_paths = [(os.path.join(p, 'train_queries.jsonl'), os.path.join(p, 'train_candidates.jsonl')) for p in - input_paths] - merge_datasets(train_input_paths, os.path.join(output_path, 'train.jsonl')) + train_input_paths = [ + ( + os.path.join(p, "train_queries.jsonl"), + os.path.join(p, "train_candidates.jsonl"), + ) + for p in input_paths + ] + merge_datasets(train_input_paths, os.path.join(output_path, "train.jsonl")) - dev_input_paths = [(os.path.join(p, 'dev_queries.jsonl'), os.path.join(p, 'dev_candidates.jsonl')) for p in - input_paths] - merge_datasets(dev_input_paths, os.path.join(output_path, 'dev.jsonl')) + dev_input_paths = [ + (os.path.join(p, "dev_queries.jsonl"), os.path.join(p, "dev_candidates.jsonl")) + for p in input_paths + ] + merge_datasets(dev_input_paths, os.path.join(output_path, "dev.jsonl")) - test_input_paths = [(os.path.join(p, 'test_queries.jsonl'), os.path.join(p, 'test_candidates.jsonl')) for p in - input_paths] - merge_datasets(test_input_paths, os.path.join(output_path, 'test.jsonl')) + test_input_paths = [ + ( + os.path.join(p, "test_queries.jsonl"), + os.path.join(p, "test_candidates.jsonl"), + ) + for p in input_paths + ] + merge_datasets(test_input_paths, os.path.join(output_path, "test.jsonl")) diff --git a/modeling/build_datasets/contrastive/partition.py b/modeling/build_datasets/contrastive/partition.py index 6ba8747..5c5115a 100644 --- a/modeling/build_datasets/contrastive/partition.py +++ b/modeling/build_datasets/contrastive/partition.py @@ -12,31 +12,42 @@ def partition(input_file, output_path, nrows=None): df = load_metadata(input_file, nrows) samples_X, samples_Y = sample_text_pairs(df) - partition = split_train_dev_test(samples_X['authorID'].tolist()) + partition = split_train_dev_test(samples_X["authorID"].tolist()) save_text_samples(samples_X, samples_Y, partition, input_file, output_path) def load_metadata(path, nrows=None): logging.info("Loading meta data...") - df = pd.read_json(path, orient='records', lines=True, nrows=nrows) - df['authorID'] = df['authorIDs'].astype('str') + df = pd.read_json(path, orient="records", lines=True, nrows=nrows) + df["authorID"] = df["authorIDs"].astype("str") return df def sample_text_pairs(df): print("Sampling text pairs...") - samples_X = df.groupby(['authorID']).sample(1) - samples_Y = df[~df['documentID'].isin(samples_X['documentID'])].groupby(['authorID']).sample(1) + samples_X = df.groupby(["authorID"]).sample(1) + samples_Y = ( + df[~df["documentID"].isin(samples_X["documentID"])] + .groupby(["authorID"]) + .sample(1) + ) del df if len(samples_X) > len(samples_Y): - samples_X = samples_X[samples_X['authorID'].isin(samples_Y['authorID'])] + samples_X = samples_X[samples_X["authorID"].isin(samples_Y["authorID"])] if len(samples_Y) < len(samples_X): - samples_Y = samples_Y[samples_Y['authorID'].isin(samples_X['authorID'])] - - assert set(samples_Y['documentID'].tolist()).intersection(set(samples_X['documentID'].tolist())) == set() - assert len(samples_X['authorID'].sort_values().tolist()) == len(samples_Y['authorID'].sort_values().tolist()) + samples_Y = samples_Y[samples_Y["authorID"].isin(samples_X["authorID"])] + + assert ( + set(samples_Y["documentID"].tolist()).intersection( + set(samples_X["documentID"].tolist()) + ) + == set() + ) + assert len(samples_X["authorID"].sort_values().tolist()) == len( + samples_Y["authorID"].sort_values().tolist() + ) return samples_X, samples_Y @@ -49,30 +60,37 @@ def split_train_dev_test(authorID): logging.info(f"{len(test)} test samples") logging.info(f"{len(train)} training samples") - return {'train': train, 'dev': dev, 'test': test} + return {"train": train, "dev": dev, "test": test} def save_text_samples(samples_X, samples_Y, partition, input_file, output_path): print("saving text pair samples") partitions = { - 'train': ['train_candidates.jsonl', 'train_queries.jsonl'], - 'dev': ['dev_candidates.jsonl', 'dev_queries.jsonl'], - 'test': ['test_candidates.jsonl', 'test_queries.jsonl'] + "train": ["train_candidates.jsonl", "train_queries.jsonl"], + "dev": ["dev_candidates.jsonl", "dev_queries.jsonl"], + "test": ["test_candidates.jsonl", "test_queries.jsonl"], } for key, value in partition.items(): samples_X_partition = set( - samples_X[samples_X['authorID'].isin(partition[key])]['documentID'].astype('str').tolist()) + samples_X[samples_X["authorID"].isin(partition[key])]["documentID"] + .astype("str") + .tolist() + ) samples_Y_partition = set( - samples_Y[samples_Y['authorID'].isin(partition[key])]['documentID'].astype('str').tolist()) - - with open(input_file, 'r') as f, \ - open(os.path.join(output_path, partitions[key][0]), 'w') as out1, \ - open(os.path.join(output_path, partitions[key][1]), 'w') as out2: - + samples_Y[samples_Y["authorID"].isin(partition[key])]["documentID"] + .astype("str") + .tolist() + ) + + with ( + open(input_file, "r") as f, + open(os.path.join(output_path, partitions[key][0]), "w") as out1, + open(os.path.join(output_path, partitions[key][1]), "w") as out2, + ): for line in tqdm(f): line = json.loads(line) - if line['documentID'] in samples_X_partition: - out1.write(json.dumps(line, ensure_ascii=False) + '\n') - elif line['documentID'] in samples_Y_partition: - out2.write(json.dumps(line, ensure_ascii=False) + '\n') + if line["documentID"] in samples_X_partition: + out1.write(json.dumps(line, ensure_ascii=False) + "\n") + elif line["documentID"] in samples_Y_partition: + out2.write(json.dumps(line, ensure_ascii=False) + "\n") diff --git a/modeling/build_datasets/curate_dataset.py b/modeling/build_datasets/curate_dataset.py index af08780..6446aea 100644 --- a/modeling/build_datasets/curate_dataset.py +++ b/modeling/build_datasets/curate_dataset.py @@ -12,7 +12,7 @@ def count_lines(input_file): """Returns the number of lines in a file""" print(f"Counting lines in {input_file}") try: - with open(input_file, 'rb') as file: + with open(input_file, "rb") as file: return sum(1 for _ in file) except FileNotFoundError: print(f"File not found: {input_file}") @@ -26,12 +26,12 @@ def get_encodings_df(input_file, total_lines): with jsonlines.open(input_file) as reader: for index, obj in tqdm(enumerate(reader), total=total_lines): - if 'encodings' in obj and obj['encodings']: - row = [index, obj['documentID']] - row.extend(obj['encodings']['binary']) + if "encodings" in obj and obj["encodings"]: + row = [index, obj["documentID"]] + row.extend(obj["encodings"]["binary"]) rows.append(row) - columns = ['index', 'documentID'] + list(range(192)) + columns = ["index", "documentID"] + list(range(192)) return pd.DataFrame(rows, columns=columns) @@ -39,15 +39,17 @@ def stratified_sample(df, sample_size): """Perform stratified sampling on the dataframe.""" documentIDs = set() - for column in tqdm(df.columns.tolist(), desc="Stratified sampling. No feature left behind"): - if column in ['index', 'documentID']: + for column in tqdm( + df.columns.tolist(), desc="Stratified sampling. No feature left behind" + ): + if column in ["index", "documentID"]: continue col_df = df[df[column] != 0] if len(col_df) < sample_size: - sample_documents = set(col_df['documentID'].unique()) + sample_documents = set(col_df["documentID"].unique()) else: sample_df = col_df.sample(sample_size, replace=False, random_state=0) - sample_documents = set(sample_df['documentID'].unique()) + sample_documents = set(sample_df["documentID"].unique()) documentIDs.update(sample_documents) return documentIDs @@ -58,15 +60,19 @@ def save_samples(input_file, output_file, total_lines, sample_documentIDs): num_docs = len(sample_documentIDs) print(f"Appending {num_docs} document samples from {input_file} to {output_file}") try: - with jsonlines.open(output_file, mode='a') as writer: + with jsonlines.open(output_file, mode="a") as writer: with jsonlines.open(input_file) as reader: - for obj in tqdm(reader, desc=f"Saving {num_docs} to {output_file}", total=total_lines): - if obj['documentID'] in sample_documentIDs: + for obj in tqdm( + reader, + desc=f"Saving {num_docs} to {output_file}", + total=total_lines, + ): + if obj["documentID"] in sample_documentIDs: line = { - 'text': obj['fullText'], - 'documentID': obj['documentID'], - 'text_biberPlus': obj['encodings']['binary'], - 'authorID': obj['authorIDs'] + "text": obj["fullText"], + "documentID": obj["documentID"], + "text_biberPlus": obj["encodings"]["binary"], + "authorID": obj["authorIDs"], } writer.write(line) except Exception as e: @@ -83,7 +89,7 @@ def curate(input_file, intermediate_encodings, output_file, K=10000): else: encodings_df = get_encodings_df(input_file, total_lines) print(f"Saving binary encodings to {intermediate_encodings}") - encodings_df.to_parquet(intermediate_encodings, index=False, compression='gzip') + encodings_df.to_parquet(intermediate_encodings, index=False, compression="gzip") sample_documentIDs = stratified_sample(encodings_df, K) save_samples(input_file, output_file, total_lines, sample_documentIDs) @@ -101,34 +107,42 @@ def split_dataset(input_file, train_file, dev_file, test_file, test_size=0.15): # Generate shuffled indices indices = np.random.permutation(total_lines) - train_indices, dev_indices, test_indices = np.split(indices, [train_lines, train_lines + dev_lines]) + train_indices, dev_indices, test_indices = np.split( + indices, [train_lines, train_lines + dev_lines] + ) # Convert to set for faster lookup train_indices = set(train_indices) dev_indices = set(dev_indices) - with jsonlines.open(input_file, 'r') as reader, \ - open(train_file, 'w') as train_writer, \ - open(dev_file, 'w') as dev_writer, \ - open(test_file, 'w') as test_writer: - + with ( + jsonlines.open(input_file, "r") as reader, + open(train_file, "w") as train_writer, + open(dev_file, "w") as dev_writer, + open(test_file, "w") as test_writer, + ): for idx, obj in tqdm(enumerate(reader), total=total_lines): - line = json.dumps({'text': obj['text'], - 'documentID': str(obj['documentID']), - 'authorID': str(obj['authorID']), - 'text_biberPlus': obj['text_biberPlus']}, ensure_ascii=False) + line = json.dumps( + { + "text": obj["text"], + "documentID": str(obj["documentID"]), + "authorID": str(obj["authorID"]), + "text_biberPlus": obj["text_biberPlus"], + }, + ensure_ascii=False, + ) if idx in train_indices: - train_writer.write(line + '\n') + train_writer.write(line + "\n") elif idx in dev_indices: - dev_writer.write(line + '\n') + dev_writer.write(line + "\n") else: - test_writer.write(line + '\n') + test_writer.write(line + "\n") def create_datasets(datasets, folder): for dataset in datasets: - print(f'Creating {dataset} dataset...') - inpath = os.path.join(folder, f'{dataset}.jsonl') + print(f"Creating {dataset} dataset...") + inpath = os.path.join(folder, f"{dataset}.jsonl") outpath = os.path.join(folder, dataset) create_dataset(inpath, outpath) @@ -138,40 +152,44 @@ def create_dataset(inpath, outpath): dataset.save_to_disk(outpath) -if __name__ == '__main__': - input_files = ['/shared/3/projects/hiatus/tagged_data/amazon/amazon.jsonl', - '/shared/3/projects/hiatus/tagged_data/reddit/reddit.jsonl', - '/shared/3/projects/hiatus/tagged_data/book3corpus/book3corpus.jsonl', - '/shared/3/projects/hiatus/tagged_data/wiki/wiki.jsonl', - '/shared/3/projects/hiatus/tagged_data/wiki_discussions/wiki_discussions.jsonl', - '/shared/3/projects/hiatus/tagged_data/realnews/realnews.jsonl', - '/shared/3/projects/hiatus/tagged_data/gmane/gmane.jsonl'] +if __name__ == "__main__": + input_files = [ + "/shared/3/projects/hiatus/tagged_data/amazon/amazon.jsonl", + "/shared/3/projects/hiatus/tagged_data/reddit/reddit.jsonl", + "/shared/3/projects/hiatus/tagged_data/book3corpus/book3corpus.jsonl", + "/shared/3/projects/hiatus/tagged_data/wiki/wiki.jsonl", + "/shared/3/projects/hiatus/tagged_data/wiki_discussions/wiki_discussions.jsonl", + "/shared/3/projects/hiatus/tagged_data/realnews/realnews.jsonl", + "/shared/3/projects/hiatus/tagged_data/gmane/gmane.jsonl", + ] intermediate_encoding_files = [ - '/shared/3/projects/hiatus/tagged_data/amazon/binary_encodings.parquet.gzip', - '/shared/3/projects/hiatus/tagged_data/reddit/binary_encodings.parquet.gzip', - '/shared/3/projects/hiatus/tagged_data/book3corpus/binary_encodings.parquet.gzip', - '/shared/3/projects/hiatus/tagged_data/wiki/binary_encodings.parquet.gzip', - '/shared/3/projects/hiatus/tagged_data/wiki_discussions/binary_encodings.parquet.gzip', - '/shared/3/projects/hiatus/tagged_data/realnews/binary_encodings.parquet.gzip', - '/shared/3/projects/hiatus/tagged_data/gmane/binary_encodings.parquet.gzip', + "/shared/3/projects/hiatus/tagged_data/amazon/binary_encodings.parquet.gzip", + "/shared/3/projects/hiatus/tagged_data/reddit/binary_encodings.parquet.gzip", + "/shared/3/projects/hiatus/tagged_data/book3corpus/binary_encodings.parquet.gzip", + "/shared/3/projects/hiatus/tagged_data/wiki/binary_encodings.parquet.gzip", + "/shared/3/projects/hiatus/tagged_data/wiki_discussions/binary_encodings.parquet.gzip", + "/shared/3/projects/hiatus/tagged_data/realnews/binary_encodings.parquet.gzip", + "/shared/3/projects/hiatus/tagged_data/gmane/binary_encodings.parquet.gzip", ] - output_file = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/corpus.jsonl' + output_file = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/corpus.jsonl" # Save a stratified sample of Biber features from each dataset - for (input_file, intermediate_encodings) in list(zip(input_files, intermediate_encoding_files)): + for input_file, intermediate_encodings in list( + zip(input_files, intermediate_encoding_files) + ): curate(input_file, intermediate_encodings, output_file) # Shuffle and split into multiple files - input_file = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/corpus.jsonl' - train_file = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/train.jsonl' - dev_file = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/dev.jsonl' - test_file = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/test.jsonl' + input_file = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/corpus.jsonl" + train_file = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/train.jsonl" + dev_file = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/dev.jsonl" + test_file = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/test.jsonl" split_dataset(input_file, train_file, dev_file, test_file) - folder = '/shared/3/projects/hiatus/tagged_data/mlm_finetuning/' - datasets = ['train', 'dev', 'test'] + folder = "/shared/3/projects/hiatus/tagged_data/mlm_finetuning/" + datasets = ["train", "dev", "test"] create_datasets(datasets, folder) diff --git a/modeling/build_datasets/multilingual/spanish_subreddit_sampling.py b/modeling/build_datasets/multilingual/spanish_subreddit_sampling.py index 3aade7d..7e38354 100644 --- a/modeling/build_datasets/multilingual/spanish_subreddit_sampling.py +++ b/modeling/build_datasets/multilingual/spanish_subreddit_sampling.py @@ -9,86 +9,100 @@ import pandas as pd import zstandard -reddit_directory = '/shared/2/datasets/reddit-dump-all/RC/' -output_directory = '/shared/3/projects/hiatus/multilingual/reddit-spanish/' +reddit_directory = "/shared/2/datasets/reddit-dump-all/RC/" +output_directory = "/shared/3/projects/hiatus/multilingual/reddit-spanish/" spanish_subreddits = { - 'r/Ciencia': 'Science topics.', - 'r/Cinefilos': 'Films from Spain and Latin America.', - 'r/Chistes': 'Jokes.', - 'r/ConsejosDePareja': 'Relationship advice for SOs.', - 'r/cuentaleareddit': 'Casual conversation.', - 'r/Desahogo': 'Sub for venting.', - 'r/espanol': 'The first subreddit in Spanish ever.', - 'r/filosofia_en_espanol': 'Philosophical discussions.', - 'r/fisica': 'News and popular science related to Physics.', - 'r/Futbol': 'World football (soccer) matters.', - 'r/HistoriasDeReddit': 'Random anecdotes and stories by the community members.', - 'r/HistoriasdeTerror': 'Horror stories,', - 'r/Jagverse': 'Technology and science topics.', - 'r/Latinos': 'Similar to the Latin American sub.', - 'r/Libros': 'Books and literature', - 'r/Programacion': 'Programmer community.', - 'r/preguntaleareddit': 'The equivalent to r/AskReddit.', - 'r/RedditPregunta': 'The equivalent to r/AskReddit.', - 'r/Redditores': 'Another multi-topic subreddit.', - 'r/relaciones': 'General relationship advice.', - 'r/Spanishhelp': 'The sister subreddit of r/Spanish. Homework questions, exercise checks and text proofreads can be requested here.', - 'r/Videojuego': 'Gaming community.', - 'r/webcomicsenespanol': 'Web comics.', - 'r/WriteStreakES': 'The equivalent to r/WriteStreak but in Spanish. The main goal is to constantly practice writing in a target language; the community offers corrections.', - 'r/yo_elvr': 'Generic "me_irl" memes.', - 'r/Argentina': None, - 'r/ArgenBeauty': 'Beauty tips in Argentina.', - 'r/ArgEntos': 'Cannabis ethusiasts from Argentina.', - 'r/ArgenCirclejerk': None, - 'r/dankgentina': 'Memes and shitposting.', - 'r/ForwardsDeMama': 'Similar to r/Forwardsfromgrandma.', - 'r/Fulbo': 'Football in Argentina.', - 'r/SquarePosting': 'Memes.', - 'r/Bolivia': None, - 'r/Chile': None, - 'r/LaRoja': 'National Chilean football soccer team.', - 'r/ChileCringe': None, - 'r/yo_ctm': 'Similar to r/yo_elvr but adapted for a Chilean audience.', - 'r/Colombia': None, - 'r/Ticos': '(Costa Rica)', - 'r/Dominican': None, - 'r/Ecuador': None, - 'r/EstadosUnidos': 'Spanish speaking people in the U.S.', - 'r/Latinoamerica': 'Latin American matters.', - 'r/Mexico': None, - 'r/Chairos': 'Low-effort Mexican liberalism and leftist activism on social media.', - 'r/FutbolMX': 'Football in Mexico.', - 'r/LigaMX': 'Mexican football league tournaments.', - 'r/MAAU': 'Memes and shitposts from mostly a Mexican audience.', - 'r/MeMexico': 'Mexican memes.', - 'r/MexicoCircleJerk': 'Self-explanatory', - 'r/Mujico': 'Memes and shitpostings.', - 'r/VideojuegosMX': 'Videogame scene.', - 'r/Panama': None, - 'r/Paraguay': None, - 'r/Peru': None, - 'r/PuertoRico': None, - 'r/Spain': None, - 'r/Asi_va_Espana': 'Memes and shitposting.', - 'r/es': 'Spanish (Spain) matters.', - 'r/LaLiga': 'Spanish football league tournament.', - 'r/mapassincanarias': 'Maps without the Canary Islands, akin to r/MapsWithoutNZ.', - 'r/SpainPolitics': None, - 'r/Uruguay': None, - 'r/Vzla': None, - 'r/Vencirclejerk': 'Circlejerk in Venezuela.', + "r/Ciencia": "Science topics.", + "r/Cinefilos": "Films from Spain and Latin America.", + "r/Chistes": "Jokes.", + "r/ConsejosDePareja": "Relationship advice for SOs.", + "r/cuentaleareddit": "Casual conversation.", + "r/Desahogo": "Sub for venting.", + "r/espanol": "The first subreddit in Spanish ever.", + "r/filosofia_en_espanol": "Philosophical discussions.", + "r/fisica": "News and popular science related to Physics.", + "r/Futbol": "World football (soccer) matters.", + "r/HistoriasDeReddit": "Random anecdotes and stories by the community members.", + "r/HistoriasdeTerror": "Horror stories,", + "r/Jagverse": "Technology and science topics.", + "r/Latinos": "Similar to the Latin American sub.", + "r/Libros": "Books and literature", + "r/Programacion": "Programmer community.", + "r/preguntaleareddit": "The equivalent to r/AskReddit.", + "r/RedditPregunta": "The equivalent to r/AskReddit.", + "r/Redditores": "Another multi-topic subreddit.", + "r/relaciones": "General relationship advice.", + "r/Spanishhelp": "The sister subreddit of r/Spanish. Homework questions, exercise checks and text proofreads can be requested here.", + "r/Videojuego": "Gaming community.", + "r/webcomicsenespanol": "Web comics.", + "r/WriteStreakES": "The equivalent to r/WriteStreak but in Spanish. The main goal is to constantly practice writing in a target language; the community offers corrections.", + "r/yo_elvr": 'Generic "me_irl" memes.', + "r/Argentina": None, + "r/ArgenBeauty": "Beauty tips in Argentina.", + "r/ArgEntos": "Cannabis ethusiasts from Argentina.", + "r/ArgenCirclejerk": None, + "r/dankgentina": "Memes and shitposting.", + "r/ForwardsDeMama": "Similar to r/Forwardsfromgrandma.", + "r/Fulbo": "Football in Argentina.", + "r/SquarePosting": "Memes.", + "r/Bolivia": None, + "r/Chile": None, + "r/LaRoja": "National Chilean football soccer team.", + "r/ChileCringe": None, + "r/yo_ctm": "Similar to r/yo_elvr but adapted for a Chilean audience.", + "r/Colombia": None, + "r/Ticos": "(Costa Rica)", + "r/Dominican": None, + "r/Ecuador": None, + "r/EstadosUnidos": "Spanish speaking people in the U.S.", + "r/Latinoamerica": "Latin American matters.", + "r/Mexico": None, + "r/Chairos": "Low-effort Mexican liberalism and leftist activism on social media.", + "r/FutbolMX": "Football in Mexico.", + "r/LigaMX": "Mexican football league tournaments.", + "r/MAAU": "Memes and shitposts from mostly a Mexican audience.", + "r/MeMexico": "Mexican memes.", + "r/MexicoCircleJerk": "Self-explanatory", + "r/Mujico": "Memes and shitpostings.", + "r/VideojuegosMX": "Videogame scene.", + "r/Panama": None, + "r/Paraguay": None, + "r/Peru": None, + "r/PuertoRico": None, + "r/Spain": None, + "r/Asi_va_Espana": "Memes and shitposting.", + "r/es": "Spanish (Spain) matters.", + "r/LaLiga": "Spanish football league tournament.", + "r/mapassincanarias": "Maps without the Canary Islands, akin to r/MapsWithoutNZ.", + "r/SpainPolitics": None, + "r/Uruguay": None, + "r/Vzla": None, + "r/Vencirclejerk": "Circlejerk in Venezuela.", +} +english_subs = { + "r/politics", + "r/ukpolitics", + "r/eupolitics", + "r/AtlantaUnited", + "r/tfc", + "r/WriteStreakEN", + "r/offmychest", + "r/soccer", + "r/england", + "r/usa", + "r/programming", + "r/dankmemes", + "r/memes", + "r/soccercirclejerk", + "r/nbacirclejerk", } -english_subs = {'r/politics', 'r/ukpolitics', 'r/eupolitics', 'r/AtlantaUnited', 'r/tfc', 'r/WriteStreakEN', - 'r/offmychest', 'r/soccer', 'r/england', 'r/usa', 'r/programming', 'r/dankmemes', 'r/memes', - 'r/soccercirclejerk', 'r/nbacirclejerk'} def process_zst_file(reddit_file): print(f"Processing {reddit_file}") dctx = zstandard.ZstdDecompressor(max_window_size=2147483648) comments = [] - with open(reddit_file, 'rb') as fh: + with open(reddit_file, "rb") as fh: try: reader = dctx.stream_reader(fh) stream = io.BufferedReader(reader) @@ -104,7 +118,7 @@ def process_zst_file(reddit_file): def process_bz2_file(reddit_file): print(f"Processing {reddit_file}") comments = [] - with bz2.BZ2File(reddit_file, 'r') as f: + with bz2.BZ2File(reddit_file, "r") as f: for line in f: try: process_line(comments, line, reddit_file) @@ -117,20 +131,22 @@ def process_bz2_file(reddit_file): def process_line(comments, line, reddit_file): - data = json.loads(line.decode('utf-8')) - sub = data['subreddit'].lower() + data = json.loads(line.decode("utf-8")) + sub = data["subreddit"].lower() if sub in spanish_subs or sub in english_subs: - language = 'spanish' if sub in spanish_subs else 'english' - comments.append({ - 'file': reddit_file, - 'author': data['author'], - 'subreddit': data['subreddit'], - 'created_utc': data['created_utc'], - 'link_id': data['link_id'], - 'parent_id': data['parent_id'], - 'text': data['body'], - 'language': language - }) + language = "spanish" if sub in spanish_subs else "english" + comments.append( + { + "file": reddit_file, + "author": data["author"], + "subreddit": data["subreddit"], + "created_utc": data["created_utc"], + "link_id": data["link_id"], + "parent_id": data["parent_id"], + "text": data["body"], + "language": language, + } + ) # Intermittently save comments if len(comments) % 5000000 == 0: @@ -140,19 +156,19 @@ def process_line(comments, line, reddit_file): def save_subreddit_comments(reddit_file, subreddit_comments): try: df = pd.DataFrame(subreddit_comments) - file_name = reddit_file.rsplit('/')[-1].replace('.zst', '') + '.parquet.gzip' + file_name = reddit_file.rsplit("/")[-1].replace(".zst", "") + ".parquet.gzip" file_path = os.path.join(output_directory, file_name) print(f"Saving {len(df)} rows to {file_path}") - df.to_parquet(file_path, compression='gzip') + df.to_parquet(file_path, compression="gzip") except Exception as e: print(f"An error occurred while saving {reddit_file}: {str(e)}") -if __name__ == '__main__': +if __name__ == "__main__": spanish_subs = set([s[2:].lower() for s, _ in spanish_subreddits.items()]) english_subs = set([e[2:].lower() for e in english_subs]) - old_reddit_files = glob(reddit_directory + '*.bz2') - reddit_files = glob(reddit_directory + '*.zst') + old_reddit_files = glob(reddit_directory + "*.bz2") + reddit_files = glob(reddit_directory + "*.zst") # Randomly select 3 older months and 6 newer months old_reddit_files = random.sample(old_reddit_files, 3) reddit_files = random.sample(reddit_files, 6) diff --git a/modeling/contrastive_training/collator.py b/modeling/contrastive_training/collator.py index 9d54cbb..8ffd211 100644 --- a/modeling/contrastive_training/collator.py +++ b/modeling/contrastive_training/collator.py @@ -21,16 +21,25 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): candidate_batch = self._prepare_batch(candidate_sents) if self.evaluate: - query_authors = [feature['query_authorID'] for feature in features] - target_authors = [feature['candidate_authorID'] for feature in features] + query_authors = [feature["query_authorID"] for feature in features] + target_authors = [feature["candidate_authorID"] for feature in features] return query_batch, candidate_batch, query_authors, target_authors else: - return query_batch, candidate_batch, + return ( + query_batch, + candidate_batch, + ) def _encode_text(self, features, feature_name): - return [{'input_ids': self.tokenizer(feature[feature_name])['input_ids'][:self.max_length]} for feature in - features] + return [ + { + "input_ids": self.tokenizer(feature[feature_name])["input_ids"][ + : self.max_length + ] + } + for feature in features + ] def _prepare_batch(self, sents): return self.tokenizer.pad( @@ -57,20 +66,33 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): query_batch = self._prepare_batch(query_sents) candidate_batch = self._prepare_batch(candidate_sents) - query_encodings = [feature['query_biberPlus'] for feature in features] - candidate_encodings = [feature['candidate_biberPlus'] for feature in features] + query_encodings = [feature["query_biberPlus"] for feature in features] + candidate_encodings = [feature["candidate_biberPlus"] for feature in features] if self.evaluate: - query_authors = [feature['query_authorID'] for feature in features] - target_authors = [feature['candidate_authorID'] for feature in features] - - return query_batch, candidate_batch, query_encodings, candidate_encodings, query_authors, target_authors + query_authors = [feature["query_authorID"] for feature in features] + target_authors = [feature["candidate_authorID"] for feature in features] + + return ( + query_batch, + candidate_batch, + query_encodings, + candidate_encodings, + query_authors, + target_authors, + ) else: return query_batch, candidate_batch, query_encodings, candidate_encodings def _encode_text(self, features, feature_name): - return [{'input_ids': self.tokenizer(feature[feature_name])['input_ids'][:self.max_length]} for feature in - features] + return [ + { + "input_ids": self.tokenizer(feature[feature_name])["input_ids"][ + : self.max_length + ] + } + for feature in features + ] def _prepare_batch(self, sents): return self.tokenizer.pad( diff --git a/modeling/contrastive_training/evaluation.py b/modeling/contrastive_training/evaluation.py index 5a68d95..194817b 100644 --- a/modeling/contrastive_training/evaluation.py +++ b/modeling/contrastive_training/evaluation.py @@ -5,7 +5,9 @@ from losses import * -def compute_ranking_metrics(queries, targets, query_authors, target_authors, metric='cosine'): +def compute_ranking_metrics( + queries, targets, query_authors, target_authors, metric="cosine" +): num_queries = len(query_authors) print("Computing ranking metrics for {} queries".format(num_queries)) ranks = np.zeros((num_queries), dtype=np.float32) @@ -28,11 +30,11 @@ def compute_ranking_metrics(queries, targets, query_authors, target_authors, met reciprocal_ranks = reciprocal_ranks[reciprocal_ranks != -100] return_dict = { - 'MRR': np.mean(reciprocal_ranks), - 'R@8': np.sum(np.less_equal(ranks + 1, 8)) / np.float32(num_queries), - 'R@50': np.sum(np.less_equal(ranks + 1, 50)) / np.float32(num_queries), - 'R@100': np.sum(np.less_equal(ranks + 1, 100)) / np.float32(num_queries), - 'R@1000': np.sum(np.less_equal(ranks + 1, 1000)) / np.float32(num_queries) + "MRR": np.mean(reciprocal_ranks), + "R@8": np.sum(np.less_equal(ranks + 1, 8)) / np.float32(num_queries), + "R@50": np.sum(np.less_equal(ranks + 1, 50)) / np.float32(num_queries), + "R@100": np.sum(np.less_equal(ranks + 1, 100)) / np.float32(num_queries), + "R@1000": np.sum(np.less_equal(ranks + 1, 1000)) / np.float32(num_queries), } return return_dict @@ -43,7 +45,7 @@ def evaluate(model, dataloader, args): model.eval() model.to(device) - print('Evaluating...') + print("Evaluating...") queries, targets = [], [] all_query_authors, all_target_authors = [], [] @@ -62,4 +64,6 @@ def evaluate(model, dataloader, args): queries = np.concatenate(queries, axis=0) targets = np.concatenate(targets, axis=0) - return compute_ranking_metrics(queries, targets, all_query_authors, all_target_authors, args.metric) + return compute_ranking_metrics( + queries, targets, all_query_authors, all_target_authors, args.metric + ) diff --git a/modeling/contrastive_training/losses.py b/modeling/contrastive_training/losses.py index 555c5f9..8c39add 100644 --- a/modeling/contrastive_training/losses.py +++ b/modeling/contrastive_training/losses.py @@ -4,7 +4,7 @@ def InfoNCE_loss_full(z1, z2, temperature=0.05): - """ InfoNCE loss function """ + """InfoNCE loss function""" loss_fn = nn.CrossEntropyLoss() sim = cosine_similarity(z1.unsqueeze(1), z2.unsqueeze(0), temperature) batch_size = sim.size(0) diff --git a/modeling/contrastive_training/main.py b/modeling/contrastive_training/main.py index ccbba16..252f7b6 100644 --- a/modeling/contrastive_training/main.py +++ b/modeling/contrastive_training/main.py @@ -2,64 +2,81 @@ import argparse import wandb -sys.path.append('../../..') +sys.path.append("../../..") -from src.custom_training.contrastive_training.models import ContrastiveModel, StyleContrastiveModel +from src.custom_training.contrastive_training.models import ( + ContrastiveModel, + StyleContrastiveModel, +) def argument_parser(): parser = argparse.ArgumentParser() - parser.add_argument('--train_data', - default='/shared/3/datasets/PAN/pan20-av-training-small/contrastive/train.jsonl', type=str) - parser.add_argument('--dev_data', default='/shared/3/datasets/PAN/pan20-av-training-small/contrastive/dev.jsonl', - type=str) - parser.add_argument('--out_dir', type=str, default='/shared/3/projects/hiatus/models/pan20/') - parser.add_argument('--pretrained_model', default='roberta-base', type=str) - parser.add_argument('--style_training', action='store_true') - parser.add_argument('--tokenizer', default=None, type=str) - parser.add_argument('--epochs', default=5, type=int) - parser.add_argument('--learning_rate', default=5e-5, type=float) - parser.add_argument('--grad_norm', default=1.0, type=float) - parser.add_argument('--batch_size', default=128, type=int) - parser.add_argument('--eval_batch_size', default=128, type=int) - parser.add_argument('--grad_acc', default=1, type=int) - parser.add_argument('--device', default='cuda', type=str) - parser.add_argument('--saving_step', default=200, type=int) - parser.add_argument('--weight_decay', default=1e-2, type=float) - parser.add_argument('--max_length', default=350, type=int) - parser.add_argument('--gradient_checkpointing', default=False, type=bool) - parser.add_argument('--num_warmup_steps', default=1000, type=int) - parser.add_argument('--num_training_samples', default=-1, type=int) - parser.add_argument('--num_eval_samples', default=-1, type=int) - parser.add_argument('--evaluate', action='store_true') + parser.add_argument( + "--train_data", + default="/shared/3/datasets/PAN/pan20-av-training-small/contrastive/train.jsonl", + type=str, + ) + parser.add_argument( + "--dev_data", + default="/shared/3/datasets/PAN/pan20-av-training-small/contrastive/dev.jsonl", + type=str, + ) + parser.add_argument( + "--out_dir", type=str, default="/shared/3/projects/hiatus/models/pan20/" + ) + parser.add_argument("--pretrained_model", default="roberta-base", type=str) + parser.add_argument("--style_training", action="store_true") + parser.add_argument("--tokenizer", default=None, type=str) + parser.add_argument("--epochs", default=5, type=int) + parser.add_argument("--learning_rate", default=5e-5, type=float) + parser.add_argument("--grad_norm", default=1.0, type=float) + parser.add_argument("--batch_size", default=128, type=int) + parser.add_argument("--eval_batch_size", default=128, type=int) + parser.add_argument("--grad_acc", default=1, type=int) + parser.add_argument("--device", default="cuda", type=str) + parser.add_argument("--saving_step", default=200, type=int) + parser.add_argument("--weight_decay", default=1e-2, type=float) + parser.add_argument("--max_length", default=350, type=int) + parser.add_argument("--gradient_checkpointing", default=False, type=bool) + parser.add_argument("--num_warmup_steps", default=1000, type=int) + parser.add_argument("--num_training_samples", default=-1, type=int) + parser.add_argument("--num_eval_samples", default=-1, type=int) + parser.add_argument("--evaluate", action="store_true") # the following arguments are only relevant if you hope to log results in wandb - parser.add_argument('--wandb', action='store_true') - parser.add_argument('--entity', default="sadiri-michigan", type=str) - parser.add_argument('--project_name', default='pan20', type=str) - parser.add_argument('--run_name', default='V1', type=str) - parser.add_argument('--code_dir', default='src/custom_training/contrastive_training/', type=str) + parser.add_argument("--wandb", action="store_true") + parser.add_argument("--entity", default="sadiri-michigan", type=str) + parser.add_argument("--project_name", default="pan20", type=str) + parser.add_argument("--run_name", default="V1", type=str) + parser.add_argument( + "--code_dir", default="src/custom_training/contrastive_training/", type=str + ) return parser.parse_args() def setup_wandb(args): - wandb.init(project=args.project_name, - entity="sadiri-michigan", - settings=wandb.Settings(code_dir=args.code_dir), - config={"epochs": args.epochs, - "batch_size": args.batch_size, - "eval_batch_size": args.eval_batch_size, - "max_length": args.max_length, - "saving_step": args.saving_step, - "grad_norm": args.grad_norm, - "learning_rate": args.learning_rate, - "pretrained_model": args.pretrained_model, - "gradient_accumulation": args.grad_acc}) + wandb.init( + project=args.project_name, + entity="sadiri-michigan", + settings=wandb.Settings(code_dir=args.code_dir), + config={ + "epochs": args.epochs, + "batch_size": args.batch_size, + "eval_batch_size": args.eval_batch_size, + "max_length": args.max_length, + "saving_step": args.saving_step, + "grad_norm": args.grad_norm, + "learning_rate": args.learning_rate, + "pretrained_model": args.pretrained_model, + "gradient_accumulation": args.grad_acc, + }, + ) wandb.run.name = args.run_name return wandb -if __name__ == '__main__': +if __name__ == "__main__": args = argument_parser() wb = setup_wandb(args) if args.wandb else None @@ -71,4 +88,4 @@ def setup_wandb(args): model.train_model() model.evaluate() - model.save_model(step=str(-1), version='last') + model.save_model(step=str(-1), version="last") diff --git a/modeling/contrastive_training/models.py b/modeling/contrastive_training/models.py index 5df8d47..b97f45b 100644 --- a/modeling/contrastive_training/models.py +++ b/modeling/contrastive_training/models.py @@ -9,10 +9,14 @@ from transformers import AutoModel, AutoModelForMaskedLM from transformers import get_cosine_schedule_with_warmup -sys.path.append('../..') +sys.path.append("../..") from src.hiatus_training.losses import InfoNCE_loss_full -from src.custom_training.model_utils import load_model, setup_accelerator, encode_batches +from src.custom_training.model_utils import ( + load_model, + setup_accelerator, + encode_batches, +) from src.custom_training.trainer_utils import get_dataloaders @@ -21,32 +25,48 @@ def __init__(self, args, wandb=None): super(ContrastiveModel, self).__init__() self.args = args self.device = args.device - self.train_loader, self.eval_loader = get_dataloaders(args, mlm=False, pairs=True) + self.train_loader, self.eval_loader = get_dataloaders( + args, mlm=False, pairs=True + ) self.wandb = wandb def init_model(self): self.accelerator = Accelerator(gradient_accumulation_steps=self.args.grad_acc) self.device = self.accelerator.device - self.model = AutoModel.from_pretrained(self.args.pretrained_model).to(self.device) + self.model = AutoModel.from_pretrained(self.args.pretrained_model).to( + self.device + ) - optimizer = AdamW(self.model.parameters(), lr=self.args.learning_rate, weight_decay=self.args.weight_decay) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=self.args.num_warmup_steps, - num_training_steps=len(self.train_loader) * self.args.epochs, - num_cycles=1) + optimizer = AdamW( + self.model.parameters(), + lr=self.args.learning_rate, + weight_decay=self.args.weight_decay, + ) + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=self.args.num_warmup_steps, + num_training_steps=len(self.train_loader) * self.args.epochs, + num_cycles=1, + ) - self.model, self.optimizer, self.train_loader, self.scheduler = self.accelerator.prepare( - self.model, optimizer, self.train_loader, scheduler + self.model, self.optimizer, self.train_loader, self.scheduler = ( + self.accelerator.prepare( + self.model, optimizer, self.train_loader, scheduler + ) ) + def train_model(self): args, device = self.args, self.device self.init_model() self.model.train() - best_eval_metric = float('inf') + best_eval_metric = float("inf") running_loss = 0 for epoch in range(args.epochs): - for i, (query_batch, candidate_batch) in tqdm(enumerate(self.train_loader), total=len(self.train_loader)): + for i, (query_batch, candidate_batch) in tqdm( + enumerate(self.train_loader), total=len(self.train_loader) + ): with self.accelerator.accumulate(self.model): loss = self.train_step(query_batch, candidate_batch) running_loss += loss.item() @@ -63,9 +83,9 @@ def train_model(self): eval_metric = self.evaluate() if eval_metric < best_eval_metric: best_eval_metric = eval_metric - self.save_model(step=str(i), version='best') + self.save_model(step=str(i), version="best") else: - self.save_model(step=str(i), version='last') + self.save_model(step=str(i), version="last") self.accelerator.end_training() @@ -85,14 +105,19 @@ def evaluate(self): # TODO: Implement HIATUS evaluation here... self.model.eval() losses = [] - for i, (query_batch, candidate_batch, query_authors, target_authors) in tqdm(enumerate(self.eval_loader), - total=len(self.eval_loader), - position=0, leave=True): + for i, (query_batch, candidate_batch, query_authors, target_authors) in tqdm( + enumerate(self.eval_loader), + total=len(self.eval_loader), + position=0, + leave=True, + ): with torch.no_grad(): z1 = self.model(**query_batch.to(self.device)).pooler_output z2 = self.model(**candidate_batch.to(self.device)).pooler_output loss = InfoNCE_loss_full(z1, z2) / self.args.grad_acc - losses.append(self.accelerator.gather(loss.repeat(self.args.batch_size))) + losses.append( + self.accelerator.gather(loss.repeat(self.args.batch_size)) + ) losses = torch.cat(losses)[: len(self.eval_loader)] @@ -104,9 +129,11 @@ def evaluate(self): return torch.mean(losses) - def save_model(self, step, version='last'): - """ Save model checkpoint """ - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', f'{version}') + def save_model(self, step, version="last"): + """Save model checkpoint""" + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", f"{version}" + ) os.makedirs(checkpoint_dir, exist_ok=True) # Save the LM @@ -115,49 +142,75 @@ def save_model(self, step, version='last'): unwrapped_model.save_pretrained(checkpoint_dir) # Save the optimizer and scheduler - torch.save({ - 'step': step, - 'optimizer_state_dict': self.optimizer.state_dict(), - 'scheduler_state_dict': self.scheduler.state_dict(), - }, os.path.join(checkpoint_dir, 'optimizer_and_scheduler.pt')) + torch.save( + { + "step": step, + "optimizer_state_dict": self.optimizer.state_dict(), + "scheduler_state_dict": self.scheduler.state_dict(), + }, + os.path.join(checkpoint_dir, "optimizer_and_scheduler.pt"), + ) print(f"Saved {version} model to {checkpoint_dir}") -class StyleContrastiveModel(nn.Module): - def __init__(self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None): +class StyleContrastiveModel(nn.Module): + def __init__( + self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None + ): super(StyleContrastiveModel, self).__init__() self.args = args self.style_dimensions = args.style_dimensions self.adversarial_loss_weight = adversarial_loss_weight self.biber_plus_size = biber_plus_size self.device = args.device - self.train_loader, self.eval_loader = get_dataloaders(args, mlm=True, pairs=False) + self.train_loader, self.eval_loader = get_dataloaders( + args, mlm=True, pairs=False + ) self.wandb = wandb def init_model(self): - """ Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" + """Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" self.accelerator = Accelerator(gradient_accumulation_steps=self.args.grad_acc) self.device = self.accelerator.device if self.args.resume: - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', 'last') + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", "last" + ) self.model, optimizer, scheduler = load_model(checkpoint_dir) else: - self.model = AutoModelForMaskedLM.from_pretrained(self.args.pretrained_model).to(self.device) - - optimizer = AdamW(self.model.parameters(), lr=self.args.learning_rate, weight_decay=self.args.weight_decay) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=self.args.num_warmup_steps, - num_training_steps=len(self.train_loader) * self.args.epochs, - num_cycles=1) - - self.style_ll = nn.Linear(in_features=self.style_dimensions, out_features=self.biber_plus_size, - device=self.device) - self.not_style_ll = nn.Linear(in_features=self.model.config.hidden_size - self.style_dimensions, - out_features=self.biber_plus_size, device=self.device) + self.model = AutoModelForMaskedLM.from_pretrained( + self.args.pretrained_model + ).to(self.device) + + optimizer = AdamW( + self.model.parameters(), + lr=self.args.learning_rate, + weight_decay=self.args.weight_decay, + ) + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=self.args.num_warmup_steps, + num_training_steps=len(self.train_loader) * self.args.epochs, + num_cycles=1, + ) + + self.style_ll = nn.Linear( + in_features=self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) + self.not_style_ll = nn.Linear( + in_features=self.model.config.hidden_size - self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) self.cls_dropout = nn.Dropout(p=0.2) - self.model, self.optimizer, self.train_loader, self.scheduler = self.accelerator.prepare( - self.model, optimizer, self.train_loader, scheduler + self.model, self.optimizer, self.train_loader, self.scheduler = ( + self.accelerator.prepare( + self.model, optimizer, self.train_loader, scheduler + ) ) def train_model(self): @@ -165,16 +218,24 @@ def train_model(self): self.init_model() self.model.train() - running_loss, running_contrastive_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 - best_eval_metric = float('inf') + ( + running_loss, + running_contrastive_loss, + running_style_loss, + running_adversarial_loss, + ) = 0, 0, 0, 0 + best_eval_metric = float("inf") for _ in range(args.epochs): - for i, (batchA, batchB, biberEncodingsA, biberEncodingsB) in tqdm(enumerate(self.train_loader), - total=len(self.train_loader)): + for i, (batchA, batchB, biberEncodingsA, biberEncodingsB) in tqdm( + enumerate(self.train_loader), total=len(self.train_loader) + ): with self.accelerator.accumulate(self.model): - loss, contrastive_loss, style_loss, adversarial_loss = self.train_step(batchA, batchB, - biberEncodingsA, - biberEncodingsB) + loss, contrastive_loss, style_loss, adversarial_loss = ( + self.train_step( + batchA, batchB, biberEncodingsA, biberEncodingsB + ) + ) running_loss += loss.item() running_contrastive_loss += contrastive_loss.item() running_style_loss += style_loss.item() @@ -186,20 +247,26 @@ def train_model(self): "Train Overall Loss": running_loss / args.grad_acc, "Train MLM Loss": running_mlm_loss / args.grad_acc, "Train Style Loss": running_style_loss / args.grad_acc, - "Train Adversarial Training Loss": running_adversarial_loss / args.grad_acc + "Train Adversarial Training Loss": running_adversarial_loss + / args.grad_acc, } self.wandb.log(train_log) print(train_log) - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 + ( + running_loss, + running_mlm_loss, + running_style_loss, + running_adversarial_loss, + ) = 0, 0, 0, 0 if i % (args.saving_step * args.grad_acc) == 0 and i > 0: if args.evaluate: eval_style_loss = self.evaluate() if eval_style_loss < best_eval_metric: best_eval_metric = eval_style_loss - self.save_model(step=str(i), version='best') + self.save_model(step=str(i), version="best") else: - self.save_model(step=str(i), version='last') + self.save_model(step=str(i), version="last") self.accelerator.end_training() @@ -211,9 +278,14 @@ def train_step(self, batchA, batchB, biberEncodingsA, biberEncodingsB): biberEncodingsA = torch.FloatTensor(biberEncodingsA).to(self.device) biberEncodingsB = torch.FloatTensor(biberEncodingsB).to(self.device) - biberLossA, biberLossB = self.biber_style_loss(z1, biberEncodingsA), self.biber_style_loss(z2, biberEncodingsB) - adversarial_lossA, adversarial_lossB = self.biber_adversarial_loss(z1, biberEncodingsA), \ - self.biber_adversarial_loss(z2, biberEncodingsB) + biberLossA, biberLossB = ( + self.biber_style_loss(z1, biberEncodingsA), + self.biber_style_loss(z2, biberEncodingsB), + ) + adversarial_lossA, adversarial_lossB = ( + self.biber_adversarial_loss(z1, biberEncodingsA), + self.biber_adversarial_loss(z2, biberEncodingsB), + ) contrastive_loss = self.contrastive_loss(z1, z2) style_loss = biberLossA + biberLossB @@ -232,11 +304,13 @@ def contrastive_loss(self, z1, z2): return (1 - self.adversarial_loss_weight) * InfoNCE_loss_full(z1, z2) def biber_style_loss(self, z, biber_encoding): - style_dimensions = z[:, :self.style_dimensions] + style_dimensions = z[:, : self.style_dimensions] style_output = self.style_ll(style_dimensions) return nn.MSELoss()(style_output, biber_encoding) def biber_adversarial_loss(self, z, biber_encoding): - not_style_dimensions = z[:, self.style_dimensions:] + not_style_dimensions = z[:, self.style_dimensions :] not_style_output = self.not_style_ll(not_style_dimensions) - return (-1 * nn.MSELoss()(not_style_output, biber_encoding)) * self.adversarial_loss_weight + return ( + -1 * nn.MSELoss()(not_style_output, biber_encoding) + ) * self.adversarial_loss_weight diff --git a/modeling/model_utils.py b/modeling/model_utils.py index a5ab107..6843c21 100644 --- a/modeling/model_utils.py +++ b/modeling/model_utils.py @@ -6,21 +6,25 @@ from torch.optim import AdamW from transformers import get_cosine_schedule_with_warmup, AutoModelForMaskedLM -sys.path.append('../') +sys.path.append("../") from modeling.contrastive_training.evaluation import evaluate def load_model(checkpoint_dir): model = AutoModelForMaskedLM.from_pretrained(checkpoint_dir) - optimizer_and_scheduler = torch.load(os.path.join(checkpoint_dir, 'optimizer_and_scheduler.pt'), - map_location='cuda:0') + optimizer_and_scheduler = torch.load( + os.path.join(checkpoint_dir, "optimizer_and_scheduler.pt"), + map_location="cuda:0", + ) optimizer = AdamW(model.parameters()) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=0) + scheduler = get_cosine_schedule_with_warmup( + optimizer, num_warmup_steps=0, num_training_steps=0 + ) - optimizer.load_state_dict(optimizer_and_scheduler['optimizer_state_dict']) - scheduler.load_state_dict(optimizer_and_scheduler['scheduler_state_dict']) + optimizer.load_state_dict(optimizer_and_scheduler["optimizer_state_dict"]) + scheduler.load_state_dict(optimizer_and_scheduler["scheduler_state_dict"]) return model, optimizer, scheduler @@ -44,16 +48,21 @@ def evaluate_and_save_model(model, dev_loader, args, best_perf): def save_checkpoints(encoder_model, args, results, best_perf): - if hasattr(encoder_model, 'save_pretrained'): + if hasattr(encoder_model, "save_pretrained"): encoder_model.save_pretrained(args.out_dir + "/last_model") else: - torch.save(encoder_model.state_dict(), args.out_dir + "/last_model/pytorch_model.pth") - if best_perf < results['MRR']: - if hasattr(encoder_model, 'save_pretrained'): + torch.save( + encoder_model.state_dict(), args.out_dir + "/last_model/pytorch_model.pth" + ) + if best_perf < results["MRR"]: + if hasattr(encoder_model, "save_pretrained"): encoder_model.save_pretrained(args.out_dir + "/best_model") else: - torch.save(encoder_model.state_dict(), args.out_dir + "/best_model/pytorch_model.pth") - best_perf = results['MRR'] + torch.save( + encoder_model.state_dict(), + args.out_dir + "/best_model/pytorch_model.pth", + ) + best_perf = results["MRR"] return best_perf @@ -61,9 +70,15 @@ def save_checkpoints(encoder_model, args, results, best_perf): def setup_accelerator(model, train_loader, args): # use HuggingFace accelerator to manage multi-gpu training accelerator = Accelerator(gradient_accumulation_steps=args.grad_acc) - optimizer = AdamW(model.parameters(), lr=args.learning_rate, weight_decay=args.weight_decay) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=args.num_warmup_steps, - num_training_steps=len(train_loader) * args.epochs, num_cycles=1) + optimizer = AdamW( + model.parameters(), lr=args.learning_rate, weight_decay=args.weight_decay + ) + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=args.num_warmup_steps, + num_training_steps=len(train_loader) * args.epochs, + num_cycles=1, + ) model, optimizer, train_dataloader, lr_scheduler = accelerator.prepare( model, optimizer, train_loader, scheduler diff --git a/modeling/neurobiber/downsample.py b/modeling/neurobiber/downsample.py index 195865f..cd206e3 100644 --- a/modeling/neurobiber/downsample.py +++ b/modeling/neurobiber/downsample.py @@ -2,72 +2,76 @@ import random import os + def downsample_by_rate(input_file, output_file, sample_rate=0.05): - print(f"\nDownsampling {input_file} to {output_file} at {sample_rate*100}% sample rate") - step = int(1/sample_rate) # e.g., 20 for 5% sampling - + print( + f"\nDownsampling {input_file} to {output_file} at {sample_rate * 100}% sample rate" + ) + step = int(1 / sample_rate) # e.g., 20 for 5% sampling + lines_read = 0 lines_written = 0 - - with open(input_file, 'r') as fin, open(output_file, 'w') as fout: + + with open(input_file, "r") as fin, open(output_file, "w") as fout: # Process file line by line for i, line in enumerate(fin): lines_read += 1 try: json.loads(line.strip()) - + if i > 0 and i % 1000000 == 0: # Log every million lines print(f"Processed {i:,} lines from {input_file}") if i % step == 0: # Take every nth line fout.write(line) lines_written += 1 except json.JSONDecodeError: - print(f"Warning: Invalid JSON on line {i+1}") + print(f"Warning: Invalid JSON on line {i + 1}") continue - + print(f"Completed: Read {lines_read:,} lines, wrote {lines_written:,} lines") return lines_read, lines_written + def sample_max_lines(datasets, output_dir, max_samples=1000000): base_paths = { - 'amazon': '/shared/3/projects/hiatus/tagged_data/amazon/', - 'reddit': '/shared/3/projects/hiatus/tagged_data/reddit/', - 'book3corpus': '/shared/3/projects/hiatus/tagged_data/book3corpus/', - 'wiki': '/shared/3/projects/hiatus/tagged_data/wiki/', - 'wiki_discussions': '/shared/3/projects/hiatus/tagged_data/wiki_discussions/', - 'realnews': '/shared/3/projects/hiatus/tagged_data/realnews/', - 'gmane': '/shared/3/projects/hiatus/tagged_data/gmane/' + "amazon": "/shared/3/projects/hiatus/tagged_data/amazon/", + "reddit": "/shared/3/projects/hiatus/tagged_data/reddit/", + "book3corpus": "/shared/3/projects/hiatus/tagged_data/book3corpus/", + "wiki": "/shared/3/projects/hiatus/tagged_data/wiki/", + "wiki_discussions": "/shared/3/projects/hiatus/tagged_data/wiki_discussions/", + "realnews": "/shared/3/projects/hiatus/tagged_data/realnews/", + "gmane": "/shared/3/projects/hiatus/tagged_data/gmane/", } - + # Create output files at the start - train_file_name = os.path.join(output_dir, f'train_{max_samples}.jsonl') - dev_file_name = os.path.join(output_dir, f'dev_{max_samples}.jsonl') - test_file_name = os.path.join(output_dir, f'test_{max_samples}.jsonl') - + train_file_name = os.path.join(output_dir, f"train_{max_samples}.jsonl") + dev_file_name = os.path.join(output_dir, f"dev_{max_samples}.jsonl") + test_file_name = os.path.join(output_dir, f"test_{max_samples}.jsonl") + # Ensure the output directory exists os.makedirs(output_dir, exist_ok=True) - + # Clear/create the output files - open(train_file_name, 'w').close() - open(dev_file_name, 'w').close() - open(test_file_name, 'w').close() + open(train_file_name, "w").close() + open(dev_file_name, "w").close() + open(test_file_name, "w").close() total_written = 0 - + for dataset in datasets: path = base_paths.get(dataset) if not path: print(f"Dataset {dataset} not found in base paths.") continue - input_file = f'{path}corpus.jsonl' + input_file = f"{path}corpus.jsonl" print(f"\nSampling up to {max_samples} lines from {input_file}") - + lines_read = 0 lines_written = 0 - + samples_for_dataset = [] - with open(input_file, 'r') as fin: + with open(input_file, "r") as fin: for i, line in enumerate(fin): lines_read += 1 try: @@ -78,9 +82,9 @@ def sample_max_lines(datasets, output_dir, max_samples=1000000): print(f"Reached maximum of {max_samples} samples for {dataset}") break except json.JSONDecodeError: - print(f"Warning: Invalid JSON on line {i+1}") + print(f"Warning: Invalid JSON on line {i + 1}") continue - + # Shuffle and split for this dataset random.shuffle(samples_for_dataset) dataset_size = len(samples_for_dataset) @@ -88,33 +92,51 @@ def sample_max_lines(datasets, output_dir, max_samples=1000000): dev_end = train_end + int(dataset_size * 0.1) # Append to the output files - with open(train_file_name, 'a') as train_file: + with open(train_file_name, "a") as train_file: train_file.writelines(samples_for_dataset[:train_end]) - with open(dev_file_name, 'a') as dev_file: + with open(dev_file_name, "a") as dev_file: dev_file.writelines(samples_for_dataset[train_end:dev_end]) - with open(test_file_name, 'a') as test_file: + with open(test_file_name, "a") as test_file: test_file.writelines(samples_for_dataset[dev_end:]) - + total_written += len(samples_for_dataset) - print(f"Completed: Read {lines_read:,} lines, wrote {lines_written:,} lines for {dataset}") + print( + f"Completed: Read {lines_read:,} lines, wrote {lines_written:,} lines for {dataset}" + ) # Count final lines in each file - train_count = sum(1 for _ in open(train_file_name, 'r')) - dev_count = sum(1 for _ in open(dev_file_name, 'r')) - test_count = sum(1 for _ in open(test_file_name, 'r')) + train_count = sum(1 for _ in open(train_file_name, "r")) + dev_count = sum(1 for _ in open(dev_file_name, "r")) + test_count = sum(1 for _ in open(test_file_name, "r")) - print(f"Total samples: {total_written}, Train: {train_count}, Dev: {dev_count}, Test: {test_count}") + print( + f"Total samples: {total_written}, Train: {train_count}, Dev: {dev_count}, Test: {test_count}" + ) return total_written, train_count, dev_count, test_count + def main(): print("Starting downsampling process...") - output_dir = '/shared/3/projects/hiatus/tagged_data/' - datasets = ['amazon', 'reddit', 'book3corpus', 'wiki', 'wiki_discussions', 'realnews', 'gmane'] - total_samples, train_count, dev_count, test_count = sample_max_lines(datasets, output_dir, max_samples=1000000) - print(f"Total samples: {total_samples}, Train: {train_count}, Dev: {dev_count}, Test: {test_count}") + output_dir = "/shared/3/projects/hiatus/tagged_data/" + datasets = [ + "amazon", + "reddit", + "book3corpus", + "wiki", + "wiki_discussions", + "realnews", + "gmane", + ] + total_samples, train_count, dev_count, test_count = sample_max_lines( + datasets, output_dir, max_samples=1000000 + ) + print( + f"Total samples: {total_samples}, Train: {train_count}, Dev: {dev_count}, Test: {test_count}" + ) print("\nDownsampling complete for all files!") + if __name__ == "__main__": main() diff --git a/modeling/neurobiber/evaluate.py b/modeling/neurobiber/evaluate.py index c4af944..1b06c00 100644 --- a/modeling/neurobiber/evaluate.py +++ b/modeling/neurobiber/evaluate.py @@ -8,87 +8,96 @@ from tqdm import tqdm -os.chdir('/home/kalkiek/projects/biber-multidimensional-register-analysis/') -sys.path.append('/home/kalkiek/projects/biber-multidimensional-register-analysis/') +os.chdir("/home/kalkiek/projects/biber-multidimensional-register-analysis/") +sys.path.append("/home/kalkiek/projects/biber-multidimensional-register-analysis/") -from modeling.neurobiber.tagger import load_model_and_tokenizer, get_predictions, tag_jsonl_file +from modeling.neurobiber.tagger import ( + load_model_and_tokenizer, + get_predictions, + tag_jsonl_file, +) # Configuration -TEST_FP = '/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_test.jsonl' -RESULTS_DIR = '/shared/3/projects/hiatus/tagged_data/biber-aggregate/evaluation/' +TEST_FP = "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_test.jsonl" +RESULTS_DIR = "/shared/3/projects/hiatus/tagged_data/biber-aggregate/evaluation/" BATCH_SIZE = 64 -def tag_test_json(input_file, output_dir, batch_size=32, text_key="text", show_progress=True): +def tag_test_json( + input_file, output_dir, batch_size=32, text_key="text", show_progress=True +): model, tokenizer = load_model_and_tokenizer() - + # Create output files - pred_file = os.path.join(output_dir, 'predictions.npy') - label_file = os.path.join(output_dir, 'labels.npy') - + pred_file = os.path.join(output_dir, "predictions.npy") + label_file = os.path.join(output_dir, "labels.npy") + # Initialize counters for total predictions total_samples = 0 - - with open(input_file, 'r', encoding='utf-8') as fin: + + with open(input_file, "r", encoding="utf-8") as fin: batch = [] label_batch = [] - + iterator = tqdm(fin, desc="Processing texts") if show_progress else fin - + for line in iterator: line = line.strip() if not line: continue - + try: data = json.loads(line) - + feature_keys = list(data["features"].keys()) label_keys = [k for k in feature_keys if k.endswith("_mean")] - - labels = [1.0 if float(data["features"][key]) != 0.0 else 0.0 for key in label_keys] - + + labels = [ + 1.0 if float(data["features"][key]) != 0.0 else 0.0 + for key in label_keys + ] + batch.append(data[text_key]) label_batch.append(labels) - + if len(batch) >= batch_size: predictions = get_predictions(model, batch, tokenizer) - + # Convert to numpy arrays pred_array = np.array(predictions) label_array = np.array(label_batch) - + # Append to files - with open(pred_file, 'ab') as f: + with open(pred_file, "ab") as f: np.save(f, pred_array) - with open(label_file, 'ab') as f: + with open(label_file, "ab") as f: np.save(f, label_array) - + total_samples += len(batch) batch = [] label_batch = [] - + except json.JSONDecodeError as e: print(f"Skipping invalid JSON line: {e}") continue - + # Handle remaining batch if batch: predictions = get_predictions(model, batch, tokenizer) - + pred_array = np.array(predictions) label_array = np.array(label_batch) - - with open(pred_file, 'ab') as f: + + with open(pred_file, "ab") as f: np.save(f, pred_array) - with open(label_file, 'ab') as f: + with open(label_file, "ab") as f: np.save(f, label_array) - + total_samples += len(batch) - + print(f"Processed {total_samples} samples total") print(f"Results saved to {output_dir}") return pred_file, label_file -tag_test_json(TEST_FP, RESULTS_DIR) \ No newline at end of file +tag_test_json(TEST_FP, RESULTS_DIR) diff --git a/modeling/neurobiber/prepare_dataset.py b/modeling/neurobiber/prepare_dataset.py index a40bc42..ea725eb 100644 --- a/modeling/neurobiber/prepare_dataset.py +++ b/modeling/neurobiber/prepare_dataset.py @@ -4,211 +4,317 @@ from sklearn.model_selection import train_test_split + def get_dataset_paths(): return { - 'amazon': '/shared/3/projects/hiatus/tagged_data/amazon/', - 'reddit': '/shared/3/projects/hiatus/tagged_data/reddit/', - 'book3corpus': '/shared/3/projects/hiatus/tagged_data/book3corpus/', - 'wiki': '/shared/3/projects/hiatus/tagged_data/wiki/', - 'wiki_discussions': '/shared/3/projects/hiatus/tagged_data/wiki_discussions/', - 'realnews': '/shared/3/projects/hiatus/tagged_data/realnews/', - 'gmane': '/shared/3/projects/hiatus/tagged_data/gmane/' + "amazon": "/shared/3/projects/hiatus/tagged_data/amazon/", + "reddit": "/shared/3/projects/hiatus/tagged_data/reddit/", + "book3corpus": "/shared/3/projects/hiatus/tagged_data/book3corpus/", + "wiki": "/shared/3/projects/hiatus/tagged_data/wiki/", + "wiki_discussions": "/shared/3/projects/hiatus/tagged_data/wiki_discussions/", + "realnews": "/shared/3/projects/hiatus/tagged_data/realnews/", + "gmane": "/shared/3/projects/hiatus/tagged_data/gmane/", } + def get_biber_features(): return [ - "BIN_QUAN_mean", "BIN_QUAN_std", - "BIN_QUPR_mean", "BIN_QUPR_std", - "BIN_AMP_mean", "BIN_AMP_std", - "BIN_PASS_mean", "BIN_PASS_std", - "BIN_XX0_mean", "BIN_XX0_std", - "BIN_JJ_mean", "BIN_JJ_std", - "BIN_BEMA_mean", "BIN_BEMA_std", - "BIN_CAUS_mean", "BIN_CAUS_std", - "BIN_CONC_mean", "BIN_CONC_std", - "BIN_COND_mean", "BIN_COND_std", - "BIN_CONJ_mean", "BIN_CONJ_std", - "BIN_CONT_mean", "BIN_CONT_std", - "BIN_DPAR_mean", "BIN_DPAR_std", - "BIN_DWNT_mean", "BIN_DWNT_std", - "BIN_EX_mean", "BIN_EX_std", - "BIN_FPP1_mean", "BIN_FPP1_std", - "BIN_GER_mean", "BIN_GER_std", - "BIN_RB_mean", "BIN_RB_std", - "BIN_PIN_mean", "BIN_PIN_std", - "BIN_INPR_mean", "BIN_INPR_std", - "BIN_TO_mean", "BIN_TO_std", - "BIN_NEMD_mean", "BIN_NEMD_std", - "BIN_OSUB_mean", "BIN_OSUB_std", - "BIN_PASTP_mean", "BIN_PASTP_std", - "BIN_VBD_mean", "BIN_VBD_std", - "BIN_PHC_mean", "BIN_PHC_std", - "BIN_PIRE_mean", "BIN_PIRE_std", - "BIN_PLACE_mean", "BIN_PLACE_std", - "BIN_POMD_mean", "BIN_POMD_std", - "BIN_PRMD_mean", "BIN_PRMD_std", - "BIN_WZPRES_mean", "BIN_WZPRES_std", - "BIN_VPRT_mean", "BIN_VPRT_std", - "BIN_PRIV_mean", "BIN_PRIV_std", - "BIN_PIT_mean", "BIN_PIT_std", - "BIN_PUBV_mean", "BIN_PUBV_std", - "BIN_SPP2_mean", "BIN_SPP2_std", - "BIN_SMP_mean", "BIN_SMP_std", - "BIN_SERE_mean", "BIN_SERE_std", - "BIN_STPR_mean", "BIN_STPR_std", - "BIN_SUAV_mean", "BIN_SUAV_std", - "BIN_SYNE_mean", "BIN_SYNE_std", - "BIN_TPP3_mean", "BIN_TPP3_std", - "BIN_TIME_mean", "BIN_TIME_std", - "BIN_NOMZ_mean", "BIN_NOMZ_std", - "BIN_BYPA_mean", "BIN_BYPA_std", - "BIN_PRED_mean", "BIN_PRED_std", - "BIN_TOBJ_mean", "BIN_TOBJ_std", - "BIN_TSUB_mean", "BIN_TSUB_std", - "BIN_THVC_mean", "BIN_THVC_std", - "BIN_NN_mean", "BIN_NN_std", - "BIN_DEMP_mean", "BIN_DEMP_std", - "BIN_DEMO_mean", "BIN_DEMO_std", - "BIN_WHQU_mean", "BIN_WHQU_std", - "BIN_EMPH_mean", "BIN_EMPH_std", - "BIN_HDG_mean", "BIN_HDG_std", - "BIN_WZPAST_mean", "BIN_WZPAST_std", - "BIN_THAC_mean", "BIN_THAC_std", - "BIN_PEAS_mean", "BIN_PEAS_std", - "BIN_ANDC_mean", "BIN_ANDC_std", - "BIN_PRESP_mean", "BIN_PRESP_std", - "BIN_PROD_mean", "BIN_PROD_std", - "BIN_SPAU_mean", "BIN_SPAU_std", - "BIN_SPIN_mean", "BIN_SPIN_std", - "BIN_THATD_mean", "BIN_THATD_std", - "BIN_WHOBJ_mean", "BIN_WHOBJ_std", - "BIN_WHSUB_mean", "BIN_WHSUB_std", - "BIN_WHCL_mean", "BIN_WHCL_std", - "BIN_ART_mean", "BIN_ART_std", - "BIN_AUXB_mean", "BIN_AUXB_std", - "BIN_CAP_mean", "BIN_CAP_std", - "BIN_SCONJ_mean", "BIN_SCONJ_std", - "BIN_CCONJ_mean", "BIN_CCONJ_std", - "BIN_DET_mean", "BIN_DET_std", - "BIN_EMOJ_mean", "BIN_EMOJ_std", - "BIN_EMOT_mean", "BIN_EMOT_std", - "BIN_EXCL_mean", "BIN_EXCL_std", - "BIN_HASH_mean", "BIN_HASH_std", - "BIN_INF_mean", "BIN_INF_std", - "BIN_UH_mean", "BIN_UH_std", - "BIN_NUM_mean", "BIN_NUM_std", - "BIN_LAUGH_mean", "BIN_LAUGH_std", - "BIN_PRP_mean", "BIN_PRP_std", - "BIN_PREP_mean", "BIN_PREP_std", - "BIN_NNP_mean", "BIN_NNP_std", - "BIN_QUES_mean", "BIN_QUES_std", - "BIN_QUOT_mean", "BIN_QUOT_std", - "BIN_AT_mean", "BIN_AT_std", - "BIN_SBJP_mean", "BIN_SBJP_std", - "BIN_URL_mean", "BIN_URL_std", - "BIN_WH_mean", "BIN_WH_std", - "BIN_INDA_mean", "BIN_INDA_std", - "BIN_ACCU_mean", "BIN_ACCU_std", - "BIN_PGAS_mean", "BIN_PGAS_std", - "BIN_CMADJ_mean", "BIN_CMADJ_std", - "BIN_SPADJ_mean", "BIN_SPADJ_std", - "BIN_X_mean", "BIN_X_std" + "BIN_QUAN_mean", + "BIN_QUAN_std", + "BIN_QUPR_mean", + "BIN_QUPR_std", + "BIN_AMP_mean", + "BIN_AMP_std", + "BIN_PASS_mean", + "BIN_PASS_std", + "BIN_XX0_mean", + "BIN_XX0_std", + "BIN_JJ_mean", + "BIN_JJ_std", + "BIN_BEMA_mean", + "BIN_BEMA_std", + "BIN_CAUS_mean", + "BIN_CAUS_std", + "BIN_CONC_mean", + "BIN_CONC_std", + "BIN_COND_mean", + "BIN_COND_std", + "BIN_CONJ_mean", + "BIN_CONJ_std", + "BIN_CONT_mean", + "BIN_CONT_std", + "BIN_DPAR_mean", + "BIN_DPAR_std", + "BIN_DWNT_mean", + "BIN_DWNT_std", + "BIN_EX_mean", + "BIN_EX_std", + "BIN_FPP1_mean", + "BIN_FPP1_std", + "BIN_GER_mean", + "BIN_GER_std", + "BIN_RB_mean", + "BIN_RB_std", + "BIN_PIN_mean", + "BIN_PIN_std", + "BIN_INPR_mean", + "BIN_INPR_std", + "BIN_TO_mean", + "BIN_TO_std", + "BIN_NEMD_mean", + "BIN_NEMD_std", + "BIN_OSUB_mean", + "BIN_OSUB_std", + "BIN_PASTP_mean", + "BIN_PASTP_std", + "BIN_VBD_mean", + "BIN_VBD_std", + "BIN_PHC_mean", + "BIN_PHC_std", + "BIN_PIRE_mean", + "BIN_PIRE_std", + "BIN_PLACE_mean", + "BIN_PLACE_std", + "BIN_POMD_mean", + "BIN_POMD_std", + "BIN_PRMD_mean", + "BIN_PRMD_std", + "BIN_WZPRES_mean", + "BIN_WZPRES_std", + "BIN_VPRT_mean", + "BIN_VPRT_std", + "BIN_PRIV_mean", + "BIN_PRIV_std", + "BIN_PIT_mean", + "BIN_PIT_std", + "BIN_PUBV_mean", + "BIN_PUBV_std", + "BIN_SPP2_mean", + "BIN_SPP2_std", + "BIN_SMP_mean", + "BIN_SMP_std", + "BIN_SERE_mean", + "BIN_SERE_std", + "BIN_STPR_mean", + "BIN_STPR_std", + "BIN_SUAV_mean", + "BIN_SUAV_std", + "BIN_SYNE_mean", + "BIN_SYNE_std", + "BIN_TPP3_mean", + "BIN_TPP3_std", + "BIN_TIME_mean", + "BIN_TIME_std", + "BIN_NOMZ_mean", + "BIN_NOMZ_std", + "BIN_BYPA_mean", + "BIN_BYPA_std", + "BIN_PRED_mean", + "BIN_PRED_std", + "BIN_TOBJ_mean", + "BIN_TOBJ_std", + "BIN_TSUB_mean", + "BIN_TSUB_std", + "BIN_THVC_mean", + "BIN_THVC_std", + "BIN_NN_mean", + "BIN_NN_std", + "BIN_DEMP_mean", + "BIN_DEMP_std", + "BIN_DEMO_mean", + "BIN_DEMO_std", + "BIN_WHQU_mean", + "BIN_WHQU_std", + "BIN_EMPH_mean", + "BIN_EMPH_std", + "BIN_HDG_mean", + "BIN_HDG_std", + "BIN_WZPAST_mean", + "BIN_WZPAST_std", + "BIN_THAC_mean", + "BIN_THAC_std", + "BIN_PEAS_mean", + "BIN_PEAS_std", + "BIN_ANDC_mean", + "BIN_ANDC_std", + "BIN_PRESP_mean", + "BIN_PRESP_std", + "BIN_PROD_mean", + "BIN_PROD_std", + "BIN_SPAU_mean", + "BIN_SPAU_std", + "BIN_SPIN_mean", + "BIN_SPIN_std", + "BIN_THATD_mean", + "BIN_THATD_std", + "BIN_WHOBJ_mean", + "BIN_WHOBJ_std", + "BIN_WHSUB_mean", + "BIN_WHSUB_std", + "BIN_WHCL_mean", + "BIN_WHCL_std", + "BIN_ART_mean", + "BIN_ART_std", + "BIN_AUXB_mean", + "BIN_AUXB_std", + "BIN_CAP_mean", + "BIN_CAP_std", + "BIN_SCONJ_mean", + "BIN_SCONJ_std", + "BIN_CCONJ_mean", + "BIN_CCONJ_std", + "BIN_DET_mean", + "BIN_DET_std", + "BIN_EMOJ_mean", + "BIN_EMOJ_std", + "BIN_EMOT_mean", + "BIN_EMOT_std", + "BIN_EXCL_mean", + "BIN_EXCL_std", + "BIN_HASH_mean", + "BIN_HASH_std", + "BIN_INF_mean", + "BIN_INF_std", + "BIN_UH_mean", + "BIN_UH_std", + "BIN_NUM_mean", + "BIN_NUM_std", + "BIN_LAUGH_mean", + "BIN_LAUGH_std", + "BIN_PRP_mean", + "BIN_PRP_std", + "BIN_PREP_mean", + "BIN_PREP_std", + "BIN_NNP_mean", + "BIN_NNP_std", + "BIN_QUES_mean", + "BIN_QUES_std", + "BIN_QUOT_mean", + "BIN_QUOT_std", + "BIN_AT_mean", + "BIN_AT_std", + "BIN_SBJP_mean", + "BIN_SBJP_std", + "BIN_URL_mean", + "BIN_URL_std", + "BIN_WH_mean", + "BIN_WH_std", + "BIN_INDA_mean", + "BIN_INDA_std", + "BIN_ACCU_mean", + "BIN_ACCU_std", + "BIN_PGAS_mean", + "BIN_PGAS_std", + "BIN_CMADJ_mean", + "BIN_CMADJ_std", + "BIN_SPADJ_mean", + "BIN_SPADJ_std", + "BIN_X_mean", + "BIN_X_std", ] + def create_dataset(output_file, dataset_paths, biber_features): - with open(output_file, 'w') as jsonl_file: + with open(output_file, "w") as jsonl_file: for name, corpus_path in dataset_paths.items(): - corpus_fp = os.path.join(corpus_path, 'corpus.jsonl') + corpus_fp = os.path.join(corpus_path, "corpus.jsonl") line_count = 0 valid_count = 0 - + print(f"\nProcessing {corpus_fp}") - with open(corpus_fp, 'r') as file: + with open(corpus_fp, "r") as file: for line in file: line_count += 1 if line_count % 1000000 == 0: print(f"Processed {line_count:,} lines...") - + data = json.loads(line) - text = data.get('fullText', '') - binary_encodings = data.get('biber_tagged', {}).get('binary', []) + text = data.get("fullText", "") + binary_encodings = data.get("biber_tagged", {}).get("binary", []) if not text or not binary_encodings: continue valid_count += 1 output_obj = { - 'text': text, - 'features': dict(zip(biber_features, binary_encodings)) + "text": text, + "features": dict(zip(biber_features, binary_encodings)), } - jsonl_file.write(json.dumps(output_obj) + '\n') - + jsonl_file.write(json.dumps(output_obj) + "\n") + print(f"\nDataset: {name}") print(f"Total lines: {line_count:,}") print(f"Valid lines: {valid_count:,}") print(f"Filtered lines: {line_count - valid_count:,}\n") + def read_and_shuffle(file_path, chunksize=50000): chunks = [] for chunk in pd.read_json(file_path, lines=True, chunksize=chunksize): chunks.append(chunk.sample(frac=1)) return pd.concat(chunks).reset_index(drop=True) - - + + def save_to_jsonl(df, file_path): if not os.path.exists(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path)) - - df.to_json(file_path, orient='records', lines=True) + + df.to_json(file_path, orient="records", lines=True) + def split_and_save_data(input_file, chunk_size=50000): print(f"Processing data in chunks of {chunk_size:,}...") - + # Initialize file writers - train_file = '/shared/3/projects/hiatus/tagged_data/binary_train.jsonl' - dev_file = '/shared/3/projects/hiatus/tagged_data/binary_dev.jsonl' - test_file = '/shared/3/projects/hiatus/tagged_data/binary_test.jsonl' - + train_file = "/shared/3/projects/hiatus/tagged_data/binary_train.jsonl" + dev_file = "/shared/3/projects/hiatus/tagged_data/binary_dev.jsonl" + test_file = "/shared/3/projects/hiatus/tagged_data/binary_test.jsonl" + # Create directories if they don't exist for file_path in [train_file, dev_file, test_file]: os.makedirs(os.path.dirname(file_path), exist_ok=True) - + # Process in chunks - with open(train_file, 'w') as train_f, \ - open(dev_file, 'w') as dev_f, \ - open(test_file, 'w') as test_f: - + with ( + open(train_file, "w") as train_f, + open(dev_file, "w") as dev_f, + open(test_file, "w") as test_f, + ): for chunk in pd.read_json(input_file, lines=True, chunksize=chunk_size): # Shuffle the chunk chunk = chunk.sample(frac=1, random_state=42) - + # Calculate split indices for 80/10/10 n = len(chunk) train_idx = int(n * 0.8) dev_idx = int(n * 0.9) - + # Split the chunk train_chunk = chunk.iloc[:train_idx] dev_chunk = chunk.iloc[train_idx:dev_idx] test_chunk = chunk.iloc[dev_idx:] - + # Write to respective files - train_chunk.to_json(train_f, orient='records', lines=True) - dev_chunk.to_json(dev_f, orient='records', lines=True) - test_chunk.to_json(test_f, orient='records', lines=True) - + train_chunk.to_json(train_f, orient="records", lines=True) + dev_chunk.to_json(dev_f, orient="records", lines=True) + test_chunk.to_json(test_f, orient="records", lines=True) + print("Data splitting and saving completed.") + def main(): - output_file = '/shared/3/projects/hiatus/tagged_data/binary_multi_label_dataset.jsonl' + output_file = ( + "/shared/3/projects/hiatus/tagged_data/binary_multi_label_dataset.jsonl" + ) # dataset_paths = get_dataset_paths() # biber_features = get_biber_features() - + # create_dataset(output_file, dataset_paths, biber_features) - + split_and_save_data(output_file) - + print("Data splitting and saving completed.") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/modeling/neurobiber/tag_counts.py b/modeling/neurobiber/tag_counts.py index 30490af..3279b4c 100644 --- a/modeling/neurobiber/tag_counts.py +++ b/modeling/neurobiber/tag_counts.py @@ -4,38 +4,36 @@ from tqdm import tqdm -train_fp = '/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_train.jsonl' -dev_fp = '/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_dev.jsonl' -test_fp = '/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_test.jsonl' +train_fp = "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_train.jsonl" +dev_fp = "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_dev.jsonl" +test_fp = "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_test.jsonl" + +output_fp = "/shared/3/projects/hiatus/tagged_data/biber-aggregate/tag_counts.csv" -output_fp = '/shared/3/projects/hiatus/tagged_data/biber-aggregate/tag_counts.csv' def process_jsonl(filepath): # Initialize counter dictionary tag_counts = {} - + # Process file line by line - with open(filepath, 'r') as f: + with open(filepath, "r") as f: for line in tqdm(f, desc=f"Processing {filepath.split('/')[-1]}"): # Parse single JSON line data = json.loads(line) - features = data['features'] - + features = data["features"] + # Count each feature that has a non-zero mean for key, value in features.items(): - if key.endswith('_mean') and value > 0: + if key.endswith("_mean") and value > 0: # Remove '_mean' suffix and increment counter - tag_name = key.replace('_mean', '') + tag_name = key.replace("_mean", "") tag_counts[tag_name] = tag_counts.get(tag_name, 0) + 1 return tag_counts + # Process each split -splits = { - 'train': train_fp, - 'dev': dev_fp, - 'test': test_fp -} +splits = {"train": train_fp, "dev": dev_fp, "test": test_fp} # Get counts for each split results = {} @@ -46,6 +44,6 @@ def process_jsonl(filepath): results_df = pd.DataFrame(results) # Add tag names as a column instead of just being the index results_df.reset_index(inplace=True) -results_df.rename(columns={'index': 'tag'}, inplace=True) +results_df.rename(columns={"index": "tag"}, inplace=True) results_df.to_csv(output_fp, index=False) diff --git a/modeling/neurobiber/tagger.py b/modeling/neurobiber/tagger.py index 8b2e406..5807680 100644 --- a/modeling/neurobiber/tagger.py +++ b/modeling/neurobiber/tagger.py @@ -14,38 +14,129 @@ MODEL_DIR = "/shared/3/projects/hiatus/tagged_data/models/roberta-base/binary-finetune-full/best_model" CHUNK_SIZE = 512 # Defined during training BIBER_FEATURES = [ - "BIN_QUAN", "BIN_QUPR", "BIN_AMP", "BIN_PASS", "BIN_XX0", "BIN_JJ", - "BIN_BEMA", "BIN_CAUS", "BIN_CONC", "BIN_COND", "BIN_CONJ", "BIN_CONT", - "BIN_DPAR", "BIN_DWNT", "BIN_EX", "BIN_FPP1", "BIN_GER", "BIN_RB", - "BIN_PIN", "BIN_INPR", "BIN_TO", "BIN_NEMD", "BIN_OSUB", "BIN_PASTP", - "BIN_VBD", "BIN_PHC", "BIN_PIRE", "BIN_PLACE", "BIN_POMD", "BIN_PRMD", - "BIN_WZPRES", "BIN_VPRT", "BIN_PRIV", "BIN_PIT", "BIN_PUBV", "BIN_SPP2", - "BIN_SMP", "BIN_SERE", "BIN_STPR", "BIN_SUAV", "BIN_SYNE", "BIN_TPP3", - "BIN_TIME", "BIN_NOMZ", "BIN_BYPA", "BIN_PRED", "BIN_TOBJ", "BIN_TSUB", - "BIN_THVC", "BIN_NN", "BIN_DEMP", "BIN_DEMO", "BIN_WHQU", "BIN_EMPH", - "BIN_HDG", "BIN_WZPAST", "BIN_THAC", "BIN_PEAS", "BIN_ANDC", "BIN_PRESP", - "BIN_PROD", "BIN_SPAU", "BIN_SPIN", "BIN_THATD", "BIN_WHOBJ", "BIN_WHSUB", - "BIN_WHCL", "BIN_ART", "BIN_AUXB", "BIN_CAP", "BIN_SCONJ", "BIN_CCONJ", - "BIN_DET", "BIN_EMOJ", "BIN_EMOT", "BIN_EXCL", "BIN_HASH", "BIN_INF", - "BIN_UH", "BIN_NUM", "BIN_LAUGH", "BIN_PRP", "BIN_PREP", "BIN_NNP", - "BIN_QUES", "BIN_QUOT", "BIN_AT", "BIN_SBJP", "BIN_URL", "BIN_WH", - "BIN_INDA", "BIN_ACCU", "BIN_PGAS", "BIN_CMADJ", "BIN_SPADJ", "BIN_X" + "BIN_QUAN", + "BIN_QUPR", + "BIN_AMP", + "BIN_PASS", + "BIN_XX0", + "BIN_JJ", + "BIN_BEMA", + "BIN_CAUS", + "BIN_CONC", + "BIN_COND", + "BIN_CONJ", + "BIN_CONT", + "BIN_DPAR", + "BIN_DWNT", + "BIN_EX", + "BIN_FPP1", + "BIN_GER", + "BIN_RB", + "BIN_PIN", + "BIN_INPR", + "BIN_TO", + "BIN_NEMD", + "BIN_OSUB", + "BIN_PASTP", + "BIN_VBD", + "BIN_PHC", + "BIN_PIRE", + "BIN_PLACE", + "BIN_POMD", + "BIN_PRMD", + "BIN_WZPRES", + "BIN_VPRT", + "BIN_PRIV", + "BIN_PIT", + "BIN_PUBV", + "BIN_SPP2", + "BIN_SMP", + "BIN_SERE", + "BIN_STPR", + "BIN_SUAV", + "BIN_SYNE", + "BIN_TPP3", + "BIN_TIME", + "BIN_NOMZ", + "BIN_BYPA", + "BIN_PRED", + "BIN_TOBJ", + "BIN_TSUB", + "BIN_THVC", + "BIN_NN", + "BIN_DEMP", + "BIN_DEMO", + "BIN_WHQU", + "BIN_EMPH", + "BIN_HDG", + "BIN_WZPAST", + "BIN_THAC", + "BIN_PEAS", + "BIN_ANDC", + "BIN_PRESP", + "BIN_PROD", + "BIN_SPAU", + "BIN_SPIN", + "BIN_THATD", + "BIN_WHOBJ", + "BIN_WHSUB", + "BIN_WHCL", + "BIN_ART", + "BIN_AUXB", + "BIN_CAP", + "BIN_SCONJ", + "BIN_CCONJ", + "BIN_DET", + "BIN_EMOJ", + "BIN_EMOT", + "BIN_EXCL", + "BIN_HASH", + "BIN_INF", + "BIN_UH", + "BIN_NUM", + "BIN_LAUGH", + "BIN_PRP", + "BIN_PREP", + "BIN_NNP", + "BIN_QUES", + "BIN_QUOT", + "BIN_AT", + "BIN_SBJP", + "BIN_URL", + "BIN_WH", + "BIN_INDA", + "BIN_ACCU", + "BIN_PGAS", + "BIN_CMADJ", + "BIN_SPADJ", + "BIN_X", ] + def predict_text(model, tokenizer, text, chunk_size=CHUNK_SIZE, subbatch_size=32): - return get_predictions_chunked_batch(model, tokenizer, [text], chunk_size, subbatch_size)[0] + return get_predictions_chunked_batch( + model, tokenizer, [text], chunk_size, subbatch_size + )[0] + def predict_batch(model, tokenizer, texts, chunk_size=CHUNK_SIZE, subbatch_size=32): - return get_predictions_chunked_batch(model, tokenizer, texts, chunk_size, subbatch_size) + return get_predictions_chunked_batch( + model, tokenizer, texts, chunk_size, subbatch_size + ) def chunk_text(text, chunk_size=CHUNK_SIZE): tokens = text.strip().split() if not tokens: return [] - return [" ".join(tokens[i:i+chunk_size]) for i in range(0, len(tokens), chunk_size)] + return [ + " ".join(tokens[i : i + chunk_size]) for i in range(0, len(tokens), chunk_size) + ] + -def get_predictions_chunked_batch(model, tokenizer, texts, chunk_size=CHUNK_SIZE, subbatch_size=32): +def get_predictions_chunked_batch( + model, tokenizer, texts, chunk_size=CHUNK_SIZE, subbatch_size=32 +): # Map each text to its chunks while preserving order chunked_texts = [] chunk_indices = [] # [(start, end)] indices for each original text's chunks @@ -53,54 +144,57 @@ def get_predictions_chunked_batch(model, tokenizer, texts, chunk_size=CHUNK_SIZE start = len(chunked_texts) text_chunks = chunk_text(text, chunk_size) chunked_texts.extend(text_chunks) - chunk_indices.append({ - 'original_idx': idx, - 'chunk_range': (start, start + len(text_chunks)) - }) + chunk_indices.append( + {"original_idx": idx, "chunk_range": (start, start + len(text_chunks))} + ) # Handle empty batch case if not chunked_texts: return np.zeros((len(texts), model.config.num_labels)) - + # Process chunks in subbatches all_chunk_preds = [] for i in range(0, len(chunked_texts), subbatch_size): - batch_chunks = chunked_texts[i:i + subbatch_size] + batch_chunks = chunked_texts[i : i + subbatch_size] encodings = tokenizer( - batch_chunks, - return_tensors='pt', - padding=True, - truncation=True, - max_length=chunk_size - ).to('cuda') - - with torch.no_grad(), torch.amp.autocast('cuda'): + batch_chunks, + return_tensors="pt", + padding=True, + truncation=True, + max_length=chunk_size, + ).to("cuda") + + with torch.no_grad(), torch.amp.autocast("cuda"): outputs = model(**encodings) probs = torch.sigmoid(outputs.logits) all_chunk_preds.append(probs.cpu()) - + # Combine all predictions - all_chunk_preds = torch.cat(all_chunk_preds, dim=0) if all_chunk_preds else torch.empty(0) - + all_chunk_preds = ( + torch.cat(all_chunk_preds, dim=0) if all_chunk_preds else torch.empty(0) + ) + # Aggregate predictions for each text in original order predictions = [None] * len(texts) # Pre-allocate list with correct size for info in chunk_indices: - start, end = info['chunk_range'] + start, end = info["chunk_range"] if start == end: # Empty text case pred = torch.zeros(model.config.num_labels) else: # Take max probability across chunks for each feature chunk_preds = all_chunk_preds[start:end] pred, _ = torch.max(chunk_preds, dim=0) - predictions[info['original_idx']] = (pred > 0.5).int().numpy() - + predictions[info["original_idx"]] = (pred > 0.5).int().numpy() + return np.array(predictions) -def tag_jsonl_file(input_file, output_file, batch_size=32, text_key='fullText', show_progress=True): +def tag_jsonl_file( + input_file, output_file, batch_size=32, text_key="fullText", show_progress=True +): model, tokenizer = load_model_and_tokenizer() - - with open(input_file, 'r') as fin, open(output_file, 'w') as fout: + + with open(input_file, "r") as fin, open(output_file, "w") as fout: batch = [] iterator = tqdm(fin, desc="Processing texts") if show_progress else fin for line in iterator: @@ -113,33 +207,33 @@ def tag_jsonl_file(input_file, output_file, batch_size=32, text_key='fullText', predictions = predict_batch(model, tokenizer, texts) for item, pred in zip(batch, predictions): output_item = { - 'documentID': item['documentID'], - 'neural_biber': pred.tolist() + "documentID": item["documentID"], + "neural_biber": pred.tolist(), } - fout.write(json.dumps(output_item) + '\n') + fout.write(json.dumps(output_item) + "\n") except Exception as e: print(f"Error processing batch: {str(e)}") batch = [] except json.JSONDecodeError as e: print(f"Error reading JSON line: {str(e)}") continue - + if batch: try: texts = [item[text_key] for item in batch] predictions = predict_batch(model, tokenizer, texts) for item, pred in zip(batch, predictions): output_item = { - 'documentID': item['documentID'], - 'neural_biber': pred.tolist() + "documentID": item["documentID"], + "neural_biber": pred.tolist(), } - fout.write(json.dumps(output_item) + '\n') + fout.write(json.dumps(output_item) + "\n") except Exception as e: print(f"Error processing final batch: {str(e)}") + def load_model_and_tokenizer(): tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR, use_fast=True) - model = AutoModelForSequenceClassification.from_pretrained(MODEL_DIR).to('cuda') + model = AutoModelForSequenceClassification.from_pretrained(MODEL_DIR).to("cuda") model.eval() return model, tokenizer - diff --git a/modeling/neurobiber/train_model.py b/modeling/neurobiber/train_model.py index b2d9c89..e6a9d9c 100644 --- a/modeling/neurobiber/train_model.py +++ b/modeling/neurobiber/train_model.py @@ -2,14 +2,24 @@ import sys import pandas as pd import numpy as np -from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, EarlyStoppingCallback +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + TrainingArguments, + EarlyStoppingCallback, +) import wandb import logging -sys.path.append('../..') +sys.path.append("../..") -from modeling.tagger.train_utils import StreamingDataset, compute_metrics, compute_tag_level_metrics, EvalSamplingTrainer +from modeling.tagger.train_utils import ( + StreamingDataset, + compute_metrics, + compute_tag_level_metrics, + EvalSamplingTrainer, +) # Path Configuration BASE_DIR = "/shared/3/projects/hiatus/tagged_data/models" @@ -22,7 +32,9 @@ OUTPUT_DIR = os.path.join(RUN_DIR, "results") WANDB_RUN_NAME = f"{MODEL_NAME}-{RUN_NAME}" MODEL_SAVE_PATH = os.path.join(RUN_DIR, "best_model") -TAG_PERFORMANCE_SUMMARY_PATH = os.path.join(RUN_DIR, "tag_level_performance_summary.csv") +TAG_PERFORMANCE_SUMMARY_PATH = os.path.join( + RUN_DIR, "tag_level_performance_summary.csv" +) # Create necessary directories try: @@ -36,8 +48,8 @@ # Add logging configuration logging.basicConfig( level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger(__name__) @@ -56,18 +68,32 @@ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) logger.info("Initializing streaming datasets...") -train_dataset = StreamingDataset('/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_train.jsonl', tokenizer, max_length=512) -dev_dataset = StreamingDataset('/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_dev.jsonl', tokenizer, max_length=512) -test_dataset = StreamingDataset('/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_test.jsonl', tokenizer, max_length=512) +train_dataset = StreamingDataset( + "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_train.jsonl", + tokenizer, + max_length=512, +) +dev_dataset = StreamingDataset( + "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_dev.jsonl", + tokenizer, + max_length=512, +) +test_dataset = StreamingDataset( + "/shared/3/projects/hiatus/tagged_data/biber-aggregate/binary_test.jsonl", + tokenizer, + max_length=512, +) logger.info(f"Loading tokenizer and model from {MODEL_NAME}...") model = AutoModelForSequenceClassification.from_pretrained( MODEL_NAME, problem_type="multi_label_classification", - num_labels=len(train_dataset.label_columns) -).to('cuda') + num_labels=len(train_dataset.label_columns), +).to("cuda") -logger.info(f"Dataset sizes - Train: {train_dataset.total_rows}, Dev: {dev_dataset.total_rows}, Test: {test_dataset.total_rows}") +logger.info( + f"Dataset sizes - Train: {train_dataset.total_rows}, Dev: {dev_dataset.total_rows}, Test: {test_dataset.total_rows}" +) logger.info(f"Number of labels: {len(train_dataset.label_columns)}") logger.info(f"Labels: {', '.join(train_dataset.label_columns)}") @@ -89,8 +115,8 @@ "labels": train_dataset.label_columns, "train_size": train_dataset.total_rows, "dev_size": dev_dataset.total_rows, - "test_size": test_dataset.total_rows - } + "test_size": test_dataset.total_rows, + }, ) training_args = TrainingArguments( @@ -112,7 +138,7 @@ bf16=True, dataloader_num_workers=16, eval_accumulation_steps=8, - gradient_checkpointing=True + gradient_checkpointing=True, ) trainer = EvalSamplingTrainer( @@ -122,9 +148,7 @@ eval_dataset=dev_dataset, tokenizer=tokenizer, compute_metrics=compute_metrics, - callbacks=[ - EarlyStoppingCallback(early_stopping_patience=5) - ] + callbacks=[EarlyStoppingCallback(early_stopping_patience=5)], ) train_result = trainer.train() @@ -134,14 +158,16 @@ logger.info("Evaluating on test set...") test_results = trainer.evaluate(test_dataset) logger.info(f"Test results: {test_results}") -wandb.log({ - "test_precision_micro": test_results["eval_precision_micro"], - "test_recall_micro": test_results["eval_recall_micro"], - "test_f1_micro": test_results["eval_f1_micro"], - "test_precision_macro": test_results["eval_precision_macro"], - "test_recall_macro": test_results["eval_recall_macro"], - "test_f1_macro": test_results["eval_f1_macro"] -}) +wandb.log( + { + "test_precision_micro": test_results["eval_precision_micro"], + "test_recall_micro": test_results["eval_recall_micro"], + "test_f1_micro": test_results["eval_f1_micro"], + "test_precision_macro": test_results["eval_precision_macro"], + "test_recall_macro": test_results["eval_recall_macro"], + "test_f1_macro": test_results["eval_f1_macro"], + } +) logger.info(f"Saving model to {MODEL_SAVE_PATH}") trainer.save_model(MODEL_SAVE_PATH) @@ -155,7 +181,7 @@ tag_level_metrics = compute_tag_level_metrics( test_predictions.predictions, test_predictions.label_ids, - train_dataset.label_columns + train_dataset.label_columns, ) # Log detailed results @@ -168,9 +194,9 @@ # Calculate and log overall statistics avg_metrics = { - 'avg_precision': np.mean([m['precision'] for m in tag_level_metrics.values()]), - 'avg_recall': np.mean([m['recall'] for m in tag_level_metrics.values()]), - 'avg_f1': np.mean([m['f1'] for m in tag_level_metrics.values()]) + "avg_precision": np.mean([m["precision"] for m in tag_level_metrics.values()]), + "avg_recall": np.mean([m["recall"] for m in tag_level_metrics.values()]), + "avg_f1": np.mean([m["f1"] for m in tag_level_metrics.values()]), } logger.info("\nOverall Statistics:") @@ -182,10 +208,10 @@ logger.info(f"Saving performance summary to {TAG_PERFORMANCE_SUMMARY_PATH}") summary_data = [ { - 'Tag': tag, - 'Precision': metrics['precision'], - 'Recall': metrics['recall'], - 'F1': metrics['f1'] + "Tag": tag, + "Precision": metrics["precision"], + "Recall": metrics["recall"], + "F1": metrics["f1"], } for tag, metrics in tag_level_metrics.items() ] @@ -194,4 +220,4 @@ summary_df.to_csv(TAG_PERFORMANCE_SUMMARY_PATH, index=False) logger.info("Training run completed successfully") -wandb.finish() \ No newline at end of file +wandb.finish() diff --git a/modeling/neurobiber/train_utils.py b/modeling/neurobiber/train_utils.py index 387dc3f..ee2ce43 100644 --- a/modeling/neurobiber/train_utils.py +++ b/modeling/neurobiber/train_utils.py @@ -1,7 +1,14 @@ from typing import Optional, Union from transformers import Trainer from torch.utils.data import IterableDataset -from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error, precision_score, recall_score, f1_score +from sklearn.metrics import ( + mean_squared_error, + r2_score, + mean_absolute_error, + precision_score, + recall_score, + f1_score, +) import json import torch import numpy as np @@ -16,102 +23,115 @@ def __init__(self, file_path, tokenizer, max_length=512, batch_size=None): self.batch_size = batch_size try: - result = subprocess.run(['wc', '-l', file_path], capture_output=True, text=True) + result = subprocess.run( + ["wc", "-l", file_path], capture_output=True, text=True + ) self.total_rows = int(result.stdout.split()[0]) print(f"Total rows in dataset: {self.total_rows}") except Exception as e: self.total_rows = sum(1 for _ in open(file_path)) - + # Read first line to get column names - with open(file_path, 'r') as f: + with open(file_path, "r") as f: first_line = json.loads(f.readline()) - feature_columns = list(first_line['features'].keys()) - self.label_columns = [col[:-5] for col in feature_columns if col.endswith('_mean')] - + feature_columns = list(first_line["features"].keys()) + self.label_columns = [ + col[:-5] for col in feature_columns if col.endswith("_mean") + ] + def __iter__(self): - with open(self.file_path, 'r') as f: + with open(self.file_path, "r") as f: for line in f: data = json.loads(line) - + # Tokenize the text encoding = self.tokenizer( - data['text'], + data["text"], max_length=self.max_length, - padding='max_length', + padding="max_length", truncation=True, - return_tensors='pt' + return_tensors="pt", ) - + # Extract mean values and convert to binary labels = [] for col in self.label_columns: - mean_value = float(data['features'][f"{col}_mean"]) + mean_value = float(data["features"][f"{col}_mean"]) binary_value = 1.0 if mean_value > 0 else 0.0 labels.append(binary_value) - + yield { - 'input_ids': encoding['input_ids'].squeeze(), - 'attention_mask': encoding['attention_mask'].squeeze(), - 'labels': torch.tensor(labels, dtype=torch.float) + "input_ids": encoding["input_ids"].squeeze(), + "attention_mask": encoding["attention_mask"].squeeze(), + "labels": torch.tensor(labels, dtype=torch.float), } - + def __len__(self): return self.total_rows + def compute_metrics(eval_pred): predictions = torch.sigmoid(torch.tensor(eval_pred.predictions)).numpy() labels = eval_pred.label_ids - + # Convert logits to binary predictions predictions = (predictions > 0.5).astype(int) labels = labels.astype(int) - + return { - 'precision_micro': precision_score(labels, predictions, average='micro', zero_division=0), - 'recall_micro': recall_score(labels, predictions, average='micro', zero_division=0), - 'f1_micro': f1_score(labels, predictions, average='micro', zero_division=0), - 'precision_macro': precision_score(labels, predictions, average='macro', zero_division=0), - 'recall_macro': recall_score(labels, predictions, average='macro', zero_division=0), - 'f1_macro': f1_score(labels, predictions, average='macro', zero_division=0) + "precision_micro": precision_score( + labels, predictions, average="micro", zero_division=0 + ), + "recall_micro": recall_score( + labels, predictions, average="micro", zero_division=0 + ), + "f1_micro": f1_score(labels, predictions, average="micro", zero_division=0), + "precision_macro": precision_score( + labels, predictions, average="macro", zero_division=0 + ), + "recall_macro": recall_score( + labels, predictions, average="macro", zero_division=0 + ), + "f1_macro": f1_score(labels, predictions, average="macro", zero_division=0), } + def compute_tag_level_metrics(predictions, labels, label_columns): predictions = torch.sigmoid(torch.tensor(predictions)).numpy() predictions = (predictions > 0.5).astype(np.int32) - + metrics = {} for i, tag in enumerate(label_columns): tag_preds = predictions[:, i] tag_labels = labels[:, i] - + metrics[tag] = { - 'precision': precision_score(tag_labels, tag_preds, zero_division=0), - 'recall': recall_score(tag_labels, tag_preds, zero_division=0), - 'f1': f1_score(tag_labels, tag_preds, zero_division=0) + "precision": precision_score(tag_labels, tag_preds, zero_division=0), + "recall": recall_score(tag_labels, tag_preds, zero_division=0), + "f1": f1_score(tag_labels, tag_preds, zero_division=0), } - + return metrics + class EvalSamplingTrainer(Trainer): def __init__(self, eval_sample_ratio=0.01, random_seed=42, *args, **kwargs): super().__init__(*args, **kwargs) self.eval_sample_ratio = eval_sample_ratio self.random_seed = random_seed - self._sampling_stats = {'total_seen': 0, 'total_sampled': 0} + self._sampling_stats = {"total_seen": 0, "total_sampled": 0} def evaluate( - self, - eval_dataset: Optional[Union[StreamingDataset, None]] = None, - **kwargs + self, eval_dataset: Optional[Union[StreamingDataset, None]] = None, **kwargs ): if eval_dataset is None: eval_dataset = self.eval_dataset - + np.random.seed(self.random_seed) - + # Reset sampling stats (handle multiple eval workers) - self._sampling_stats = {'total_seen': 0, 'total_sampled': 0} - + self._sampling_stats = {"total_seen": 0, "total_sampled": 0} + # Create a new dataset with sampling class SampledDataset(IterableDataset): def __init__(self, original_dataset, sample_ratio, stats_dict): @@ -119,19 +139,21 @@ def __init__(self, original_dataset, sample_ratio, stats_dict): self.sample_ratio = sample_ratio self.length = int(len(original_dataset) * sample_ratio) self.stats_dict = stats_dict - + def __iter__(self): iterator = iter(self.original_dataset) for item in iterator: - self.stats_dict['total_seen'] += 1 + self.stats_dict["total_seen"] += 1 if np.random.random() < self.sample_ratio: - self.stats_dict['total_sampled'] += 1 + self.stats_dict["total_sampled"] += 1 yield item - + def __len__(self): return self.length - - sampled_dataset = SampledDataset(eval_dataset, self.eval_sample_ratio, self._sampling_stats) + + sampled_dataset = SampledDataset( + eval_dataset, self.eval_sample_ratio, self._sampling_stats + ) metrics = super().evaluate(sampled_dataset, **kwargs) return metrics diff --git a/modeling/polybiberta/collators.py b/modeling/polybiberta/collators.py index 7d8eda6..8efda8c 100644 --- a/modeling/polybiberta/collators.py +++ b/modeling/polybiberta/collators.py @@ -25,8 +25,14 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): return batch def _encode_text(self, features, feature_name): - return [{'input_ids': self.tokenizer(feature[feature_name])['input_ids'][:self.max_length]} for feature in - features] + return [ + { + "input_ids": self.tokenizer(feature[feature_name])["input_ids"][ + : self.max_length + ] + } + for feature in features + ] def _prepare_batch(self, sents): return self.tokenizer.pad( @@ -48,7 +54,7 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): text_sents = self._encode_text(features, "text") batch = self._prepare_batch(text_sents) - encodings = [feature['text_biberPlus'] for feature in features] + encodings = [feature["text_biberPlus"] for feature in features] # If special token mask has been preprocessed, pop it from the dict. special_tokens_mask = batch.pop("special_tokens_mask", None) @@ -60,8 +66,14 @@ def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]): return batch, encodings def _encode_text(self, features, feature_name): - return [{'input_ids': self.tokenizer(feature[feature_name])['input_ids'][:self.max_length]} for feature in - features] + return [ + { + "input_ids": self.tokenizer(feature[feature_name])["input_ids"][ + : self.max_length + ] + } + for feature in features + ] def _prepare_batch(self, sents): return self.tokenizer.pad( diff --git a/modeling/polybiberta/main.py b/modeling/polybiberta/main.py index a9990cb..ec0e5ba 100644 --- a/modeling/polybiberta/main.py +++ b/modeling/polybiberta/main.py @@ -3,7 +3,7 @@ import wandb -sys.path.append('../../..') +sys.path.append("../../..") from src.custom_training.multilingual_finetuning.models import XLMStyleMLM @@ -11,63 +11,85 @@ def argument_parser(): parser = argparse.ArgumentParser() - parser.add_argument('--en_train_file', default='/shared/3/projects/hiatus/tagged_data/mlm_finetuning/train.jsonl', - type=str) - parser.add_argument('--en_dev_file', default='/shared/3/projects/hiatus/tagged_data/mlm_finetuning/dev.jsonl', - type=str) - parser.add_argument('--train_file', default='/shared/3/projects/hiatus/multilingual/train.jsonl', type=str) - parser.add_argument('--dev_file', default='/shared/3/projects/hiatus/multilingual/dev.jsonl', type=str) - parser.add_argument('--out_dir', type=str, default='/shared/3/projects/hiatus/models/multilingual/') - parser.add_argument('--pretrained_model', default='xlm-roberta-base', type=str) - parser.add_argument('--style_dimensions', default=64, type=int) - parser.add_argument('--resume', action='store_true') - parser.add_argument('--tokenizer', default=None, type=str) - parser.add_argument('--epochs', default=5, type=int) - parser.add_argument('--learning_rate', default=5e-5, type=float) - parser.add_argument('--grad_norm', default=1.0, type=float) - parser.add_argument('--batch_size', default=128, type=int) - parser.add_argument('--eval_batch_size', default=128, type=int) - parser.add_argument('--grad_acc', default=1, type=int) - parser.add_argument('--device', default='cuda', type=str) - parser.add_argument('--saving_step', default=200, type=int) - parser.add_argument('--weight_decay', default=1e-2, type=float) - parser.add_argument('--max_length', default=350, type=int) - parser.add_argument('--gradient_checkpointing', default=False, type=bool) - parser.add_argument('--num_warmup_steps', default=1000, type=int) - parser.add_argument('--num_training_samples', default=-1, type=int) - parser.add_argument('--num_eval_samples', default=-1, type=int) - parser.add_argument('--evaluate', action='store_true') + parser.add_argument( + "--en_train_file", + default="/shared/3/projects/hiatus/tagged_data/mlm_finetuning/train.jsonl", + type=str, + ) + parser.add_argument( + "--en_dev_file", + default="/shared/3/projects/hiatus/tagged_data/mlm_finetuning/dev.jsonl", + type=str, + ) + parser.add_argument( + "--train_file", + default="/shared/3/projects/hiatus/multilingual/train.jsonl", + type=str, + ) + parser.add_argument( + "--dev_file", + default="/shared/3/projects/hiatus/multilingual/dev.jsonl", + type=str, + ) + parser.add_argument( + "--out_dir", type=str, default="/shared/3/projects/hiatus/models/multilingual/" + ) + parser.add_argument("--pretrained_model", default="xlm-roberta-base", type=str) + parser.add_argument("--style_dimensions", default=64, type=int) + parser.add_argument("--resume", action="store_true") + parser.add_argument("--tokenizer", default=None, type=str) + parser.add_argument("--epochs", default=5, type=int) + parser.add_argument("--learning_rate", default=5e-5, type=float) + parser.add_argument("--grad_norm", default=1.0, type=float) + parser.add_argument("--batch_size", default=128, type=int) + parser.add_argument("--eval_batch_size", default=128, type=int) + parser.add_argument("--grad_acc", default=1, type=int) + parser.add_argument("--device", default="cuda", type=str) + parser.add_argument("--saving_step", default=200, type=int) + parser.add_argument("--weight_decay", default=1e-2, type=float) + parser.add_argument("--max_length", default=350, type=int) + parser.add_argument("--gradient_checkpointing", default=False, type=bool) + parser.add_argument("--num_warmup_steps", default=1000, type=int) + parser.add_argument("--num_training_samples", default=-1, type=int) + parser.add_argument("--num_eval_samples", default=-1, type=int) + parser.add_argument("--evaluate", action="store_true") # the following arguments are only relevant if you hope to log results in wandb - parser.add_argument('--wandb', action='store_true') - parser.add_argument('--entity', default="sadiri-michigan", type=str) - parser.add_argument('--project_name', default='multilingual-style-mlm', type=str) - parser.add_argument('--run_name', default='V1', type=str) - parser.add_argument('--code_dir', default='src/custom_training/mlm_finetuning', type=str) + parser.add_argument("--wandb", action="store_true") + parser.add_argument("--entity", default="sadiri-michigan", type=str) + parser.add_argument("--project_name", default="multilingual-style-mlm", type=str) + parser.add_argument("--run_name", default="V1", type=str) + parser.add_argument( + "--code_dir", default="src/custom_training/mlm_finetuning", type=str + ) return parser.parse_args() def setup_wandb(args): - wandb.init(project=args.project_name, - entity="sadiri-michigan", - resume=args.resume, - settings=wandb.Settings(code_dir=args.code_dir), - config={"epochs": args.epochs, - "batch_size": args.batch_size, - "eval_batch_size": args.eval_batch_size, - "max_length": args.max_length, - "saving_step": args.saving_step, - "learning_rate": args.learning_rate, - "pretrained_model": args.pretrained_model, - "gradient_accumulation": args.grad_acc}) + wandb.init( + project=args.project_name, + entity="sadiri-michigan", + resume=args.resume, + settings=wandb.Settings(code_dir=args.code_dir), + config={ + "epochs": args.epochs, + "batch_size": args.batch_size, + "eval_batch_size": args.eval_batch_size, + "max_length": args.max_length, + "saving_step": args.saving_step, + "learning_rate": args.learning_rate, + "pretrained_model": args.pretrained_model, + "gradient_accumulation": args.grad_acc, + }, + ) wandb.run.name = args.run_name return wandb -if __name__ == '__main__': +if __name__ == "__main__": args = argument_parser() wb = setup_wandb(args) if args.wandb else None model = XLMStyleMLM(args, wandb=wb) model.train_model() model.evaluate() - model.save_model(step=str(-1), version='last') + model.save_model(step=str(-1), version="last") diff --git a/modeling/polybiberta/models.py b/modeling/polybiberta/models.py index 4d5ed42..3afdf65 100644 --- a/modeling/polybiberta/models.py +++ b/modeling/polybiberta/models.py @@ -9,22 +9,28 @@ from transformers import AutoModelForMaskedLM from transformers import get_cosine_schedule_with_warmup -sys.path.append('../..') +sys.path.append("../..") from src.custom_training.model_utils import load_model, calculate_perplexity -from src.custom_training.multilingual_finetuning.multilingual_trainer_utils import load_all_dataloaders, \ - alternate_loaders +from src.custom_training.multilingual_finetuning.multilingual_trainer_utils import ( + load_all_dataloaders, + alternate_loaders, +) class XLMStyleMLM(nn.Module): - def __init__(self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None): + def __init__( + self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb=None + ): super(XLMStyleMLM, self).__init__() self.args = args self.style_dimensions = args.style_dimensions self.adversarial_loss_weight = adversarial_loss_weight self.biber_plus_size = biber_plus_size self.device = args.device - self.train_loader, self.dev_loader, self.en_train_loader, self.en_dev_loader = load_all_dataloaders(args) + self.train_loader, self.dev_loader, self.en_train_loader, self.en_dev_loader = ( + load_all_dataloaders(args) + ) self.wandb = wandb print(f"Multilingual train batches: {len(self.train_loader)}") @@ -33,28 +39,51 @@ def __init__(self, args, biber_plus_size=192, adversarial_loss_weight=0.1, wandb print(f"English/Biber dev batches: {len(self.en_dev_loader)}") def init_model(self): - """ Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" + """Initialize model in the training loop to accommodate accelerate and the multi-GPU setup""" self.accelerator = Accelerator(gradient_accumulation_steps=self.args.grad_acc) self.device = self.accelerator.device if self.args.resume: - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', 'last') + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", "last" + ) self.model, optimizer, scheduler = load_model(checkpoint_dir) else: - self.model = AutoModelForMaskedLM.from_pretrained(self.args.pretrained_model).to(self.device) - - optimizer = AdamW(self.model.parameters(), lr=self.args.learning_rate, weight_decay=self.args.weight_decay) - scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=self.args.num_warmup_steps, - num_training_steps=(len(self.train_loader) + - len(self.en_train_loader)) * self.args.epochs, - num_cycles=1) - - self.style_ll = nn.Linear(in_features=self.style_dimensions, out_features=self.biber_plus_size, - device=self.device) - self.not_style_ll = nn.Linear(in_features=self.model.config.hidden_size - self.style_dimensions, - out_features=self.biber_plus_size, device=self.device) + self.model = AutoModelForMaskedLM.from_pretrained( + self.args.pretrained_model + ).to(self.device) + + optimizer = AdamW( + self.model.parameters(), + lr=self.args.learning_rate, + weight_decay=self.args.weight_decay, + ) + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=self.args.num_warmup_steps, + num_training_steps=(len(self.train_loader) + len(self.en_train_loader)) + * self.args.epochs, + num_cycles=1, + ) + + self.style_ll = nn.Linear( + in_features=self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) + self.not_style_ll = nn.Linear( + in_features=self.model.config.hidden_size - self.style_dimensions, + out_features=self.biber_plus_size, + device=self.device, + ) self.cls_dropout = nn.Dropout(p=0.2) - self.model, self.optimizer, self.train_loader, self.en_train_loader, self.scheduler = self.accelerator.prepare( + ( + self.model, + self.optimizer, + self.train_loader, + self.en_train_loader, + self.scheduler, + ) = self.accelerator.prepare( self.model, optimizer, self.train_loader, self.en_train_loader, scheduler ) @@ -63,21 +92,30 @@ def train_model(self): self.init_model() self.model.train() - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 - best_eval_metric = float('inf') + running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = ( + 0, + 0, + 0, + 0, + ) + best_eval_metric = float("inf") total_steps = len(self.train_loader) + len(self.en_train_loader) for epoch in range(args.epochs): - for i, (batch, batch_type) in tqdm(enumerate(alternate_loaders(self.train_loader, self.en_train_loader)), - total=total_steps): + for i, (batch, batch_type) in tqdm( + enumerate(alternate_loaders(self.train_loader, self.en_train_loader)), + total=total_steps, + ): with self.accelerator.accumulate(self.model): - if batch_type == 'multilingual': + if batch_type == "multilingual": mlm_loss = self.mlm_train_step(batch) running_mlm_loss += mlm_loss.item() running_loss += mlm_loss.item() else: batch, biber_encoding = batch - loss, mlm_loss, style_loss, adversarial_loss = self.biberta_train_step(batch, biber_encoding) + loss, mlm_loss, style_loss, adversarial_loss = ( + self.biberta_train_step(batch, biber_encoding) + ) running_loss += loss.item() running_mlm_loss += mlm_loss.item() running_style_loss += style_loss.item() @@ -89,20 +127,26 @@ def train_model(self): "Train Overall Loss": running_loss / args.grad_acc, "Train MLM Loss": running_mlm_loss / args.grad_acc, "Train Style Loss": running_style_loss / args.grad_acc, - "Train Adversarial Training Loss": running_adversarial_loss / args.grad_acc + "Train Adversarial Training Loss": running_adversarial_loss + / args.grad_acc, } self.wandb.log(train_log) print(train_log) - running_loss, running_mlm_loss, running_style_loss, running_adversarial_loss = 0, 0, 0, 0 + ( + running_loss, + running_mlm_loss, + running_style_loss, + running_adversarial_loss, + ) = 0, 0, 0, 0 if i % (args.saving_step * args.grad_acc) == 0 and i > 0: if args.evaluate: eval_style_loss = self.evaluate() if eval_style_loss < best_eval_metric: best_eval_metric = eval_style_loss - self.save_model(step=str(i), version='best') + self.save_model(step=str(i), version="best") else: - self.save_model(step=str(i), version='last') + self.save_model(step=str(i), version="last") self.accelerator.end_training() @@ -134,15 +178,17 @@ def biberta_train_step(self, batch, biber_encoding): def biber_style_loss(self, cls, biber_encoding): biber_encoding = torch.FloatTensor(biber_encoding).to(self.device) - style_dimensions = cls[:, :self.style_dimensions] + style_dimensions = cls[:, : self.style_dimensions] style_output = self.style_ll(style_dimensions) return nn.MSELoss()(style_output, biber_encoding) def biber_adversarial_loss(self, cls, biber_encoding): biber_encoding = torch.FloatTensor(biber_encoding).to(self.device) - not_style_dimensions = cls[:, self.style_dimensions:] + not_style_dimensions = cls[:, self.style_dimensions :] not_style_output = self.not_style_ll(not_style_dimensions) - adversarial_loss = (-1 * nn.MSELoss()(not_style_output, biber_encoding)) * self.adversarial_loss_weight + adversarial_loss = ( + -1 * nn.MSELoss()(not_style_output, biber_encoding) + ) * self.adversarial_loss_weight return adversarial_loss def evaluate(self): @@ -150,28 +196,45 @@ def evaluate(self): self.model.eval() mlm_losses, style_losses, adversarial_losses = [], [], [] total_steps = len(self.dev_loader) + len(self.en_dev_loader) - subset_steps = round(total_steps * 0.05) # Only use a random 5% of the evaluation data to save time - - for i, (batch, batch_type) in tqdm(enumerate(alternate_loaders(self.dev_loader, self.en_dev_loader)), - total=total_steps, - position=0, leave=True): + subset_steps = round( + total_steps * 0.05 + ) # Only use a random 5% of the evaluation data to save time + + for i, (batch, batch_type) in tqdm( + enumerate(alternate_loaders(self.dev_loader, self.en_dev_loader)), + total=total_steps, + position=0, + leave=True, + ): if i >= subset_steps: # Stop after subset_steps break with torch.no_grad(): - if batch_type == 'multilingual': + if batch_type == "multilingual": outputs = self.model(**batch.to(self.device)) mlm_loss = outputs.loss - mlm_losses.append(self.accelerator.gather(mlm_loss.repeat(self.args.batch_size))) + mlm_losses.append( + self.accelerator.gather(mlm_loss.repeat(self.args.batch_size)) + ) - if batch_type == 'english': + if batch_type == "english": batch, biber_encoding = batch outputs = self.model(**batch.to(self.device)) mlm_loss = outputs.loss - style_loss, adversarial_loss = self.get_biber_losses(batch, biber_encoding) - mlm_losses.append(self.accelerator.gather(mlm_loss.repeat(self.args.batch_size))) - style_losses.append(self.accelerator.gather(style_loss.repeat(self.args.batch_size))) - adversarial_losses.append(self.accelerator.gather(adversarial_loss.repeat(self.args.batch_size))) + style_loss, adversarial_loss = self.get_biber_losses( + batch, biber_encoding + ) + mlm_losses.append( + self.accelerator.gather(mlm_loss.repeat(self.args.batch_size)) + ) + style_losses.append( + self.accelerator.gather(style_loss.repeat(self.args.batch_size)) + ) + adversarial_losses.append( + self.accelerator.gather( + adversarial_loss.repeat(self.args.batch_size) + ) + ) mlm_losses = torch.cat(mlm_losses)[:total_steps] style_losses = torch.cat(style_losses)[:total_steps] @@ -182,7 +245,7 @@ def evaluate(self): "Eval Perplexity": perplexity, "Eval MLM Loss": torch.mean(mlm_losses).item(), "Eval Style Loss": torch.mean(style_losses).item(), - "Eval Adversarial Loss": torch.mean(adversarial_losses).item() + "Eval Adversarial Loss": torch.mean(adversarial_losses).item(), } print(eval_log) @@ -199,15 +262,17 @@ def get_biber_losses(self, batch, biber_encoding): return style_loss, adversarial_loss def _get_cls(self, outputs, training=True): - last_hidden_state = outputs['hidden_states'][-1] + last_hidden_state = outputs["hidden_states"][-1] cls_representation = last_hidden_state[:, 0, :] if training: return self.cls_dropout(cls_representation) return cls_representation - def save_model(self, step, version='last'): - """ Save model checkpoint """ - checkpoint_dir = os.path.join(self.args.out_dir, f'{self.args.run_name}', f'{version}') + def save_model(self, step, version="last"): + """Save model checkpoint""" + checkpoint_dir = os.path.join( + self.args.out_dir, f"{self.args.run_name}", f"{version}" + ) os.makedirs(checkpoint_dir, exist_ok=True) # Save the LM @@ -216,10 +281,13 @@ def save_model(self, step, version='last'): unwrapped_model.save_pretrained(checkpoint_dir) # Save the optimizer and scheduler - torch.save({ - 'step': step, - 'optimizer_state_dict': self.optimizer.state_dict(), - 'scheduler_state_dict': self.scheduler.state_dict(), - }, os.path.join(checkpoint_dir, 'optimizer_and_scheduler.pt')) + torch.save( + { + "step": step, + "optimizer_state_dict": self.optimizer.state_dict(), + "scheduler_state_dict": self.scheduler.state_dict(), + }, + os.path.join(checkpoint_dir, "optimizer_and_scheduler.pt"), + ) print(f"Saved {version} model to {checkpoint_dir}") diff --git a/modeling/polybiberta/multilingual_trainer_utils.py b/modeling/polybiberta/multilingual_trainer_utils.py index bc06525..ec23166 100644 --- a/modeling/polybiberta/multilingual_trainer_utils.py +++ b/modeling/polybiberta/multilingual_trainer_utils.py @@ -4,17 +4,22 @@ from torch.utils.data import DataLoader from transformers import AutoTokenizer -sys.path.append('../') +sys.path.append("../") -from src.custom_training.multilingual_finetuning.collators import MLMTextBiberCollator, MLMTextCollator +from src.custom_training.multilingual_finetuning.collators import ( + MLMTextBiberCollator, + MLMTextCollator, +) def alternate_loaders(multilingual_dataloader, english_dataloader): # Initialize the iterators for both dataloaders multilingual_dataloader_iter = iter(multilingual_dataloader) english_dataloader_iter = iter(english_dataloader) - print(f"Loading alternate data loaders. Multilingual batches: {len(multilingual_dataloader)} \t English batches: " - f"{len(english_dataloader)}") + print( + f"Loading alternate data loaders. Multilingual batches: {len(multilingual_dataloader)} \t English batches: " + f"{len(english_dataloader)}" + ) assert len(multilingual_dataloader) > len(english_dataloader) total_steps = len(multilingual_dataloader) + len(english_dataloader) @@ -22,11 +27,15 @@ def alternate_loaders(multilingual_dataloader, english_dataloader): for i in range(total_steps): if (i + 1) % alternate_index == 0: - batch, english_dataloader_iter = get_next_batch(english_dataloader, english_dataloader_iter) - yield batch, 'english' + batch, english_dataloader_iter = get_next_batch( + english_dataloader, english_dataloader_iter + ) + yield batch, "english" else: - batch, multilingual_dataloader_iter = get_next_batch(multilingual_dataloader, multilingual_dataloader_iter) - yield batch, 'multilingual' + batch, multilingual_dataloader_iter = get_next_batch( + multilingual_dataloader, multilingual_dataloader_iter + ) + yield batch, "multilingual" def get_next_batch(dataloader, iterator): @@ -40,7 +49,9 @@ def get_next_batch(dataloader, iterator): def load_all_dataloaders(args): - tokenizer = load_tokenizer(args.tokenizer if args.tokenizer else args.pretrained_model) + tokenizer = load_tokenizer( + args.tokenizer if args.tokenizer else args.pretrained_model + ) collator = MLMTextCollator(tokenizer=tokenizer, max_length=args.max_length) en_collator = MLMTextBiberCollator(tokenizer=tokenizer, max_length=args.max_length) @@ -53,16 +64,29 @@ def load_all_dataloaders(args): def get_dataloaders(args, collator, is_english=False): print( - f"Loading in the {'English' if is_english else 'multilingual'} training data from {args.en_train_file if is_english else args.train_file}") - train = load_dataset("json", data_files=args.en_train_file if is_english else args.train_file, split="train") - dev = load_dataset("json", data_files=args.en_dev_file if is_english else args.dev_file, split="train") + f"Loading in the {'English' if is_english else 'multilingual'} training data from {args.en_train_file if is_english else args.train_file}" + ) + train = load_dataset( + "json", + data_files=args.en_train_file if is_english else args.train_file, + split="train", + ) + dev = load_dataset( + "json", + data_files=args.en_dev_file if is_english else args.dev_file, + split="train", + ) if args.num_training_samples > 1: train = train.select(range(args.num_training_samples)) if args.num_eval_samples > 1: dev = dev.select(range(args.num_eval_samples)) - train_loader = DataLoader(train, batch_size=args.batch_size, shuffle=True, collate_fn=collator) - dev_loader = DataLoader(dev, batch_size=args.batch_size, shuffle=True, collate_fn=collator) + train_loader = DataLoader( + train, batch_size=args.batch_size, shuffle=True, collate_fn=collator + ) + dev_loader = DataLoader( + dev, batch_size=args.batch_size, shuffle=True, collate_fn=collator + ) return train_loader, dev_loader diff --git a/modeling/tagger/downsample.py b/modeling/tagger/downsample.py index 0505fab..a45d6c9 100644 --- a/modeling/tagger/downsample.py +++ b/modeling/tagger/downsample.py @@ -1,40 +1,46 @@ import json + def downsample_and_save(input_file, output_file, sample_rate=0.05): - print(f"\nDownsampling {input_file} to {output_file} at {sample_rate*100}% sample rate") - step = int(1/sample_rate) # e.g., 20 for 5% sampling - + print( + f"\nDownsampling {input_file} to {output_file} at {sample_rate * 100}% sample rate" + ) + step = int(1 / sample_rate) # e.g., 20 for 5% sampling + lines_read = 0 lines_written = 0 - - with open(input_file, 'r') as fin, open(output_file, 'w') as fout: + + with open(input_file, "r") as fin, open(output_file, "w") as fout: # Process file line by line for i, line in enumerate(fin): lines_read += 1 try: json.loads(line.strip()) - + if i > 0 and i % 1000000 == 0: # Log every million lines print(f"Processed {i:,} lines from {input_file}") if i % step == 0: # Take every nth line fout.write(line) lines_written += 1 except json.JSONDecodeError: - print(f"Warning: Invalid JSON on line {i+1}") + print(f"Warning: Invalid JSON on line {i + 1}") continue - + print(f"Completed: Read {lines_read:,} lines, wrote {lines_written:,} lines") return lines_read, lines_written + # Create downsampled versions -base_path = '/shared/3/projects/hiatus/tagged_data' +base_path = "/shared/3/projects/hiatus/tagged_data" print("Starting downsampling process...") for sample_rate in [0.01]: # 5% and 1% sampling percentage = int(sample_rate * 100) - for dataset in ['train', 'dev', 'test']: - input_file = f'{base_path}/binary_{dataset}.jsonl' - output_file = f'{base_path}/binary_{dataset}_{percentage}pct.jsonl' - total_read, total_written = downsample_and_save(input_file, output_file, sample_rate=sample_rate) + for dataset in ["train", "dev", "test"]: + input_file = f"{base_path}/binary_{dataset}.jsonl" + output_file = f"{base_path}/binary_{dataset}_{percentage}pct.jsonl" + total_read, total_written = downsample_and_save( + input_file, output_file, sample_rate=sample_rate + ) print("\nDownsampling complete for all files!") diff --git a/modeling/tagger/prepare_dataset.py b/modeling/tagger/prepare_dataset.py index a40bc42..ea725eb 100644 --- a/modeling/tagger/prepare_dataset.py +++ b/modeling/tagger/prepare_dataset.py @@ -4,211 +4,317 @@ from sklearn.model_selection import train_test_split + def get_dataset_paths(): return { - 'amazon': '/shared/3/projects/hiatus/tagged_data/amazon/', - 'reddit': '/shared/3/projects/hiatus/tagged_data/reddit/', - 'book3corpus': '/shared/3/projects/hiatus/tagged_data/book3corpus/', - 'wiki': '/shared/3/projects/hiatus/tagged_data/wiki/', - 'wiki_discussions': '/shared/3/projects/hiatus/tagged_data/wiki_discussions/', - 'realnews': '/shared/3/projects/hiatus/tagged_data/realnews/', - 'gmane': '/shared/3/projects/hiatus/tagged_data/gmane/' + "amazon": "/shared/3/projects/hiatus/tagged_data/amazon/", + "reddit": "/shared/3/projects/hiatus/tagged_data/reddit/", + "book3corpus": "/shared/3/projects/hiatus/tagged_data/book3corpus/", + "wiki": "/shared/3/projects/hiatus/tagged_data/wiki/", + "wiki_discussions": "/shared/3/projects/hiatus/tagged_data/wiki_discussions/", + "realnews": "/shared/3/projects/hiatus/tagged_data/realnews/", + "gmane": "/shared/3/projects/hiatus/tagged_data/gmane/", } + def get_biber_features(): return [ - "BIN_QUAN_mean", "BIN_QUAN_std", - "BIN_QUPR_mean", "BIN_QUPR_std", - "BIN_AMP_mean", "BIN_AMP_std", - "BIN_PASS_mean", "BIN_PASS_std", - "BIN_XX0_mean", "BIN_XX0_std", - "BIN_JJ_mean", "BIN_JJ_std", - "BIN_BEMA_mean", "BIN_BEMA_std", - "BIN_CAUS_mean", "BIN_CAUS_std", - "BIN_CONC_mean", "BIN_CONC_std", - "BIN_COND_mean", "BIN_COND_std", - "BIN_CONJ_mean", "BIN_CONJ_std", - "BIN_CONT_mean", "BIN_CONT_std", - "BIN_DPAR_mean", "BIN_DPAR_std", - "BIN_DWNT_mean", "BIN_DWNT_std", - "BIN_EX_mean", "BIN_EX_std", - "BIN_FPP1_mean", "BIN_FPP1_std", - "BIN_GER_mean", "BIN_GER_std", - "BIN_RB_mean", "BIN_RB_std", - "BIN_PIN_mean", "BIN_PIN_std", - "BIN_INPR_mean", "BIN_INPR_std", - "BIN_TO_mean", "BIN_TO_std", - "BIN_NEMD_mean", "BIN_NEMD_std", - "BIN_OSUB_mean", "BIN_OSUB_std", - "BIN_PASTP_mean", "BIN_PASTP_std", - "BIN_VBD_mean", "BIN_VBD_std", - "BIN_PHC_mean", "BIN_PHC_std", - "BIN_PIRE_mean", "BIN_PIRE_std", - "BIN_PLACE_mean", "BIN_PLACE_std", - "BIN_POMD_mean", "BIN_POMD_std", - "BIN_PRMD_mean", "BIN_PRMD_std", - "BIN_WZPRES_mean", "BIN_WZPRES_std", - "BIN_VPRT_mean", "BIN_VPRT_std", - "BIN_PRIV_mean", "BIN_PRIV_std", - "BIN_PIT_mean", "BIN_PIT_std", - "BIN_PUBV_mean", "BIN_PUBV_std", - "BIN_SPP2_mean", "BIN_SPP2_std", - "BIN_SMP_mean", "BIN_SMP_std", - "BIN_SERE_mean", "BIN_SERE_std", - "BIN_STPR_mean", "BIN_STPR_std", - "BIN_SUAV_mean", "BIN_SUAV_std", - "BIN_SYNE_mean", "BIN_SYNE_std", - "BIN_TPP3_mean", "BIN_TPP3_std", - "BIN_TIME_mean", "BIN_TIME_std", - "BIN_NOMZ_mean", "BIN_NOMZ_std", - "BIN_BYPA_mean", "BIN_BYPA_std", - "BIN_PRED_mean", "BIN_PRED_std", - "BIN_TOBJ_mean", "BIN_TOBJ_std", - "BIN_TSUB_mean", "BIN_TSUB_std", - "BIN_THVC_mean", "BIN_THVC_std", - "BIN_NN_mean", "BIN_NN_std", - "BIN_DEMP_mean", "BIN_DEMP_std", - "BIN_DEMO_mean", "BIN_DEMO_std", - "BIN_WHQU_mean", "BIN_WHQU_std", - "BIN_EMPH_mean", "BIN_EMPH_std", - "BIN_HDG_mean", "BIN_HDG_std", - "BIN_WZPAST_mean", "BIN_WZPAST_std", - "BIN_THAC_mean", "BIN_THAC_std", - "BIN_PEAS_mean", "BIN_PEAS_std", - "BIN_ANDC_mean", "BIN_ANDC_std", - "BIN_PRESP_mean", "BIN_PRESP_std", - "BIN_PROD_mean", "BIN_PROD_std", - "BIN_SPAU_mean", "BIN_SPAU_std", - "BIN_SPIN_mean", "BIN_SPIN_std", - "BIN_THATD_mean", "BIN_THATD_std", - "BIN_WHOBJ_mean", "BIN_WHOBJ_std", - "BIN_WHSUB_mean", "BIN_WHSUB_std", - "BIN_WHCL_mean", "BIN_WHCL_std", - "BIN_ART_mean", "BIN_ART_std", - "BIN_AUXB_mean", "BIN_AUXB_std", - "BIN_CAP_mean", "BIN_CAP_std", - "BIN_SCONJ_mean", "BIN_SCONJ_std", - "BIN_CCONJ_mean", "BIN_CCONJ_std", - "BIN_DET_mean", "BIN_DET_std", - "BIN_EMOJ_mean", "BIN_EMOJ_std", - "BIN_EMOT_mean", "BIN_EMOT_std", - "BIN_EXCL_mean", "BIN_EXCL_std", - "BIN_HASH_mean", "BIN_HASH_std", - "BIN_INF_mean", "BIN_INF_std", - "BIN_UH_mean", "BIN_UH_std", - "BIN_NUM_mean", "BIN_NUM_std", - "BIN_LAUGH_mean", "BIN_LAUGH_std", - "BIN_PRP_mean", "BIN_PRP_std", - "BIN_PREP_mean", "BIN_PREP_std", - "BIN_NNP_mean", "BIN_NNP_std", - "BIN_QUES_mean", "BIN_QUES_std", - "BIN_QUOT_mean", "BIN_QUOT_std", - "BIN_AT_mean", "BIN_AT_std", - "BIN_SBJP_mean", "BIN_SBJP_std", - "BIN_URL_mean", "BIN_URL_std", - "BIN_WH_mean", "BIN_WH_std", - "BIN_INDA_mean", "BIN_INDA_std", - "BIN_ACCU_mean", "BIN_ACCU_std", - "BIN_PGAS_mean", "BIN_PGAS_std", - "BIN_CMADJ_mean", "BIN_CMADJ_std", - "BIN_SPADJ_mean", "BIN_SPADJ_std", - "BIN_X_mean", "BIN_X_std" + "BIN_QUAN_mean", + "BIN_QUAN_std", + "BIN_QUPR_mean", + "BIN_QUPR_std", + "BIN_AMP_mean", + "BIN_AMP_std", + "BIN_PASS_mean", + "BIN_PASS_std", + "BIN_XX0_mean", + "BIN_XX0_std", + "BIN_JJ_mean", + "BIN_JJ_std", + "BIN_BEMA_mean", + "BIN_BEMA_std", + "BIN_CAUS_mean", + "BIN_CAUS_std", + "BIN_CONC_mean", + "BIN_CONC_std", + "BIN_COND_mean", + "BIN_COND_std", + "BIN_CONJ_mean", + "BIN_CONJ_std", + "BIN_CONT_mean", + "BIN_CONT_std", + "BIN_DPAR_mean", + "BIN_DPAR_std", + "BIN_DWNT_mean", + "BIN_DWNT_std", + "BIN_EX_mean", + "BIN_EX_std", + "BIN_FPP1_mean", + "BIN_FPP1_std", + "BIN_GER_mean", + "BIN_GER_std", + "BIN_RB_mean", + "BIN_RB_std", + "BIN_PIN_mean", + "BIN_PIN_std", + "BIN_INPR_mean", + "BIN_INPR_std", + "BIN_TO_mean", + "BIN_TO_std", + "BIN_NEMD_mean", + "BIN_NEMD_std", + "BIN_OSUB_mean", + "BIN_OSUB_std", + "BIN_PASTP_mean", + "BIN_PASTP_std", + "BIN_VBD_mean", + "BIN_VBD_std", + "BIN_PHC_mean", + "BIN_PHC_std", + "BIN_PIRE_mean", + "BIN_PIRE_std", + "BIN_PLACE_mean", + "BIN_PLACE_std", + "BIN_POMD_mean", + "BIN_POMD_std", + "BIN_PRMD_mean", + "BIN_PRMD_std", + "BIN_WZPRES_mean", + "BIN_WZPRES_std", + "BIN_VPRT_mean", + "BIN_VPRT_std", + "BIN_PRIV_mean", + "BIN_PRIV_std", + "BIN_PIT_mean", + "BIN_PIT_std", + "BIN_PUBV_mean", + "BIN_PUBV_std", + "BIN_SPP2_mean", + "BIN_SPP2_std", + "BIN_SMP_mean", + "BIN_SMP_std", + "BIN_SERE_mean", + "BIN_SERE_std", + "BIN_STPR_mean", + "BIN_STPR_std", + "BIN_SUAV_mean", + "BIN_SUAV_std", + "BIN_SYNE_mean", + "BIN_SYNE_std", + "BIN_TPP3_mean", + "BIN_TPP3_std", + "BIN_TIME_mean", + "BIN_TIME_std", + "BIN_NOMZ_mean", + "BIN_NOMZ_std", + "BIN_BYPA_mean", + "BIN_BYPA_std", + "BIN_PRED_mean", + "BIN_PRED_std", + "BIN_TOBJ_mean", + "BIN_TOBJ_std", + "BIN_TSUB_mean", + "BIN_TSUB_std", + "BIN_THVC_mean", + "BIN_THVC_std", + "BIN_NN_mean", + "BIN_NN_std", + "BIN_DEMP_mean", + "BIN_DEMP_std", + "BIN_DEMO_mean", + "BIN_DEMO_std", + "BIN_WHQU_mean", + "BIN_WHQU_std", + "BIN_EMPH_mean", + "BIN_EMPH_std", + "BIN_HDG_mean", + "BIN_HDG_std", + "BIN_WZPAST_mean", + "BIN_WZPAST_std", + "BIN_THAC_mean", + "BIN_THAC_std", + "BIN_PEAS_mean", + "BIN_PEAS_std", + "BIN_ANDC_mean", + "BIN_ANDC_std", + "BIN_PRESP_mean", + "BIN_PRESP_std", + "BIN_PROD_mean", + "BIN_PROD_std", + "BIN_SPAU_mean", + "BIN_SPAU_std", + "BIN_SPIN_mean", + "BIN_SPIN_std", + "BIN_THATD_mean", + "BIN_THATD_std", + "BIN_WHOBJ_mean", + "BIN_WHOBJ_std", + "BIN_WHSUB_mean", + "BIN_WHSUB_std", + "BIN_WHCL_mean", + "BIN_WHCL_std", + "BIN_ART_mean", + "BIN_ART_std", + "BIN_AUXB_mean", + "BIN_AUXB_std", + "BIN_CAP_mean", + "BIN_CAP_std", + "BIN_SCONJ_mean", + "BIN_SCONJ_std", + "BIN_CCONJ_mean", + "BIN_CCONJ_std", + "BIN_DET_mean", + "BIN_DET_std", + "BIN_EMOJ_mean", + "BIN_EMOJ_std", + "BIN_EMOT_mean", + "BIN_EMOT_std", + "BIN_EXCL_mean", + "BIN_EXCL_std", + "BIN_HASH_mean", + "BIN_HASH_std", + "BIN_INF_mean", + "BIN_INF_std", + "BIN_UH_mean", + "BIN_UH_std", + "BIN_NUM_mean", + "BIN_NUM_std", + "BIN_LAUGH_mean", + "BIN_LAUGH_std", + "BIN_PRP_mean", + "BIN_PRP_std", + "BIN_PREP_mean", + "BIN_PREP_std", + "BIN_NNP_mean", + "BIN_NNP_std", + "BIN_QUES_mean", + "BIN_QUES_std", + "BIN_QUOT_mean", + "BIN_QUOT_std", + "BIN_AT_mean", + "BIN_AT_std", + "BIN_SBJP_mean", + "BIN_SBJP_std", + "BIN_URL_mean", + "BIN_URL_std", + "BIN_WH_mean", + "BIN_WH_std", + "BIN_INDA_mean", + "BIN_INDA_std", + "BIN_ACCU_mean", + "BIN_ACCU_std", + "BIN_PGAS_mean", + "BIN_PGAS_std", + "BIN_CMADJ_mean", + "BIN_CMADJ_std", + "BIN_SPADJ_mean", + "BIN_SPADJ_std", + "BIN_X_mean", + "BIN_X_std", ] + def create_dataset(output_file, dataset_paths, biber_features): - with open(output_file, 'w') as jsonl_file: + with open(output_file, "w") as jsonl_file: for name, corpus_path in dataset_paths.items(): - corpus_fp = os.path.join(corpus_path, 'corpus.jsonl') + corpus_fp = os.path.join(corpus_path, "corpus.jsonl") line_count = 0 valid_count = 0 - + print(f"\nProcessing {corpus_fp}") - with open(corpus_fp, 'r') as file: + with open(corpus_fp, "r") as file: for line in file: line_count += 1 if line_count % 1000000 == 0: print(f"Processed {line_count:,} lines...") - + data = json.loads(line) - text = data.get('fullText', '') - binary_encodings = data.get('biber_tagged', {}).get('binary', []) + text = data.get("fullText", "") + binary_encodings = data.get("biber_tagged", {}).get("binary", []) if not text or not binary_encodings: continue valid_count += 1 output_obj = { - 'text': text, - 'features': dict(zip(biber_features, binary_encodings)) + "text": text, + "features": dict(zip(biber_features, binary_encodings)), } - jsonl_file.write(json.dumps(output_obj) + '\n') - + jsonl_file.write(json.dumps(output_obj) + "\n") + print(f"\nDataset: {name}") print(f"Total lines: {line_count:,}") print(f"Valid lines: {valid_count:,}") print(f"Filtered lines: {line_count - valid_count:,}\n") + def read_and_shuffle(file_path, chunksize=50000): chunks = [] for chunk in pd.read_json(file_path, lines=True, chunksize=chunksize): chunks.append(chunk.sample(frac=1)) return pd.concat(chunks).reset_index(drop=True) - - + + def save_to_jsonl(df, file_path): if not os.path.exists(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path)) - - df.to_json(file_path, orient='records', lines=True) + + df.to_json(file_path, orient="records", lines=True) + def split_and_save_data(input_file, chunk_size=50000): print(f"Processing data in chunks of {chunk_size:,}...") - + # Initialize file writers - train_file = '/shared/3/projects/hiatus/tagged_data/binary_train.jsonl' - dev_file = '/shared/3/projects/hiatus/tagged_data/binary_dev.jsonl' - test_file = '/shared/3/projects/hiatus/tagged_data/binary_test.jsonl' - + train_file = "/shared/3/projects/hiatus/tagged_data/binary_train.jsonl" + dev_file = "/shared/3/projects/hiatus/tagged_data/binary_dev.jsonl" + test_file = "/shared/3/projects/hiatus/tagged_data/binary_test.jsonl" + # Create directories if they don't exist for file_path in [train_file, dev_file, test_file]: os.makedirs(os.path.dirname(file_path), exist_ok=True) - + # Process in chunks - with open(train_file, 'w') as train_f, \ - open(dev_file, 'w') as dev_f, \ - open(test_file, 'w') as test_f: - + with ( + open(train_file, "w") as train_f, + open(dev_file, "w") as dev_f, + open(test_file, "w") as test_f, + ): for chunk in pd.read_json(input_file, lines=True, chunksize=chunk_size): # Shuffle the chunk chunk = chunk.sample(frac=1, random_state=42) - + # Calculate split indices for 80/10/10 n = len(chunk) train_idx = int(n * 0.8) dev_idx = int(n * 0.9) - + # Split the chunk train_chunk = chunk.iloc[:train_idx] dev_chunk = chunk.iloc[train_idx:dev_idx] test_chunk = chunk.iloc[dev_idx:] - + # Write to respective files - train_chunk.to_json(train_f, orient='records', lines=True) - dev_chunk.to_json(dev_f, orient='records', lines=True) - test_chunk.to_json(test_f, orient='records', lines=True) - + train_chunk.to_json(train_f, orient="records", lines=True) + dev_chunk.to_json(dev_f, orient="records", lines=True) + test_chunk.to_json(test_f, orient="records", lines=True) + print("Data splitting and saving completed.") + def main(): - output_file = '/shared/3/projects/hiatus/tagged_data/binary_multi_label_dataset.jsonl' + output_file = ( + "/shared/3/projects/hiatus/tagged_data/binary_multi_label_dataset.jsonl" + ) # dataset_paths = get_dataset_paths() # biber_features = get_biber_features() - + # create_dataset(output_file, dataset_paths, biber_features) - + split_and_save_data(output_file) - + print("Data splitting and saving completed.") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/modeling/tagger/train_model.py b/modeling/tagger/train_model.py index e31735d..ce5f504 100644 --- a/modeling/tagger/train_model.py +++ b/modeling/tagger/train_model.py @@ -2,14 +2,25 @@ import sys import pandas as pd import numpy as np -from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer, EarlyStoppingCallback +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + TrainingArguments, + Trainer, + EarlyStoppingCallback, +) import wandb import logging -sys.path.append('../..') +sys.path.append("../..") -from modeling.tagger.train_utils import StreamingDataset, compute_metrics, compute_tag_level_metrics, EvalSamplingTrainer +from modeling.tagger.train_utils import ( + StreamingDataset, + compute_metrics, + compute_tag_level_metrics, + EvalSamplingTrainer, +) # Path Configuration BASE_DIR = "/shared/3/projects/hiatus/tagged_data/models" @@ -22,7 +33,9 @@ OUTPUT_DIR = os.path.join(RUN_DIR, "results") WANDB_RUN_NAME = f"{MODEL_NAME}-{RUN_NAME}" MODEL_SAVE_PATH = os.path.join(RUN_DIR, "best_model") -TAG_PERFORMANCE_SUMMARY_PATH = os.path.join(RUN_DIR, "tag_level_performance_summary.csv") +TAG_PERFORMANCE_SUMMARY_PATH = os.path.join( + RUN_DIR, "tag_level_performance_summary.csv" +) # Create necessary directories try: @@ -36,8 +49,8 @@ # Add logging configuration logging.basicConfig( level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger(__name__) @@ -56,18 +69,26 @@ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) logger.info("Initializing streaming datasets...") -train_dataset = StreamingDataset('/shared/3/projects/hiatus/tagged_data/binary_train.jsonl', tokenizer, max_length=512) -dev_dataset = StreamingDataset('/shared/3/projects/hiatus/tagged_data/binary_dev.jsonl', tokenizer, max_length=512) -test_dataset = StreamingDataset('/shared/3/projects/hiatus/tagged_data/binary_test.jsonl', tokenizer, max_length=512) +train_dataset = StreamingDataset( + "/shared/3/projects/hiatus/tagged_data/binary_train.jsonl", + tokenizer, + max_length=512, +) +dev_dataset = StreamingDataset( + "/shared/3/projects/hiatus/tagged_data/binary_dev.jsonl", tokenizer, max_length=512 +) +test_dataset = StreamingDataset( + "/shared/3/projects/hiatus/tagged_data/binary_test.jsonl", tokenizer, max_length=512 +) logger.info(f"Loading tokenizer and model from {MODEL_NAME}...") model = AutoModelForSequenceClassification.from_pretrained( - MODEL_NAME, - problem_type="regression", - num_labels=len(train_dataset.label_columns) -).to('cuda') + MODEL_NAME, problem_type="regression", num_labels=len(train_dataset.label_columns) +).to("cuda") -logger.info(f"Dataset sizes - Train: {train_dataset.total_rows}, Dev: {dev_dataset.total_rows}, Test: {test_dataset.total_rows}") +logger.info( + f"Dataset sizes - Train: {train_dataset.total_rows}, Dev: {dev_dataset.total_rows}, Test: {test_dataset.total_rows}" +) logger.info(f"Number of labels: {len(train_dataset.label_columns)}") logger.info(f"Labels: {', '.join(train_dataset.label_columns)}") @@ -89,8 +110,8 @@ "labels": train_dataset.label_columns, "train_size": train_dataset.total_rows, "dev_size": dev_dataset.total_rows, - "test_size": test_dataset.total_rows - } + "test_size": test_dataset.total_rows, + }, ) training_args = TrainingArguments( @@ -109,7 +130,7 @@ report_to="wandb", metric_for_best_model="eval_mse", greater_is_better=True, - fp16=True + fp16=True, ) trainer = EvalSamplingTrainer( @@ -119,9 +140,7 @@ eval_dataset=dev_dataset, tokenizer=tokenizer, compute_metrics=compute_metrics, - callbacks=[ - EarlyStoppingCallback(early_stopping_patience=5) - ] + callbacks=[EarlyStoppingCallback(early_stopping_patience=5)], ) train_result = trainer.train() @@ -131,18 +150,20 @@ logger.info("Evaluating on test set...") test_results = trainer.evaluate(test_dataset) logger.info(f"Test results: {test_results}") -wandb.log({ - "test_mse": test_results["eval_mse"], - "test_rmse": test_results["eval_rmse"], - "test_mae": test_results["eval_mae"], - "test_r2": test_results["eval_r2"], - "test_precision_micro": test_results["eval_precision_micro"], - "test_recall_micro": test_results["eval_recall_micro"], - "test_f1_micro": test_results["eval_f1_micro"], - "test_precision_macro": test_results["eval_precision_macro"], - "test_recall_macro": test_results["eval_recall_macro"], - "test_f1_macro": test_results["eval_f1_macro"] -}) +wandb.log( + { + "test_mse": test_results["eval_mse"], + "test_rmse": test_results["eval_rmse"], + "test_mae": test_results["eval_mae"], + "test_r2": test_results["eval_r2"], + "test_precision_micro": test_results["eval_precision_micro"], + "test_recall_micro": test_results["eval_recall_micro"], + "test_f1_micro": test_results["eval_f1_micro"], + "test_precision_macro": test_results["eval_precision_macro"], + "test_recall_macro": test_results["eval_recall_macro"], + "test_f1_macro": test_results["eval_f1_macro"], + } +) logger.info(f"Saving model to {MODEL_SAVE_PATH}") trainer.save_model(MODEL_SAVE_PATH) @@ -156,7 +177,7 @@ tag_level_metrics = compute_tag_level_metrics( test_predictions.predictions, test_predictions.label_ids, - train_dataset.label_columns + train_dataset.label_columns, ) # Log detailed results @@ -173,9 +194,9 @@ # 'avg_rmse': np.mean([m['rmse'] for m in tag_level_metrics.values()]), # 'avg_mae': np.mean([m['mae'] for m in tag_level_metrics.values()]), # 'avg_r2': np.mean([m['r2'] for m in tag_level_metrics.values()]), - 'avg_precision': np.mean([m['precision'] for m in tag_level_metrics.values()]), - 'avg_recall': np.mean([m['recall'] for m in tag_level_metrics.values()]), - 'avg_f1': np.mean([m['f1'] for m in tag_level_metrics.values()]) + "avg_precision": np.mean([m["precision"] for m in tag_level_metrics.values()]), + "avg_recall": np.mean([m["recall"] for m in tag_level_metrics.values()]), + "avg_f1": np.mean([m["f1"] for m in tag_level_metrics.values()]), } logger.info("\nOverall Statistics:") @@ -187,14 +208,14 @@ logger.info(f"Saving performance summary to {TAG_PERFORMANCE_SUMMARY_PATH}") summary_data = [ { - 'Tag': tag, - 'MSE': metrics['mse'], - 'RMSE': metrics['rmse'], - 'MAE': metrics['mae'], - 'R2': metrics['r2'], - 'Precision': metrics['precision'], - 'Recall': metrics['recall'], - 'F1': metrics['f1'] + "Tag": tag, + "MSE": metrics["mse"], + "RMSE": metrics["rmse"], + "MAE": metrics["mae"], + "R2": metrics["r2"], + "Precision": metrics["precision"], + "Recall": metrics["recall"], + "F1": metrics["f1"], } for tag, metrics in tag_level_metrics.items() ] @@ -203,4 +224,4 @@ summary_df.to_csv(TAG_PERFORMANCE_SUMMARY_PATH, index=False) logger.info("Training run completed successfully") -wandb.finish() \ No newline at end of file +wandb.finish() diff --git a/modeling/tagger/train_utils.py b/modeling/tagger/train_utils.py index d13a8c3..0a27b19 100644 --- a/modeling/tagger/train_utils.py +++ b/modeling/tagger/train_utils.py @@ -2,7 +2,14 @@ from typing import Optional, Union from transformers import Trainer from torch.utils.data import IterableDataset -from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error, precision_score, recall_score, f1_score +from sklearn.metrics import ( + mean_squared_error, + r2_score, + mean_absolute_error, + precision_score, + recall_score, + f1_score, +) import json import torch import numpy as np @@ -15,94 +22,108 @@ def chunk_df_iterator(file_path, chunk_size=1000): except Exception as e: raise RuntimeError(f"Error reading file {file_path}: {str(e)}") + class StreamingDataset(IterableDataset): def __init__(self, data_path, tokenizer, max_length=512): self.data_path = data_path self.tokenizer = tokenizer self.max_length = max_length - + # Load first row to get label columns - with open(data_path, 'r') as f: + with open(data_path, "r") as f: first_row = json.loads(f.readline()) - self.label_columns = list(first_row['features'].keys()) - + self.label_columns = list(first_row["features"].keys()) + # Get total rows for length calculation self.total_rows = sum(1 for _ in open(self.data_path)) def __iter__(self): - with open(self.data_path, 'r') as f: + with open(self.data_path, "r") as f: for line in f: item = json.loads(line) - + # Tokenize text encoding = self.tokenizer( - item['text'], - padding='max_length', + item["text"], + padding="max_length", truncation=True, max_length=self.max_length, - return_tensors='pt' + return_tensors="pt", ) - + # Add labels - labels = [float(item['features'][col]) for col in self.label_columns] - encoding['labels'] = torch.tensor(labels, dtype=torch.float) - + labels = [float(item["features"][col]) for col in self.label_columns] + encoding["labels"] = torch.tensor(labels, dtype=torch.float) + # Remove batch dimension added by tokenizer encoding = {k: v.squeeze(0) for k, v in encoding.items()} - + yield encoding def __len__(self): return self.total_rows + def compute_metrics(eval_pred): predictions = eval_pred.predictions labels = eval_pred.label_ids - + if predictions.shape != labels.shape: - raise ValueError(f"Predictions shape {predictions.shape} doesn't match labels shape {labels.shape}") - + raise ValueError( + f"Predictions shape {predictions.shape} doesn't match labels shape {labels.shape}" + ) + binary_predictions = (predictions != 0).astype(int) binary_labels = (labels != 0).astype(int) - + return { # Regression metrics - 'mse': mean_squared_error(labels, predictions), - 'rmse': mean_squared_error(labels, predictions, squared=False), - 'mae': mean_absolute_error(labels, predictions), - 'r2': r2_score(labels, predictions), + "mse": mean_squared_error(labels, predictions), + "rmse": mean_squared_error(labels, predictions, squared=False), + "mae": mean_absolute_error(labels, predictions), + "r2": r2_score(labels, predictions), # Classification metrics - 'precision_micro': precision_score(binary_labels, binary_predictions, average='micro'), - 'recall_micro': recall_score(binary_labels, binary_predictions, average='micro'), - 'f1_micro': f1_score(binary_labels, binary_predictions, average='micro'), - 'precision_macro': precision_score(binary_labels, binary_predictions, average='macro'), - 'recall_macro': recall_score(binary_labels, binary_predictions, average='macro'), - 'f1_macro': f1_score(binary_labels, binary_predictions, average='macro') + "precision_micro": precision_score( + binary_labels, binary_predictions, average="micro" + ), + "recall_micro": recall_score( + binary_labels, binary_predictions, average="micro" + ), + "f1_micro": f1_score(binary_labels, binary_predictions, average="micro"), + "precision_macro": precision_score( + binary_labels, binary_predictions, average="macro" + ), + "recall_macro": recall_score( + binary_labels, binary_predictions, average="macro" + ), + "f1_macro": f1_score(binary_labels, binary_predictions, average="macro"), } + def compute_tag_level_metrics(predictions, labels, tag_names): tag_metrics = {} for i, tag in enumerate(tag_names): tag_predictions = predictions[:, i] tag_labels = labels[:, i] - + # Convert to binary for classification metrics binary_predictions = (tag_predictions != 0).astype(int) binary_labels = (tag_labels != 0).astype(int) - + tag_metrics[tag] = { # Regression metrics - 'mse': mean_squared_error(tag_labels, tag_predictions), - 'rmse': mean_squared_error(tag_labels, tag_predictions, squared=False), - 'mae': mean_absolute_error(tag_labels, tag_predictions), - 'r2': r2_score(tag_labels, tag_predictions), + "mse": mean_squared_error(tag_labels, tag_predictions), + "rmse": mean_squared_error(tag_labels, tag_predictions, squared=False), + "mae": mean_absolute_error(tag_labels, tag_predictions), + "r2": r2_score(tag_labels, tag_predictions), # Classification metrics - 'precision': precision_score(binary_labels, binary_predictions), - 'recall': recall_score(binary_labels, binary_predictions), - 'f1': f1_score(binary_labels, binary_predictions) + "precision": precision_score(binary_labels, binary_predictions), + "recall": recall_score(binary_labels, binary_predictions), + "f1": f1_score(binary_labels, binary_predictions), } return tag_metrics + class EvalSamplingTrainer(Trainer): def __init__(self, eval_sample_ratio=0.05, random_seed=42, *args, **kwargs): super().__init__(*args, **kwargs) @@ -110,25 +131,24 @@ def __init__(self, eval_sample_ratio=0.05, random_seed=42, *args, **kwargs): self.random_seed = random_seed def evaluate( - self, - eval_dataset: Optional[Union[StreamingDataset, None]] = None, - **kwargs + self, eval_dataset: Optional[Union[StreamingDataset, None]] = None, **kwargs ): if eval_dataset is None: eval_dataset = self.eval_dataset - + np.random.seed(self.random_seed) - + # Create a sampled version of the dataset by wrapping the iterator original_iter = eval_dataset.__iter__ + def sampled_iter(): for item in original_iter(): if np.random.random() < self.eval_sample_ratio: yield item - + # Better to use a context manager pattern from contextlib import contextmanager - + @contextmanager def temporary_iterator(): original_iter = eval_dataset.__iter__ @@ -137,8 +157,8 @@ def temporary_iterator(): yield finally: eval_dataset.__iter__ = original_iter - + with temporary_iterator(): metrics = super().evaluate(eval_dataset, **kwargs) - + return metrics diff --git a/modeling/trainer_utils.py b/modeling/trainer_utils.py index e6ad63f..354478c 100644 --- a/modeling/trainer_utils.py +++ b/modeling/trainer_utils.py @@ -5,13 +5,16 @@ from torch.utils.data import DataLoader from transformers import AutoModel, AutoTokenizer -sys.path.append('../') +sys.path.append("../") from modeling.biberta.collator import MLMBiberPairCollator, MLMBiberCollator -from modeling.contrastive_training.collator import ContrastiveBiberCollator, ContrastiveCollator +from modeling.contrastive_training.collator import ( + ContrastiveBiberCollator, + ContrastiveCollator, +) -def load_model_tokenizer(pretrained_model='roberta-base', gradient_checkpointing=False): +def load_model_tokenizer(pretrained_model="roberta-base", gradient_checkpointing=False): print(f"Loading in {pretrained_model} model") model = AutoModel.from_pretrained(pretrained_model) @@ -35,11 +38,22 @@ def load_tokenizer(pretrained_model): def get_dataloaders(args, pairs=True, mlm=False, biber=False): - tokenizer = load_tokenizer(args.tokenizer if args.tokenizer else args.pretrained_model) + tokenizer = load_tokenizer( + args.tokenizer if args.tokenizer else args.pretrained_model + ) train_dataset, dev_dataset = load_datasets(args) - collator, eval_collator = get_collators(tokenizer, args.max_length, mlm, pairs, biber) - train_dataloader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, collate_fn=collator) - eval_dataloader = DataLoader(dev_dataset, batch_size=args.eval_batch_size, shuffle=False, collate_fn=eval_collator) + collator, eval_collator = get_collators( + tokenizer, args.max_length, mlm, pairs, biber + ) + train_dataloader = DataLoader( + train_dataset, batch_size=args.batch_size, shuffle=True, collate_fn=collator + ) + eval_dataloader = DataLoader( + dev_dataset, + batch_size=args.eval_batch_size, + shuffle=False, + collate_fn=eval_collator, + ) return train_dataloader, eval_dataloader @@ -55,7 +69,9 @@ def get_collators(tokenizer, max_length, mlm=False, pairs=True, biber=False): def create_collators(CollatorClass, tokenizer, max_length): collator = CollatorClass(tokenizer=tokenizer, max_length=max_length) - eval_collator = CollatorClass(tokenizer=tokenizer, max_length=max_length, evaluate=True) + eval_collator = CollatorClass( + tokenizer=tokenizer, max_length=max_length, evaluate=True + ) return collator, eval_collator @@ -63,7 +79,9 @@ def load_datasets(args): print(f"Reading in train data from {args.train_data}") train_dataset = load_dataset("json", data_files=args.train_data, split="train") print(f"Reading in evaluation data from {args.dev_data}") - dev_dataset = load_dataset("json", data_files=args.dev_data, split="train").shuffle(seed=42) + dev_dataset = load_dataset("json", data_files=args.dev_data, split="train").shuffle( + seed=42 + ) if args.num_training_samples > 1: train_dataset = train_dataset.select(range(args.num_training_samples)) diff --git a/setup.py b/setup.py index 8fc320d..d358dab 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,17 @@ from setuptools import setup, find_packages setup( - name='biberplus', - version='0.3.0', + name="biberplus", + version="0.3.0", description="A pure Python implementation of Biber's (1988) Variation across Speech and Writing linguistic tags with additional features", - url='https://github.com/davidjurgens/biberplus', - author='Kenan Alkiek, David Jurgens', - author_email='kalkiek@umich.edu', - license='MIT License', + url="https://github.com/davidjurgens/biberplus", + author="Kenan Alkiek, David Jurgens", + author_email="kalkiek@umich.edu", + license="MIT License", license_files=["LICENSE"], packages=find_packages(), include_package_data=True, - package_data={'': ['tagger/constants/*.txt', 'tagger/config.yaml']}, + package_data={"": ["tagger/constants/*.txt", "tagger/config.yaml"]}, install_requires=[ "pandas", "numpy", @@ -22,13 +22,13 @@ "PyYAML", "factor_analyzer", "matplotlib", - "seaborn" + "seaborn", ], - python_requires='>=3.7', + python_requires=">=3.7", classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3" + "Programming Language :: Python :: 3", ], ) diff --git a/tests/tagger/test_additional_features.py b/tests/tagger/test_additional_features.py index 4b7d7fe..d4f272c 100644 --- a/tests/tagger/test_additional_features.py +++ b/tests/tagger/test_additional_features.py @@ -6,194 +6,195 @@ class TestAdditionalFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_articles(self): text = "The cat chased a mouse across the room." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('ART', tagged_words[0]['tags']) - self.assertIn('ART', tagged_words[3]['tags']) - self.assertIn('ART', tagged_words[6]['tags']) + self.assertIn("ART", tagged_words[0]["tags"]) + self.assertIn("ART", tagged_words[3]["tags"]) + self.assertIn("ART", tagged_words[6]["tags"]) def test_auxillary_be(self): text = "You have been standing" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('AUXB', tagged_words[2]['tags']) + self.assertIn("AUXB", tagged_words[2]["tags"]) def test_capitalizations(self): text = "The Eiffel Tower in Paris is a famous landmark." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('CAP', tagged_words[1]['tags']) - self.assertIn('CAP', tagged_words[2]['tags']) - self.assertIn('CAP', tagged_words[4]['tags']) + self.assertIn("CAP", tagged_words[1]["tags"]) + self.assertIn("CAP", tagged_words[2]["tags"]) + self.assertIn("CAP", tagged_words[4]["tags"]) def test_subordinating_conjunctions(self): text = "When the doorbell rang, my dog Skeeter barked loudly." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SCONJ', tagged_words[0]['tags']) + self.assertIn("SCONJ", tagged_words[0]["tags"]) def test_coordinating_conjunctions(self): text = "I wanted to go swimming, but it started raining." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('CCONJ', tagged_words[6]['tags']) + self.assertIn("CCONJ", tagged_words[6]["tags"]) def test_determiners(self): text = "Please pass me the book on the table." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('DET', tagged_words[3]['tags']) - self.assertIn('DET', tagged_words[6]['tags']) + self.assertIn("DET", tagged_words[3]["tags"]) + self.assertIn("DET", tagged_words[6]["tags"]) def test_emoji(self): text = "I'm so excited for the party tonight! 🎉" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('EMOJ', tagged_words[-1]['tags']) + self.assertIn("EMOJ", tagged_words[-1]["tags"]) def test_emoticon(self): text = "She received good news today! :)" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('EMOT', tagged_words[-1]['tags']) + self.assertIn("EMOT", tagged_words[-1]["tags"]) def test_exclamation_mark(self): text = "What a beautiful sunset!" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('EXCL', tagged_words[-1]['tags']) + self.assertIn("EXCL", tagged_words[-1]["tags"]) def test_hashtag(self): text = "I love going hiking in the #mountains." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('HASH', tagged_words[6]['tags']) + self.assertIn("HASH", tagged_words[6]["tags"]) def test_infinitives(self): text = "She plans to travel around the world." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('INF', tagged_words[3]['tags']) + self.assertIn("INF", tagged_words[3]["tags"]) def test_interjections(self): text = "Wow, that was an amazing performance!" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('UH', tagged_words[0]['tags']) + self.assertIn("UH", tagged_words[0]["tags"]) def test_interjections_end(self): text = "That's a great idea, right?" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('UH', tagged_words[-2]['tags']) + self.assertIn("UH", tagged_words[-2]["tags"]) def test_numerals(self): text = "I have three dogs and two cats." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('NUM', tagged_words[2]['tags']) - self.assertIn('NUM', tagged_words[5]['tags']) + self.assertIn("NUM", tagged_words[2]["tags"]) + self.assertIn("NUM", tagged_words[5]["tags"]) def test_laughter_acronyms(self): text = "His joke was so funny, LOL!" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('LAUGH', tagged_words[-2]['tags']) + self.assertIn("LAUGH", tagged_words[-2]["tags"]) def test_interjections_middle(self): text = "I mean gosh, are you really sure about that?" tagged_words = tag_text(text, pipeline=self.pipeline) print(tagged_words) - self.assertIn('UH', tagged_words[2]['tags']) + self.assertIn("UH", tagged_words[2]["tags"]) def test_interjections_end(self): text = "That's a bad idea, gosh" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('UH', tagged_words[-1]['tags']) + self.assertIn("UH", tagged_words[-1]["tags"]) def test_possessive_pronoun(self): text = "That car is hers, not mine." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PRP', tagged_words[3]['tags']) + self.assertIn("PRP", tagged_words[3]["tags"]) def test_preposition(self): text = "He walked through the park to get to the other side." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PREP', tagged_words[2]['tags']) + self.assertIn("PREP", tagged_words[2]["tags"]) def test_proper_noun(self): text = "John went to visit the Statue of Liberty in New York City." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('NNP', tagged_words[0]['tags']) + self.assertIn("NNP", tagged_words[0]["tags"]) def test_question_mark(self): text = "This sentence ends in a question mark?" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('QUES', tagged_words[-1]['tags']) + self.assertIn("QUES", tagged_words[-1]["tags"]) def test_quotation_mark(self): text = '"Life is either a daring adventure or nothing at all." - Helen Keller' tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('QUOT', tagged_words[0]['tags']) - self.assertIn('QUOT', tagged_words[12]['tags']) + self.assertIn("QUOT", tagged_words[0]["tags"]) + self.assertIn("QUOT", tagged_words[12]["tags"]) def test_at(self): text = "Please email me your report @johnsmith." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('AT', tagged_words[-2]['tags']) + self.assertIn("AT", tagged_words[-2]["tags"]) def test_subject_pronouns(self): text = "She is going to the store." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SBJP', tagged_words[0]['tags']) + self.assertIn("SBJP", tagged_words[0]["tags"]) def test_subject_pronouns_end(self): text = "The person going to the store is she" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SBJP', tagged_words[-1]['tags']) + self.assertIn("SBJP", tagged_words[-1]["tags"]) def test_url(self): text = "Check out the latest news on our website: www.example.com." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('URL', tagged_words[-2]['tags']) + self.assertIn("URL", tagged_words[-2]["tags"]) def test_url_org(self): text = "Check out the latest news on our website: https://website.org" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('URL', tagged_words[-1]['tags']) + self.assertIn("URL", tagged_words[-1]["tags"]) def test_wh_word(self): text = "Who is coming to the party tonight?" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('WH', tagged_words[0]['tags']) + self.assertIn("WH", tagged_words[0]["tags"]) def test_indefinite_article(self): text = "I saw a bird flying in the sky." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('INDA', tagged_words[2]['tags']) + self.assertIn("INDA", tagged_words[2]["tags"]) def test_accusative_case(self): text = "He helped me carry the heavy boxes." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('ACCU', tagged_words[2]['tags']) + self.assertIn("ACCU", tagged_words[2]["tags"]) def test_progressive_aspect(self): text = "They are studying for their exams right now." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PGAS', tagged_words[2]['tags']) + self.assertIn("PGAS", tagged_words[2]["tags"]) def test_progressive_aspect_end(self): text = "Despite the noise, they kept studying." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PGAS', tagged_words[-2]['tags']) + self.assertIn("PGAS", tagged_words[-2]["tags"]) def test_comparative(self): text = "She is taller than her sister." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('CMADJ', tagged_words[2]['tags']) + self.assertIn("CMADJ", tagged_words[2]["tags"]) def test_superlative(self): text = "This is the best pizza I've ever had." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SPADJ', tagged_words[3]['tags']) + self.assertIn("SPADJ", tagged_words[3]["tags"]) def test_non_pos(self): text = "Check out the latest news on our website: www.example.com." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('X', tagged_words[-2]['tags']) + self.assertIn("X", tagged_words[-2]["tags"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_coordination.py b/tests/tagger/test_coordination.py index d5b4af3..be2c008 100644 --- a/tests/tagger/test_coordination.py +++ b/tests/tagger/test_coordination.py @@ -6,88 +6,92 @@ class TestCoordinationFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_phc(self): - text = "I dont think it was as good as Suicide Notes and Butterfly Kisses . " \ - "They should try to be as" + text = ( + "I dont think it was as good as Suicide Notes and Butterfly Kisses . " + "They should try to be as" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # And should be tagged as a PHC - self.assertIn('PHC', tagged_words[11]['tags']) + self.assertIn("PHC", tagged_words[11]["tags"]) def test_pred_direct_case(self): text = "The cake is delicious in every way." tagged_words = tag_text(text, pipeline=self.pipeline) # 'delicious' should be tagged as a PRED - self.assertIn('PRED', tagged_words[3]['tags']) + self.assertIn("PRED", tagged_words[3]["tags"]) def test_pred_phrasal_coordinator(self): text = "The car is fast and reliable for long trips." tagged_words = tag_text(text, pipeline=self.pipeline) # 'reliable' should be tagged as a PRED - self.assertIn('PRED', tagged_words[5]['tags']) + self.assertIn("PRED", tagged_words[5]["tags"]) def test_andc(self): text = "to write a book that has already been written , and she fails in comparison . Spend your money on" tagged_words = tag_text(text, pipeline=self.pipeline) # And should be tagged as a ANDC - self.assertIn('ANDC', tagged_words[10]['tags']) + self.assertIn("ANDC", tagged_words[10]["tags"]) def test_andc_preceded_by_punctuation(self): text = "She finished her chores; and she went to bed." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as ANDC since it's preceded by a semicolon - self.assertIn('ANDC', tagged_words[5]['tags']) + self.assertIn("ANDC", tagged_words[5]["tags"]) def test_andc_followed_by_wh_word(self): text = "They asked him about his preferences and why he made that choice." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as ANDC since it's followed by 'why' which is a WH word - self.assertIn('ANDC', tagged_words[6]['tags']) + self.assertIn("ANDC", tagged_words[6]["tags"]) def test_phc_adjectives(self): text = "The car was fast and reliable." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as PHC since it coordinates two adjectives - self.assertIn('PHC', tagged_words[4]['tags']) + self.assertIn("PHC", tagged_words[4]["tags"]) def test_phc_nouns(self): text = "We bought books and magazines." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as PHC since it coordinates two nouns - self.assertIn('PHC', tagged_words[3]['tags']) + self.assertIn("PHC", tagged_words[3]["tags"]) def test_phc_adverbs(self): text = "She spoke quickly and clearly." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as PHC since it coordinates two adverbs - self.assertIn('PHC', tagged_words[3]['tags']) + self.assertIn("PHC", tagged_words[3]["tags"]) def test_phc_verbs(self): text = "He runs and jumps every day." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as PHC since it coordinates two verbs - self.assertIn('PHC', tagged_words[2]['tags']) + self.assertIn("PHC", tagged_words[2]["tags"]) def test_andc_with_so(self): text = "I finished work, and so I went home." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as ANDC since it's followed by 'so' - self.assertIn('ANDC', tagged_words[4]['tags']) + self.assertIn("ANDC", tagged_words[4]["tags"]) def test_andc_with_then(self): text = "He opened the door, and then walked in." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as ANDC since it's followed by 'then' - self.assertIn('ANDC', tagged_words[5]['tags']) + self.assertIn("ANDC", tagged_words[5]["tags"]) def test_andc_with_demonstrative(self): text = "The car broke down, and that caused problems." tagged_words = tag_text(text, pipeline=self.pipeline) # 'and' should be tagged as ANDC since it's followed by demonstrative 'that' - self.assertIn('ANDC', tagged_words[5]['tags']) + self.assertIn("ANDC", tagged_words[5]["tags"]) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_lexical_classes.py b/tests/tagger/test_lexical_classes.py index c8cfbfa..d318bd4 100644 --- a/tests/tagger/test_lexical_classes.py +++ b/tests/tagger/test_lexical_classes.py @@ -6,167 +6,172 @@ class TestLexicalClassesFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_conj(self): text = "tips are a great feature . the wires are slick instead of the iPod 's slightly grippy wires , which" tagged_words = tag_text(text, pipeline=self.pipeline) # Instead should be tagged as CONJ - self.assertIn('CONJ', tagged_words[10]['tags']) + self.assertIn("CONJ", tagged_words[10]["tags"]) def test_conj_altogether(self): text = "The event was well organized . Altogether, it was a success." tagged_words = tag_text(text, pipeline=self.pipeline) # Altogether should be tagged as CONJ when it is preceded by punctuation - self.assertIn('CONJ', tagged_words[6]['tags']) + self.assertIn("CONJ", tagged_words[6]["tags"]) def test_conj_rather(self): text = "The movie was okay . Rather, it exceeded my expectations." tagged_words = tag_text(text, pipeline=self.pipeline) # Rather should be tagged as CONJ when it is preceded by punctuation - self.assertIn('CONJ', tagged_words[5]['tags']) + self.assertIn("CONJ", tagged_words[5]["tags"]) def test_dwnt(self): - text = 'a tangent point , and at such a point can only change by an even integer . Thus the multiplicity' + text = "a tangent point , and at such a point can only change by an even integer . Thus the multiplicity" tagged_words = tag_text(text, pipeline=self.pipeline) # Only should be tagged as a DWNT - self.assertIn('DWNT', tagged_words[10]['tags']) + self.assertIn("DWNT", tagged_words[10]["tags"]) def test_hdg(self): - text = 'that blow to be borderline . To kayo him and maybe or maybe not kill . You hit again about' + text = "that blow to be borderline . To kayo him and maybe or maybe not kill . You hit again about" tagged_words = tag_text(text, pipeline=self.pipeline) # Maybe should be tagged as a HDG - self.assertIn('HDG', tagged_words[10]['tags']) + self.assertIn("HDG", tagged_words[10]["tags"]) def test_hdg_single_word(self): - text = 'I think maybe he went to the park.' + text = "I think maybe he went to the park." tagged_words = tag_text(text, pipeline=self.pipeline) # Maybe should be tagged as HDG - self.assertIn('HDG', tagged_words[2]['tags']) + self.assertIn("HDG", tagged_words[2]["tags"]) def test_hdg_two_word(self): - text = 'It was at about 5pm yesterday' + text = "It was at about 5pm yesterday" tagged_words = tag_text(text, pipeline=self.pipeline) # Kind should be tagged as HDG in the phrase "kind of" - self.assertIn('HDG', tagged_words[2]['tags']) + self.assertIn("HDG", tagged_words[2]["tags"]) def test_three_word_hdg(self): - text = 'that blow to be borderline . To kayo him and more or less or maybe not kill . You hit again about' + text = "that blow to be borderline . To kayo him and more or less or maybe not kill . You hit again about" tagged_words = tag_text(text, pipeline=self.pipeline) # More should be tagged as a HDG - self.assertIn('HDG', tagged_words[10]['tags']) + self.assertIn("HDG", tagged_words[10]["tags"]) def test_amp(self): - text = 'lie around on the rug during the meal , a very pretty sight as Rob Roy , ' + text = ( + "lie around on the rug during the meal , a very pretty sight as Rob Roy , " + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Very should be tagged as an AMP - self.assertIn('AMP', tagged_words[10]['tags']) + self.assertIn("AMP", tagged_words[10]["tags"]) def test_emph(self): - text = 'not be subjected to such a risk , or that such a possibility should not be permitted to endanger the' + text = "not be subjected to such a risk , or that such a possibility should not be permitted to endanger the" tagged_words = tag_text(text, pipeline=self.pipeline) # Such should be tagged as a EMPH - self.assertIn('EMPH', tagged_words[10]['tags']) + self.assertIn("EMPH", tagged_words[10]["tags"]) def test_emph_does(self): - text = 'He really does like the cake.' + text = "He really does like the cake." tagged_words = tag_text(text, pipeline=self.pipeline) # Does should be tagged as a EMPH - self.assertIn('EMPH', tagged_words[2]['tags']) + self.assertIn("EMPH", tagged_words[2]["tags"]) def test_emph_so(self): - text = 'She is so happy about the surprise.' + text = "She is so happy about the surprise." tagged_words = tag_text(text, pipeline=self.pipeline) # So should be tagged as a EMPH - self.assertIn('EMPH', tagged_words[2]['tags']) + self.assertIn("EMPH", tagged_words[2]["tags"]) def test_two_word_emph(self): - text = 'not be subjected to such a risk , or that for sure a possibility should not be permitted to endanger the' + text = "not be subjected to such a risk , or that for sure a possibility should not be permitted to endanger the" tagged_words = tag_text(text, pipeline=self.pipeline) # For should be tagged as a EMPH - self.assertIn('EMPH', tagged_words[10]['tags']) + self.assertIn("EMPH", tagged_words[10]["tags"]) def test_dpar(self): text = "were and because we all wanted to be thin . Now , as a woman in her middle 30 's" tagged_words = tag_text(text, pipeline=self.pipeline) # Now should be tagged as a DPAR - self.assertIn('DPAR', tagged_words[10]['tags']) + self.assertIn("DPAR", tagged_words[10]["tags"]) def test_demo(self): text = "a little bigger than i expected . I just purchased this item and I have not found anywhere on the" tagged_words = tag_text(text, pipeline=self.pipeline) # This should be tagged as DEMO - self.assertIn('DEMO', tagged_words[10]['tags']) + self.assertIn("DEMO", tagged_words[10]["tags"]) def test_demo_this(self): text = "a little bigger than i expected . I just purchased this item and I have not found anywhere on the" tagged_words = tag_text(text, pipeline=self.pipeline) # This should be tagged as DEMO - self.assertIn('DEMO', tagged_words[10]['tags']) + self.assertIn("DEMO", tagged_words[10]["tags"]) def test_demo_these(self): - text = "Those shoes look great. However, these ones seem to be more comfortable." + text = ( + "Those shoes look great. However, these ones seem to be more comfortable." + ) tagged_words = tag_text(text, pipeline=self.pipeline) # These should be tagged as DEMO - self.assertIn('DEMO', tagged_words[7]['tags']) + self.assertIn("DEMO", tagged_words[7]["tags"]) def test_perfect_aspect_with_adverb(self): text = "I have really enjoyed the movie." tagged_words = tag_text(text, pipeline=self.pipeline) # Have enjoyed should be tagged as PEAS even with intervening adverb - self.assertIn('PEAS', tagged_words[1]['tags']) + self.assertIn("PEAS", tagged_words[1]["tags"]) def test_perfect_aspect_interrogative(self): text = "Have you seen the movie?" tagged_words = tag_text(text, pipeline=self.pipeline) # Have seen should be tagged as PEAS in questions - self.assertIn('PEAS', tagged_words[0]['tags']) + self.assertIn("PEAS", tagged_words[0]["tags"]) def test_demonstrative_pronoun_with_verb(self): text = "This works well." tagged_words = tag_text(text, pipeline=self.pipeline) # This should be tagged as DEMP when followed by verb - self.assertIn('DEMP', tagged_words[0]['tags']) + self.assertIn("DEMP", tagged_words[0]["tags"]) def test_demonstrative_pronoun_with_aux(self): text = "Those will arrive tomorrow." tagged_words = tag_text(text, pipeline=self.pipeline) # Those should be tagged as DEMP when followed by auxiliary - self.assertIn('DEMP', tagged_words[0]['tags']) + self.assertIn("DEMP", tagged_words[0]["tags"]) def test_that_as_demonstrative(self): text = "That is interesting." tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as DEMP when followed by is - self.assertIn('DEMP', tagged_words[0]['tags']) + self.assertIn("DEMP", tagged_words[0]["tags"]) def test_nomz_singular(self): text = "The administration made a decision." tagged_words = tag_text(text, pipeline=self.pipeline) # Words ending in -tion should be tagged as NOMZ - self.assertIn('NOMZ', tagged_words[1]['tags']) - self.assertIn('NOMZ', tagged_words[4]['tags']) + self.assertIn("NOMZ", tagged_words[1]["tags"]) + self.assertIn("NOMZ", tagged_words[4]["tags"]) def test_nomz_plural(self): text = "Many suggestions were made." tagged_words = tag_text(text, pipeline=self.pipeline) # Plural forms of -tion words should be tagged as NOMZ - self.assertIn('NOMZ', tagged_words[1]['tags']) + self.assertIn("NOMZ", tagged_words[1]["tags"]) def test_gerund_long_word(self): text = "Understanding complex concepts takes time." tagged_words = tag_text(text, pipeline=self.pipeline) # Words >10 chars ending in -ing should be tagged as GER - self.assertIn('GER', tagged_words[0]['tags']) + self.assertIn("GER", tagged_words[0]["tags"]) def test_gerund_short_word(self): text = "Running is good exercise." tagged_words = tag_text(text, pipeline=self.pipeline) # Words <=10 chars ending in -ing should not be tagged as GER - self.assertNotIn('GER', tagged_words[0]['tags']) + self.assertNotIn("GER", tagged_words[0]["tags"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_lexical_specificity.py b/tests/tagger/test_lexical_specificity.py index c1c7588..e79821c 100644 --- a/tests/tagger/test_lexical_specificity.py +++ b/tests/tagger/test_lexical_specificity.py @@ -8,14 +8,13 @@ class TestLexicalSpecificityFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_calculate_mean_word_length(self): - sample_data = { - 'text': ["apple", "banana", "cherry", "date", "fig"] - } + sample_data = {"text": ["apple", "banana", "cherry", "date", "fig"]} df = pd.DataFrame(sample_data) result = calculate_mean_word_length(df) @@ -23,7 +22,16 @@ def test_calculate_mean_word_length(self): def test_calculate_type_token_ratio_without_limit(self): sample_data = { - 'text': ["apple", "apple", "banana", "banana", "cherry", "date", "date", "fig"] + "text": [ + "apple", + "apple", + "banana", + "banana", + "cherry", + "date", + "date", + "fig", + ] } df = pd.DataFrame(sample_data) @@ -32,7 +40,16 @@ def test_calculate_type_token_ratio_without_limit(self): def test_calculate_type_token_ratio_with_limit(self): sample_data = { - 'text': ["apple", "apple", "banana", "banana", "cherry", "date", "date", "fig"] + "text": [ + "apple", + "apple", + "banana", + "banana", + "cherry", + "date", + "date", + "fig", + ] } df = pd.DataFrame(sample_data) @@ -41,8 +58,8 @@ def test_calculate_type_token_ratio_with_limit(self): def test_calculate_total_adverbs(self): sample_data = { - 'text': ["quickly", "runs", "slowly", "jumps"], - 'upos': ["ADV", "VERB", "ADV", "VERB"] + "text": ["quickly", "runs", "slowly", "jumps"], + "upos": ["ADV", "VERB", "ADV", "VERB"], } df = pd.DataFrame(sample_data) @@ -54,5 +71,6 @@ def test_calculate_total_adverbs(self): def test_calculate_tag_frequencies(self): pass -if __name__ == '__main__': - unittest.main() \ No newline at end of file + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tagger/test_modals.py b/tests/tagger/test_modals.py index 45df190..25a9ff4 100644 --- a/tests/tagger/test_modals.py +++ b/tests/tagger/test_modals.py @@ -6,70 +6,73 @@ class TestModalsFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_pomd(self): text = ", we 'll have to get organized . The baby can have an early nap . Victoria , I want" tagged_words = tag_text(text, pipeline=self.pipeline) # Can should be tagged as a POMD - self.assertIn('POMD', tagged_words[10]['tags']) + self.assertIn("POMD", tagged_words[10]["tags"]) def test_nemd(self): - text = "social values , it is clear that the educational profession must work for the values which " \ - "are characteristic of the" + text = ( + "social values , it is clear that the educational profession must work for the values which " + "are characteristic of the" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Must should be tagged as a NEMD - self.assertIn('NEMD', tagged_words[10]['tags']) + self.assertIn("NEMD", tagged_words[10]["tags"]) def test_prmd(self): text = "holds for values as well as life styles . One would need to test this proposition carefully ; after all" tagged_words = tag_text(text, pipeline=self.pipeline) # Would should be tagged as a PRMD - self.assertIn('PRMD', tagged_words[10]['tags']) + self.assertIn("PRMD", tagged_words[10]["tags"]) def test_multiple_pomd(self): text = "You can go now. She may come later. We might need help. They could arrive soon." tagged_words = tag_text(text, pipeline=self.pipeline) # Test each possibility modal - self.assertIn('POMD', tagged_words[1]['tags']) # can - self.assertIn('POMD', tagged_words[6]['tags']) # may - self.assertIn('POMD', tagged_words[11]['tags']) # might - self.assertIn('POMD', tagged_words[16]['tags']) # could + self.assertIn("POMD", tagged_words[1]["tags"]) # can + self.assertIn("POMD", tagged_words[6]["tags"]) # may + self.assertIn("POMD", tagged_words[11]["tags"]) # might + self.assertIn("POMD", tagged_words[16]["tags"]) # could def test_multiple_nemd(self): text = "You must leave now. She should study more. We ought to help." tagged_words = tag_text(text, pipeline=self.pipeline) # Test each necessity modal - self.assertIn('NEMD', tagged_words[1]['tags']) # must - self.assertIn('NEMD', tagged_words[6]['tags']) # should - self.assertIn('NEMD', tagged_words[11]['tags']) # ought + self.assertIn("NEMD", tagged_words[1]["tags"]) # must + self.assertIn("NEMD", tagged_words[6]["tags"]) # should + self.assertIn("NEMD", tagged_words[11]["tags"]) # ought def test_multiple_prmd(self): text = "I will help. You would agree. He shall proceed. We'll continue. They'd come." tagged_words = tag_text(text, pipeline=self.pipeline) # Test each predictive modal including contractions - self.assertIn('PRMD', tagged_words[1]['tags']) # will - self.assertIn('PRMD', tagged_words[5]['tags']) # would - self.assertIn('PRMD', tagged_words[9]['tags']) # shall - self.assertIn('PRMD', tagged_words[13]['tags']) # 'll - self.assertIn('PRMD', tagged_words[17]['tags']) # 'd + self.assertIn("PRMD", tagged_words[1]["tags"]) # will + self.assertIn("PRMD", tagged_words[5]["tags"]) # would + self.assertIn("PRMD", tagged_words[9]["tags"]) # shall + self.assertIn("PRMD", tagged_words[13]["tags"]) # 'll + self.assertIn("PRMD", tagged_words[17]["tags"]) # 'd def test_modal_negation(self): text = "You cannot go. She won't help. They shouldn't leave." tagged_words = tag_text(text, pipeline=self.pipeline) # Modals should still be tagged when negated - self.assertIn('POMD', tagged_words[1]['tags']) # cannot + self.assertIn("POMD", tagged_words[1]["tags"]) # cannot def test_modal_questions(self): text = "Can you help? Would they agree? Must we leave?" tagged_words = tag_text(text, pipeline=self.pipeline) # Modals should be tagged in questions - self.assertIn('POMD', tagged_words[0]['tags']) # Can - self.assertIn('PRMD', tagged_words[4]['tags']) # Would - self.assertIn('NEMD', tagged_words[8]['tags']) # Must + self.assertIn("POMD", tagged_words[0]["tags"]) # Can + self.assertIn("PRMD", tagged_words[4]["tags"]) # Would + self.assertIn("NEMD", tagged_words[8]["tags"]) # Must -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_negations.py b/tests/tagger/test_negations.py index 0276c9e..dac838b 100644 --- a/tests/tagger/test_negations.py +++ b/tests/tagger/test_negations.py @@ -6,71 +6,74 @@ class TestNegationFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_syne(self): - text = "I did n't even want to give it away . No Beatrix Potter appreciator should " \ - "be exposed to this ;" + text = ( + "I did n't even want to give it away . No Beatrix Potter appreciator should " + "be exposed to this ;" + ) tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SYNE', tagged_words[10]['tags']) + self.assertIn("SYNE", tagged_words[10]["tags"]) def test_syne_neith(self): text = "I have neither skill nor patience for this task." tagged_words = tag_text(text, pipeline=self.pipeline) # Neither should be tagged as SYNE before skill - self.assertIn('SYNE', tagged_words[2]['tags']) + self.assertIn("SYNE", tagged_words[2]["tags"]) def test_syne_no(self): text = "No good deed goes unpunished." tagged_words = tag_text(text, pipeline=self.pipeline) # No should be tagged as SYNE before good - self.assertIn('SYNE', tagged_words[0]['tags']) + self.assertIn("SYNE", tagged_words[0]["tags"]) def test_xx0(self): - text = ', and General Motors on the other . It is not a medieval mental quirk or an attitude `` unnourished' + text = ", and General Motors on the other . It is not a medieval mental quirk or an attitude `` unnourished" tagged_words = tag_text(text, pipeline=self.pipeline) # Not should be tagged as a XXO - self.assertIn('XX0', tagged_words[10]['tags']) + self.assertIn("XX0", tagged_words[10]["tags"]) def test_syne_neither(self): text = "Neither option was acceptable." tagged_words = tag_text(text, pipeline=self.pipeline) # Neither should be tagged as SYNE - self.assertIn('SYNE', tagged_words[0]['tags']) + self.assertIn("SYNE", tagged_words[0]["tags"]) def test_syne_nor(self): text = "The food was neither hot nor cold." tagged_words = tag_text(text, pipeline=self.pipeline) # Both neither and nor should be tagged as SYNE - self.assertIn('SYNE', tagged_words[3]['tags']) - self.assertIn('SYNE', tagged_words[5]['tags']) + self.assertIn("SYNE", tagged_words[3]["tags"]) + self.assertIn("SYNE", tagged_words[5]["tags"]) def test_syne_no_with_adjective_noun(self): text = "There were no happy children at the party." tagged_words = tag_text(text, pipeline=self.pipeline) # No should be tagged as SYNE when followed by adj+noun - self.assertIn('SYNE', tagged_words[2]['tags']) + self.assertIn("SYNE", tagged_words[2]["tags"]) def test_xx0_not(self): text = "The movie was not interesting." tagged_words = tag_text(text, pipeline=self.pipeline) # Not should be tagged as XX0 - self.assertIn('XX0', tagged_words[3]['tags']) + self.assertIn("XX0", tagged_words[3]["tags"]) def test_xx0_contraction(self): text = "I don't like this movie." tagged_words = tag_text(text, pipeline=self.pipeline) # n't should be tagged as XX0 - self.assertIn('XX0', tagged_words[2]['tags']) + self.assertIn("XX0", tagged_words[2]["tags"]) def test_xx0_with_adverb(self): text = "The movie was definitely not interesting." tagged_words = tag_text(text, pipeline=self.pipeline) # Not should be tagged as XX0 even with intervening adverb - self.assertIn('XX0', tagged_words[4]['tags']) + self.assertIn("XX0", tagged_words[4]["tags"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_nominal_forms.py b/tests/tagger/test_nominal_forms.py index 62736a3..91ec467 100644 --- a/tests/tagger/test_nominal_forms.py +++ b/tests/tagger/test_nominal_forms.py @@ -6,71 +6,77 @@ class TestNominalFormFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_nomz_basic(self): - text = 'consular materials to reveal the motives which led the British government to permit Garibaldi to cross ' \ - 'the Straits of Messina' + text = ( + "consular materials to reveal the motives which led the British government to permit Garibaldi to cross " + "the Straits of Messina" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Government should be tagged as a NOMZ - self.assertIn('NOMZ', tagged_words[10]['tags']) + self.assertIn("NOMZ", tagged_words[10]["tags"]) def test_ger(self): - text = "His voice carries the album well even with some subpar songwriting . I do n't know where " \ - "people are getting" + text = ( + "His voice carries the album well even with some subpar songwriting . I do n't know where " + "people are getting" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Songwriting should be tagged as a GER - self.assertIn('GER', tagged_words[10]['tags']) + self.assertIn("GER", tagged_words[10]["tags"]) def test_nomz_tion(self): text = "The administration's decision led to much frustration in the population" tagged_words = tag_text(text, pipeline=self.pipeline) # Test multiple -tion nominalizations - self.assertIn('NOMZ', tagged_words[1]['tags']) # administration - self.assertIn('NOMZ', tagged_words[3]['tags']) # decision - self.assertIn('NOMZ', tagged_words[7]['tags']) # frustration - self.assertIn('NOMZ', tagged_words[10]['tags']) # population + self.assertIn("NOMZ", tagged_words[1]["tags"]) # administration + self.assertIn("NOMZ", tagged_words[3]["tags"]) # decision + self.assertIn("NOMZ", tagged_words[7]["tags"]) # frustration + self.assertIn("NOMZ", tagged_words[10]["tags"]) # population def test_nomz_ment(self): text = "The government's assessment of the development and management situation" tagged_words = tag_text(text, pipeline=self.pipeline) # Test -ment nominalizations - self.assertIn('NOMZ', tagged_words[3]['tags']) # assessment - self.assertIn('NOMZ', tagged_words[6]['tags']) # development - self.assertIn('NOMZ', tagged_words[8]['tags']) # management + self.assertIn("NOMZ", tagged_words[3]["tags"]) # assessment + self.assertIn("NOMZ", tagged_words[6]["tags"]) # development + self.assertIn("NOMZ", tagged_words[8]["tags"]) # management def test_nomz_ness(self): text = "The darkness and loneliness led to great sadness" tagged_words = tag_text(text, pipeline=self.pipeline) # Test -ness nominalizations - self.assertIn('NOMZ', tagged_words[1]['tags']) # darkness - self.assertIn('NOMZ', tagged_words[3]['tags']) # loneliness - self.assertIn('NOMZ', tagged_words[7]['tags']) # sadness + self.assertIn("NOMZ", tagged_words[1]["tags"]) # darkness + self.assertIn("NOMZ", tagged_words[3]["tags"]) # loneliness + self.assertIn("NOMZ", tagged_words[7]["tags"]) # sadness def test_nomz_ity(self): text = "The possibility of activity depends on ability and creativity" tagged_words = tag_text(text, pipeline=self.pipeline) # Test -ity nominalizations - self.assertIn('NOMZ', tagged_words[1]['tags']) # possibility - self.assertIn('NOMZ', tagged_words[3]['tags']) # activity - self.assertIn('NOMZ', tagged_words[6]['tags']) # ability - self.assertIn('NOMZ', tagged_words[8]['tags']) # creativity + self.assertIn("NOMZ", tagged_words[1]["tags"]) # possibility + self.assertIn("NOMZ", tagged_words[3]["tags"]) # activity + self.assertIn("NOMZ", tagged_words[6]["tags"]) # ability + self.assertIn("NOMZ", tagged_words[8]["tags"]) # creativity def test_ger_complex(self): text = "Understanding and implementing these changes requires careful planning" tagged_words = tag_text(text, pipeline=self.pipeline) # Test multiple gerunds (words > 10 chars) - self.assertIn('GER', tagged_words[0]['tags']) # understanding - self.assertIn('GER', tagged_words[2]['tags']) # implementing + self.assertIn("GER", tagged_words[0]["tags"]) # understanding + self.assertIn("GER", tagged_words[2]["tags"]) # implementing def test_ger_short_words(self): text = "The running and jumping exercises were too demanding" tagged_words = tag_text(text, pipeline=self.pipeline) # Short words (< 10 chars) should not be tagged as gerunds - self.assertNotIn('GER', tagged_words[1]['tags']) # running - self.assertNotIn('GER', tagged_words[3]['tags']) # jumping + self.assertNotIn("GER", tagged_words[1]["tags"]) # running + self.assertNotIn("GER", tagged_words[3]["tags"]) # jumping + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_passives.py b/tests/tagger/test_passives.py index 26987b6..e2e5f44 100644 --- a/tests/tagger/test_passives.py +++ b/tests/tagger/test_passives.py @@ -7,50 +7,52 @@ class TestPassivesFunctions(unittest.TestCase): def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_pass(self): # Test basic BE + VBN pattern - text1 = 'The document was signed yesterday.' + text1 = "The document was signed yesterday." tagged_words1 = tag_text(text1, pipeline=self.pipeline) - self.assertIn('PASS', tagged_words1[2]['tags']) + self.assertIn("PASS", tagged_words1[2]["tags"]) # Test BE + ADV + VBN pattern - text2 = 'The cake was quickly eaten.' + text2 = "The cake was quickly eaten." tagged_words2 = tag_text(text2, pipeline=self.pipeline) - self.assertIn('PASS', tagged_words2[2]['tags']) + self.assertIn("PASS", tagged_words2[2]["tags"]) # Test BE + ADV + ADV + VBN pattern - text3 = 'The message was very carefully written.' + text3 = "The message was very carefully written." tagged_words3 = tag_text(text3, pipeline=self.pipeline) - self.assertIn('PASS', tagged_words3[2]['tags']) + self.assertIn("PASS", tagged_words3[2]["tags"]) # Test BE + N/PRO + VBN pattern - text4 = 'The house is itself designed.' + text4 = "The house is itself designed." tagged_words4 = tag_text(text4, pipeline=self.pipeline) - self.assertIn('PASS', tagged_words4[2]['tags']) + self.assertIn("PASS", tagged_words4[2]["tags"]) def test_bypa(self): # Test basic by-passive - text1 = 'The letter was written by John.' + text1 = "The letter was written by John." tagged_words1 = tag_text(text1, pipeline=self.pipeline) - self.assertIn('BYPA', tagged_words1[2]['tags']) + self.assertIn("BYPA", tagged_words1[2]["tags"]) # Test by-passive with intervening words - text2 = 'The book was carefully reviewed by the committee.' + text2 = "The book was carefully reviewed by the committee." tagged_words2 = tag_text(text2, pipeline=self.pipeline) - self.assertIn('BYPA', tagged_words2[2]['tags']) + self.assertIn("BYPA", tagged_words2[2]["tags"]) # Test by-passive with multiple intervening words - text3 = 'The building was very thoroughly inspected by the experts.' + text3 = "The building was very thoroughly inspected by the experts." tagged_words3 = tag_text(text3, pipeline=self.pipeline) - self.assertIn('BYPA', tagged_words3[2]['tags']) + self.assertIn("BYPA", tagged_words3[2]["tags"]) # Test by-passive with nominal form - text4 = 'The decision is itself influenced by recent events.' + text4 = "The decision is itself influenced by recent events." tagged_words4 = tag_text(text4, pipeline=self.pipeline) - self.assertIn('BYPA', tagged_words4[2]['tags']) + self.assertIn("BYPA", tagged_words4[2]["tags"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_place_time_adverbials.py b/tests/tagger/test_place_time_adverbials.py index 9b7f14c..4930c35 100644 --- a/tests/tagger/test_place_time_adverbials.py +++ b/tests/tagger/test_place_time_adverbials.py @@ -6,110 +6,110 @@ class TestPlaceTimeAdverbialFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_place_adverbials(self): # Test basic place adverbials - text1 = 'The ship sailed abroad on its maiden voyage.' + text1 = "The ship sailed abroad on its maiden voyage." tagged_words1 = tag_text(text1, pipeline=self.pipeline) - self.assertIn('PLACE', tagged_words1[3]['tags']) + self.assertIn("PLACE", tagged_words1[3]["tags"]) # Test directional adverbials - text2 = 'The hikers went upstream to find the source.' + text2 = "The hikers went upstream to find the source." tagged_words2 = tag_text(text2, pipeline=self.pipeline) - self.assertIn('PLACE', tagged_words2[3]['tags']) + self.assertIn("PLACE", tagged_words2[3]["tags"]) # Test indoor/outdoor locations - text3 = 'The children played outdoors while it was sunny.' + text3 = "The children played outdoors while it was sunny." tagged_words3 = tag_text(text3, pipeline=self.pipeline) - self.assertIn('PLACE', tagged_words3[3]['tags']) + self.assertIn("PLACE", tagged_words3[3]["tags"]) # Test cardinal directions - text4 = 'Birds fly south for the winter.' + text4 = "Birds fly south for the winter." tagged_words4 = tag_text(text4, pipeline=self.pipeline) - self.assertIn('PLACE', tagged_words4[2]['tags']) + self.assertIn("PLACE", tagged_words4[2]["tags"]) # Test relative positions - text5 = 'The cat sat underneath the table.' + text5 = "The cat sat underneath the table." tagged_words5 = tag_text(text5, pipeline=self.pipeline) - self.assertIn('PLACE', tagged_words5[3]['tags']) + self.assertIn("PLACE", tagged_words5[3]["tags"]) def test_place_adverbial_exceptions(self): # Test proper noun exception (e.g., North America should not be tagged) - text = 'They visited North America last summer.' + text = "They visited North America last summer." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertNotIn('PLACE', tagged_words[2]['tags']) + self.assertNotIn("PLACE", tagged_words[2]["tags"]) def test_time_adverbials(self): # Test basic time adverbials - text1 = 'I will see you tomorrow at noon.' + text1 = "I will see you tomorrow at noon." tagged_words1 = tag_text(text1, pipeline=self.pipeline) - self.assertIn('TIME', tagged_words1[4]['tags']) + self.assertIn("TIME", tagged_words1[4]["tags"]) # Test relative time markers - text2 = 'He had previously visited this place.' + text2 = "He had previously visited this place." tagged_words2 = tag_text(text2, pipeline=self.pipeline) - self.assertIn('TIME', tagged_words2[2]['tags']) + self.assertIn("TIME", tagged_words2[2]["tags"]) # Test immediate time markers - text3 = 'The response came instantly.' + text3 = "The response came instantly." tagged_words3 = tag_text(text3, pipeline=self.pipeline) - self.assertIn('TIME', tagged_words3[3]['tags']) + self.assertIn("TIME", tagged_words3[3]["tags"]) # Test sequential time markers - text4 = 'First eat dinner, then do homework, and afterwards watch TV.' + text4 = "First eat dinner, then do homework, and afterwards watch TV." tagged_words4 = tag_text(text4, pipeline=self.pipeline) - self.assertIn('TIME', tagged_words4[9]['tags']) - - + self.assertIn("TIME", tagged_words4[9]["tags"]) def test_time_adverbial_exceptions(self): # Test 'soon as' exception - text = 'As soon as possible, we will start.' + text = "As soon as possible, we will start." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertNotIn('TIME', tagged_words[2]['tags']) + self.assertNotIn("TIME", tagged_words[2]["tags"]) def test_multiple_adverbials(self): # Test combination of place and time adverbials - text = 'We went abroad yesterday to explore.' + text = "We went abroad yesterday to explore." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PLACE', tagged_words[2]['tags']) - self.assertIn('TIME', tagged_words[3]['tags']) + self.assertIn("PLACE", tagged_words[2]["tags"]) + self.assertIn("TIME", tagged_words[3]["tags"]) def test_caus(self): - text = 'they did also fall under the power of death , because they did eat in disobedience ; and disobedience to' + text = "they did also fall under the power of death , because they did eat in disobedience ; and disobedience to" tagged_words = tag_text(text, pipeline=self.pipeline) # Because should be tagged as a CAUS - self.assertIn('CAUS', tagged_words[10]['tags']) + self.assertIn("CAUS", tagged_words[10]["tags"]) def test_conc(self): text = "outsider . When they learn you 're in the hills though , they 'll rally , do n't worry about" tagged_words = tag_text(text, pipeline=self.pipeline) # Though should be tagged as a CONC - self.assertIn('CONC', tagged_words[10]['tags']) + self.assertIn("CONC", tagged_words[10]["tags"]) def test_cond(self): - text = 'so high that the top falls gently over , as if to show that it really is hair and not' + text = "so high that the top falls gently over , as if to show that it really is hair and not" tagged_words = tag_text(text, pipeline=self.pipeline) # If should be tagged as a COND - self.assertIn('COND', tagged_words[10]['tags']) + self.assertIn("COND", tagged_words[10]["tags"]) def test_osub(self): - text = 'his comment on the planter dynasties as they have existed since the decades before the Civil' \ - ' War . It may' + text = ( + "his comment on the planter dynasties as they have existed since the decades before the Civil" + " War . It may" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Since should be tagged as an OSUB - self.assertIn('OSUB', tagged_words[10]['tags']) + self.assertIn("OSUB", tagged_words[10]["tags"]) def test_osub_since(self): - text = 'his comment on the planter dynasties as they have existed since the decades before the Civil War . It may' + text = "his comment on the planter dynasties as they have existed since the decades before the Civil War . It may" tagged_words = tag_text(text, pipeline=self.pipeline) # Since should be tagged as an OSUB - self.assertIn('OSUB', tagged_words[10]['tags']) - + self.assertIn("OSUB", tagged_words[10]["tags"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_prep_adj_adverbs.py b/tests/tagger/test_prep_adj_adverbs.py index 4389274..08f535b 100644 --- a/tests/tagger/test_prep_adj_adverbs.py +++ b/tests/tagger/test_prep_adj_adverbs.py @@ -6,66 +6,66 @@ class TestPrepPhrasesAdjectiveAdverbFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_pin(self): text = "have kept my hair in great condition ! A waste of money that 's all I have to say about" tagged_words = tag_text(text, pipeline=self.pipeline) # Of should be tagged as PIN - self.assertIn('PIN', tagged_words[10]['tags']) + self.assertIn("PIN", tagged_words[10]["tags"]) def test_pred(self): text = "and rambling . Yeah , these guys were profound and impressive when I was in the 8th grade , but" tagged_words = tag_text(text, pipeline=self.pipeline) # Impressive should be tagged as a PRED - self.assertIn('PRED', tagged_words[10]['tags']) + self.assertIn("PRED", tagged_words[10]["tags"]) def test_pred_impressive(self): text = "and rambling . Yeah , these guys were profound and impressive when I was in the 8th grade , but" tagged_words = tag_text(text, pipeline=self.pipeline) # Impressive should be tagged as a PRED - self.assertIn('PRED', tagged_words[10]['tags']) + self.assertIn("PRED", tagged_words[10]["tags"]) def test_pred_magnificent(self): text = "The flowers are beautiful but short-lived. The building is tall and magnificent." tagged_words = tag_text(text, pipeline=self.pipeline) # Beautiful and magnificent should be tagged as a PRED - self.assertIn('PRED', tagged_words[3]['tags']) - self.assertIn('PRED', tagged_words[-2]['tags']) + self.assertIn("PRED", tagged_words[3]["tags"]) + self.assertIn("PRED", tagged_words[-2]["tags"]) def test_pin_multiple(self): text = "The book on the shelf in the corner of the room was dusty" tagged_words = tag_text(text, pipeline=self.pipeline) - + # Check multiple prepositions are tagged as PIN - self.assertIn('PIN', tagged_words[2]['tags']) # on - self.assertIn('PIN', tagged_words[5]['tags']) # in - self.assertIn('PIN', tagged_words[8]['tags']) # of + self.assertIn("PIN", tagged_words[2]["tags"]) # on + self.assertIn("PIN", tagged_words[5]["tags"]) # in + self.assertIn("PIN", tagged_words[8]["tags"]) # of def test_pred_be_forms(self): text = "The flowers are beautiful. The cat was lazy. The students have been diligent." tagged_words = tag_text(text, pipeline=self.pipeline) - - # Test predicative adjectives with different BE forms - self.assertIn('PRED', tagged_words[3]['tags']) # beautiful - self.assertIn('PRED', tagged_words[8]['tags']) # lazy - self.assertIn('PRED', tagged_words[14]['tags']) # diligent + # Test predicative adjectives with different BE forms + self.assertIn("PRED", tagged_words[3]["tags"]) # beautiful + self.assertIn("PRED", tagged_words[8]["tags"]) # lazy + self.assertIn("PRED", tagged_words[14]["tags"]) # diligent def test_rb_adverbs(self): text = "She quickly and efficiently completed the task. He spoke very softly." tagged_words = tag_text(text, pipeline=self.pipeline) - + # Test different types of adverbs - self.assertIn('RB', tagged_words[1]['tags']) # quickly - self.assertIn('RB', tagged_words[3]['tags']) # efficiently - self.assertIn('RB', tagged_words[10]['tags']) # very - self.assertIn('RB', tagged_words[11]['tags']) # softly + self.assertIn("RB", tagged_words[1]["tags"]) # quickly + self.assertIn("RB", tagged_words[3]["tags"]) # efficiently + self.assertIn("RB", tagged_words[10]["tags"]) # very + self.assertIn("RB", tagged_words[11]["tags"]) # softly -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_pronouns_proverbs.py b/tests/tagger/test_pronouns_proverbs.py index f450e49..374f13c 100644 --- a/tests/tagger/test_pronouns_proverbs.py +++ b/tests/tagger/test_pronouns_proverbs.py @@ -6,65 +6,74 @@ class TestPronounProverbFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_fpp1(self): - text = 'the soil soft during these early days of growth . I like sawdust for this , or hay . When' + text = "the soil soft during these early days of growth . I like sawdust for this , or hay . When" tagged_words = tag_text(text, pipeline=self.pipeline) # I should be tagged as a FPP1 - self.assertIn('FPP1', tagged_words[10]['tags']) + self.assertIn("FPP1", tagged_words[10]["tags"]) def test_spp2(self): - text = ". . -RRB- The new interpretation makes sense though if you think about it . " \ - "By the way , he" + text = ( + ". . -RRB- The new interpretation makes sense though if you think about it . " + "By the way , he" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # You should be tagged as a SPP2 - self.assertIn('SPP2', tagged_words[10]['tags']) + self.assertIn("SPP2", tagged_words[10]["tags"]) def test_tpp3(self): - text = 'a child till he was sixteen , a youth till he was five-and-twenty , and a young man till he' + text = "a child till he was sixteen , a youth till he was five-and-twenty , and a young man till he" tagged_words = tag_text(text, pipeline=self.pipeline) # He should be tagged as a TPP3 - self.assertIn('TPP3', tagged_words[10]['tags']) + self.assertIn("TPP3", tagged_words[10]["tags"]) def test_pit(self): - text = 'sometimes answers itself , and that the way in which it is posed frequently shapes ' \ - 'the answer . Chewing it' + text = ( + "sometimes answers itself , and that the way in which it is posed frequently shapes " + "the answer . Chewing it" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # It should be tagged as a PIT - self.assertIn('PIT', tagged_words[10]['tags']) + self.assertIn("PIT", tagged_words[10]["tags"]) def test_demp(self): - text = 'Vernon on the morning of the regular tallyho run . This was an honor , ' \ - 'like dining with a captain' + text = ( + "Vernon on the morning of the regular tallyho run . This was an honor , " + "like dining with a captain" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # This should be tagged as a DEMP - self.assertIn('DEMP', tagged_words[10]['tags']) + self.assertIn("DEMP", tagged_words[10]["tags"]) - text = 'Vernon on the morning of the regular tallyho run . This was an honor , ' \ - 'like dining with a captain' + text = ( + "Vernon on the morning of the regular tallyho run . This was an honor , " + "like dining with a captain" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # "This" should be tagged as a DEMP - self.assertIn('DEMP', tagged_words[10]['tags']) + self.assertIn("DEMP", tagged_words[10]["tags"]) - text = 'Those are the moments I cherish the most. And these inspire me to strive harder.' + text = "Those are the moments I cherish the most. And these inspire me to strive harder." tagged_words = tag_text(text, pipeline=self.pipeline) # "Those" should be tagged as a DEMP - self.assertIn('DEMP', tagged_words[0]['tags']) + self.assertIn("DEMP", tagged_words[0]["tags"]) def test_inpr(self): text = "I turned away from her coldly . `` It was nobody 's fault . She overplayed her hand '' ." tagged_words = tag_text(text, pipeline=self.pipeline) # Nobody should be tagged as a INPR - self.assertIn('INPR', tagged_words[11]['tags']) + self.assertIn("INPR", tagged_words[11]["tags"]) def test_prod(self): text = "Whom do they seek?" tagged_words = tag_text(text, pipeline=self.pipeline) # The "do" shouldn't be tagged as a PROD because it's preceded by a WH pronoun - self.assertNotIn('PROD', tagged_words[1]['tags']) + self.assertNotIn("PROD", tagged_words[1]["tags"]) def test_fpp1_variants(self): """Test different forms of first person pronouns""" @@ -74,13 +83,15 @@ def test_fpp1_variants(self): ("Our team worked hard on this.", "Our"), ("I did this task myself.", "myself"), ("Let us handle this situation.", "us"), - ("All of ourselves were present.", "ourselves") + ("All of ourselves were present.", "ourselves"), ] - + for text, target_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == target_word) - self.assertIn('FPP1', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == target_word + ) + self.assertIn("FPP1", tagged_words[word_index]["tags"]) def test_spp2_variants(self): """Test different forms of second person pronouns""" @@ -93,13 +104,15 @@ def test_spp2_variants(self): ("Thou art wise.", "Thou"), ("This is thy choice.", "thy"), ("Speak thee truth.", "thee"), - ("Compose thyself.", "thyself") + ("Compose thyself.", "thyself"), ] - + for text, target_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == target_word) - self.assertIn('SPP2', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == target_word + ) + self.assertIn("SPP2", tagged_words[word_index]["tags"]) def test_tpp3_variants(self): """Test different forms of third person pronouns""" @@ -112,26 +125,30 @@ def test_tpp3_variants(self): ("The book is their property.", "their"), ("He did it himself.", "himself"), ("She wrote it herself.", "herself"), - ("They did it themselves.", "themselves") + ("They did it themselves.", "themselves"), ] - + for text, target_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == target_word) - self.assertIn('TPP3', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == target_word + ) + self.assertIn("TPP3", tagged_words[word_index]["tags"]) def test_pit_variants(self): """Test different forms of pronoun it""" texts = [ ("It seems to work fine.", "It"), ("The car and its engine.", "its"), - ("The machine fixed itself.", "itself") + ("The machine fixed itself.", "itself"), ] - + for text, target_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == target_word) - self.assertIn('PIT', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == target_word + ) + self.assertIn("PIT", tagged_words[word_index]["tags"]) def test_demp_patterns(self): """Test demonstrative pronouns in different patterns""" @@ -145,13 +162,15 @@ def test_demp_patterns(self): # Followed by WH pronoun ("This which I saw.", "This"), # Followed by 'and' - ("These and those.", "These") + ("These and those.", "These"), ] - + for text, target_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == target_word) - self.assertIn('DEMP', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == target_word + ) + self.assertIn("DEMP", tagged_words[word_index]["tags"]) def test_inpr_variants(self): """Test different indefinite pronouns""" @@ -164,46 +183,58 @@ def test_inpr_variants(self): ("Nobody answered.", "Nobody"), ("Something strange happened.", "Something"), ("There was none left.", "none"), - ("They went nowhere.", "nowhere") + ("They went nowhere.", "nowhere"), ] - + for text, target_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == target_word) - self.assertIn('INPR', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == target_word + ) + self.assertIn("INPR", tagged_words[word_index]["tags"]) def test_prod_patterns(self): """Test pro-verb do patterns""" texts_should_be_prod = [ "I did the homework.", "What did you do today?", - "They do good work." + "They do good work.", ] - + texts_should_not_be_prod = [ "Do you know the answer?", # Auxiliary DO - "What do they want?", # DO after WH word - "How do they manage?", # DO after WH word - "Do not go there." # Auxiliary DO with negation + "What do they want?", # DO after WH word + "How do they manage?", # DO after WH word + "Do not go there.", # Auxiliary DO with negation ] - + for text in texts_should_be_prod: tagged_words = tag_text(text, pipeline=self.pipeline) prod_tokens = [ - w for w in tagged_words - if w['text'].lower() in ['do', 'does', 'did', 'done'] and 'PROD' in w['tags'] + w + for w in tagged_words + if w["text"].lower() in ["do", "does", "did", "done"] + and "PROD" in w["tags"] ] - self.assertTrue(len(prod_tokens) > 0, - f"Expected PROD tag in one of the 'do' tokens in: {text}") - + self.assertTrue( + len(prod_tokens) > 0, + f"Expected PROD tag in one of the 'do' tokens in: {text}", + ) + for text in texts_should_not_be_prod: tagged_words = tag_text(text, pipeline=self.pipeline) prod_tokens = [ - w for w in tagged_words - if w['text'].lower() in ['do', 'does', 'did', 'done'] and 'PROD' in w['tags'] + w + for w in tagged_words + if w["text"].lower() in ["do", "does", "did", "done"] + and "PROD" in w["tags"] ] - self.assertEqual(len(prod_tokens), 0, f"Did not expect PROD tag in any 'do' token in: {text}") + self.assertEqual( + len(prod_tokens), + 0, + f"Did not expect PROD tag in any 'do' token in: {text}", + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_questions.py b/tests/tagger/test_questions.py index 70397c8..045db0a 100644 --- a/tests/tagger/test_questions.py +++ b/tests/tagger/test_questions.py @@ -6,21 +6,22 @@ class TestQuestionFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_whqu_why(self): text = "only ended up as one due to Columbia Records . Why did it bomb ? Because it 's awful ," tagged_words = tag_text(text, pipeline=self.pipeline) # Why should be tagged as WHQU - self.assertIn('WHQU', tagged_words[10]['tags']) + self.assertIn("WHQU", tagged_words[10]["tags"]) def test_whqu_what(self): text = "She was happy with her results . What are the consequences of that decision ? I'm not sure." tagged_words = tag_text(text, pipeline=self.pipeline) # What should be tagged as WHQU - self.assertIn('WHQU', tagged_words[7]['tags']) + self.assertIn("WHQU", tagged_words[7]["tags"]) def test_whqu_basic_forms(self): """Test basic WH-question forms with different WH-words""" @@ -29,13 +30,15 @@ def test_whqu_basic_forms(self): ("She finished the project . When did you start ?", "When"), ("He seems confused . Where are you going ?", "Where"), ("The solution failed . How can we fix it ?", "How"), - ("They made a decision . Which option did they choose ?", "Which") + ("They made a decision . Which option did they choose ?", "Which"), ] - + for text, wh_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == wh_word) - self.assertIn('WHQU', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == wh_word + ) + self.assertIn("WHQU", tagged_words[word_index]["tags"]) def test_whqu_with_discourse_markers(self): """Test WH-questions with intervening discourse markers""" @@ -43,13 +46,15 @@ def test_whqu_with_discourse_markers(self): ("The system crashed . So , what should we do ?", "what"), ("It's complicated . Well , how does it work ?", "how"), ("That's interesting . Anyway , why did it fail ?", "why"), - ("I'm confused . Now , where should we look ?", "where") + ("I'm confused . Now , where should we look ?", "where"), ] - + for text, wh_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == wh_word) - self.assertIn('WHQU', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == wh_word + ) + self.assertIn("WHQU", tagged_words[word_index]["tags"]) def test_whqu_negative_cases(self): """Test cases where WH-words should not be tagged as WHQU""" @@ -61,31 +66,50 @@ def test_whqu_negative_cases(self): # Not question-forming WH-words "However you look at it, it's wrong.", "Whatever happens, we'll be ready.", - "Whenever you're ready, let's begin." + "Whenever you're ready, let's begin.", ] - + for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - wh_words = [w for w in tagged_words if w['text'].lower() in - ['what', 'where', 'when', 'how', 'which', 'why', 'whoever', - 'whomever', 'whichever', 'wherever', 'whenever', 'whatever', 'however']] + wh_words = [ + w + for w in tagged_words + if w["text"].lower() + in [ + "what", + "where", + "when", + "how", + "which", + "why", + "whoever", + "whomever", + "whichever", + "wherever", + "whenever", + "whatever", + "however", + ] + ] for word in wh_words: - self.assertNotIn('WHQU', word['tags']) + self.assertNotIn("WHQU", word["tags"]) def test_whqu_with_auxiliaries(self): """Test WH-questions with different auxiliary verbs""" texts = [ ("The task is complex . What do we need?", "What"), # DO auxiliary - ("Things changed . What have they done?", "What"), # HAVE auxiliary - ("It's unclear . What will happen?", "What"), # Modal auxiliary - ("Nobody knows . What is going on?", "What") # BE auxiliary + ("Things changed . What have they done?", "What"), # HAVE auxiliary + ("It's unclear . What will happen?", "What"), # Modal auxiliary + ("Nobody knows . What is going on?", "What"), # BE auxiliary ] - + for text, wh_word in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - word_index = next(i for i, w in enumerate(tagged_words) if w['text'] == wh_word) - self.assertIn('WHQU', tagged_words[word_index]['tags']) + word_index = next( + i for i, w in enumerate(tagged_words) if w["text"] == wh_word + ) + self.assertIn("WHQU", tagged_words[word_index]["tags"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_reduced_forms_dispreferred_structures.py b/tests/tagger/test_reduced_forms_dispreferred_structures.py index e53617e..23298ae 100644 --- a/tests/tagger/test_reduced_forms_dispreferred_structures.py +++ b/tests/tagger/test_reduced_forms_dispreferred_structures.py @@ -6,118 +6,123 @@ class TestReducedFormsDispreferredStructuresFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_stpr(self): text = "plus a clicking noise each time you zoom in or out . My other complaints are that it does n't" tagged_words = tag_text(text, pipeline=self.pipeline) # Out should be tagged as STPR - self.assertIn('STPR', tagged_words[10]['tags']) + self.assertIn("STPR", tagged_words[10]["tags"]) def test_spin(self): text = "When all is said and done , this film seeks to financially cash in on the Rap\/Hip Hop culture and" tagged_words = tag_text(text, pipeline=self.pipeline) # To should be tagged as SPIN - self.assertIn('SPIN', tagged_words[10]['tags']) + self.assertIn("SPIN", tagged_words[10]["tags"]) def test_spin_to(self): text = "It's really hard to quickly understand some concepts, especially when trying to accurately capture details." tagged_words = tag_text(text, pipeline=self.pipeline) # To (before quickly understand) should be tagged as SPIN - self.assertIn('SPIN', tagged_words[4]['tags']) + self.assertIn("SPIN", tagged_words[4]["tags"]) def test_spau(self): - text = "portray her three narrators in distinct fashions so that we can easily follow when one stops " \ - "and another begins ." + text = ( + "portray her three narrators in distinct fashions so that we can easily follow when one stops " + "and another begins ." + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Are should be tagged as SPAU - self.assertIn('SPAU', tagged_words[10]['tags']) + self.assertIn("SPAU", tagged_words[10]["tags"]) def test_spau_with_single_adverb(self): - text = "portray her three narrators in distinct fashions so that we can easily follow when one stops " \ - "and another begins ." + text = ( + "portray her three narrators in distinct fashions so that we can easily follow when one stops " + "and another begins ." + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Can should be tagged as SPAU due to the presence of the adverb "easily" followed by the verb "follow". - self.assertIn('SPAU', tagged_words[10]['tags']) + self.assertIn("SPAU", tagged_words[10]["tags"]) def test_spau_with_double_adverbs(self): text = "She might quickly run to the store or she could slowly walk there." tagged_words = tag_text(text, pipeline=self.pipeline) # Might should be tagged as SPAU due to the presence of the adverb "quickly" followed by the verb "run". - self.assertIn('SPAU', tagged_words[1]['tags']) + self.assertIn("SPAU", tagged_words[1]["tags"]) def test_thatd(self): text = "passes away and his wealth is gone ? Overall I thought this was a good book , it was n't" tagged_words = tag_text(text, pipeline=self.pipeline) # Though should be tagged as THATD - self.assertIn('THATD', tagged_words[10]['tags']) + self.assertIn("THATD", tagged_words[10]["tags"]) def test_thatd_with_demp_or_subject_ppronoun(self): text = "I said she should leave now." tagged_words = tag_text(text, pipeline=self.pipeline) # Said should be tagged as THATD - self.assertIn('THATD', tagged_words[1]['tags']) + self.assertIn("THATD", tagged_words[1]["tags"]) def test_thatd_with_modifier_noun_verb_pattern(self): text = "He suggests a different approach might work." tagged_words = tag_text(text, pipeline=self.pipeline) # Suggests should be tagged as THATD - self.assertIn('THATD', tagged_words[1]['tags']) + self.assertIn("THATD", tagged_words[1]["tags"]) def test_cont_n_t(self): text = "I can't believe it's true." tagged_words = tag_text(text, pipeline=self.pipeline) # "can't" should be tagged as CONT - self.assertIn('CONT', tagged_words[2]['tags']) + self.assertIn("CONT", tagged_words[2]["tags"]) def test_cont_apostrophe(self): text = "They're going to the '80s themed party." tagged_words = tag_text(text, pipeline=self.pipeline) # "They're" should be tagged as CONT - self.assertIn('CONT', tagged_words[1]['tags']) + self.assertIn("CONT", tagged_words[1]["tags"]) def test_stpr_multiple_cases(self): text = "That's the store I went to. This is what I was thinking of. Who did you talk with?" tagged_words = tag_text(text, pipeline=self.pipeline) # Check each stranded preposition - self.assertIn('STPR', tagged_words[6]['tags']) # 'to' - self.assertIn('STPR', tagged_words[14]['tags']) # 'of' - self.assertIn('STPR', tagged_words[20]['tags']) # 'with' + self.assertIn("STPR", tagged_words[6]["tags"]) # 'to' + self.assertIn("STPR", tagged_words[14]["tags"]) # 'of' + self.assertIn("STPR", tagged_words[20]["tags"]) # 'with' def test_spin_negative(self): text = "I want to explain this concept." tagged_words = tag_text(text, pipeline=self.pipeline) # 'to' should NOT be tagged as SPIN since there's no intervening adverb - self.assertNotIn('SPIN', tagged_words[2]['tags']) + self.assertNotIn("SPIN", tagged_words[2]["tags"]) def test_spau_modal(self): text = "You should carefully consider the options." tagged_words = tag_text(text, pipeline=self.pipeline) # 'should' should be tagged as SPAU - self.assertIn('SPAU', tagged_words[1]['tags']) + self.assertIn("SPAU", tagged_words[1]["tags"]) def test_spau_have(self): text = "They have thoroughly reviewed the document." tagged_words = tag_text(text, pipeline=self.pipeline) # 'have' should be tagged as SPAU - self.assertIn('SPAU', tagged_words[1]['tags']) + self.assertIn("SPAU", tagged_words[1]["tags"]) def test_spau_be(self): text = "The results are clearly shown in the graph." tagged_words = tag_text(text, pipeline=self.pipeline) # 'are' should be tagged as SPAU - self.assertIn('SPAU', tagged_words[2]['tags']) + self.assertIn("SPAU", tagged_words[2]["tags"]) def test_cont_possessive(self): text = "That's John's book. The cat's tail." tagged_words = tag_text(text, pipeline=self.pipeline) # Test that possessive 's is not tagged as contraction - self.assertNotIn('CONT', tagged_words[1]['tags']) # "'s" from "That's" - self.assertNotIn('CONT', tagged_words[3]['tags']) # "'s" from "John's" - self.assertNotIn('CONT', tagged_words[8]['tags']) # "'s" from "cat's" + self.assertNotIn("CONT", tagged_words[1]["tags"]) # "'s" from "That's" + self.assertNotIn("CONT", tagged_words[3]["tags"]) # "'s" from "John's" + self.assertNotIn("CONT", tagged_words[8]["tags"]) # "'s" from "cat's" -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_specialized_verbs.py b/tests/tagger/test_specialized_verbs.py index 411ce4f..0b6192b 100644 --- a/tests/tagger/test_specialized_verbs.py +++ b/tests/tagger/test_specialized_verbs.py @@ -7,31 +7,33 @@ class TestSpecializedVerbFunctions(unittest.TestCase): def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_priv(self): - text = 'I expected to see ten, but instead saw twelve' + text = "I expected to see ten, but instead saw twelve" tagged_words = tag_text(text, pipeline=self.pipeline) # Expected should be tagged as a priv - self.assertIn('PRIV', tagged_words[1]['tags']) + self.assertIn("PRIV", tagged_words[1]["tags"]) def test_pubv(self): - text = 'The candidate conceded the election late last night' + text = "The candidate conceded the election late last night" tagged_words = tag_text(text, pipeline=self.pipeline) # Conceded should be tagged as a pubv - self.assertIn('PUBV', tagged_words[2]['tags']) + self.assertIn("PUBV", tagged_words[2]["tags"]) def test_suav(self): - text = 'I proposed extending the deadline' + text = "I proposed extending the deadline" tagged_words = tag_text(text, pipeline=self.pipeline) # Proposed should be tagged as a suav - self.assertIn('SUAV', tagged_words[1]['tags']) + self.assertIn("SUAV", tagged_words[1]["tags"]) def test_smp(self): - text = 'edge of the bank . From the outside , it seemed no more than a low drumlin , a lump' + text = "edge of the bank . From the outside , it seemed no more than a low drumlin , a lump" tagged_words = tag_text(text, pipeline=self.pipeline) # Seemed should be tagged as SMP - self.assertIn('SMP', tagged_words[10]['tags']) + self.assertIn("SMP", tagged_words[10]["tags"]) def test_priv_multiple_forms(self): """Test different forms of private verbs""" @@ -40,12 +42,12 @@ def test_priv_multiple_forms(self): ("She thinks about the future", 1), ("They assumed it would work", 1), ("We are considering the proposal", 2), - ("He felt the temperature rising", 1) + ("He felt the temperature rising", 1), ] for text, verb_index in test_cases: tagged_words = tag_text(text, pipeline=self.pipeline) # Check that the main verb is tagged as PRIV - self.assertIn('PRIV', tagged_words[verb_index]['tags']) + self.assertIn("PRIV", tagged_words[verb_index]["tags"]) def test_pubv_multiple_forms(self): """Test different forms of public verbs""" @@ -54,12 +56,12 @@ def test_pubv_multiple_forms(self): ("They claimed victory in the election", 1), ("She replied to the accusations", 1), ("The witness testified in court", 2), - ("The document states clearly that", 2) + ("The document states clearly that", 2), ] for text, verb_index in test_cases: tagged_words = tag_text(text, pipeline=self.pipeline) # Check that the main verb is tagged as PUBV - self.assertIn('PUBV', tagged_words[verb_index]['tags']) + self.assertIn("PUBV", tagged_words[verb_index]["tags"]) def test_suav_multiple_forms(self): """Test different forms of suasive verbs""" @@ -68,12 +70,12 @@ def test_suav_multiple_forms(self): ("We demand immediate action", 1), ("They urged caution", 1), ("The manager suggested improvements", 2), - ("The policy requires compliance", 2) + ("The policy requires compliance", 2), ] for text, verb_index in test_cases: tagged_words = tag_text(text, pipeline=self.pipeline) # Check that the main verb is tagged as SUAV - self.assertIn('SUAV', tagged_words[verb_index]['tags']) + self.assertIn("SUAV", tagged_words[verb_index]["tags"]) def test_smp_multiple_forms(self): """Test different forms of seem/appear""" @@ -82,46 +84,46 @@ def test_smp_multiple_forms(self): ("The solution appeared effective", 2), ("Everything seems to work", 1), ("The results appear conclusive", 2), - ("That seemed strange to me", 1) + ("That seemed strange to me", 1), ] for text, verb_index in test_cases: tagged_words = tag_text(text, pipeline=self.pipeline) # Check that seem/appear is tagged as SMP - self.assertIn('SMP', tagged_words[verb_index]['tags']) + self.assertIn("SMP", tagged_words[verb_index]["tags"]) def test_priv_with_negation(self): """Test private verbs with negation""" text = "I don't believe their story" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PRIV', tagged_words[3]['tags']) + self.assertIn("PRIV", tagged_words[3]["tags"]) def test_pubv_with_that_complement(self): """Test public verbs with that-complement""" text = "The report stated that the findings were significant" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PUBV', tagged_words[2]['tags']) + self.assertIn("PUBV", tagged_words[2]["tags"]) def test_suav_with_to_infinitive(self): """Test suasive verbs with to-infinitive""" text = "The board proposed to increase funding" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SUAV', tagged_words[2]['tags']) + self.assertIn("SUAV", tagged_words[2]["tags"]) def test_smp_with_to_complement(self): """Test seem/appear with to-complement""" text = "The project appears to be successful" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('SMP', tagged_words[2]['tags']) + self.assertIn("SMP", tagged_words[2]["tags"]) def test_multiple_specialized_verbs(self): """Test text with multiple specialized verbs""" text = "I think they claimed that we should propose new solutions, which seems reasonable" tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PRIV', tagged_words[1]['tags']) # think - self.assertIn('PUBV', tagged_words[3]['tags']) # claimed - self.assertIn('SUAV', tagged_words[7]['tags']) # propose - self.assertIn('SMP', tagged_words[12]['tags']) # seems + self.assertIn("PRIV", tagged_words[1]["tags"]) # think + self.assertIn("PUBV", tagged_words[3]["tags"]) # claimed + self.assertIn("SUAV", tagged_words[7]["tags"]) # propose + self.assertIn("SMP", tagged_words[12]["tags"]) # seems -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_stative_forms.py b/tests/tagger/test_stative_forms.py index 0d73082..7825a3f 100644 --- a/tests/tagger/test_stative_forms.py +++ b/tests/tagger/test_stative_forms.py @@ -6,92 +6,91 @@ class TestStativeFormFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_bema(self): - text = 'have a little boy that likes tractors , you can be certain this will be entertaining to him ! ' + text = "have a little boy that likes tractors , you can be certain this will be entertaining to him ! " tagged_words = tag_text(text, pipeline=self.pipeline) # 'Be' should be tagged as BEMA - self.assertIn('BEMA', tagged_words[10]['tags']) + self.assertIn("BEMA", tagged_words[10]["tags"]) def test_bema_directly_followed(self): - text = 'They are wonderful people to work with.' + text = "They are wonderful people to work with." tagged_words = tag_text(text, pipeline=self.pipeline) # 'Are' should be tagged as BEMA because it's followed directly by an adjective (JJ) - "wonderful". - self.assertIn('BEMA', tagged_words[1]['tags']) + self.assertIn("BEMA", tagged_words[1]["tags"]) def test_bema_with_determiners(self): """Test BE as main verb followed by determiners""" - texts = [ - "This is the answer", - "Those are my books", - "That was their decision" - ] + texts = ["This is the answer", "Those are my books", "That was their decision"] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('BEMA', tagged_words[1]['tags']) + self.assertIn("BEMA", tagged_words[1]["tags"]) def test_bema_with_adjectives(self): """Test BE as main verb followed by adjectives""" text = "The results are significant. The test was successful. They were happy." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('BEMA', tagged_words[2]['tags']) # are - self.assertIn('BEMA', tagged_words[7]['tags']) # was - self.assertIn('BEMA', tagged_words[11]['tags']) # were + self.assertIn("BEMA", tagged_words[2]["tags"]) # are + self.assertIn("BEMA", tagged_words[7]["tags"]) # was + self.assertIn("BEMA", tagged_words[11]["tags"]) # were def test_bema_with_prepositions(self): """Test BE as main verb followed by prepositions""" text = "The book is on the shelf. They were in the garden." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('BEMA', tagged_words[2]['tags']) # is - self.assertIn('BEMA', tagged_words[8]['tags']) # were + self.assertIn("BEMA", tagged_words[2]["tags"]) # is + self.assertIn("BEMA", tagged_words[8]["tags"]) # were def test_pass_simple(self): """Test agentless passive constructions""" texts = [ "The paper was published last year", "The data were collected carefully", - "The building is being renovated" + "The building is being renovated", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PASS', tagged_words[2]['tags']) # was/were/is + self.assertIn("PASS", tagged_words[2]["tags"]) # was/were/is def test_pass_with_adverbs(self): """Test passives with intervening adverbs""" text = "The results were carefully analyzed. The text is quickly processed." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PASS', tagged_words[2]['tags']) # were - self.assertIn('PASS', tagged_words[8]['tags']) # is + self.assertIn("PASS", tagged_words[2]["tags"]) # were + self.assertIn("PASS", tagged_words[8]["tags"]) # is def test_bypa_identification(self): """Test by-passive constructions""" text = "The book was written by the author. The study was conducted by researchers." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('BYPA', tagged_words[2]['tags']) # was - self.assertIn('BYPA', tagged_words[10]['tags']) # was + self.assertIn("BYPA", tagged_words[2]["tags"]) # was + self.assertIn("BYPA", tagged_words[10]["tags"]) # was def test_ex_there(self): """Test existential there constructions""" texts = [ "There is a problem", "There are many solutions", - "There were several issues" + "There were several issues", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('EX', tagged_words[0]['tags']) # there - self.assertNotIn('BEMA', tagged_words[1]['tags']) # is/are/were should not be tagged as BEMA + self.assertIn("EX", tagged_words[0]["tags"]) # there + self.assertNotIn( + "BEMA", tagged_words[1]["tags"] + ) # is/are/were should not be tagged as BEMA def test_bema_negation(self): """Test BE as main verb with negation""" text = "The solution is not obvious. They are not in the office." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('BEMA', tagged_words[2]['tags']) # is - self.assertIn('BEMA', tagged_words[7]['tags']) # are + self.assertIn("BEMA", tagged_words[2]["tags"]) # is + self.assertIn("BEMA", tagged_words[7]["tags"]) # are -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_subordination_features.py b/tests/tagger/test_subordination_features.py index 3ff56bd..afe5bcc 100644 --- a/tests/tagger/test_subordination_features.py +++ b/tests/tagger/test_subordination_features.py @@ -6,185 +6,200 @@ class TestSubordinationFeatureFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_thvc(self): text = "I've read a few of these reviews and think that Fisher Price must have a quality control issue ." tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as a THVC - self.assertIn('THVC', tagged_words[10]['tags']) + self.assertIn("THVC", tagged_words[10]["tags"]) def test_thvc_case_followed_by_determiner(self): text = "I've read a few of these reviews and think that Fisher Price must have a quality control issue ." tagged_words = tag_text(text, pipeline=self.pipeline) # 'That' should be tagged as a THVC - self.assertIn('THVC', tagged_words[10]['tags']) + self.assertIn("THVC", tagged_words[10]["tags"]) def test_thvc_case_preceded_by_noun_and_preposition(self): text = "I heard that they had a great time at the party ." tagged_words = tag_text(text, pipeline=self.pipeline) # 'That' should be tagged as a THVC - self.assertIn('THVC', tagged_words[2]['tags']) + self.assertIn("THVC", tagged_words[2]["tags"]) def test_thac(self): text = "twice a day for 20 minutes per use . Disappointing that it failed so quickly . I have now owned" tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as a THAC - self.assertIn('THAC', tagged_words[10]['tags']) + self.assertIn("THAC", tagged_words[10]["tags"]) def test_whcl(self): text = "it gingerly with his foot . How could anyone know what to do with an assortment like that ? Perhaps" tagged_words = tag_text(text, pipeline=self.pipeline) # What should be tagged as WHCL - self.assertIn('WHCL', tagged_words[10]['tags']) + self.assertIn("WHCL", tagged_words[10]["tags"]) def test_presp(self): - text = 'practice and for that it is a good resource . Knowing why some aspects are not included and having the' + text = "practice and for that it is a good resource . Knowing why some aspects are not included and having the" tagged_words = tag_text(text, pipeline=self.pipeline) # Knowing should be tagged as PRESP - self.assertIn('PRESP', tagged_words[10]['tags']) + self.assertIn("PRESP", tagged_words[10]["tags"]) def test_pastp(self): - text = '. Built in a single week, the house would stand for fifty years' + text = ". Built in a single week, the house would stand for fifty years" tagged_words = tag_text(text, pipeline=self.pipeline) # Built should be tagged as PASTP - self.assertIn('PASTP', tagged_words[1]['tags']) + self.assertIn("PASTP", tagged_words[1]["tags"]) def test_wzpast(self): - text = 'in most cases with understanding and restraint . The progress reported by the advisory ' \ - 'committee is real . While some' + text = ( + "in most cases with understanding and restraint . The progress reported by the advisory " + "committee is real . While some" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Reported should be tagged as WZPAST - self.assertIn('WZPAST', tagged_words[10]['tags']) + self.assertIn("WZPAST", tagged_words[10]["tags"]) - text = 'The toy created by the child was innovative. However, the mechanism used in its operation was complex.' + text = "The toy created by the child was innovative. However, the mechanism used in its operation was complex." tagged_words = tag_text(text, pipeline=self.pipeline) # 'created' should be tagged as WZPAST - self.assertIn('WZPAST', tagged_words[2]['tags']) + self.assertIn("WZPAST", tagged_words[2]["tags"]) # Additionally, 'used' should also be tagged as WZPAST - self.assertIn('WZPAST', tagged_words[13]['tags']) + self.assertIn("WZPAST", tagged_words[13]["tags"]) def test_wzpres(self): - text = "and the mean , and he sees the Compson family disintegrating from within . If the barn-burner 's " \ - "family produces" + text = ( + "and the mean , and he sees the Compson family disintegrating from within . If the barn-burner 's " + "family produces" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Disintegrating should be tagged as WZPAST - self.assertIn('WZPRES', tagged_words[10]['tags']) + self.assertIn("WZPRES", tagged_words[10]["tags"]) def test_tsub(self): - text = 'we proceed through the seasons of life . Minor characters that surround the love ' \ - 'triangle are colorful and woven with' + text = ( + "we proceed through the seasons of life . Minor characters that surround the love " + "triangle are colorful and woven with" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as a TSUB - self.assertIn('TSUB', tagged_words[10]['tags']) + self.assertIn("TSUB", tagged_words[10]["tags"]) def test_tsub_with_intervening_adverb(self): - text = 'The movies that often showcase historical events are thrilling. It presents a blend of facts and fiction.' + text = "The movies that often showcase historical events are thrilling. It presents a blend of facts and fiction." tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as TSUB - self.assertIn('TSUB', tagged_words[2]['tags']) + self.assertIn("TSUB", tagged_words[2]["tags"]) def test_whsub(self): - text = '. There are plenty of reference mentioned at the end which can be followed ' \ - 'up for more curiosity . Must' + text = ( + ". There are plenty of reference mentioned at the end which can be followed " + "up for more curiosity . Must" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Which should be tagged as a WHSUB - self.assertIn('WHSUB', tagged_words[10]['tags']) + self.assertIn("WHSUB", tagged_words[10]["tags"]) def test_whobj(self): text = "can be brave and courageous . Mafatu is a boy whose mom dies at sea and ever since he was" tagged_words = tag_text(text, pipeline=self.pipeline) # Whose should be tagged as a WHOBJ - self.assertIn('WHOBJ', tagged_words[10]['tags']) + self.assertIn("WHOBJ", tagged_words[10]["tags"]) def test_whobj_complex_sentence(self): - text = 'I have a friend whom everyone in town admires for her kindness. She is truly special.' + text = "I have a friend whom everyone in town admires for her kindness. She is truly special." tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('WHOBJ', tagged_words[4]['tags']) + self.assertIn("WHOBJ", tagged_words[4]["tags"]) def test_pire(self): - text = 'pencil ! I am a semi-professional singer , one of whose idols is the great Judy Garland . No one' + text = "pencil ! I am a semi-professional singer , one of whose idols is the great Judy Garland . No one" tagged_words = tag_text(text, pipeline=self.pipeline) # Whose should be tagged as a PIRE - self.assertIn('PIRE', tagged_words[12]['tags']) + self.assertIn("PIRE", tagged_words[12]["tags"]) def test_sere(self): - text = 'does not stop until you put the book down , which you will not do until you have finished it' + text = "does not stop until you put the book down , which you will not do until you have finished it" tagged_words = tag_text(text, pipeline=self.pipeline) # Which should be tagged as a SERE - self.assertIn('SERE', tagged_words[10]['tags']) + self.assertIn("SERE", tagged_words[10]["tags"]) def test_tobj(self): - text = 'the dog that I saw' + text = "the dog that I saw" tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as a TOBJ - self.assertIn('TOBJ', tagged_words[2]['tags']) + self.assertIn("TOBJ", tagged_words[2]["tags"]) def test_tobj_with_proper_noun(self): - text = 'The painting that Picasso painted is priceless.' + text = "The painting that Picasso painted is priceless." tagged_words = tag_text(text, pipeline=self.pipeline) # That should be tagged as TOBJ - self.assertIn('TOBJ', tagged_words[2]['tags']) + self.assertIn("TOBJ", tagged_words[2]["tags"]) def test_caus(self): - text = 'they did also fall under the power of death , because they did eat in disobedience ; and disobedience to' + text = "they did also fall under the power of death , because they did eat in disobedience ; and disobedience to" tagged_words = tag_text(text, pipeline=self.pipeline) # Because should be tagged as a CAUS - self.assertIn('CAUS', tagged_words[10]['tags']) + self.assertIn("CAUS", tagged_words[10]["tags"]) def test_conc(self): text = "outsider . When they learn you 're in the hills though , they 'll rally , do n't worry about" tagged_words = tag_text(text, pipeline=self.pipeline) # Though should be tagged as a CONC - self.assertIn('CONC', tagged_words[10]['tags']) + self.assertIn("CONC", tagged_words[10]["tags"]) def test_cond(self): - text = 'so high that the top falls gently over , as if to show that it really is hair and not' + text = "so high that the top falls gently over , as if to show that it really is hair and not" tagged_words = tag_text(text, pipeline=self.pipeline) # If should be tagged as a COND - self.assertIn('COND', tagged_words[10]['tags']) + self.assertIn("COND", tagged_words[10]["tags"]) def test_osub(self): - text = 'his comment on the planter dynasties as they have existed since the decades before the \ - Civil War . It may' + text = ( + "his comment on the planter dynasties as they have existed since the decades before the \ + Civil War . It may" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Since should be tagged as an OSUB - self.assertIn('OSUB', tagged_words[10]['tags']) + self.assertIn("OSUB", tagged_words[10]["tags"]) def test_causative_subordinators(self): """Test causative subordinator 'because'""" texts = [ "I stayed home because I was sick", "The experiment failed because of poor controls", - "Because the weather was bad, we cancelled" + "Because the weather was bad, we cancelled", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertTrue(any('CAUS' in word['tags'] - for word in tagged_words - if word['text'].lower() == 'because')) + self.assertTrue( + any( + "CAUS" in word["tags"] + for word in tagged_words + if word["text"].lower() == "because" + ) + ) def test_concessive_subordinators(self): """Test concessive subordinators (although, though, tho)""" texts = [ "Although it was raining, we went out", "We continued though it was difficult", - "They succeeded, tho nobody believed in them" + "They succeeded, tho nobody believed in them", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - concessive_words = ['although', 'though', 'tho'] + concessive_words = ["although", "though", "tho"] found = False for word in tagged_words: - if word['text'].lower() in concessive_words: - self.assertIn('CONC', word['tags']) + if word["text"].lower() in concessive_words: + self.assertIn("CONC", word["tags"]) found = True self.assertTrue(found) @@ -193,15 +208,15 @@ def test_conditional_subordinators(self): texts = [ "If it rains, bring an umbrella", "Unless you study, you won't pass", - "The experiment will fail if proper controls aren't used" + "The experiment will fail if proper controls aren't used", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - conditional_words = ['if', 'unless'] + conditional_words = ["if", "unless"] found = False for word in tagged_words: - if word['text'].lower() in conditional_words: - self.assertIn('COND', word['tags']) + if word["text"].lower() in conditional_words: + self.assertIn("COND", word["tags"]) found = True self.assertTrue(found) @@ -210,14 +225,14 @@ def test_other_subordinators(self): texts = [ "Since you asked, I'll explain", "While the experiment ran, we collected data", - "Whereas the first trial succeeded, the second failed" + "Whereas the first trial succeeded, the second failed", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) found = False for word in tagged_words: - if word['text'].lower() in ['since', 'while', 'whereas']: - self.assertIn('OSUB', word['tags']) + if word["text"].lower() in ["since", "while", "whereas"]: + self.assertIn("OSUB", word["tags"]) found = True self.assertTrue(found) @@ -225,12 +240,15 @@ def test_complex_other_subordinators(self): """Test multi-word other subordinators""" text = "I'll wait as long as necessary" tagged_words = tag_text(text, pipeline=self.pipeline) - + # Find the "as" that starts "as long as" for i, word in enumerate(tagged_words): - if word['text'].lower() == 'as' and i + 2 < len(tagged_words): - if tagged_words[i+1]['text'].lower() == 'long' and tagged_words[i+2]['text'].lower() == 'as': - self.assertIn('OSUB', word['tags']) + if word["text"].lower() == "as" and i + 2 < len(tagged_words): + if ( + tagged_words[i + 1]["text"].lower() == "long" + and tagged_words[i + 2]["text"].lower() == "as" + ): + self.assertIn("OSUB", word["tags"]) return self.fail("Multi-word subordinator 'as long as' not properly tagged") @@ -238,15 +256,15 @@ def test_multiple_subordinators(self): """Test text with multiple subordinators""" text = "Although it was difficult, we continued because we believed that if we persisted, we would succeed" tagged_words = tag_text(text, pipeline=self.pipeline) - + # Find indices dynamically instead of hardcoding for i, word in enumerate(tagged_words): - if word['text'].lower() == 'although': - self.assertIn('CONC', word['tags']) - elif word['text'].lower() == 'because': - self.assertIn('CAUS', word['tags']) - elif word['text'].lower() == 'if': - self.assertIn('COND', word['tags']) + if word["text"].lower() == "although": + self.assertIn("CONC", word["tags"]) + elif word["text"].lower() == "because": + self.assertIn("CAUS", word["tags"]) + elif word["text"].lower() == "if": + self.assertIn("COND", word["tags"]) def test_complex_osub_cases(self): """Test complex cases of other subordinators""" @@ -254,35 +272,34 @@ def test_complex_osub_cases(self): "Insofar as the data shows", "Inasmuch as we understand", "Such that the results were clear", - "So that we could proceed" + "So that we could proceed", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) # These are multi-word subordinators, need to check first word - first_word = tagged_words[0]['text'].lower() - if first_word in ['insofar', 'inasmuch', 'such', 'so']: - self.assertIn('OSUB', tagged_words[0]['tags']) + first_word = tagged_words[0]["text"].lower() + if first_word in ["insofar", "inasmuch", "such", "so"]: + self.assertIn("OSUB", tagged_words[0]["tags"]) def test_subordinator_with_punctuation(self): """Test subordinators with various punctuation patterns""" texts = [ "We proceeded, although with caution", - "If, and only if, the conditions are met" + "If, and only if, the conditions are met", ] - expected_tags = { - 'although': 'CONC', - 'if': 'COND' - } - + expected_tags = {"although": "CONC", "if": "COND"} + for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) found = False for word in tagged_words: - word_lower = word['text'].lower() + word_lower = word["text"].lower() if word_lower in expected_tags: - self.assertIn(expected_tags[word_lower], word['tags']) + self.assertIn(expected_tags[word_lower], word["tags"]) found = True break self.assertTrue(found, f"No subordinator found in text: {text}") -if __name__ == '__main__': + + +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_tag_frequencies.py b/tests/tagger/test_tag_frequencies.py index a6929d6..3c81a08 100644 --- a/tests/tagger/test_tag_frequencies.py +++ b/tests/tagger/test_tag_frequencies.py @@ -6,5 +6,5 @@ def test_something(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/tagger/test_tense_aspect_markers.py b/tests/tagger/test_tense_aspect_markers.py index 29c6ce5..5eaa216 100644 --- a/tests/tagger/test_tense_aspect_markers.py +++ b/tests/tagger/test_tense_aspect_markers.py @@ -6,120 +6,130 @@ class TestTenseAspectFunctions(unittest.TestCase): - def setUp(self) -> None: - self.pipeline = spacy.load("en_core_web_sm", disable=['parser', 'lemmatizer', 'ner']) + self.pipeline = spacy.load( + "en_core_web_sm", disable=["parser", "lemmatizer", "ner"] + ) def test_peas(self): - text = "the exchange , so far all my dealings with amazon have been easy and fair . " \ - "Waterloo Station is a" + text = ( + "the exchange , so far all my dealings with amazon have been easy and fair . " + "Waterloo Station is a" + ) tagged_words = tag_text(text, pipeline=self.pipeline) # Have should be tagged as PEAS - self.assertIn('PEAS', tagged_words[10]['tags']) + self.assertIn("PEAS", tagged_words[10]["tags"]) def test_peas_direct_verb(self): text = "They have visited the museum several times." tagged_words = tag_text(text, pipeline=self.pipeline) # Have should be tagged as PEAS - self.assertIn('PEAS', tagged_words[1]['tags']) + self.assertIn("PEAS", tagged_words[1]["tags"]) def test_peas_one_intervening_word(self): text = "I have always loved that painting." tagged_words = tag_text(text, pipeline=self.pipeline) # Have should be tagged as PEAS - self.assertIn('PEAS', tagged_words[1]['tags']) + self.assertIn("PEAS", tagged_words[1]["tags"]) def test_peas_two_intervening_adverbs(self): text = "She has probably never been to the opera." tagged_words = tag_text(text, pipeline=self.pipeline) # Has should be tagged as PEAS - self.assertIn('PEAS', tagged_words[1]['tags']) + self.assertIn("PEAS", tagged_words[1]["tags"]) def test_past_tense_simple(self): """Test simple past tense verbs""" texts = [ "She walked to the store", "They ran the experiment", - "The results showed significant differences" + "The results showed significant differences", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - past_verbs = [word for word in tagged_words if word['text'] in ['walked', 'ran', 'showed']] + past_verbs = [ + word + for word in tagged_words + if word["text"] in ["walked", "ran", "showed"] + ] for verb in past_verbs: - self.assertIn('VBD', verb['tags']) + self.assertIn("VBD", verb["tags"]) def test_perfect_aspect_basic(self): """Test basic perfect aspect constructions""" texts = [ ("I have finished the report", 1), ("They have completed the study", 1), - ("The researchers have published their findings", 2) + ("The researchers have published their findings", 2), ] for text, have_index in texts: tagged_words = tag_text(text, pipeline=self.pipeline) # Have should be tagged as PEAS - self.assertIn('PEAS', tagged_words[have_index]['tags']) + self.assertIn("PEAS", tagged_words[have_index]["tags"]) def test_perfect_aspect_with_adverbs(self): """Test perfect aspect with intervening adverbs""" texts = [ "We have successfully implemented the system", "They have carefully analyzed the data", - "She has thoroughly reviewed the literature" + "She has thoroughly reviewed the literature", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PEAS', tagged_words[1]['tags']) # have + self.assertIn("PEAS", tagged_words[1]["tags"]) # have def test_perfect_aspect_with_negation(self): """Test perfect aspect with negation""" texts = [ ("I have not seen the results", 1), ("They haven't completed the analysis", 1), # haven't is one token - ("The team has not published their findings", 2) + ("The team has not published their findings", 2), ] for text, have_index in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn('PEAS', tagged_words[have_index]['tags']) # have/has + self.assertIn("PEAS", tagged_words[have_index]["tags"]) # have/has def test_present_tense_third_person(self): """Test present tense verbs (third person)""" texts = [ "She writes clearly", "The system works well", - "This paper describes the method" + "This paper describes the method", ] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - present_verbs = [word for word in tagged_words if word['text'] in ['writes', 'works', 'describes']] + present_verbs = [ + word + for word in tagged_words + if word["text"] in ["writes", "works", "describes"] + ] for verb in present_verbs: - self.assertIn('VPRT', verb['tags']) + self.assertIn("VPRT", verb["tags"]) def test_present_tense_non_third_person(self): """Test present tense verbs (non-third person)""" - texts = [ - "I write papers", - "We analyze data", - "They study behavior" - ] + texts = ["I write papers", "We analyze data", "They study behavior"] for text in texts: tagged_words = tag_text(text, pipeline=self.pipeline) - present_verbs = [word for word in tagged_words if word['text'] in ['write', 'analyze', 'study']] + present_verbs = [ + word + for word in tagged_words + if word["text"] in ["write", "analyze", "study"] + ] for verb in present_verbs: - self.assertIn('VPRT', verb['tags']) + self.assertIn("VPRT", verb["tags"]) def test_complex_tense_combinations(self): """Test sentences with multiple tense/aspect markers""" text = "While I have analyzed the data, she writes the conclusion, and they completed the introduction." tagged_words = tag_text(text, pipeline=self.pipeline) - + # Check perfect aspect - self.assertIn('PEAS', tagged_words[2]['tags']) # have - + self.assertIn("PEAS", tagged_words[2]["tags"]) # have + # Check present tense - self.assertIn('VPRT', tagged_words[8]['tags']) # writes - + self.assertIn("VPRT", tagged_words[8]["tags"]) # writes -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() From 7b134b8ccb1998b9484879c75c0e4f97b0aa9dcd Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:09:18 +0000 Subject: [PATCH 02/16] style: fix flake8 lints --- experiments/performance/biber_plus.py | 3 --- experiments/performance/data_utils.py | 10 ++++------ experiments/performance/neurobiber.py | 3 --- modeling/biberta/models.py | 11 +++++------ modeling/contrastive_training/evaluation.py | 3 +-- modeling/contrastive_training/models.py | 15 +++++++++------ 6 files changed, 19 insertions(+), 26 deletions(-) diff --git a/experiments/performance/biber_plus.py b/experiments/performance/biber_plus.py index caeef92..e45cb7c 100644 --- a/experiments/performance/biber_plus.py +++ b/experiments/performance/biber_plus.py @@ -1,8 +1,6 @@ -import os import sys import time import json -import numpy as np import pandas as pd sys.path.append("/home/kalkiek/projects/biber-multidimensional-register-analysis/") @@ -13,7 +11,6 @@ load_config, load_pipeline, calculate_tag_frequencies, - tag_text, ) diff --git a/experiments/performance/data_utils.py b/experiments/performance/data_utils.py index b623856..f029762 100644 --- a/experiments/performance/data_utils.py +++ b/experiments/performance/data_utils.py @@ -1,10 +1,8 @@ -import os -import sys -import time import json -import numpy as np -import pandas as pd +import os import pickle + +import numpy as np from tqdm import tqdm @@ -79,7 +77,7 @@ def main(): stats = calculate_statistics(all_texts) print(f"Total tokens: {stats['total']:,}") print(f"Average token length: {stats['mean']:.2f} tokens") - print(f"\nDistribution statistics:") + print("\nDistribution statistics:") print(f"Min: {stats['min']}") print(f"Max: {stats['max']}") print(f"Median: {stats['median']}") diff --git a/experiments/performance/neurobiber.py b/experiments/performance/neurobiber.py index c424a5f..5a4fad7 100644 --- a/experiments/performance/neurobiber.py +++ b/experiments/performance/neurobiber.py @@ -1,4 +1,3 @@ -import os import sys import time @@ -7,8 +6,6 @@ from experiments.performance.data_utils import load_saved_dataset from modeling.neurobiber.tagger import ( load_model_and_tokenizer, - get_predictions, - get_predictions_chunked_batch, predict_batch, ) diff --git a/modeling/biberta/models.py b/modeling/biberta/models.py index 10ff334..4a95580 100644 --- a/modeling/biberta/models.py +++ b/modeling/biberta/models.py @@ -6,12 +6,11 @@ from accelerate import Accelerator from torch.optim import AdamW from tqdm import tqdm -from transformers import AutoModelForMaskedLM -from transformers import get_cosine_schedule_with_warmup +from transformers import AutoModelForMaskedLM, get_cosine_schedule_with_warmup sys.path.append("../..") -from src.custom_training.model_utils import load_model, calculate_perplexity +from src.custom_training.model_utils import calculate_perplexity, load_model from src.custom_training.trainer_utils import get_dataloaders @@ -75,7 +74,7 @@ def init_model(self): ) def train_model(self): - args, device = self.args, self.device + args = self.args self.init_model() self.model.train() @@ -253,7 +252,7 @@ def __init__( self.adversarial_loss_weight = adversarial_loss_weight self.biber_plus_size = biber_plus_size self.device = args.device - self.train_loader, self.eval_loader = get_dataloaders_style( + self.train_loader, self.eval_loader = get_dataloaders( args, mlm=True, pairs=True ) self.wandb = wandb @@ -302,7 +301,7 @@ def init_model(self): ) def train_model(self): - args, device = self.args, self.device + args = self.args self.init_model() self.model.train() diff --git a/modeling/contrastive_training/evaluation.py b/modeling/contrastive_training/evaluation.py index 194817b..602cab4 100644 --- a/modeling/contrastive_training/evaluation.py +++ b/modeling/contrastive_training/evaluation.py @@ -1,9 +1,8 @@ import numpy as np +import torch from sklearn.metrics import pairwise_distances from tqdm import tqdm -from losses import * - def compute_ranking_metrics( queries, targets, query_authors, target_authors, metric="cosine" diff --git a/modeling/contrastive_training/models.py b/modeling/contrastive_training/models.py index b97f45b..f1d46e6 100644 --- a/modeling/contrastive_training/models.py +++ b/modeling/contrastive_training/models.py @@ -6,18 +6,21 @@ from accelerate import Accelerator from torch.optim import AdamW from tqdm import tqdm -from transformers import AutoModel, AutoModelForMaskedLM -from transformers import get_cosine_schedule_with_warmup +from transformers import ( + AutoModel, + AutoModelForMaskedLM, + get_cosine_schedule_with_warmup, +) sys.path.append("../..") -from src.hiatus_training.losses import InfoNCE_loss_full from src.custom_training.model_utils import ( + encode_batches, load_model, setup_accelerator, - encode_batches, ) from src.custom_training.trainer_utils import get_dataloaders +from src.hiatus_training.losses import InfoNCE_loss_full class ContrastiveModel(nn.Module): @@ -56,7 +59,7 @@ def init_model(self): ) def train_model(self): - args, device = self.args, self.device + args = self.args self.init_model() self.model.train() @@ -214,7 +217,7 @@ def init_model(self): ) def train_model(self): - args, device = self.args, self.device + args = self.args self.init_model() self.model.train() From 9321c78376461ac99597cab957325bdd6ab7fbc6 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:09:44 +0000 Subject: [PATCH 03/16] fix: unused variable (MLM should be contrastive) --- modeling/contrastive_training/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modeling/contrastive_training/models.py b/modeling/contrastive_training/models.py index f1d46e6..9b24fc3 100644 --- a/modeling/contrastive_training/models.py +++ b/modeling/contrastive_training/models.py @@ -248,7 +248,8 @@ def train_model(self): if self.wandb: train_log = { "Train Overall Loss": running_loss / args.grad_acc, - "Train MLM Loss": running_mlm_loss / args.grad_acc, + "Train Contrastive Loss": running_contrastive_loss + / args.grad_acc, "Train Style Loss": running_style_loss / args.grad_acc, "Train Adversarial Training Loss": running_adversarial_loss / args.grad_acc, @@ -257,7 +258,7 @@ def train_model(self): print(train_log) ( running_loss, - running_mlm_loss, + running_contrastive_loss, running_style_loss, running_adversarial_loss, ) = 0, 0, 0, 0 From 90db95b2981b8e0f12158288b1d34c5018d68503 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:12:29 +0000 Subject: [PATCH 04/16] style: fix flake8 lints (cont.) --- modeling/contrastive_training/evaluation.py | 2 +- modeling/contrastive_training/models.py | 6 +---- modeling/neurobiber/evaluate.py | 3 --- modeling/neurobiber/prepare_dataset.py | 5 ++--- modeling/neurobiber/tag_counts.py | 1 - modeling/neurobiber/tagger.py | 3 --- modeling/neurobiber/train_model.py | 16 ++++++------- modeling/neurobiber/train_utils.py | 20 ++++++++--------- modeling/polybiberta/models.py | 9 ++++---- modeling/tagger/prepare_dataset.py | 5 ++--- modeling/tagger/train_model.py | 17 +++++++------- tests/tagger/test_additional_features.py | 25 ++++++++++----------- tests/tagger/test_lexical_specificity.py | 12 +++++----- 13 files changed, 54 insertions(+), 70 deletions(-) diff --git a/modeling/contrastive_training/evaluation.py b/modeling/contrastive_training/evaluation.py index 602cab4..b3b8a9f 100644 --- a/modeling/contrastive_training/evaluation.py +++ b/modeling/contrastive_training/evaluation.py @@ -21,7 +21,7 @@ def compute_ranking_metrics( sorted_target_authors = target_authors[sorted_indices] ranks[i] = np.where(sorted_target_authors == query_authors[i])[0].item() reciprocal_ranks[i] = 1.0 / float(ranks[i] + 1) - except: + except Exception: errors.append(i) ranks[errors] = -100 reciprocal_ranks[errors] = -100 diff --git a/modeling/contrastive_training/models.py b/modeling/contrastive_training/models.py index 9b24fc3..732c2d5 100644 --- a/modeling/contrastive_training/models.py +++ b/modeling/contrastive_training/models.py @@ -14,11 +14,7 @@ sys.path.append("../..") -from src.custom_training.model_utils import ( - encode_batches, - load_model, - setup_accelerator, -) +from src.custom_training.model_utils import load_model from src.custom_training.trainer_utils import get_dataloaders from src.hiatus_training.losses import InfoNCE_loss_full diff --git a/modeling/neurobiber/evaluate.py b/modeling/neurobiber/evaluate.py index 1b06c00..395afa7 100644 --- a/modeling/neurobiber/evaluate.py +++ b/modeling/neurobiber/evaluate.py @@ -2,8 +2,6 @@ import sys import numpy as np -import pandas as pd -import torch import json from tqdm import tqdm @@ -14,7 +12,6 @@ from modeling.neurobiber.tagger import ( load_model_and_tokenizer, get_predictions, - tag_jsonl_file, ) # Configuration diff --git a/modeling/neurobiber/prepare_dataset.py b/modeling/neurobiber/prepare_dataset.py index ea725eb..a9f7272 100644 --- a/modeling/neurobiber/prepare_dataset.py +++ b/modeling/neurobiber/prepare_dataset.py @@ -1,8 +1,7 @@ -import os import json -import pandas as pd +import os -from sklearn.model_selection import train_test_split +import pandas as pd def get_dataset_paths(): diff --git a/modeling/neurobiber/tag_counts.py b/modeling/neurobiber/tag_counts.py index 3279b4c..aaa6c17 100644 --- a/modeling/neurobiber/tag_counts.py +++ b/modeling/neurobiber/tag_counts.py @@ -1,5 +1,4 @@ import json -import numpy as np import pandas as pd from tqdm import tqdm diff --git a/modeling/neurobiber/tagger.py b/modeling/neurobiber/tagger.py index 5807680..0d9c294 100644 --- a/modeling/neurobiber/tagger.py +++ b/modeling/neurobiber/tagger.py @@ -1,7 +1,4 @@ -import os -import sys import json -from itertools import islice import torch import numpy as np diff --git a/modeling/neurobiber/train_model.py b/modeling/neurobiber/train_model.py index e6a9d9c..45d1b5a 100644 --- a/modeling/neurobiber/train_model.py +++ b/modeling/neurobiber/train_model.py @@ -1,24 +1,24 @@ +import logging import os import sys -import pandas as pd + import numpy as np +import pandas as pd +import wandb from transformers import ( - AutoTokenizer, AutoModelForSequenceClassification, - TrainingArguments, + AutoTokenizer, EarlyStoppingCallback, + TrainingArguments, ) -import wandb -import logging - sys.path.append("../..") from modeling.tagger.train_utils import ( + EvalSamplingTrainer, StreamingDataset, compute_metrics, compute_tag_level_metrics, - EvalSamplingTrainer, ) # Path Configuration @@ -53,7 +53,7 @@ ) logger = logging.getLogger(__name__) -logger.info(f"Starting training run with configuration:") +logger.info("Starting training run with configuration:") logger.info(f"Model: {MODEL_NAME}") logger.info(f"Run name: {RUN_NAME}") logger.info(f"Output directory: {OUTPUT_DIR}") diff --git a/modeling/neurobiber/train_utils.py b/modeling/neurobiber/train_utils.py index ee2ce43..99dc0a6 100644 --- a/modeling/neurobiber/train_utils.py +++ b/modeling/neurobiber/train_utils.py @@ -1,18 +1,16 @@ +import json +import subprocess from typing import Optional, Union -from transformers import Trainer -from torch.utils.data import IterableDataset + +import numpy as np +import torch from sklearn.metrics import ( - mean_squared_error, - r2_score, - mean_absolute_error, + f1_score, precision_score, recall_score, - f1_score, ) -import json -import torch -import numpy as np -import subprocess +from torch.utils.data import IterableDataset +from transformers import Trainer class StreamingDataset(IterableDataset): @@ -28,7 +26,7 @@ def __init__(self, file_path, tokenizer, max_length=512, batch_size=None): ) self.total_rows = int(result.stdout.split()[0]) print(f"Total rows in dataset: {self.total_rows}") - except Exception as e: + except Exception: self.total_rows = sum(1 for _ in open(file_path)) # Read first line to get column names diff --git a/modeling/polybiberta/models.py b/modeling/polybiberta/models.py index 3afdf65..18f3d98 100644 --- a/modeling/polybiberta/models.py +++ b/modeling/polybiberta/models.py @@ -6,15 +6,14 @@ from accelerate import Accelerator from torch.optim import AdamW from tqdm import tqdm -from transformers import AutoModelForMaskedLM -from transformers import get_cosine_schedule_with_warmup +from transformers import AutoModelForMaskedLM, get_cosine_schedule_with_warmup sys.path.append("../..") -from src.custom_training.model_utils import load_model, calculate_perplexity +from src.custom_training.model_utils import calculate_perplexity, load_model from src.custom_training.multilingual_finetuning.multilingual_trainer_utils import ( - load_all_dataloaders, alternate_loaders, + load_all_dataloaders, ) @@ -88,7 +87,7 @@ def init_model(self): ) def train_model(self): - args, device = self.args, self.device + args = self.args self.init_model() self.model.train() diff --git a/modeling/tagger/prepare_dataset.py b/modeling/tagger/prepare_dataset.py index ea725eb..a9f7272 100644 --- a/modeling/tagger/prepare_dataset.py +++ b/modeling/tagger/prepare_dataset.py @@ -1,8 +1,7 @@ -import os import json -import pandas as pd +import os -from sklearn.model_selection import train_test_split +import pandas as pd def get_dataset_paths(): diff --git a/modeling/tagger/train_model.py b/modeling/tagger/train_model.py index ce5f504..5c3dfd0 100644 --- a/modeling/tagger/train_model.py +++ b/modeling/tagger/train_model.py @@ -1,25 +1,24 @@ +import logging import os import sys -import pandas as pd + import numpy as np +import pandas as pd +import wandb from transformers import ( - AutoTokenizer, AutoModelForSequenceClassification, - TrainingArguments, - Trainer, + AutoTokenizer, EarlyStoppingCallback, + TrainingArguments, ) -import wandb -import logging - sys.path.append("../..") from modeling.tagger.train_utils import ( + EvalSamplingTrainer, StreamingDataset, compute_metrics, compute_tag_level_metrics, - EvalSamplingTrainer, ) # Path Configuration @@ -54,7 +53,7 @@ ) logger = logging.getLogger(__name__) -logger.info(f"Starting training run with configuration:") +logger.info("Starting training run with configuration:") logger.info(f"Model: {MODEL_NAME}") logger.info(f"Run name: {RUN_NAME}") logger.info(f"Output directory: {OUTPUT_DIR}") diff --git a/tests/tagger/test_additional_features.py b/tests/tagger/test_additional_features.py index d4f272c..fbf7442 100644 --- a/tests/tagger/test_additional_features.py +++ b/tests/tagger/test_additional_features.py @@ -1,7 +1,6 @@ import unittest import spacy - from biberplus.tagger import tag_text @@ -76,7 +75,18 @@ def test_interjections(self): tagged_words = tag_text(text, pipeline=self.pipeline) self.assertIn("UH", tagged_words[0]["tags"]) - def test_interjections_end(self): + def test_interjections_middle(self): + text = "I mean gosh, are you really sure about that?" + tagged_words = tag_text(text, pipeline=self.pipeline) + print(tagged_words) + self.assertIn("UH", tagged_words[2]["tags"]) + + def test_interjections_end1(self): + text = "That's a bad idea, gosh" + tagged_words = tag_text(text, pipeline=self.pipeline) + self.assertIn("UH", tagged_words[-1]["tags"]) + + def test_interjections_end2(self): text = "That's a great idea, right?" tagged_words = tag_text(text, pipeline=self.pipeline) self.assertIn("UH", tagged_words[-2]["tags"]) @@ -92,17 +102,6 @@ def test_laughter_acronyms(self): tagged_words = tag_text(text, pipeline=self.pipeline) self.assertIn("LAUGH", tagged_words[-2]["tags"]) - def test_interjections_middle(self): - text = "I mean gosh, are you really sure about that?" - tagged_words = tag_text(text, pipeline=self.pipeline) - print(tagged_words) - self.assertIn("UH", tagged_words[2]["tags"]) - - def test_interjections_end(self): - text = "That's a bad idea, gosh" - tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn("UH", tagged_words[-1]["tags"]) - def test_possessive_pronoun(self): text = "That car is hers, not mine." tagged_words = tag_text(text, pipeline=self.pipeline) diff --git a/tests/tagger/test_lexical_specificity.py b/tests/tagger/test_lexical_specificity.py index e79821c..04bad33 100644 --- a/tests/tagger/test_lexical_specificity.py +++ b/tests/tagger/test_lexical_specificity.py @@ -1,10 +1,12 @@ import unittest -from unittest.mock import patch -import spacy -import pandas as pd -from biberplus.tagger.tag_frequencies import * -from biberplus.tagger.biber_plus_tagger import BiberPlusTagger +import pandas as pd +import spacy +from biberplus.tagger.tag_frequencies import ( + calculate_mean_word_length, + calculate_total_adverbs, + calculate_type_token_ratio, +) class TestLexicalSpecificityFunctions(unittest.TestCase): From 724dd650815945082bed79e6afef1d64abf21465 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:28:00 +0000 Subject: [PATCH 05/16] chore: upgrade setup.py to pyproject.toml --- pyproject.toml | 40 ++++++++++++++++++++++++++++++++++++++++ setup.py | 34 ---------------------------------- 2 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f32e704 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "biberplus" +version = "0.3.0" +description = "A pure Python implementation of Biber's (1988) Variation across Speech and Writing linguistic tags with additional features" +readme = "README.md" +license = "MIT" +requires-python = ">=3.7" +authors = [ + { name = "Kenan Alkiek", email = "kalkiek@umich.edu" }, + { name = "David Jurgens" }, +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] +dependencies = [ + "pandas", + "numpy", + "spacy>=3.0.0", + "tqdm", + "blis", + "confection", + "PyYAML", + "factor_analyzer", + "matplotlib", + "seaborn", +] + +[project.urls] +Homepage = "https://github.com/davidjurgens/biberplus" +Repository = "https://github.com/davidjurgens/biberplus" + +[tool.setuptools.package-data] +"*" = ["tagger/constants/*.txt", "tagger/config.yaml"] diff --git a/setup.py b/setup.py deleted file mode 100644 index d358dab..0000000 --- a/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="biberplus", - version="0.3.0", - description="A pure Python implementation of Biber's (1988) Variation across Speech and Writing linguistic tags with additional features", - url="https://github.com/davidjurgens/biberplus", - author="Kenan Alkiek, David Jurgens", - author_email="kalkiek@umich.edu", - license="MIT License", - license_files=["LICENSE"], - packages=find_packages(), - include_package_data=True, - package_data={"": ["tagger/constants/*.txt", "tagger/config.yaml"]}, - install_requires=[ - "pandas", - "numpy", - "spacy>=3.0.0", - "tqdm", - "blis", - "confection", - "PyYAML", - "factor_analyzer", - "matplotlib", - "seaborn", - ], - python_requires=">=3.7", - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - ], -) From 0c5acbb7fcf3a00803576c5b2390d1e4bf1634cf Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:30:41 +0000 Subject: [PATCH 06/16] deprecate: Python 3.7 is EOL, bump to 3.10 See https://endoflife.date/python for exact dates --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f32e704..c9881aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.3.0" description = "A pure Python implementation of Biber's (1988) Variation across Speech and Writing linguistic tags with additional features" readme = "README.md" license = "MIT" -requires-python = ">=3.7" +requires-python = ">=3.10" authors = [ { name = "Kenan Alkiek", email = "kalkiek@umich.edu" }, { name = "David Jurgens" }, From 6313dc2f976edf71fc583d8b329c5b15b0107b96 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:34:50 +0000 Subject: [PATCH 07/16] docs: add PyPI trove classifiers --- pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c9881aa..346e308 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,16 @@ authors = [ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Text Processing :: Linguistic", + "Natural Language :: English", + "Operating System :: OS Independent", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "pandas", From 36a9b3d680c034304c6730f7a19c62eb670f039a Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:37:56 +0000 Subject: [PATCH 08/16] docs: bug tracker repo link --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 346e308..0a5fb29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,6 @@ dependencies = [ "numpy", "spacy>=3.0.0", "tqdm", - "blis", - "confection", "PyYAML", "factor_analyzer", "matplotlib", @@ -43,6 +41,7 @@ dependencies = [ [project.urls] Homepage = "https://github.com/davidjurgens/biberplus" Repository = "https://github.com/davidjurgens/biberplus" +Issues = "https://github.com/davidjurgens/biberplus/issues" [tool.setuptools.package-data] "*" = ["tagger/constants/*.txt", "tagger/config.yaml"] From 3aadbc64cec8a06e1a36f7f60bf1d13e7357a1f9 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:44:59 +0000 Subject: [PATCH 09/16] chore: setuptools v77; named package for data; keywords --- pyproject.toml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0a5fb29..9261bab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61.0", "wheel"] +requires = ["setuptools>=77.0", "wheel"] build-backend = "setuptools.build_meta" [project] @@ -27,6 +27,16 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] +keywords = [ + "nlp", + "linguistics", + "biber", + "register", + "corpus", + "text-analysis", + "spacy", + "variation", +] dependencies = [ "pandas", "numpy", @@ -44,4 +54,4 @@ Repository = "https://github.com/davidjurgens/biberplus" Issues = "https://github.com/davidjurgens/biberplus/issues" [tool.setuptools.package-data] -"*" = ["tagger/constants/*.txt", "tagger/config.yaml"] +biberplus = ["tagger/constants/*.txt", "tagger/config.yaml"] From f745831a58d2ec5bc22ef54c96b4ee192f9ee4fc Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:48:02 +0000 Subject: [PATCH 10/16] chore: add dev dep group (pytest, ruff) --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9261bab..006c359 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,12 @@ dependencies = [ "seaborn", ] +[project.optional-dependencies] +dev = [ + "pytest>=8.0.0", + "ruff>=0.14.0", +] + [project.urls] Homepage = "https://github.com/davidjurgens/biberplus" Repository = "https://github.com/davidjurgens/biberplus" From 7e24f954d0df34d1355273426a11eecf4633091f Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 10:59:28 +0000 Subject: [PATCH 11/16] chore: do not require plotting library installation --- README.md | 26 ++++++++++++++++++-------- biberplus/reducer/pca_reducer.py | 27 ++++++++++++++++++++++----- pyproject.toml | 6 ++++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 64c9cd9..6342395 100644 --- a/README.md +++ b/README.md @@ -35,23 +35,33 @@ Biberplus is a pure Python implementation of the linguistic tagging system intro --- ## Installation - -### From PyPI (Stable Release) -Install the latest version (0.3.0) from PyPI: - ```bash pip install biberplus ``` -For more details and package history, visit the [Biberplus project page on PyPI](https://pypi.org/project/biberplus/0.3.0/). +Biberplus requires a spaCy language model. After installation, download one: +```bash +python -m spacy download en_core_web_sm +``` + +### Optional extras -**Important:** -Biberplus depends on spaCy for text processing. After installing biberplus, you must manually download the spaCy English model by running: +For PCA/factor analysis plotting functions: ```bash -python -m spacy download en_core_web_sm +pip install biberplus[plots] ``` +For development (pytest, ruff): + +```bash +pip install biberplus[dev] +``` + +## Dependencies + +Python 3.10+ and pandas, NumPy, spaCy, tqdm, PyYAML, and factor_analyzer. + --- ## Quickstart Guide diff --git a/biberplus/reducer/pca_reducer.py b/biberplus/reducer/pca_reducer.py index 6ea3e4c..bf20cf0 100644 --- a/biberplus/reducer/pca_reducer.py +++ b/biberplus/reducer/pca_reducer.py @@ -1,10 +1,24 @@ -import matplotlib.pyplot as plt +from __future__ import annotations + import numpy as np import pandas as pd -import seaborn as sns - from sklearn.decomposition import PCA +try: + import matplotlib.pyplot as plt + import seaborn as sns + _HAS_PLOTTING = True +except ImportError: + _HAS_PLOTTING = False + + +def _require_plotting(): + if not _HAS_PLOTTING: + raise ImportError( + "Plotting requires matplotlib and seaborn. " + "Install with: pip install biberplus[plots]" + ) + def tags_pca(frequencies_df, components=2, name=None): pca = PCA(n_components=components) @@ -20,6 +34,7 @@ def tags_pca(frequencies_df, components=2, name=None): def plot_pca_2d(df): + _require_plotting() sns.set() sns.lmplot(x="PC1", y="PC2", data=df, hue="name", fit_reg=False, legend=True) plt.title("2D PCA Graph") @@ -27,8 +42,8 @@ def plot_pca_2d(df): def visualize_explained_variance(explained_variance): + _require_plotting() plt.bar(range(1, len(explained_variance) + 1), explained_variance) - plt.xlabel("PCA Feature") plt.ylabel("Explained variance") plt.title("Feature Explained Variance") @@ -36,6 +51,7 @@ def visualize_explained_variance(explained_variance): def plot_explained_variance(pca): + _require_plotting() n_components = pca.n_components_ plt.figure(figsize=(10, 5)) plt.bar(range(n_components), pca.explained_variance_, align="center") @@ -47,6 +63,7 @@ def plot_explained_variance(pca): def plot_cumulative_explained_variance(pca): + _require_plotting() n_components = pca.n_components_ explained_variance_ratio_cumsum = np.cumsum(pca.explained_variance_ratio_) plt.figure(figsize=(10, 5)) @@ -56,4 +73,4 @@ def plot_cumulative_explained_variance(pca): plt.xlabel("Principal Components") plt.title("Cumulative Explained Variance Ratio of PCA Components") plt.grid(True) - plt.show() + plt.show() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 006c359..b6f4106 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,6 @@ dependencies = [ "tqdm", "PyYAML", "factor_analyzer", - "matplotlib", - "seaborn", ] [project.optional-dependencies] @@ -53,6 +51,10 @@ dev = [ "pytest>=8.0.0", "ruff>=0.14.0", ] +plots = [ + "matplotlib", + "seaborn", +] [project.urls] Homepage = "https://github.com/davidjurgens/biberplus" From 1a258652196dc96e4d335e1b787c9fb694962470 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 11:25:50 +0000 Subject: [PATCH 12/16] docs: document the requirements and optional extras; deprecate requirements.txt --- README.md | 28 ++++++++++++++++++++++++---- pyproject.toml | 2 +- requirements.txt | 11 ----------- 3 files changed, 25 insertions(+), 16 deletions(-) delete mode 100644 requirements.txt diff --git a/README.md b/README.md index 6342395..294c9b5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Biberplus is a pure Python implementation of the linguistic tagging system intro - [Features](#features) - [Installation](#installation) + - [Optional extras](#optional-extras) - [Quickstart Guide](#quickstart-guide) - [Biber Tagger](#1-biber-tagger) - [Function Words Tagger](#2-function-words-tagger) @@ -35,11 +36,19 @@ Biberplus is a pure Python implementation of the linguistic tagging system intro --- ## Installation + ```bash pip install biberplus ``` -Biberplus requires a spaCy language model. After installation, download one: +Requires: + +- Python 3.10+ +- pandas, NumPy, spaCy, tqdm, PyYAML, factor_analyzer. +- a spaCy English language model + +After installation, download a spaCy model with: + ```bash python -m spacy download en_core_web_sm ``` @@ -52,15 +61,18 @@ For PCA/factor analysis plotting functions: pip install biberplus[plots] ``` -For development (pytest, ruff): +For development (ruff, flake8): ```bash pip install biberplus[dev] ``` -## Dependencies +Then run linting with: -Python 3.10+ and pandas, NumPy, spaCy, tqdm, PyYAML, and factor_analyzer. +```bash +ruff check . +flake8 . +``` --- @@ -69,6 +81,7 @@ Python 3.10+ and pandas, NumPy, spaCy, tqdm, PyYAML, and factor_analyzer. ### 1. Biber Tagger **Tag a string using the default configuration:** + ```python from biberplus.tagger import calculate_tag_frequencies @@ -77,6 +90,7 @@ print(frequencies_df) ``` **Tag a large corpus with GPU and multi-processing:** + ```python from biberplus.tagger import load_config, load_pipeline, calculate_tag_frequencies @@ -90,6 +104,7 @@ print(frequencies_df) ### 2. Function Words Tagger **Using the default list:** + ```python from biberplus.tagger import load_config, calculate_tag_frequencies @@ -100,6 +115,7 @@ print(frequencies_df) ``` **Using a custom list:** + ```python from biberplus.tagger import load_config, calculate_tag_frequencies @@ -118,6 +134,7 @@ print(frequencies_df) ### 3. Word-Level Tagging See exactly which tags are applied to each word: + ```python import spacy from biberplus.tagger import tag_text, load_config, load_pipeline @@ -138,6 +155,7 @@ for word in tagged_words: ``` Example output: + ``` Word: It Tags: it, PIT, CAP, PRP, SBJP Word: does Tags: VPRT, SPAU @@ -149,6 +167,7 @@ Word: likely Tags: JJ ### 4. Text Embeddings Generate an embedding vector from the textual data: + ```python from biberplus.tagger import load_config from biberplus.reducer import encode_text @@ -161,6 +180,7 @@ print(embedding) ### 5. Dimension Reduction **Using PCA:** + ```python from biberplus.tagger import load_config, load_pipeline, calculate_tag_frequencies from biberplus.reducer import tags_pca diff --git a/pyproject.toml b/pyproject.toml index b6f4106..98d9ddd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,8 +48,8 @@ dependencies = [ [project.optional-dependencies] dev = [ - "pytest>=8.0.0", "ruff>=0.14.0", + "flake8>=7.0", ] plots = [ "matplotlib", diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8a2a303..0000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -spacy>=3.0.0 -pandas -numpy -tqdm -confection -PyYAML -blis -https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.5.0/en_core_web_sm-3.5.0-py3-none-any.whl -factor_analyzer -matplotlib -seaborn \ No newline at end of file From f7274179627b727f1dd0ff200725c4cbe782d9f7 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 11:34:54 +0000 Subject: [PATCH 13/16] fix: tell setuptools package to find; lock --- pyproject.toml | 3 + uv.lock | 2356 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2359 insertions(+) create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 98d9ddd..3f022b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,3 +63,6 @@ Issues = "https://github.com/davidjurgens/biberplus/issues" [tool.setuptools.package-data] biberplus = ["tagger/constants/*.txt", "tagger/config.yaml"] + +[tool.setuptools.packages.find] +include = ["biberplus", "biberplus.*"] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..8b40057 --- /dev/null +++ b/uv.lock @@ -0,0 +1,2356 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "biberplus" +version = "0.3.0" +source = { editable = "." } +dependencies = [ + { name = "factor-analyzer" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyyaml" }, + { name = "spacy" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +dev = [ + { name = "flake8" }, + { name = "ruff" }, +] +plots = [ + { name = "matplotlib" }, + { name = "seaborn" }, +] + +[package.metadata] +requires-dist = [ + { name = "factor-analyzer" }, + { name = "flake8", marker = "extra == 'dev'", specifier = ">=7.0" }, + { name = "matplotlib", marker = "extra == 'plots'" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pyyaml" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.0" }, + { name = "seaborn", marker = "extra == 'plots'" }, + { name = "spacy", specifier = ">=3.0.0" }, + { name = "tqdm" }, +] +provides-extras = ["dev", "plots"] + +[[package]] +name = "blis" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/db/d80daf6c060618c72acecf026410b806f620cdea62b2e72f3235d7389d05/blis-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:650f1d2b28e3c875927c63deebda463a6f9d237dff30e445bfe2127718c1a344", size = 6925724, upload-time = "2025-11-17T12:27:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/06/cd/7ac854c92e33cfccc0eded48e979a9fc26a447952d07a9c7c7da7c1d6eec/blis-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b0d42420ddd543eec51ccb99d38364a0c0833b6895eced37127822de6ecacff", size = 1233606, upload-time = "2025-11-17T12:27:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ae/ad3165fdbc4ef6afef585686a778c72cd67fb5aa16ab2fd2f4494186705e/blis-1.3.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0628a030d44aa71cac5973e40c9e95ec767abaaf2fd366a094b9398885f82f2", size = 2769094, upload-time = "2025-11-17T12:27:17.883Z" }, + { url = "https://files.pythonhosted.org/packages/25/d4/7b0820f139b4ea67606d01b59ba6afbee4552ce7b2fd179f2fb7908e294f/blis-1.3.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0114cf2d8f19e0ed210f9ae92594cd0a12efa1bbbce444028b0fc365bbbb8af", size = 11300520, upload-time = "2025-11-17T12:27:20.058Z" }, + { url = "https://files.pythonhosted.org/packages/85/f3/865a4322bdbeb944744c1908e67fdabecd476613a17204956cff12d568c9/blis-1.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7e88181e9dd8430029ebaf22d41bf79e756e8c95363e9471717102c66beb4a6d", size = 2962083, upload-time = "2025-11-17T12:27:22.098Z" }, + { url = "https://files.pythonhosted.org/packages/65/a2/c2842fa1e2e6bd56eb93e41b34859a9af8b5b63669ee0442bea585d8f607/blis-1.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62fb8c731347b0f98f5f81d19d339049e61489798738467d156c66cc329b0754", size = 14177001, upload-time = "2025-11-17T12:27:24.345Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9b/3b1532f23db8bdddf3a976e9acf51e8debd94c63be5dafb8ccbab3e62935/blis-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:631836d4f335e62c30aa50a1aa0170773265c73654d296361f95180006e88c04", size = 6184429, upload-time = "2025-11-17T12:27:27.054Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0a/a4c8736bc497d386b0ffc76d321f478c03f1a4725e52092f93b38beb3786/blis-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e10c8d3e892b1dbdff365b9d00e08291876fc336915bf1a5e9f188ed087e1a91", size = 6925522, upload-time = "2025-11-17T12:27:29.199Z" }, + { url = "https://files.pythonhosted.org/packages/83/5a/3437009282f23684ecd3963a8b034f9307cdd2bf4484972e5a6b096bf9ac/blis-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66e6249564f1db22e8af1e0513ff64134041fa7e03c8dd73df74db3f4d8415a7", size = 1232787, upload-time = "2025-11-17T12:27:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0e/82221910d16259ce3017c1442c468a3f206a4143a96fbba9f5b5b81d62e8/blis-1.3.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7260da065958b4e5475f62f44895ef9d673b0f47dcf61b672b22b7dae1a18505", size = 2844596, upload-time = "2025-11-17T12:27:32.601Z" }, + { url = "https://files.pythonhosted.org/packages/6c/93/ab547f1a5c23e20bca16fbcf04021c32aac3f969be737ea4980509a7ca90/blis-1.3.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9327a6ca67de8ae76fe071e8584cc7f3b2e8bfadece4961d40f2826e1cda2df", size = 11377746, upload-time = "2025-11-17T12:27:35.342Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a6/7733820aa62da32526287a63cd85c103b2b323b186c8ee43b7772ff7017c/blis-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c4ae70629cf302035d268858a10ca4eb6242a01b2dc8d64422f8e6dcb8a8ee74", size = 3041954, upload-time = "2025-11-17T12:27:37.479Z" }, + { url = "https://files.pythonhosted.org/packages/87/53/e39d67fd3296b649772780ca6aab081412838ecb54e0b0c6432d01626a50/blis-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45866a9027d43b93e8b59980a23c5d7358b6536fc04606286e39fdcfce1101c2", size = 14251222, upload-time = "2025-11-17T12:27:39.705Z" }, + { url = "https://files.pythonhosted.org/packages/ea/44/b749f8777b020b420bceaaf60f66432fc30cc904ca5b69640ec9cbef11ed/blis-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:27f82b8633030f8d095d2b412dffa7eb6dbc8ee43813139909a20012e54422ea", size = 6171233, upload-time = "2025-11-17T12:27:41.921Z" }, + { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663, upload-time = "2025-11-17T12:27:44.482Z" }, + { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939, upload-time = "2025-11-17T12:27:46.19Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835, upload-time = "2025-11-17T12:27:48.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550, upload-time = "2025-11-17T12:27:49.958Z" }, + { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686, upload-time = "2025-11-17T12:27:52.062Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939, upload-time = "2025-11-17T12:27:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759, upload-time = "2025-11-17T12:27:56.176Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322, upload-time = "2025-11-17T12:27:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635, upload-time = "2025-11-17T12:28:00.118Z" }, + { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650, upload-time = "2025-11-17T12:28:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008, upload-time = "2025-11-17T12:28:04.589Z" }, + { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959, upload-time = "2025-11-17T12:28:06.862Z" }, + { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456, upload-time = "2025-11-17T12:28:08.779Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624, upload-time = "2025-11-17T12:28:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081, upload-time = "2025-11-17T12:28:16.436Z" }, + { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486, upload-time = "2025-11-17T12:28:18.008Z" }, + { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944, upload-time = "2025-11-17T12:28:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825, upload-time = "2025-11-17T12:28:21.354Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771, upload-time = "2025-11-17T12:28:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213, upload-time = "2025-11-17T12:28:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695, upload-time = "2025-11-17T12:28:28.401Z" }, +] + +[[package]] +name = "catalogue" +version = "2.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561, upload-time = "2023-09-25T06:29:24.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325, upload-time = "2023-09-25T06:29:23.337Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "cloudpathlib" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126, upload-time = "2025-10-07T22:47:56.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755, upload-time = "2025-10-07T22:47:54.905Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "confection" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "srsly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/d3/57c6631159a1b48d273b40865c315cf51f89df7a9d1101094ef12e3a37c2/confection-0.1.5.tar.gz", hash = "sha256:8e72dd3ca6bd4f48913cd220f10b8275978e740411654b6e8ca6d7008c590f0e", size = 38924, upload-time = "2024-05-31T16:17:01.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/00/3106b1854b45bd0474ced037dfe6b73b90fe68a68968cef47c23de3d43d2/confection-0.1.5-py3-none-any.whl", hash = "sha256:e29d3c3f8eac06b3f77eb9dfb4bf2fc6bcc9622a98ca00a698e3d019c6430b14", size = 35451, upload-time = "2024-05-31T16:16:59.075Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "cymem" +version = "2.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320, upload-time = "2025-11-14T14:58:36.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/14/462018dd384ee1848ac9c1951534a813a325abbfc161a74e2cbcb38d2469/cymem-2.0.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8efc4f308169237aade0e82877a65a563833dec32eb7ab2326120253e0e9e918", size = 43747, upload-time = "2025-11-14T14:57:11.287Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9b/c123ba65dddcd8a2bc0b3c9046766c15abe0e257c315b3040eed22cce1e2/cymem-2.0.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e03bb575a96c59bc210d7d59862747f0012696b0dac3427ce8af33c7afb3d4a2", size = 43328, upload-time = "2025-11-14T14:57:12.578Z" }, + { url = "https://files.pythonhosted.org/packages/bd/be/7b7a4cf9cd2d37e674612a86fc90b3d59bff12177f83430e62b25afaf7fc/cymem-2.0.13-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1775d3fd34cf099929b79c3e48469283642463f977af6801231f3c0e5d9c9369", size = 231539, upload-time = "2025-11-14T14:57:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/79/6d/d165c38cd4caaaf60942e2cec9998b667008f2384047ccfe0b4b5f7a1ffe/cymem-2.0.13-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e2976e38cd663f758e40b5497fa5cd183d7c5fb0d04ce81a4b42a1ba124ff0", size = 229674, upload-time = "2025-11-14T14:57:15.685Z" }, + { url = "https://files.pythonhosted.org/packages/95/c1/af83c03a93f890ca81149561b18a4a67a9aa36a1109f15e291dd2703ab12/cymem-2.0.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed9de1b9b042f76fe5c312e4359eab58bf52ac7dfdf6887368a760410d809440", size = 229805, upload-time = "2025-11-14T14:57:17.289Z" }, + { url = "https://files.pythonhosted.org/packages/03/2d/12900758b80345d9aed5892a9d61e8a5f6abbbe5837e4def373a53cd0da2/cymem-2.0.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1366c7437a209230f4b797fae10227a8206d4021d37c9f9c0d31fd97ea4feb35", size = 234018, upload-time = "2025-11-14T14:57:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8b/5fcf5430fc81098aef58cc20340e51f37b49b9d8c15766e0d5d63e7288a3/cymem-2.0.13-cp310-cp310-win_amd64.whl", hash = "sha256:7700b116524b087e0169f10f267539223b48240ef2734c3a727a9e6b4db9a671", size = 40102, upload-time = "2025-11-14T14:57:19.972Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d3/cb6c83758fe399443b858faafb7096b72535621a7af7dd9a54ff0989fa14/cymem-2.0.13-cp310-cp310-win_arm64.whl", hash = "sha256:c8dbfddfe5c604974e17c6f373cedd4d25cd67f84812ede7dea12128fa0c2015", size = 36282, upload-time = "2025-11-14T14:57:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/64/1db41f7576a6b69f70367e3c15e968fd775ba7419e12059c9966ceb826f8/cymem-2.0.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:673183466b0ff2e060d97ec5116711d44200b8f7be524323e080d215ee2d44a5", size = 43587, upload-time = "2025-11-14T14:57:22.39Z" }, + { url = "https://files.pythonhosted.org/packages/81/13/57f936fc08551323aab3f92ff6b7f4d4b89d5b4e495c870a67cb8d279757/cymem-2.0.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bee2791b3f6fc034ce41268851462bf662ff87e8947e35fb6dd0115b4644a61f", size = 43139, upload-time = "2025-11-14T14:57:23.363Z" }, + { url = "https://files.pythonhosted.org/packages/32/a6/9345754be51e0479aa387b7b6cffc289d0fd3201aaeb8dade4623abd1e02/cymem-2.0.13-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f3aee3adf16272bca81c5826eed55ba3c938add6d8c9e273f01c6b829ecfde22", size = 245063, upload-time = "2025-11-14T14:57:24.839Z" }, + { url = "https://files.pythonhosted.org/packages/d6/01/6bc654101526fa86e82bf6b05d99b2cd47c30a333cfe8622c26c0592beb2/cymem-2.0.13-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:30c4e75a3a1d809e89106b0b21803eb78e839881aa1f5b9bd27b454bc73afde3", size = 244496, upload-time = "2025-11-14T14:57:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fb/853b7b021e701a1f41687f3704d5f469aeb2a4f898c3fbb8076806885955/cymem-2.0.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec99efa03cf8ec11c8906aa4d4cc0c47df393bc9095c9dd64b89b9b43e220b04", size = 243287, upload-time = "2025-11-14T14:57:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2b/0e4664cafc581de2896d75000651fd2ce7094d33263f466185c28ffc96e4/cymem-2.0.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c90a6ecba994a15b17a3f45d7ec74d34081df2f73bd1b090e2adc0317e4e01b6", size = 248287, upload-time = "2025-11-14T14:57:29.055Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/f94c6950edbfc2aafb81194fc40b6cacc8e994e9359d3cb4328c5705b9b5/cymem-2.0.13-cp311-cp311-win_amd64.whl", hash = "sha256:ce821e6ba59148ed17c4567113b8683a6a0be9c9ac86f14e969919121efb61a5", size = 40116, upload-time = "2025-11-14T14:57:30.592Z" }, + { url = "https://files.pythonhosted.org/packages/00/df/2455eff6ac0381ff165db6883b311f7016e222e3dd62185517f8e8187ed0/cymem-2.0.13-cp311-cp311-win_arm64.whl", hash = "sha256:0dca715e708e545fd1d97693542378a00394b20a37779c1ae2c8bdbb43acef79", size = 36349, upload-time = "2025-11-14T14:57:31.573Z" }, + { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745, upload-time = "2025-11-14T14:57:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927, upload-time = "2025-11-14T14:57:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346, upload-time = "2025-11-14T14:57:34.917Z" }, + { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843, upload-time = "2025-11-14T14:57:36.503Z" }, + { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607, upload-time = "2025-11-14T14:57:38.036Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421, upload-time = "2025-11-14T14:57:39.265Z" }, + { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176, upload-time = "2025-11-14T14:57:40.706Z" }, + { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959, upload-time = "2025-11-14T14:57:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478, upload-time = "2025-11-14T14:57:42.682Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695, upload-time = "2025-11-14T14:57:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573, upload-time = "2025-11-14T14:57:44.81Z" }, + { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572, upload-time = "2025-11-14T14:57:46.023Z" }, + { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060, upload-time = "2025-11-14T14:57:47.605Z" }, + { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601, upload-time = "2025-11-14T14:57:48.861Z" }, + { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103, upload-time = "2025-11-14T14:57:50.396Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016, upload-time = "2025-11-14T14:57:51.611Z" }, + { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429, upload-time = "2025-11-14T14:57:52.582Z" }, + { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205, upload-time = "2025-11-14T14:57:53.64Z" }, + { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083, upload-time = "2025-11-14T14:57:55.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159, upload-time = "2025-11-14T14:57:57.106Z" }, + { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186, upload-time = "2025-11-14T14:57:58.334Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353, upload-time = "2025-11-14T14:58:00.562Z" }, + { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764, upload-time = "2025-11-14T14:58:01.793Z" }, + { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964, upload-time = "2025-11-14T14:58:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812, upload-time = "2025-11-14T14:58:04.227Z" }, + { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951, upload-time = "2025-11-14T14:58:05.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878, upload-time = "2025-11-14T14:58:06.95Z" }, + { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571, upload-time = "2025-11-14T14:58:08.232Z" }, + { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555, upload-time = "2025-11-14T14:58:09.429Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177, upload-time = "2025-11-14T14:58:10.594Z" }, + { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853, upload-time = "2025-11-14T14:58:12.116Z" }, + { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970, upload-time = "2025-11-14T14:58:13.114Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804, upload-time = "2025-11-14T14:58:14.113Z" }, + { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254, upload-time = "2025-11-14T14:58:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061, upload-time = "2025-11-14T14:58:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784, upload-time = "2025-11-14T14:58:17.412Z" }, + { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062, upload-time = "2025-11-14T14:58:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465, upload-time = "2025-11-14T14:58:21.815Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665, upload-time = "2025-11-14T14:58:23.512Z" }, + { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715, upload-time = "2025-11-14T14:58:24.773Z" }, +] + +[[package]] +name = "factor-analyzer" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/19/089a65c4cf7ea407ab001846989b44cf83bc488a6e1fe68e950607251720/factor_analyzer-0.5.1.tar.gz", hash = "sha256:03703cd128ee9f91c9bd191a452b06e4a28296b5a24d11823ebb5d8250e8fdf6", size = 42824, upload-time = "2024-02-08T15:10:54.279Z" } + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, + { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, + { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, + { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, + { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, + { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, + { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, + { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, + { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, + { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, + { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "murmurhash" +version = "1.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291, upload-time = "2025-11-14T09:51:15.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/3c/5e59e29fe971365d27f191a5cbf8a5fb492746e458604fe5d39810da4668/murmurhash-1.0.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4989c16053a9a83b02c520dd00a31f0877d5fd2ab8a9b6b75ed9eba0e25c489", size = 27463, upload-time = "2025-11-14T09:49:53.158Z" }, + { url = "https://files.pythonhosted.org/packages/38/3d/ace00a9b82beaa99a8a7a52e98171cfbf13c0066d2f820e84a5d572e3bd0/murmurhash-1.0.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:899068ba3d7c371e7edd093852c634cce802fefd9aaddfcc0d2fda1d7433c7f9", size = 27714, upload-time = "2025-11-14T09:49:54.855Z" }, + { url = "https://files.pythonhosted.org/packages/10/0f/34f1c4f97424ea1bc72b1e3bdf61ac34f4c5555ec9163721f1e4cafe5b1d/murmurhash-1.0.15-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe883982114de576c793fd1cf55945c8ee6453ad4c4785ac1a48f84e74fdc650", size = 122570, upload-time = "2025-11-14T09:49:55.977Z" }, + { url = "https://files.pythonhosted.org/packages/b9/75/0019717a16ce5a7b088fc50a3ecb513035e4196c5e569bf4a2e16bcc0414/murmurhash-1.0.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:342277d8d7f712d136507fb3ccdba26c076a34ca0f8d1b96f65f0daa556da2e9", size = 123194, upload-time = "2025-11-14T09:49:57.462Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a4/c1c95ce60b816c2255098164e424752779269c93f5d6dceaa213346789a2/murmurhash-1.0.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc54facccb32fe1e97d6231edd4f3e2937467c35658b26aa35bbd6a87ebb7cb0", size = 122461, upload-time = "2025-11-14T09:49:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/63/28/e1f79369a6e8d1a5901346ed2fd3a5c56e647d0b849044870c071cb64e1c/murmurhash-1.0.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e525bbd8e26e6b9ab1b56758a59b16c2fffd73bad2f7b8bf361c16f70ff1d980", size = 121676, upload-time = "2025-11-14T09:49:59.888Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7c/e2be1f5387e5898f6551cf81c4220975858b9dbda4d471b133750945599a/murmurhash-1.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:2224f30f7729717644745a6f513ea7662517dfe7b1867cf1588177f64c61df3c", size = 25156, upload-time = "2025-11-14T09:50:01.016Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/0df6e1a753de68368662cbbb8f88558e2c877d3886ac12b30953fb8ed335/murmurhash-1.0.15-cp310-cp310-win_arm64.whl", hash = "sha256:8a181494b5f03ba831f9a13f2de3aab9ef591e508e57239043d65c5c592f5837", size = 23270, upload-time = "2025-11-14T09:50:01.99Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ca/77d3e69924a8eb4508bb4f0ad34e46adbeedeb93616a71080e61e53dad71/murmurhash-1.0.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f32307fb9347680bb4fe1cbef6362fb39bd994f1b59abd8c09ca174e44199081", size = 27397, upload-time = "2025-11-14T09:50:03.077Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/a936f577d35b245d47b310f29e5e9f09fcac776c8c992f1ab51a9fb0cee2/murmurhash-1.0.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:539d8405885d1d19c005f3a2313b47e8e54b0ee89915eb8dfbb430b194328e6c", size = 27692, upload-time = "2025-11-14T09:50:04.144Z" }, + { url = "https://files.pythonhosted.org/packages/4d/64/5f8cfd1fd9cbeb43fcff96672f5bd9e7e1598d1c970f808ecd915490dc20/murmurhash-1.0.15-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4cd739a00f5a4602201b74568ddabae46ec304719d9be752fd8f534a9464b5e", size = 128396, upload-time = "2025-11-14T09:50:05.268Z" }, + { url = "https://files.pythonhosted.org/packages/ac/10/d9ce29d559a75db0d8a3f13ea12c7f541ec9de2afca38dc70418b890eedb/murmurhash-1.0.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44d211bcc3ec203c47dac06f48ee871093fcbdffa6652a6cc5ea7180306680a8", size = 128687, upload-time = "2025-11-14T09:50:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/48/cd/dc97ab7e68cdfa1537a56e36dbc846c5a66701cc39ecee2d4399fe61996c/murmurhash-1.0.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f9bf47101354fb1dc4b2e313192566f04ba295c28a37e2f71c692759acc1ba3c", size = 128198, upload-time = "2025-11-14T09:50:08.062Z" }, + { url = "https://files.pythonhosted.org/packages/53/73/32f2aaa22c1e4afae337106baf0c938abf36a6cc879cfee83a00461bbbf7/murmurhash-1.0.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c69b4d3bcd6233782a78907fe10b9b7a796bdc5d28060cf097d067bec280a5d", size = 127214, upload-time = "2025-11-14T09:50:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/82/ed/812103a7f353eba2d83655b08205e13a38c93b4db0692f94756e1eb44516/murmurhash-1.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:e43a69496342ce530bdd670264cb7c8f45490b296e4764c837ce577e3c7ebd53", size = 25241, upload-time = "2025-11-14T09:50:10.373Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5f/2c511bdd28f7c24da37a00116ffd0432b65669d098f0d0260c66ac0ffdc2/murmurhash-1.0.15-cp311-cp311-win_arm64.whl", hash = "sha256:f3e99a6ee36ef5372df5f138e3d9c801420776d3641a34a49e5c2555f44edba7", size = 23216, upload-time = "2025-11-14T09:50:11.651Z" }, + { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884, upload-time = "2025-11-14T09:50:13.133Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855, upload-time = "2025-11-14T09:50:14.859Z" }, + { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088, upload-time = "2025-11-14T09:50:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978, upload-time = "2025-11-14T09:50:17.572Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956, upload-time = "2025-11-14T09:50:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184, upload-time = "2025-11-14T09:50:19.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647, upload-time = "2025-11-14T09:50:21.049Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338, upload-time = "2025-11-14T09:50:22.359Z" }, + { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861, upload-time = "2025-11-14T09:50:23.804Z" }, + { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840, upload-time = "2025-11-14T09:50:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080, upload-time = "2025-11-14T09:50:26.566Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648, upload-time = "2025-11-14T09:50:27.788Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502, upload-time = "2025-11-14T09:50:29.339Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736, upload-time = "2025-11-14T09:50:30.545Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682, upload-time = "2025-11-14T09:50:31.624Z" }, + { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370, upload-time = "2025-11-14T09:50:33.264Z" }, + { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955, upload-time = "2025-11-14T09:50:34.488Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108, upload-time = "2025-11-14T09:50:35.53Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054, upload-time = "2025-11-14T09:50:36.591Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153, upload-time = "2025-11-14T09:50:37.856Z" }, + { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345, upload-time = "2025-11-14T09:50:39.346Z" }, + { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990, upload-time = "2025-11-14T09:50:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812, upload-time = "2025-11-14T09:50:41.971Z" }, + { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398, upload-time = "2025-11-14T09:50:43.023Z" }, + { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029, upload-time = "2025-11-14T09:50:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912, upload-time = "2025-11-14T09:50:45.266Z" }, + { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847, upload-time = "2025-11-14T09:50:46.819Z" }, + { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267, upload-time = "2025-11-14T09:50:48.298Z" }, + { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894, upload-time = "2025-11-14T09:50:49.485Z" }, + { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054, upload-time = "2025-11-14T09:50:50.747Z" }, + { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579, upload-time = "2025-11-14T09:50:52.278Z" }, + { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341, upload-time = "2025-11-14T09:50:53.295Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146, upload-time = "2025-11-14T09:50:54.705Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141, upload-time = "2025-11-14T09:50:55.829Z" }, + { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898, upload-time = "2025-11-14T09:50:56.946Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040, upload-time = "2025-11-14T09:50:58.234Z" }, + { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239, upload-time = "2025-11-14T09:50:59.95Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811, upload-time = "2025-11-14T09:51:01.289Z" }, + { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817, upload-time = "2025-11-14T09:51:02.493Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219, upload-time = "2025-11-14T09:51:03.563Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/1e/b184654a856e75e975a6ee95d6577b51c271cd92cb2b020c9378f53e0032/pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850", size = 10313247, upload-time = "2026-01-21T15:50:15.775Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/e04a547ad0f0183bf151fd7c7a477468e3b85ff2ad231c566389e6cc9587/pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2", size = 9913131, upload-time = "2026-01-21T15:50:18.611Z" }, + { url = "https://files.pythonhosted.org/packages/a2/93/bb77bfa9fc2aba9f7204db807d5d3fb69832ed2854c60ba91b4c65ba9219/pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5", size = 10741925, upload-time = "2026-01-21T15:50:21.058Z" }, + { url = "https://files.pythonhosted.org/packages/62/fb/89319812eb1d714bfc04b7f177895caeba8ab4a37ef6712db75ed786e2e0/pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae", size = 11245979, upload-time = "2026-01-21T15:50:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/a9/63/684120486f541fc88da3862ed31165b3b3e12b6a1c7b93be4597bc84e26c/pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9", size = 11756337, upload-time = "2026-01-21T15:50:25.932Z" }, + { url = "https://files.pythonhosted.org/packages/39/92/7eb0ad232312b59aec61550c3c81ad0743898d10af5df7f80bc5e5065416/pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d", size = 12325517, upload-time = "2026-01-21T15:50:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/51/27/bf9436dd0a4fc3130acec0828951c7ef96a0631969613a9a35744baf27f6/pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd", size = 9881576, upload-time = "2026-01-21T15:50:30.149Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/c618b871fce0159fd107516336e82891b404e3f340821853c2fc28c7830f/pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b", size = 9140807, upload-time = "2026-01-21T15:50:32.308Z" }, + { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, + { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, + { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, + { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, + { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, + { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, + { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, + { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, + { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, + { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, + { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +] + +[[package]] +name = "preshed" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cymem" }, + { name = "murmurhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/34/eb4f5f0f678e152a96e826da867d2f41c4b18a2d589e40e1dd3347219e91/preshed-3.0.12.tar.gz", hash = "sha256:b73f9a8b54ee1d44529cc6018356896cff93d48f755f29c134734d9371c0d685", size = 15027, upload-time = "2025-11-17T13:00:33.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/d0/1245d6d89b051dd5356ffaaa43da05408f37d2da4cfadcf77356ba46da4f/preshed-3.0.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8f0bc207bb5bfe69e3a232367c264cac900dc14e9219cd061b98eaca9e7da61", size = 128866, upload-time = "2025-11-17T12:59:06.633Z" }, + { url = "https://files.pythonhosted.org/packages/24/24/f06650f22450888434a51b17971b650186d2e68f5eaf292e6e8e4be7974c/preshed-3.0.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8a8d571c044ddab5369d30d172c87545f44daa1510bde92b7e0144a8f4f92b", size = 124848, upload-time = "2025-11-17T12:59:08.641Z" }, + { url = "https://files.pythonhosted.org/packages/88/a1/78bdd4938c3286998c0609491c4a0a8aee2f4de4003364112c295a2f32b8/preshed-3.0.12-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6cca080ac9bbc978625c8f0c56ef17471162193c7c1a4622fbde7721da1bdd40", size = 780279, upload-time = "2025-11-17T12:59:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/6fbf083346a007927a9e4ce3686ae54ba74191e74fc3af34863ea7be9dea/preshed-3.0.12-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cfd3672007c7b7cac554a0e5f263d7bc94109dc508ee1ef43b2f6ec8c2e2e9e8", size = 781954, upload-time = "2025-11-17T12:59:11.574Z" }, + { url = "https://files.pythonhosted.org/packages/91/c3/f28c7a6cc03e85002780b75249c3557c0fe503792ac66a7b9c5379569999/preshed-3.0.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e01609074713aba93a8143480e67942fbe6898fe134b98d813819bec42a8cae7", size = 799772, upload-time = "2025-11-17T12:59:14.371Z" }, + { url = "https://files.pythonhosted.org/packages/46/25/ca22fa0db162e286db7a94a4f08c1ceb4872d3d64610b807148935ae084c/preshed-3.0.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:30d8a53015663b0d666012bc10d22e8bdd7359191d84a8980ae902e0b87caf24", size = 820532, upload-time = "2025-11-17T12:59:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/0f/57/459a6eea7e15034756f4c2650a9aba6d023aa7976748b18476bd4c0b6fef/preshed-3.0.12-cp310-cp310-win_amd64.whl", hash = "sha256:bf2235bbe09b4862b914086f37a065cc84259e1b53c8ed996cbbd6519ea36b62", size = 117482, upload-time = "2025-11-17T12:59:18.36Z" }, + { url = "https://files.pythonhosted.org/packages/80/1f/a7b648a57d259891bd9b2c8ef1978622fa37b46a9368f054881488b9b4fe/preshed-3.0.12-cp310-cp310-win_arm64.whl", hash = "sha256:139d08b10693bfccb0ea000f47dcca5fc4a78fc1b96c1832c920be9b0a4c8f04", size = 105504, upload-time = "2025-11-17T12:59:19.562Z" }, + { url = "https://files.pythonhosted.org/packages/1e/54/d1e02d0a0ea348fb6a769506166e366abfe87ee917c2f11f7139c7acbf10/preshed-3.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc45fda3fd4ae1ae15c37f18f0777cf389ce9184ef8884b39b18894416fd1341", size = 128439, upload-time = "2025-11-17T12:59:21.317Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cb/685ca57ca6e438345b3f6c20226705a0e056a3de399a5bf8a9ee89b3dd2b/preshed-3.0.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75d6e628bc78c022dbb9267242715718f862c3105927732d166076ff009d65de", size = 124544, upload-time = "2025-11-17T12:59:22.944Z" }, + { url = "https://files.pythonhosted.org/packages/f8/07/018fcd3bf298304e1570065cf80601ac16acd29f799578fd47b715dd3ca2/preshed-3.0.12-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b901cff5c814facf7a864b0a4c14a16d45fa1379899a585b3fb48ee36a2dccdb", size = 824728, upload-time = "2025-11-17T12:59:24.614Z" }, + { url = "https://files.pythonhosted.org/packages/79/dc/d888b328fcedae530df53396d9fc0006026aa8793fec54d7d34f57f31ff5/preshed-3.0.12-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d1099253bf73dd3c39313280bd5331841f769637b27ddb576ff362c4e7bad298", size = 825969, upload-time = "2025-11-17T12:59:26.493Z" }, + { url = "https://files.pythonhosted.org/packages/21/51/f19933301f42ece1ffef1f7f4c370d09f0351c43c528e66fac24560e44d2/preshed-3.0.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1af4a049ffe9d0246e5dc10d6f54820ed064c40e5c3f7b6526127c664008297c", size = 842346, upload-time = "2025-11-17T12:59:28.092Z" }, + { url = "https://files.pythonhosted.org/packages/51/46/025f60fd3d51bf60606a0f8f0cd39c40068b9b5e4d249bca1682e4ff09c3/preshed-3.0.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57159bcedca0cb4c99390f8a6e730f8659fdb663a5a3efcd9c4531e0f54b150e", size = 865504, upload-time = "2025-11-17T12:59:29.648Z" }, + { url = "https://files.pythonhosted.org/packages/88/b5/2e6ee5ab19b03e7983fc5e1850c812fb71dc178dd140d6aca3b45306bdf7/preshed-3.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:8fe9cf1745e203e5aa58b8700436f78da1dcf0f0e2efb0054b467effd9d7d19d", size = 117736, upload-time = "2025-11-17T12:59:30.974Z" }, + { url = "https://files.pythonhosted.org/packages/1e/17/8a0a8f4b01e71b5fb7c5cd4c9fec04d7b852d42f1f9e096b01e7d2b16b17/preshed-3.0.12-cp311-cp311-win_arm64.whl", hash = "sha256:12d880f8786cb6deac34e99b8b07146fb92d22fbca0023208e03325f5944606b", size = 105127, upload-time = "2025-11-17T12:59:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f7/ff3aca937eeaee19c52c45ddf92979546e52ed0686e58be4bc09c47e7d88/preshed-3.0.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2779861f5d69480493519ed123a622a13012d1182126779036b99d9d989bf7e9", size = 129958, upload-time = "2025-11-17T12:59:33.391Z" }, + { url = "https://files.pythonhosted.org/packages/80/24/fd654a9c0f5f3ed1a9b1d8a392f063ae9ca29ad0b462f0732ae0147f7cee/preshed-3.0.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffe1fd7d92f51ed34383e20d8b734780c814ca869cfdb7e07f2d31651f90cdf4", size = 124550, upload-time = "2025-11-17T12:59:34.688Z" }, + { url = "https://files.pythonhosted.org/packages/71/49/8271c7f680696f4b0880f44357d2a903d649cb9f6e60a1efc97a203104df/preshed-3.0.12-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:91893404858502cc4e856d338fef3d2a4a552135f79a1041c24eb919817c19db", size = 874987, upload-time = "2025-11-17T12:59:36.062Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a5/ca200187ca1632f1e2c458b72f1bd100fa8b55deecd5d72e1e4ebf09e98c/preshed-3.0.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9e06e8f2ba52f183eb9817a616cdebe84a211bb859a2ffbc23f3295d0b189638", size = 866499, upload-time = "2025-11-17T12:59:37.586Z" }, + { url = "https://files.pythonhosted.org/packages/87/a1/943b61f850c44899910c21996cb542d0ef5931744c6d492fdfdd8457e693/preshed-3.0.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbe8b8a2d4f9af14e8a39ecca524b9de6defc91d8abcc95eb28f42da1c23272c", size = 878064, upload-time = "2025-11-17T12:59:39.651Z" }, + { url = "https://files.pythonhosted.org/packages/3e/75/d7fff7f1fa3763619aa85d6ba70493a5d9c6e6ea7958a6e8c9d3e6e88bbe/preshed-3.0.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5d0aaac9c5862f5471fddd0c931dc64d3af2efc5fe3eb48b50765adb571243b9", size = 900540, upload-time = "2025-11-17T12:59:41.384Z" }, + { url = "https://files.pythonhosted.org/packages/e4/12/a2285b78bd097a1e53fb90a1743bc8ce0d35e5b65b6853f3b3c47da398ca/preshed-3.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:0eb8d411afcb1e3b12a0602fb6a0e33140342a732a795251a0ce452aba401dc0", size = 118298, upload-time = "2025-11-17T12:59:42.65Z" }, + { url = "https://files.pythonhosted.org/packages/0b/34/4e8443fe99206a2fcfc63659969a8f8c8ab184836533594a519f3899b1ad/preshed-3.0.12-cp312-cp312-win_arm64.whl", hash = "sha256:dcd3d12903c9f720a39a5c5f1339f7f46e3ab71279fb7a39776768fb840b6077", size = 104746, upload-time = "2025-11-17T12:59:43.934Z" }, + { url = "https://files.pythonhosted.org/packages/1e/36/1d3df6f9f37efc34be4ee3013b3bb698b06f1e372f80959851b54d8efdb2/preshed-3.0.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3deb3ab93d50c785eaa7694a8e169eb12d00263a99c91d56511fe943bcbacfb6", size = 128023, upload-time = "2025-11-17T12:59:45.157Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d4/3ca81f42978da1b81aa57b3e9b5193d8093e187787a3b2511d16b30b7c62/preshed-3.0.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604350001238dab63dc14774ee30c257b5d71c7be976dbecd1f1ed37529f60f", size = 122851, upload-time = "2025-11-17T12:59:46.439Z" }, + { url = "https://files.pythonhosted.org/packages/17/73/f388398f8d789f69b510272d144a9186d658423f6d3ecc484c0fe392acec/preshed-3.0.12-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04fb860a8aab18d2201f06159337eda5568dc5eed218570d960fad79e783c7d0", size = 835926, upload-time = "2025-11-17T12:59:47.882Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/b7170933451cbc27eaefd57b36f61a5e7e7c8da50ae24f819172e0ca8a4d/preshed-3.0.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d0c8fcd44996031c46a0aa6773c7b7aa5ee58c3ee87bc05236dacd5599d35063", size = 827294, upload-time = "2025-11-17T12:59:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ec/6504730d811c0a375721db2107d31684ec17ee5b7bb3796ecfa41e704d41/preshed-3.0.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b07efc3abd3714ce01cf67db0a2dada6e829ab7def74039d446e49ddb32538c5", size = 838809, upload-time = "2025-11-17T12:59:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/7e/1a/09d13240c1fbadcc0603e2fe029623045a36c88b4b50b02e7fdc89e3b88e/preshed-3.0.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f184ef184b76e0e4707bce2395008779e4dfa638456b13b18469c2c1a42903a6", size = 861448, upload-time = "2025-11-17T12:59:52.702Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/9523160153037ee8337672249449be416ee92236f32602e7dd643767814f/preshed-3.0.12-cp313-cp313-win_amd64.whl", hash = "sha256:ebb3da2dc62ab09e5dc5a00ec38e7f5cdf8741c175714ab4a80773d8ee31b495", size = 117413, upload-time = "2025-11-17T12:59:54.4Z" }, + { url = "https://files.pythonhosted.org/packages/79/eb/4263e6e896753b8e2ffa93035458165850a5ea81d27e8888afdbfd8fa9c4/preshed-3.0.12-cp313-cp313-win_arm64.whl", hash = "sha256:b36a2cf57a5ca6e78e69b569c92ef3bdbfb00e3a14859e201eec6ab3bdc27085", size = 104041, upload-time = "2025-11-17T12:59:55.596Z" }, + { url = "https://files.pythonhosted.org/packages/77/39/7b33910b7ba3db9ce1515c39eb4657232913fb171fe701f792ef50726e60/preshed-3.0.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0d8b458dfbd6cc5007d045fa5638231328e3d6f214fd24ab999cc10f8b9097e5", size = 129211, upload-time = "2025-11-17T12:59:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/32/67/97dceebe0b2b4dd94333e4ec283d38614f92996de615859a952da082890d/preshed-3.0.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8e9196e2ea704243a69df203e0c9185eb7c5c58c3632ba1c1e2e2e0aa3aae3b4", size = 123311, upload-time = "2025-11-17T12:59:58.449Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6f/f3772f6eaad1eae787f82ffb65a81a4a1993277eacf5a78a29da34608323/preshed-3.0.12-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ffa644e1730012ed435fb9d0c3031ea19a06b11136eff5e9b96b2aa25ec7a5f5", size = 831683, upload-time = "2025-11-17T13:00:00.229Z" }, + { url = "https://files.pythonhosted.org/packages/1a/93/997d39ca61202486dd06c669b4707a5b8e5d0c2c922db9f7744fd6a12096/preshed-3.0.12-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:39e83a16ce53e4a3c41c091fe4fe1c3d28604e63928040da09ba0c5d5a7ca41e", size = 830035, upload-time = "2025-11-17T13:00:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f2/51bf44e3fdbef08d40a832181842cd9b21b11c3f930989f4ff17e9201e12/preshed-3.0.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2ec9bc0baee426303a644c7bf531333d4e7fd06fedf07f62ee09969c208d578d", size = 841728, upload-time = "2025-11-17T13:00:03.643Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b1/2d0e3d23d9f885f7647654d770227eb13e4d892deb9b0ed50b993d63fb18/preshed-3.0.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7db058f1b4a3d4d51c4c05b379c6cc9c36fcad00160923cb20ca1c7030581ea4", size = 858860, upload-time = "2025-11-17T13:00:05.185Z" }, + { url = "https://files.pythonhosted.org/packages/e7/57/7c28c7f6f9bfce02796b54f1f6acd2cebb3fa3f14a2dce6fb3c686e3c3a8/preshed-3.0.12-cp314-cp314-win_amd64.whl", hash = "sha256:c87a54a55a2ba98d0c3fd7886295f2825397aff5a7157dcfb89124f6aa2dca41", size = 120325, upload-time = "2025-11-17T13:00:06.428Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/df235ca679a08e09103983ec17c668f96abe897eadbe18d635972b43d8a9/preshed-3.0.12-cp314-cp314-win_arm64.whl", hash = "sha256:d9c5f10b4b971d71d163c2416b91b7136eae54ef3183b1742bb5993269af1b18", size = 107393, upload-time = "2025-11-17T13:00:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f1/51a2a72381c8aa3aeb8305d88e720c745048527107e649c01b8d49d6b5bf/preshed-3.0.12-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2739a9c57efcfa16466fa6e0257d67f0075a9979dc729585fbadaed7383ab449", size = 137703, upload-time = "2025-11-17T13:00:09.001Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/f3c3d50647f3af6ce6441c596a4f6fb0216d549432ef51f61c0c1744c9b9/preshed-3.0.12-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:364249656bfbf98b4008fac707f35835580ec56207f7cbecdafef6ebb6a595a6", size = 134889, upload-time = "2025-11-17T13:00:10.29Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/012dbae28a0b88cd98eae99f87701ffbe3a7d2ea3de345cb8a6a6e1b16cd/preshed-3.0.12-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f933d509ee762a90f62573aaf189eba94dfee478fca13ea2183b2f8a1bb8f7e", size = 911078, upload-time = "2025-11-17T13:00:11.911Z" }, + { url = "https://files.pythonhosted.org/packages/88/c1/0cd0f8cdb91f63c298320cf946c4b97adfb8e8d3a5d454267410c90fcfaa/preshed-3.0.12-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f73f4e29bf90e58034e6f5fa55e6029f3f2d7c042a7151ed487b49898b0ce887", size = 930506, upload-time = "2025-11-17T13:00:13.375Z" }, + { url = "https://files.pythonhosted.org/packages/20/1a/cab79b3181b2150eeeb0e2541c2bd4e0830e1e068b8836b24ea23610cec3/preshed-3.0.12-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a61ede0c3d18f1ae128113f785a396351a46f4634beccfdf617b0a86008b154d", size = 900009, upload-time = "2025-11-17T13:00:14.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/9a/5ea9d6d95d5c07ba70166330a43bff7f0a074d0134eb7984eca6551e8c70/preshed-3.0.12-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eafc08a86f77be78e722d96aa8a3a0aef0e3c7ac2f2ada22186a138e63d4033c", size = 910826, upload-time = "2025-11-17T13:00:16.861Z" }, + { url = "https://files.pythonhosted.org/packages/92/71/39024f9873ff317eac724b2759e94d013703800d970d51de77ccc6afff7e/preshed-3.0.12-cp314-cp314t-win_amd64.whl", hash = "sha256:fadaad54973b8697d5ef008735e150bd729a127b6497fd2cb068842074a6f3a7", size = 141358, upload-time = "2025-11-17T13:00:18.167Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0d/431bb85252119f5d2260417fa7d164619b31eed8f1725b364dc0ade43a8e/preshed-3.0.12-cp314-cp314t-win_arm64.whl", hash = "sha256:c0c0d3b66b4c1e40aa6042721492f7b07fc9679ab6c361bc121aa54a1c3ef63f", size = 114839, upload-time = "2025-11-17T13:00:19.513Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835, upload-time = "2025-12-10T07:07:39.385Z" }, + { url = "https://files.pythonhosted.org/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381, upload-time = "2025-12-10T07:07:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632, upload-time = "2025-12-10T07:07:43.899Z" }, + { url = "https://files.pythonhosted.org/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788, upload-time = "2025-12-10T07:07:45.982Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706, upload-time = "2025-12-10T07:07:48.111Z" }, + { url = "https://files.pythonhosted.org/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451, upload-time = "2025-12-10T07:07:49.873Z" }, + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, + { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, + { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, + { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, + { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, + { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, + { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, + { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, + { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, + { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, + { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, + { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, + { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, + { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, + { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2d/51006cd369b8e7879e1c630999a19d1fbf6f8b5ed3e33374f29dc87e53b3/scipy-1.17.0-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a", size = 31346803, upload-time = "2026-01-10T21:28:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2e/2349458c3ce445f53a6c93d4386b1c4c5c0c540917304c01222ff95ff317/scipy-1.17.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2", size = 27967182, upload-time = "2026-01-10T21:29:04.107Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7c/df525fbfa77b878d1cfe625249529514dc02f4fd5f45f0f6295676a76528/scipy-1.17.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467", size = 20139125, upload-time = "2026-01-10T21:29:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/33/11/fcf9d43a7ed1234d31765ec643b0515a85a30b58eddccc5d5a4d12b5f194/scipy-1.17.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e", size = 22443554, upload-time = "2026-01-10T21:29:15.888Z" }, + { url = "https://files.pythonhosted.org/packages/80/5c/ea5d239cda2dd3d31399424967a24d556cf409fbea7b5b21412b0fd0a44f/scipy-1.17.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67", size = 32757834, upload-time = "2026-01-10T21:29:23.406Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7e/8c917cc573310e5dc91cbeead76f1b600d3fb17cf0969db02c9cf92e3cfa/scipy-1.17.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73", size = 34995775, upload-time = "2026-01-10T21:29:31.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/43/176c0c3c07b3f7df324e7cdd933d3e2c4898ca202b090bd5ba122f9fe270/scipy-1.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b", size = 34841240, upload-time = "2026-01-10T21:29:39.995Z" }, + { url = "https://files.pythonhosted.org/packages/44/8c/d1f5f4b491160592e7f084d997de53a8e896a3ac01cd07e59f43ca222744/scipy-1.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b", size = 37394463, upload-time = "2026-01-10T21:29:48.723Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ec/42a6657f8d2d087e750e9a5dde0b481fd135657f09eaf1cf5688bb23c338/scipy-1.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061", size = 37053015, upload-time = "2026-01-10T21:30:51.418Z" }, + { url = "https://files.pythonhosted.org/packages/27/58/6b89a6afd132787d89a362d443a7bddd511b8f41336a1ae47f9e4f000dc4/scipy-1.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:9244608d27eafe02b20558523ba57f15c689357c85bdcfe920b1828750aa26eb", size = 24951312, upload-time = "2026-01-10T21:30:56.771Z" }, + { url = "https://files.pythonhosted.org/packages/e9/01/f58916b9d9ae0112b86d7c3b10b9e685625ce6e8248df139d0fcb17f7397/scipy-1.17.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1", size = 31706502, upload-time = "2026-01-10T21:29:56.326Z" }, + { url = "https://files.pythonhosted.org/packages/59/8e/2912a87f94a7d1f8b38aabc0faf74b82d3b6c9e22be991c49979f0eceed8/scipy-1.17.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1", size = 28380854, upload-time = "2026-01-10T21:30:01.554Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1c/874137a52dddab7d5d595c1887089a2125d27d0601fce8c0026a24a92a0b/scipy-1.17.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232", size = 20552752, upload-time = "2026-01-10T21:30:05.93Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/7518d171cb735f6400f4576cf70f756d5b419a07fe1867da34e2c2c9c11b/scipy-1.17.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d", size = 22803972, upload-time = "2026-01-10T21:30:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/7c/74/3498563a2c619e8a3ebb4d75457486c249b19b5b04a30600dfd9af06bea5/scipy-1.17.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba", size = 32829770, upload-time = "2026-01-10T21:30:16.359Z" }, + { url = "https://files.pythonhosted.org/packages/48/d1/7b50cedd8c6c9d6f706b4b36fa8544d829c712a75e370f763b318e9638c1/scipy-1.17.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db", size = 35051093, upload-time = "2026-01-10T21:30:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/e2/82/a2d684dfddb87ba1b3ea325df7c3293496ee9accb3a19abe9429bce94755/scipy-1.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf", size = 34909905, upload-time = "2026-01-10T21:30:28.704Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5e/e565bd73991d42023eb82bb99e51c5b3d9e2c588ca9d4b3e2cc1d3ca62a6/scipy-1.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f", size = 37457743, upload-time = "2026-01-10T21:30:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/58/a8/a66a75c3d8f1fb2b83f66007d6455a06a6f6cf5618c3dc35bc9b69dd096e/scipy-1.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088", size = 37098574, upload-time = "2026-01-10T21:30:40.782Z" }, + { url = "https://files.pythonhosted.org/packages/56/a5/df8f46ef7da168f1bc52cd86e09a9de5c6f19cc1da04454d51b7d4f43408/scipy-1.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:031121914e295d9791319a1875444d55079885bbae5bdc9c5e0f2ee5f09d34ff", size = 25246266, upload-time = "2026-01-10T21:30:45.923Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "setuptools" +version = "80.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smart-open" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/9a/0a7acb748b86e2922982366d780ca4b16c33f7246fa5860d26005c97e4f3/smart_open-7.5.0.tar.gz", hash = "sha256:f394b143851d8091011832ac8113ea4aba6b92e6c35f6e677ddaaccb169d7cb9", size = 53920, upload-time = "2025-11-08T21:38:40.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/95/bc978be7ea0babf2fb48a414b6afaad414c6a9e8b1eafc5b8a53c030381a/smart_open-7.5.0-py3-none-any.whl", hash = "sha256:87e695c5148bbb988f15cec00971602765874163be85acb1c9fb8abc012e6599", size = 63940, upload-time = "2025-11-08T21:38:39.024Z" }, +] + +[[package]] +name = "spacy" +version = "3.8.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, + { name = "cymem" }, + { name = "jinja2" }, + { name = "murmurhash" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "spacy-legacy" }, + { name = "spacy-loggers" }, + { name = "srsly" }, + { name = "thinc" }, + { name = "tqdm" }, + { name = "typer-slim" }, + { name = "wasabi" }, + { name = "weasel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/9f/424244b0e2656afc9ff82fb7a96931a47397bfce5ba382213827b198312a/spacy-3.8.11.tar.gz", hash = "sha256:54e1e87b74a2f9ea807ffd606166bf29ac45e2bd81ff7f608eadc7b05787d90d", size = 1326804, upload-time = "2025-11-17T20:40:03.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/63/f23db7119e0bb7740d74eff4583543824be84e7c0aad1c87683b8f40a17e/spacy-3.8.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9cc7f775cfc41ccb8be63bd6258a1ec4613d4ad3859f2ba2c079f34240b21f6", size = 6499016, upload-time = "2025-11-17T20:38:22.359Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e4/e8c0f0561e8b29b4f38ba3d491fca427faa750765df3e27850036af28762/spacy-3.8.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be9d665be8581926fba4303543ba189d34e8517803052551b000cf1a1af33b87", size = 6159121, upload-time = "2025-11-17T20:38:24.85Z" }, + { url = "https://files.pythonhosted.org/packages/15/7a/7ce7320f2a384023240fad0e6b7ffb2e3717ae4cc09ec0770706fd20c419/spacy-3.8.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:06e46ad776a1b20cc6296fe04890dea8a7b4e4653d7e8c143dd4a707f7ae2670", size = 30763429, upload-time = "2025-11-17T20:38:27.001Z" }, + { url = "https://files.pythonhosted.org/packages/db/36/b16df8f5ba8d5fc3d2b23f004eb55f3edf4f3345e743efdd560b6b20faf8/spacy-3.8.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1b91199926eb9de507f7bfc63090b17ee9a12663bcfc76357560c2c7ef4750a", size = 31002535, upload-time = "2025-11-17T20:38:30.115Z" }, + { url = "https://files.pythonhosted.org/packages/6e/be/58183313f1401fff896d3dd8f8da977847fb1c205a2c2a8a7030e81da265/spacy-3.8.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1d4c506adcbefd19ead59daca2e0e61ce669ff35372cc9c23aae1b292c57f94", size = 31033341, upload-time = "2025-11-17T20:38:33.06Z" }, + { url = "https://files.pythonhosted.org/packages/94/08/d490ed3a4ea070734c58cf1f2e3e6081a20630067bca2c58d5dbcfb36558/spacy-3.8.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d885a2bf427c854c5a5f1dda7451924a1f2c036aefaa2946c741201ff05a915a", size = 31882346, upload-time = "2025-11-17T20:38:35.596Z" }, + { url = "https://files.pythonhosted.org/packages/79/38/e64856b3f768754def0f5dc4c5fb3f692d96a193eec7e2eee03d37c233b6/spacy-3.8.11-cp310-cp310-win_amd64.whl", hash = "sha256:909d12ff2365c2e7ebf0258ddc566d2b361ef1fd2e7684ce1af5f7022111e366", size = 15346864, upload-time = "2025-11-17T20:38:37.95Z" }, + { url = "https://files.pythonhosted.org/packages/74/d3/0c795e6f31ee3535b6e70d08e89fc22247b95b61f94fc8334a01d39bf871/spacy-3.8.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a12d83e8bfba07563300ae5e0086548e41aa4bfe3734c97dda87e0eec813df0d", size = 6487958, upload-time = "2025-11-17T20:38:40.378Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2a/83ca9b4d0a2b31adcf0ced49fa667212d12958f75d4e238618a60eb50b10/spacy-3.8.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e07a50b69500ef376326545353a470f00d1ed7203c76341b97242af976e3681a", size = 6148078, upload-time = "2025-11-17T20:38:42.524Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f0/ff520df18a6152ba2dbf808c964014308e71a48feb4c7563f2a6cd6e668d/spacy-3.8.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:718b7bb5e83c76cb841ed6e407f7b40255d0b46af7101a426c20e04af3afd64e", size = 32056451, upload-time = "2025-11-17T20:38:44.92Z" }, + { url = "https://files.pythonhosted.org/packages/9d/3a/6c44c0b9b6a70595888b8d021514ded065548a5b10718ac253bd39f9fd73/spacy-3.8.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f860f9d51c1aeb2d61852442b232576e4ca4d239cb3d1b40ac452118b8eb2c68", size = 32302908, upload-time = "2025-11-17T20:38:47.672Z" }, + { url = "https://files.pythonhosted.org/packages/db/77/00e99e00efd4c2456772befc48400c2e19255140660d663e16b6924a0f2e/spacy-3.8.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ff8d928ce70d751b7bb27f60ee5e3a308216efd4ab4517291e6ff05d9b194840", size = 32280936, upload-time = "2025-11-17T20:38:50.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/da/692b51e9e5be2766d2d1fb9a7c8122cfd99c337570e621f09c40ce94ad17/spacy-3.8.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3f3cb91d7d42fafd92b8d5bf9f696571170d2f0747f85724a2c5b997753e33c9", size = 33117270, upload-time = "2025-11-17T20:38:53.596Z" }, + { url = "https://files.pythonhosted.org/packages/9b/13/a542ac9b61d071f3328fda1fd8087b523fb7a4f2c340010bc70b1f762485/spacy-3.8.11-cp311-cp311-win_amd64.whl", hash = "sha256:745c190923584935272188c604e0cc170f4179aace1025814a25d92ee90cf3de", size = 15348350, upload-time = "2025-11-17T20:38:56.833Z" }, + { url = "https://files.pythonhosted.org/packages/23/53/975c16514322f6385d6caa5929771613d69f5458fb24f03e189ba533f279/spacy-3.8.11-cp311-cp311-win_arm64.whl", hash = "sha256:27535d81d9dee0483b66660cadd93d14c1668f55e4faf4386aca4a11a41a8b97", size = 14701913, upload-time = "2025-11-17T20:38:59.507Z" }, + { url = "https://files.pythonhosted.org/packages/51/fb/01eadf4ba70606b3054702dc41fc2ccf7d70fb14514b3cd57f0ff78ebea8/spacy-3.8.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aa1ee8362074c30098feaaf2dd888c829a1a79c4311eec1b117a0a61f16fa6dd", size = 6073726, upload-time = "2025-11-17T20:39:01.679Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f8/07b03a2997fc2621aaeafae00af50f55522304a7da6926b07027bb6d0709/spacy-3.8.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75a036d04c2cf11d6cb566c0a689860cc5a7a75b439e8fea1b3a6b673dabf25d", size = 5724702, upload-time = "2025-11-17T20:39:03.486Z" }, + { url = "https://files.pythonhosted.org/packages/13/0c/c4fa0f379dbe3258c305d2e2df3760604a9fcd71b34f8f65c23e43f4cf55/spacy-3.8.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cb599d2747d4a59a5f90e8a453c149b13db382a8297925cf126333141dbc4f7", size = 32727774, upload-time = "2025-11-17T20:39:05.894Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8e/6a4ba82bed480211ebdf5341b0f89e7271b454307525ac91b5e447825914/spacy-3.8.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:94632e302ad2fb79dc285bf1e9e4d4a178904d5c67049e0e02b7fb4a77af85c4", size = 33215053, upload-time = "2025-11-17T20:39:08.588Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bc/44d863d248e9d7358c76a0aa8b3f196b8698df520650ed8de162e18fbffb/spacy-3.8.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aeca6cf34009d48cda9fb1bbfb532469e3d643817241a73e367b34ab99a5806f", size = 32074195, upload-time = "2025-11-17T20:39:11.601Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7d/0b115f3f16e1dd2d3f99b0f89497867fc11c41aed94f4b7a4367b4b54136/spacy-3.8.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:368a79b8df925b15d89dccb5e502039446fb2ce93cf3020e092d5b962c3349b9", size = 32996143, upload-time = "2025-11-17T20:39:14.705Z" }, + { url = "https://files.pythonhosted.org/packages/7d/48/7e9581b476df76aaf9ee182888d15322e77c38b0bbbd5e80160ba0bddd4c/spacy-3.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:88d65941a87f58d75afca1785bd64d01183a92f7269dcbcf28bd9d6f6a77d1a7", size = 14217511, upload-time = "2025-11-17T20:39:17.316Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1f/307a16f32f90aa5ee7ad8d29ff8620a57132b80a4c8c536963d46d192e1a/spacy-3.8.11-cp312-cp312-win_arm64.whl", hash = "sha256:97b865d6d3658e2ab103a67d6c8a2d678e193e84a07f40d9938565b669ceee39", size = 13614446, upload-time = "2025-11-17T20:39:19.748Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5c/3f07cff8bc478fcf48a915ca9fe8637486a1ec676587ed3e6fd775423301/spacy-3.8.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea4adeb399636059925be085c5bb852c1f3a2ebe1c2060332cbad6257d223bbc", size = 6051355, upload-time = "2025-11-17T20:39:22.243Z" }, + { url = "https://files.pythonhosted.org/packages/6d/44/4671e8098b62befec69c7848538a0824086559f74065284bbd57a5747781/spacy-3.8.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd785e6bd85a58fa037da0c18fcd7250e2daecdfc30464d3882912529d1ad588", size = 5700468, upload-time = "2025-11-17T20:39:23.87Z" }, + { url = "https://files.pythonhosted.org/packages/0c/98/5708bdfb39f94af0655568e14d953886117e18bd04c3aa3ab5ff1a60ea89/spacy-3.8.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:598c177054eb6196deed03cac6fb7a3229f4789719ad0c9f7483f9491e375749", size = 32521877, upload-time = "2025-11-17T20:39:26.291Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1f/731beb48f2c7415a71e2f655876fea8a0b3a6798be3d4d51b794f939623d/spacy-3.8.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a5a449ed3f2d03399481870b776f3ec61f2b831812d63dc1acedf6da70e5ab03", size = 32848355, upload-time = "2025-11-17T20:39:28.971Z" }, + { url = "https://files.pythonhosted.org/packages/47/6b/f3d131d3f9bb1c7de4f355a12adcd0a5fa77f9f624711ddd0f19c517e88b/spacy-3.8.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a6c35c2cb93bade9b7360d1f9db608a066246a41301bb579309efb50764ba55b", size = 31764944, upload-time = "2025-11-17T20:39:31.788Z" }, + { url = "https://files.pythonhosted.org/packages/72/bf/37ea8134667a4f2787b5f0e0146f2e8df1fb36ab67d598ad06eb5ed2e7db/spacy-3.8.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0156ae575b20290021573faa1fed8a82b11314e9a1c28f034713359a5240a325", size = 32718517, upload-time = "2025-11-17T20:39:35.286Z" }, + { url = "https://files.pythonhosted.org/packages/79/fe/436435dfa93cc355ed511f21cf3cda5302b7aa29716457317eb07f1cf2da/spacy-3.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:6f39cf36f86bd6a8882076f86ca80f246c73aa41d7ebc8679fbbe41b6f8ec045", size = 14211913, upload-time = "2025-11-17T20:39:37.906Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/f89cfa51f54aa5e9c6c7a37f8bf4952d678f0902a5e1d81dfda33a94bfb2/spacy-3.8.11-cp313-cp313-win_arm64.whl", hash = "sha256:9a7151eee0814a5ced36642b42b1ecc8f98ac7225f3e378fb9f862ffbe84b8bf", size = 13605169, upload-time = "2025-11-17T20:39:40.455Z" }, + { url = "https://files.pythonhosted.org/packages/d7/78/ddeb09116b593f3cccc7eb489a713433076b11cf8cdfb98aec641b73a2c2/spacy-3.8.11-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:43c24d19a3f85bde0872935294a31fd9b3a6db3f92bb2b75074177cd3acec03f", size = 6067734, upload-time = "2025-11-17T20:39:42.629Z" }, + { url = "https://files.pythonhosted.org/packages/65/bb/1bb630250dc70e00fa3821879c6e2cb65c19425aba38840d3484061285c1/spacy-3.8.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b6158c21da57b8373d2d1afb2b73977c4bc4235d2563e7788d44367fc384939a", size = 5732963, upload-time = "2025-11-17T20:39:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/7a/56/c58071b3db23932ab2b934af3462a958e7edf472da9668e4869fe2a2199e/spacy-3.8.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1c0bd1bde1d91f1d7a44774ca4ca3fcf064946b72599a8eb34c25e014362ace1", size = 32447290, upload-time = "2025-11-17T20:39:47.392Z" }, + { url = "https://files.pythonhosted.org/packages/34/eb/d3947efa2b46848372e89ced8371671d77219612a3eebef15db5690aa4d2/spacy-3.8.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99b767c41a772e544cf2d48e0808764f42f17eb2fd6188db4a729922ff7f0c1e", size = 32488011, upload-time = "2025-11-17T20:39:50.408Z" }, + { url = "https://files.pythonhosted.org/packages/04/9e/8c6c01558b62388557247e553e48874f52637a5648b957ed01fbd628391d/spacy-3.8.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3c500f04c164e4366a1163a61bf39fd50f0c63abdb1fc17991281ec52a54ab4", size = 31731340, upload-time = "2025-11-17T20:39:53.221Z" }, + { url = "https://files.pythonhosted.org/packages/23/1f/21812ec34b187ef6ba223389760dfea09bbe27d2b84b553c5205576b4ac2/spacy-3.8.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a2bfe45c0c1530eaabc68f5434c52b1be8df10d5c195c54d4dc2e70cea97dc65", size = 32478557, upload-time = "2025-11-17T20:39:55.826Z" }, + { url = "https://files.pythonhosted.org/packages/f3/16/a0c9174a232dfe7b48281c05364957e2c6d0f80ef26b67ce8d28a49c2d91/spacy-3.8.11-cp314-cp314-win_amd64.whl", hash = "sha256:45d0bbc8442d18dcea9257be0d1ab26e884067e038b1fa133405bf2f20c74edf", size = 14396041, upload-time = "2025-11-17T20:39:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d0/a6aad5b73d523e4686474b0cfcf46f37f3d7a18765be5c1f56c1dcee4c18/spacy-3.8.11-cp314-cp314-win_arm64.whl", hash = "sha256:90a12961ecc44e0195fd42db9f0ce4aade17e6fe03f8ab98d4549911d9e6f992", size = 13823760, upload-time = "2025-11-17T20:40:00.831Z" }, +] + +[[package]] +name = "spacy-legacy" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806, upload-time = "2023-01-23T09:04:15.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971, upload-time = "2023-01-23T09:04:13.45Z" }, +] + +[[package]] +name = "spacy-loggers" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811, upload-time = "2023-09-11T12:26:52.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343, upload-time = "2023-09-11T12:26:50.586Z" }, +] + +[[package]] +name = "srsly" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/77/5633c4ba65e3421b72b5b4bd93aa328360b351b3a1e5bf3c90eb224668e5/srsly-2.5.2.tar.gz", hash = "sha256:4092bc843c71b7595c6c90a0302a197858c5b9fe43067f62ae6a45bc3baa1c19", size = 492055, upload-time = "2025-11-17T14:11:02.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/58/ff9fd981b6e0fae261c48a3a941aeca5735eace4a137de883c8d69029bc7/srsly-2.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5491fe0683da900cd0c563538510c70a007380e1f6b29ebbb5225e7590981e2a", size = 655635, upload-time = "2025-11-17T14:09:41.167Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a6/5b03c2a3b407caec3e7a5df61523154de3c5d36dc2f9328be91d3df368d5/srsly-2.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7375c2955935b73a6cad3851fe819c2f4ec506504afe7ca92b917555e6850fae", size = 653395, upload-time = "2025-11-17T14:09:42.827Z" }, + { url = "https://files.pythonhosted.org/packages/62/5d/1829a208d6d291c1ab3b81acd6e7a9f11984afc674ba2778e57984eee1a7/srsly-2.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0709a97ca463c1e85b03432c7d8028c82439f0248816707bafc553ffe66ec6f9", size = 1121898, upload-time = "2025-11-17T14:09:44.461Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ce/71766be1488ce4058dc5eded6f5c0ce7cbb18ff7263f3cc718fe8b1033ad/srsly-2.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea2ee0122312802ed531fee6de679d74ce99ce8addce49aff8d52ee670d810f8", size = 1122831, upload-time = "2025-11-17T14:09:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5c/259e5b0e70c22c5bbd1327a79bb4b2d75efb38295475229e9310251c240e/srsly-2.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2e9fc418585832c7ce01bfc7fe85b96afe11165eb9a31ff0ed52aa3e32ec08b", size = 1080719, upload-time = "2025-11-17T14:09:47.685Z" }, + { url = "https://files.pythonhosted.org/packages/32/c4/20face1113cfa436434c7c152b374edae1631177d0d44dd60103297ffe03/srsly-2.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3df0ef22d571e733b181ac488823b01f4dd13da23497f46956839c718e48f36b", size = 1092783, upload-time = "2025-11-17T14:09:49.295Z" }, + { url = "https://files.pythonhosted.org/packages/c1/aa/16c405cf830bf3d843a631d62681403eb44563e27a42648f417f40209045/srsly-2.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:a116b926dd24702f5474f6367d8083412f218ddf82d5c7b5831a7b2ba3d8bd55", size = 654041, upload-time = "2025-11-17T14:09:51.056Z" }, + { url = "https://files.pythonhosted.org/packages/59/6e/2e3d07b38c1c2e98487f0af92f93b392c6741062d85c65cdc18c7b77448a/srsly-2.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e07babdcece2405b32c9eea25ef415749f214c889545e38965622bb66837ce", size = 655286, upload-time = "2025-11-17T14:09:52.468Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/587bcade6b72f919133e587edf60e06039d88049aef9015cd0bdea8df189/srsly-2.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1718fe40b73e5cc73b14625233f57e15fb23643d146f53193e8fe653a49e9a0f", size = 653094, upload-time = "2025-11-17T14:09:53.837Z" }, + { url = "https://files.pythonhosted.org/packages/8d/24/5c3aabe292cb4eb906c828f2866624e3a65603ef0a73e964e486ff146b84/srsly-2.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7b07e6103db7dd3199c0321935b0c8b9297fd6e018a66de97dc836068440111", size = 1141286, upload-time = "2025-11-17T14:09:55.535Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fe/2cbdcef2495e0c40dafb96da205d9ab3b9e59f64938277800bf65f923281/srsly-2.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2dedf03b2ae143dd70039f097d128fb901deba2482c3a749ac0a985ac735aad", size = 1144667, upload-time = "2025-11-17T14:09:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/91/7c/9a2c9d8141daf7b7a6f092c2be403421a0ab280e7c03cc62c223f37fdf47/srsly-2.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d5be1d8b79a4c4180073461425cb49c8924a184ab49d976c9c81a7bf87731d9", size = 1103935, upload-time = "2025-11-17T14:09:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ad/8ae727430368fedbb1a7fa41b62d7a86237558bc962c5c5a9aa8bfa82548/srsly-2.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c8e42d6bcddda2e6fc1a8438cc050c4a36d0e457a63bcc7117d23c5175dfedec", size = 1117985, upload-time = "2025-11-17T14:10:00.348Z" }, + { url = "https://files.pythonhosted.org/packages/60/69/d6afaef1a8d5192fd802752115c7c3cc104493a7d604b406112b8bc2b610/srsly-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:e7362981e687eead00248525c3ef3b8ddd95904c93362c481988d91b26b6aeef", size = 654148, upload-time = "2025-11-17T14:10:01.772Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1c/21f658d98d602a559491b7886c7ca30245c2cd8987ff1b7709437c0f74b1/srsly-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f92b4f883e6be4ca77f15980b45d394d310f24903e25e1b2c46df783c7edcce", size = 656161, upload-time = "2025-11-17T14:10:03.181Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a2/bc6fd484ed703857043ae9abd6c9aea9152f9480a6961186ee6c1e0c49e8/srsly-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac4790a54b00203f1af5495b6b8ac214131139427f30fcf05cf971dde81930eb", size = 653237, upload-time = "2025-11-17T14:10:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ea/e3895da29a15c8d325e050ad68a0d1238eece1d2648305796adf98dcba66/srsly-2.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce5c6b016050857a7dd365c9dcdd00d96e7ac26317cfcb175db387e403de05bf", size = 1174418, upload-time = "2025-11-17T14:10:05.945Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a5/21996231f53ee97191d0746c3a672ba33a4d86a19ffad85a1c0096c91c5f/srsly-2.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:539c6d0016e91277b5e9be31ebed03f03c32580d49c960e4a92c9003baecf69e", size = 1183089, upload-time = "2025-11-17T14:10:07.335Z" }, + { url = "https://files.pythonhosted.org/packages/7b/df/eb17aa8e4a828e8df7aa7dc471295529d9126e6b710f1833ebe0d8568a8e/srsly-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f24b2c4f4c29da04083f09158543eb3f8893ba0ac39818693b3b259ee8044f0", size = 1122594, upload-time = "2025-11-17T14:10:08.899Z" }, + { url = "https://files.pythonhosted.org/packages/80/74/1654a80e6c8ec3ee32370ea08a78d3651e0ba1c4d6e6be31c9efdb9a2d10/srsly-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d34675047460a3f6999e43478f40d9b43917ea1e93a75c41d05bf7648f3e872d", size = 1139594, upload-time = "2025-11-17T14:10:10.286Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/8393344ca7f0e81965febba07afc5cad68335ed0426408d480b861ab915b/srsly-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:81fd133ba3c66c07f0e3a889d2b4c852984d71ea833a665238a9d47d8e051ba5", size = 654750, upload-time = "2025-11-17T14:10:11.637Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c5/dc29e65419692444253ea549106be156c5911041f16791f3b62fb90c14f2/srsly-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d976d6ae8e66006797b919e3d58533dce64cd48a5447a8ff7277f9b0505b0185", size = 654723, upload-time = "2025-11-17T14:10:13.305Z" }, + { url = "https://files.pythonhosted.org/packages/80/8c/8111e7e8c766b47b5a5f9864f27f532cf6bb92837a3e277eb297170bd6af/srsly-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:24f52ecd27409ea24ba116ee9f07a2bb1c4b9ba11284b32a0bf2ca364499d1c1", size = 651651, upload-time = "2025-11-17T14:10:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/45/de/3f99d4e44af427ee09004df6586d0746640536b382c948f456be027c599b/srsly-2.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b0667ce1effb32a57522db10705db7c78d144547fcacc8a06df62c4bb7f96e", size = 1158012, upload-time = "2025-11-17T14:10:16.176Z" }, + { url = "https://files.pythonhosted.org/packages/c3/2f/66044ef5a10a487652913c1a7f32396cb0e9e32ecfc3fdc0a0bc0382e703/srsly-2.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60782f6f79c340cdaf1ba7cbaa1d354a0f7c8f86b285f1e14e75edb51452895a", size = 1163258, upload-time = "2025-11-17T14:10:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/74/6b/698834048672b52937e8cf09b554adb81b106c0492f9bc62e41e3b46a69b/srsly-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec51abb1b58e1e6c689714104aeeba6290c40c0bfad0243b9b594df89f05881", size = 1112214, upload-time = "2025-11-17T14:10:18.679Z" }, + { url = "https://files.pythonhosted.org/packages/85/17/1efc70426be93d32a3c6c5c12d795eb266a9255d8b537fcb924a3de57fcb/srsly-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:76464e45f73afd20c2c34d2ef145bf788afc32e7d45f36f6393ed92a85189ed3", size = 1130687, upload-time = "2025-11-17T14:10:20.346Z" }, + { url = "https://files.pythonhosted.org/packages/e2/25/07f8c8a778bc0447ee15e37089b08af81b24fcc1d4a2c09eff4c3a79b241/srsly-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:009424a96d763951e4872b36ba38823f973bef094a1adbc11102e23e8d1ef429", size = 653128, upload-time = "2025-11-17T14:10:21.552Z" }, + { url = "https://files.pythonhosted.org/packages/39/03/3d248f538abc141d9c7ed1aa10e61506c0f95515a61066ee90e888f0cd8f/srsly-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a0911dcf1026f982bd8c5f73e1c43f1bc868416408fcbc1f3d99eb59475420c5", size = 659866, upload-time = "2025-11-17T14:10:22.811Z" }, + { url = "https://files.pythonhosted.org/packages/43/22/0fcff4c977ddfb32a6b10f33d904868b16ce655323756281f973c5a3449e/srsly-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0ff3ac2942aee44235ca3c7712fcbd6e0d1a092e10ee16e07cef459ed6d7f65", size = 655868, upload-time = "2025-11-17T14:10:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c1/e158f26a5597ac31b0f306d2584411ec1f984058e8171d76c678bf439e96/srsly-2.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:78385fb75e1bf7b81ffde97555aee094d270a5e0ea66f8280f6e95f5bb508b3e", size = 1156753, upload-time = "2025-11-17T14:10:25.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bc/2001cd27fd6ecdae79050cf6b655ca646dedc0b69a756e6a87993cc47314/srsly-2.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2e9943b70bd7655b9eefca77aab838c3b7acea00c9dd244fd218a43dc61c518b", size = 1157916, upload-time = "2025-11-17T14:10:26.705Z" }, + { url = "https://files.pythonhosted.org/packages/5c/dd/56f563c2d0cd76c8fd22fb9f1589f18af50b54d31dd3323ceb05fe7999b8/srsly-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7d235a2bb08f5240e47c6aba4d9688b228d830fbf4c858388d9c151a10039e6d", size = 1114582, upload-time = "2025-11-17T14:10:27.997Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/e155facc965a119e6f5d32b7e95082cadfb62cc5d97087d53db93f3a5a98/srsly-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ad94ee18b3042a6cdfdc022556e2ed9a7b52b876de86fe334c4d8ec58d59ecbc", size = 1129875, upload-time = "2025-11-17T14:10:29.295Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3a/c12a4d556349c9f491b0a9d27968483f22934d2a02dfb14fb1d3a7d9b837/srsly-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:6658467165d8fa4aec0f5f6e2da8fe977e087eaff13322b0ff20450f0d762cee", size = 658858, upload-time = "2025-11-17T14:10:30.612Z" }, + { url = "https://files.pythonhosted.org/packages/70/db/52510cbf478ab3ae8cb6c95aff3a499f2ded69df6d84df8a293630e9f10a/srsly-2.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:517e907792acf574979752ce33e7b15985c95d4ed7d8e38ee47f36063dc985ac", size = 666843, upload-time = "2025-11-17T14:10:32.082Z" }, + { url = "https://files.pythonhosted.org/packages/3d/da/4257b1d4c3eb005ecd135414398c033c13c4d3dffb715f63c3acd63d8d1a/srsly-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5602797e6f87bf030b11ad356828142367c5c81e923303b5ff2a88dfb12d1e4", size = 663981, upload-time = "2025-11-17T14:10:33.542Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f8/1ec5edd7299d8599def20fc3440372964f7c750022db8063e321747d1cf8/srsly-2.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3452306118f8604daaaac6d770ee8f910fca449e8f066dcc96a869b43ece5340", size = 1267808, upload-time = "2025-11-17T14:10:35.285Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5c/4ef9782c9a3f331ef80e1ea8fc6fab50fc3d32ae61a494625d2c5f30cc4c/srsly-2.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e2d59f1ce00d73397a7f5b9fc33e76d17816ce051abe4eb920cec879d2a9d4f4", size = 1252838, upload-time = "2025-11-17T14:10:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/39/da/d13cfc662d71eec3ccd4072433bf435bd2e11e1c5340150b4cc43fad46f4/srsly-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ebda3736651d33d92b17e26c525ba8d0b94d0ee379c9f92e8d937ba89dca8978", size = 1244558, upload-time = "2025-11-17T14:10:38.73Z" }, + { url = "https://files.pythonhosted.org/packages/26/50/92bf62dfb19532b823ef52251bb7003149e1d4a89f50a63332c8ff5f894b/srsly-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:74a9338fcc044f4bdc7113b2d9db2db8e0a263c69f1cba965acf12c845d8b365", size = 1244935, upload-time = "2025-11-17T14:10:42.324Z" }, + { url = "https://files.pythonhosted.org/packages/95/81/6ea10ef6228ce4438a240c803639f7ccf5eae3469fbc015f33bd84aa8df1/srsly-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:8e2b9058623c44b07441eb0d711dfdf6302f917f0634d0a294cae37578dcf899", size = 676105, upload-time = "2025-11-17T14:10:43.633Z" }, +] + +[[package]] +name = "thinc" +version = "8.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blis" }, + { name = "catalogue" }, + { name = "confection" }, + { name = "cymem" }, + { name = "murmurhash" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "setuptools" }, + { name = "srsly" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/3a/2d0f0be132b9faaa6d56f04565ae122684273e4bf4eab8dee5f48dc00f68/thinc-8.3.10.tar.gz", hash = "sha256:5a75109f4ee1c968fc055ce651a17cb44b23b000d9e95f04a4d047ab3cb3e34e", size = 194196, upload-time = "2025-11-17T17:21:46.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/bc/d3c364c0278e420e0e3d328cbae7cd7aac8d2cfe4d9b8022a12e99f03755/thinc-8.3.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fbe0313cb3c898f4e6a3f13b704af51f4bf8f927078deb0fe2d6eaf3c6c5b31b", size = 821615, upload-time = "2025-11-17T17:20:31.257Z" }, + { url = "https://files.pythonhosted.org/packages/0e/97/70fe96d86fe5d024882fd96f054be94f87828da67862749aa439de33d452/thinc-8.3.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:892ac91cf7cc8d3ac9a4527c68ead37a96e87132c9f589de56b057b50358e895", size = 772280, upload-time = "2025-11-17T17:20:34.408Z" }, + { url = "https://files.pythonhosted.org/packages/08/a8/a6906490a756a4ad09781bcd02490e5427d942a918abed8424f639d317c3/thinc-8.3.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0fbf142050feb5490f6366e251d48e0429315abe487faa7d371fac4d043efd1e", size = 3881222, upload-time = "2025-11-17T17:20:36.525Z" }, + { url = "https://files.pythonhosted.org/packages/e6/bf/bebeddbab816c4d909455499f7e1b0a88cec9497fd737412e1189971d193/thinc-8.3.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:470b05fd1af4024cf183f387f71270943f652dd711304d1fa8b672d268052af8", size = 3905534, upload-time = "2025-11-17T17:20:38.901Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/c78f1e1091b73dbeee8623f856e2dd25888aab600ded5fa9944dfbe38efb/thinc-8.3.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ebf4aa642991b8dc5c2a6db4c0aedf6d5589a361c93531ec3721d76eabe859", size = 4888188, upload-time = "2025-11-17T17:20:41.394Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bc/36297efade38e0f3e56795f49094d19fbe560bda60a42ce134bbfc1796da/thinc-8.3.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:026999d749075c890fbb1df47d75389a81b712afccea519a5c7bb86783d0cd73", size = 5033361, upload-time = "2025-11-17T17:20:45.332Z" }, + { url = "https://files.pythonhosted.org/packages/a8/bf/70d97758b5b1c7ee06afca8240b6e02bdf5b18d18eb59b873e319b3e01b2/thinc-8.3.10-cp310-cp310-win_amd64.whl", hash = "sha256:8d5ae7d96ff3ea2e4f23bd4005c773f4765f41b11dfb79598a81e5feb1437b91", size = 1792397, upload-time = "2025-11-17T17:20:47.014Z" }, + { url = "https://files.pythonhosted.org/packages/38/43/01b662540888140b5e9f76c957c7118c203cb91f17867ce78fc4f2d3800f/thinc-8.3.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72793e0bd3f0f391ca36ab0996b3c21db7045409bd3740840e7d6fcd9a044d81", size = 818632, upload-time = "2025-11-17T17:20:49.123Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ba/e0edcc84014bdde1bc9a082408279616a061566a82b5e3b90b9e64f33c1b/thinc-8.3.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b13311acb061e04e3a0c4bd677b85ec2971e3a3674558252443b5446e378256", size = 770622, upload-time = "2025-11-17T17:20:50.467Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/0558f8cb69c13e1114428726a3fb36fe1adc5821a62ccd3fa7b7c1a5bd9a/thinc-8.3.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9ffddcf311fb7c998eb8988d22c618dc0f33b26303853c0445edb8a69819ac60", size = 4094652, upload-time = "2025-11-17T17:20:52.104Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/bb78601f74f9bcadb2d3d4d5b057c4dc3f2e52d9771bad3d93a4e38a9dc1/thinc-8.3.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b1e0511e8421f20abe4f22d8c8073a0d7ce4a31597cc7a404fdbad72bf38058", size = 4124379, upload-time = "2025-11-17T17:20:53.781Z" }, + { url = "https://files.pythonhosted.org/packages/f6/3e/961e1b9794111c89f2ceadfef5692aba5097bec4aaaf89f1b8a04c5bc961/thinc-8.3.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e31e49441dfad8fd64b8ca5f5c9b8c33ee87a553bf79c830a15b4cd02efcc444", size = 5094221, upload-time = "2025-11-17T17:20:55.466Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/da163a1533faaef5b17dd11dfb9ffd9fd5627dbef56e1160da6edbe1b224/thinc-8.3.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9de5dd73ce7135dcf41d68625d35cd9f5cf8e5f55a3932001a188b45057c3379", size = 5262834, upload-time = "2025-11-17T17:20:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4e/449d29e33f7ddda6ba1b9e06de3ea5155c2dc33c21f438f8faafebde4e13/thinc-8.3.10-cp311-cp311-win_amd64.whl", hash = "sha256:b6d64e390a1996d489872b9d99a584142542aba59ebdc60f941f473732582f6f", size = 1791864, upload-time = "2025-11-17T17:20:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b3/68038d88d45d83a501c3f19bd654d275b7ac730c807f52bbb46f35f591bc/thinc-8.3.10-cp311-cp311-win_arm64.whl", hash = "sha256:3991b6ad72e611dfbfb58235de5b67bcc9f61426127cc023607f97e8c5f43e0e", size = 1717563, upload-time = "2025-11-17T17:21:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/d3/34/ba3b386d92edf50784b60ee34318d47c7f49c198268746ef7851c5bbe8cf/thinc-8.3.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51bc6ef735bdbcab75ab2916731b8f61f94c66add6f9db213d900d3c6a244f95", size = 794509, upload-time = "2025-11-17T17:21:03.21Z" }, + { url = "https://files.pythonhosted.org/packages/07/f3/9f52d18115cd9d8d7b2590d226cb2752d2a5ffec61576b19462b48410184/thinc-8.3.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4f48b4d346915f98e9722c0c50ef911cc16c6790a2b7afebc6e1a2c96a6ce6c6", size = 741084, upload-time = "2025-11-17T17:21:04.568Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9c/129c2b740c4e3d3624b6fb3dec1577ef27cb804bc1647f9bc3e1801ea20c/thinc-8.3.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5003f4db2db22cc8d686db8db83509acc3c50f4c55ebdcb2bbfcc1095096f7d2", size = 3846337, upload-time = "2025-11-17T17:21:06.079Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/738cf188dea8240c2be081c83ea47270fea585eba446171757d2cdb9b675/thinc-8.3.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b12484c3ed0632331fada2c334680dd6bc35972d0717343432dfc701f04a9b4c", size = 3901216, upload-time = "2025-11-17T17:21:07.842Z" }, + { url = "https://files.pythonhosted.org/packages/22/92/32f66eb9b1a29b797bf378a0874615d810d79eefca1d6c736c5ca3f8b918/thinc-8.3.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8677c446d3f9b97a465472c58683b785b25dfcf26c683e3f4e8f8c7c188e4362", size = 4827286, upload-time = "2025-11-17T17:21:09.62Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/7ceae1e1f2029efd67ed88e23cd6dc13a5ee647cdc2b35113101b2a62c10/thinc-8.3.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:759c385ac08dcf950238b60b96a28f9c04618861141766928dff4a51b1679b25", size = 5024421, upload-time = "2025-11-17T17:21:11.199Z" }, + { url = "https://files.pythonhosted.org/packages/0b/66/30f9d8d41049b78bc614213d492792fbcfeb1b28642adf661c42110a7ebd/thinc-8.3.10-cp312-cp312-win_amd64.whl", hash = "sha256:bf3f188c3fa1fdcefd547d1f90a1245c29025d6d0e3f71d7fdf21dad210b990c", size = 1718631, upload-time = "2025-11-17T17:21:12.965Z" }, + { url = "https://files.pythonhosted.org/packages/f8/44/32e2a5018a1165a304d25eb9b1c74e5310da19a533a35331e8d824dc6a88/thinc-8.3.10-cp312-cp312-win_arm64.whl", hash = "sha256:234b7e57a6ef4e0260d99f4e8fdc328ed12d0ba9bbd98fdaa567294a17700d1c", size = 1642224, upload-time = "2025-11-17T17:21:14.371Z" }, + { url = "https://files.pythonhosted.org/packages/53/fc/17a2818d1f460b8c4f33b8bd3f21b19d263a647bfd23b572768d175e6b64/thinc-8.3.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c7c3a50ddd423d1c49419899acef4ac80d800af3b423593acb9e40578384b543", size = 789771, upload-time = "2025-11-17T17:21:15.784Z" }, + { url = "https://files.pythonhosted.org/packages/8d/24/649f54774b1fbe791a1c2efd7d7f0a95cfd9244902553ca7dcf19daab1dd/thinc-8.3.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a1cb110398f51fc2b9a07a2a4daec6f91e166533a9c9f1c565225330f46569a", size = 737051, upload-time = "2025-11-17T17:21:17.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8c/5840c6c504c1fa9718e1c74d6e04d77a474f594888867dbba53f9317285f/thinc-8.3.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42318746a67403d04be57d862fe0c0015b58b6fb9bbbf7b6db01f3f103b73a99", size = 3839221, upload-time = "2025-11-17T17:21:20.003Z" }, + { url = "https://files.pythonhosted.org/packages/45/ef/e7fca88074cb0aa1c1a23195470b4549492c2797fe7dc9ff79a85500153a/thinc-8.3.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6b0e41e79973f8828adead770f885db8d0f199bfbaa9591d1d896c385842e993", size = 3885024, upload-time = "2025-11-17T17:21:21.735Z" }, + { url = "https://files.pythonhosted.org/packages/9a/eb/805e277aa019896009028d727460f071c6cf83843d70f6a69e58994d2203/thinc-8.3.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed982daa1eddbad813bfd079546483b849a68b98c01ad4a7e4efd125ddc5d7b", size = 4815939, upload-time = "2025-11-17T17:21:23.942Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f5/6425f12a60e3782091c9ec16394b9239f0c18c52c70218f3c8c047ff985c/thinc-8.3.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d22bd381410749dec5f629b3162b7d1f1e2d9b7364fd49a7ea555b61c93772b9", size = 5020260, upload-time = "2025-11-17T17:21:25.507Z" }, + { url = "https://files.pythonhosted.org/packages/85/a2/ae98feffe0b161400e87b7bfc8859e6fa1e6023fa7bcfa0a8cacd83b39a1/thinc-8.3.10-cp313-cp313-win_amd64.whl", hash = "sha256:9c32830446a57da13b6856cacb0225bc2f2104f279d9928d40500081c13aa9ec", size = 1717562, upload-time = "2025-11-17T17:21:27.468Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e0/faa1d04a6890ea33b9541727d2a3ca88bad794a89f73b9111af6f9aefe10/thinc-8.3.10-cp313-cp313-win_arm64.whl", hash = "sha256:aa43f9af76781d32f5f9fe29299204c8841d71e64cbb56e0e4f3d1e0387c2783", size = 1641536, upload-time = "2025-11-17T17:21:30.129Z" }, + { url = "https://files.pythonhosted.org/packages/b8/32/7a96e1f2cac159d778c6b0ab4ddd8a139bb57c602cef793b7606cd32428d/thinc-8.3.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:44d7038a5d28572105332b44ec9c4c3b6f7953b41d224588ad0473c9b79ccf9e", size = 793037, upload-time = "2025-11-17T17:21:32.538Z" }, + { url = "https://files.pythonhosted.org/packages/12/d8/81e8495e8ef412767c09d1f9d0d86dc60cd22e6ed75e61b49fbf1dcfcd65/thinc-8.3.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:639f20952af722cb0ab4c3d8a00e661686b60c04f82ef48d12064ceda3b8cd0c", size = 740768, upload-time = "2025-11-17T17:21:34.852Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6d/716488a301d65c5463e92cb0eddae3672ca84f1d70937808cea9760f759c/thinc-8.3.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9306e62c7e7066c63b0c0ba1d164ae0c23bf38edf5a7df2e09cce69a2c290500", size = 3834983, upload-time = "2025-11-17T17:21:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a1/d28b21cab9b79e9c803671bebd14489e14c5226136fad6a1c44f96f8e4ef/thinc-8.3.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2982604c21096de1a87b04a781a645863eece71ec6ee9f139ac01b998fb5622d", size = 3845215, upload-time = "2025-11-17T17:21:38.362Z" }, + { url = "https://files.pythonhosted.org/packages/93/9d/ff64ead5f1c2298d9e6a9ccc1c676b2347ac06162ad3c5e5d895c32a719e/thinc-8.3.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6b82698e27846004d4eafc38317ace482eced888d4445f7fb9c548fd36777af", size = 4826596, upload-time = "2025-11-17T17:21:40.027Z" }, + { url = "https://files.pythonhosted.org/packages/4a/44/b80c863608d0fd31641a2d50658560c22d4841f1e445529201e22b3e1d0f/thinc-8.3.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2950acab8ae77427a86d11655ed0a161bc83a1edf9d31ba5c43deca6cd27ed4f", size = 4988146, upload-time = "2025-11-17T17:21:41.73Z" }, + { url = "https://files.pythonhosted.org/packages/93/6d/1bdd9344b2e7299faa55129dda624d50c334eed16a3761eb8b1dacd8bfcd/thinc-8.3.10-cp314-cp314-win_amd64.whl", hash = "sha256:c253139a5c873edf75a3b17ec9d8b6caebee072fdb489594bc64e35115df7625", size = 1738054, upload-time = "2025-11-17T17:21:43.328Z" }, + { url = "https://files.pythonhosted.org/packages/45/c4/44e3163d48e398efb3748481656963ac6265c14288012871c921dc81d004/thinc-8.3.10-cp314-cp314-win_arm64.whl", hash = "sha256:ad6da67f534995d6ec257f16665377d7ad95bef5c1b1c89618fd4528657a6f24", size = 1665001, upload-time = "2025-11-17T17:21:45.019Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/89/4b0001b2dab8df0a5ee2787dcbe771de75ded01f18f1f8d53dedeea2882b/tqdm-4.67.2.tar.gz", hash = "sha256:649aac53964b2cb8dec76a14b405a4c0d13612cb8933aae547dd144eacc99653", size = 169514, upload-time = "2026-01-30T23:12:06.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/e2/31eac96de2915cf20ccaed0225035db149dfb9165a9ed28d4b252ef3f7f7/tqdm-4.67.2-py3-none-any.whl", hash = "sha256:9a12abcbbff58b6036b2167d9d3853042b9d436fe7330f06ae047867f2f8e0a7", size = 78354, upload-time = "2026-01-30T23:12:04.368Z" }, +] + +[[package]] +name = "typer-slim" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/d4/064570dec6358aa9049d4708e4a10407d74c99258f8b2136bb8702303f1a/typer_slim-0.21.1.tar.gz", hash = "sha256:73495dd08c2d0940d611c5a8c04e91c2a0a98600cbd4ee19192255a233b6dbfd", size = 110478, upload-time = "2026-01-06T11:21:11.176Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/0a/4aca634faf693e33004796b6cee0ae2e1dba375a800c16ab8d3eff4bb800/typer_slim-0.21.1-py3-none-any.whl", hash = "sha256:6e6c31047f171ac93cc5a973c9e617dbc5ab2bddc4d0a3135dc161b4e2020e0d", size = 47444, upload-time = "2026-01-06T11:21:12.441Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "wasabi" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, +] + +[[package]] +name = "weasel" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpathlib" }, + { name = "confection" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "smart-open" }, + { name = "srsly" }, + { name = "typer-slim" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/d7/edd9c24e60cf8e5de130aa2e8af3b01521f4d0216c371d01212f580d0d8e/weasel-0.4.3.tar.gz", hash = "sha256:f293d6174398e8f478c78481e00c503ee4b82ea7a3e6d0d6a01e46a6b1396845", size = 38733, upload-time = "2025-11-13T23:52:28.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/74/a148b41572656904a39dfcfed3f84dd1066014eed94e209223ae8e9d088d/weasel-0.4.3-py3-none-any.whl", hash = "sha256:08f65b5d0dbded4879e08a64882de9b9514753d9eaa4c4e2a576e33666ac12cf", size = 50757, upload-time = "2025-11-13T23:52:26.982Z" }, +] + +[[package]] +name = "wrapt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/31/afb4cf08b9892430ec419a3f0f469fb978cb013f4432e0edb9c2cf06f081/wrapt-2.1.0.tar.gz", hash = "sha256:757ff1de7e1d8db1839846672aaecf4978af433cc57e808255b83980e9651914", size = 80924, upload-time = "2026-01-31T23:25:58.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/6f/9773ddbf70d2f787d049fb5a412c18fd8140b8a33e90e8b911f0d512a7b5/wrapt-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba00229045bc0ec808f12f7d2fd02166631657c56d5b7acbbb8f03ea70fc1cd6", size = 60561, upload-time = "2026-01-31T23:26:51.314Z" }, + { url = "https://files.pythonhosted.org/packages/a1/91/f6cc8762153ebcdccf7d7aa7ca3a75fe688b7cebc250f1eac72229943d09/wrapt-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:657c7d0dcca7df8cfdce9d4e9062d51d2a2b2c8f4bdd41dc908a717099cf552a", size = 61501, upload-time = "2026-01-31T23:26:30.857Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/ce91f1e9cd77bf6c11700ac0643ccb747d7c4cbd948f63fba90d345aa85a/wrapt-2.1.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cb21ff015afe80cc30daca53136427463c364fb7c1ca96e4b7013dc6f56b2829", size = 113531, upload-time = "2026-01-31T23:26:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/91/be/231563aaf305c930705b4455023155bf485974b431bb4bf9ddc53be5ae9b/wrapt-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8d25f31cf032bfa70ec1872cdf0f7e1f1154c5a5bc6c73444bb3375b904f97f", size = 115538, upload-time = "2026-01-31T23:25:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9e/576265c0173e85e77eb9713ccecedeab34a1785d493bfa511fd98b7154bc/wrapt-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:099c88ce146134786577b892d2748ac79c16c9f70304367eee17295732907045", size = 114719, upload-time = "2026-01-31T23:25:25.153Z" }, + { url = "https://files.pythonhosted.org/packages/77/07/b374bd08739bf2f5c1accbb4c77e34bd21c3e9a0c5c49f54269d014c263f/wrapt-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dfe3f09f5ce33a4e54a3340c3cde774fd19eca0da8a83343889a3673a33ee579", size = 113204, upload-time = "2026-01-31T23:26:02.779Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e6/09285cbca4467c9701bea4c8bece8bf7cebdf2721516e6b6ccc8737d086f/wrapt-2.1.0-cp310-cp310-win32.whl", hash = "sha256:73d77cc1698bf2f0580616a2eadb94aa15b47ae09ade7d9828a5c413dbbabab8", size = 57878, upload-time = "2026-01-31T23:25:18.276Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6c/b027af42f6c8aacf1ab83c0a4b278ded3488f452b8b0478c5204637338dc/wrapt-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8012f863320ece76c6b95527b8ee831b818e186cafa356620cba15ba19c904de", size = 60226, upload-time = "2026-01-31T23:26:38.631Z" }, + { url = "https://files.pythonhosted.org/packages/22/ab/1fc44e40f4a7277f67eac33c645c88d54192fa2a8c6cad2735f8eb86fe3b/wrapt-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccd99596ae95bc7b844196e6691b4987749ba7832c9ba437fdd99885ee5e7a84", size = 58648, upload-time = "2026-01-31T23:26:20.163Z" }, + { url = "https://files.pythonhosted.org/packages/97/0a/de541b2543e33144043cd58da09bda8d837ba42e13ae90baca32b0553023/wrapt-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d877003dbc601e1365bd03f6a980965a20d585f90c056f33e1fc241b63a6f0e7", size = 60558, upload-time = "2026-01-31T23:25:27.784Z" }, + { url = "https://files.pythonhosted.org/packages/84/2e/7e48207420e6ca7e7a05c0e4ebe9464ec9965c8face256f3ef8cc2acd862/wrapt-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:771ec962fe3ccb078177c9b8f3529e204ffcbb11d62d509e0a438e6a83f7ca68", size = 61501, upload-time = "2026-01-31T23:26:46.477Z" }, + { url = "https://files.pythonhosted.org/packages/67/2b/639a4970ecdc7143acb69a1162c76b0f1620218ad502c33e1a88d28f00b1/wrapt-2.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73e742368b52f9cf0921e1d2bcb8a6a44ede2e372e33df6e77caa136a942099f", size = 113954, upload-time = "2026-01-31T23:26:01.493Z" }, + { url = "https://files.pythonhosted.org/packages/81/5d/8d9177c8c0ecaf5313b462be63c5aa9672044b02bfd644dd65c6cb420d2a/wrapt-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e9129d1b582c55ad0dfb9e29e221daa0e02b18c67d8642bc8d08dd7038b3aed", size = 115994, upload-time = "2026-01-31T23:25:57.118Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e3/c5a514a0ed1dc463f5b6b4e31abbaa3b8df48b9fd391a6e8412608155a29/wrapt-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc9e37bfe67f6ea738851dd606640a87692ff81bcc76df313fb75d08e05e855f", size = 115245, upload-time = "2026-01-31T23:26:11.171Z" }, + { url = "https://files.pythonhosted.org/packages/35/9c/2fc6a31f5758266de2cf9dc6111d3bda7b7dd6cbdcabfd755103bbcda08f/wrapt-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46583aae3c807aa76f96355c4943031225785ed160c84052612bba0e9d456639", size = 113679, upload-time = "2026-01-31T23:25:19.475Z" }, + { url = "https://files.pythonhosted.org/packages/6c/81/ce52694dc8184f4898c01c8af20e145b348fc7a0e4766a7345c45f0e9ce6/wrapt-2.1.0-cp311-cp311-win32.whl", hash = "sha256:e3958ba70aef2895d8c62c2d31f51ced188f60451212294677b92f4b32c12978", size = 57865, upload-time = "2026-01-31T23:25:50.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/31/0df5d38243c2a538e7bd481e676d286b41f98a729e0d37cfed9f4421ad4d/wrapt-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0ff9797e6e0b82b330ef80b0cdba7fcd0ca056d4c7af2ca44e3d05fd47929ede", size = 60227, upload-time = "2026-01-31T23:25:35.954Z" }, + { url = "https://files.pythonhosted.org/packages/a3/79/b587edbab21d6b8a7460234440c784e08344bcdf4fdfd9a6e9125ea14923/wrapt-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:4b0a29509ef7b501abe47b693a3c91d1f21c9a948711f6ce7afa81eb274c7eae", size = 58648, upload-time = "2026-01-31T23:25:32.887Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6f/c731b1fbbcdf9bd202809c6fa354c4237b663dd82a95035a7cbe899cfd25/wrapt-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a64c0fb29c89810973f312a04c067b63523e7303b9a2653820cbf16474c2e5cf", size = 61149, upload-time = "2026-01-31T23:25:29.092Z" }, + { url = "https://files.pythonhosted.org/packages/b2/da/7022458a1d99f0c59720a0b0fd4b1966f8df6d41e741aadfe43bc5350547/wrapt-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5509d9150ed01c4149e40020fa68e917d5c4bb77d311e79535565c2a0418afcb", size = 61743, upload-time = "2026-01-31T23:26:14.338Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f4/57cc12c3fc6f4fe6ccfc15567cc1ac8aeb53a9946a675adc3df7a1ee4e6a/wrapt-2.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:52bb58b3207ace156b6134235fd43140994597704fd07d148cbcfb474ee084ea", size = 121331, upload-time = "2026-01-31T23:25:37.294Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a4/a96ea114298f81f02c07313da85fd46a2a57bbe12389d0619ac3371f691c/wrapt-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7112cbf72fc4035afe1e3314a311654c41dd92c2932021ef76f5ca87583917b3", size = 122907, upload-time = "2026-01-31T23:26:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/df73362b6e47f92aaff0fc3fc459314025c795f75d61724c83232dee199c/wrapt-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e90656b433808a0ab68e95aaf9f588aea5c8c7a514e180849dfc638ba00ec449", size = 121337, upload-time = "2026-01-31T23:26:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/51/4f/8147e3b9a7887cee4eeb3a3414265ad4649a156832a08063f55aa7842af0/wrapt-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e45f54903da38fc4f6f66397fd550fc0dac6164b4c5e721c1b4eb05664181821", size = 120461, upload-time = "2026-01-31T23:26:43.055Z" }, + { url = "https://files.pythonhosted.org/packages/35/b1/eea720fcca8a05dec848a6d11a47c20f59bdabdcc444ba3be0589350eb7a/wrapt-2.1.0-cp312-cp312-win32.whl", hash = "sha256:6653bf30dbbafd55cb4553195cc60b94920b6711a8835866c0e02aa9f22c5598", size = 58089, upload-time = "2026-01-31T23:26:47.773Z" }, + { url = "https://files.pythonhosted.org/packages/af/79/8a8f3f8c71ee3379191b69e47f32115fa25cdb6d5b581d74c64d5c897fa7/wrapt-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d61238a072501ed071a9f4b9567d10c2eb3d2f1a0258ae79b47160871d8f29c3", size = 60330, upload-time = "2026-01-31T23:26:12.518Z" }, + { url = "https://files.pythonhosted.org/packages/08/4e/e992d05c3d2f7163883a65ead2620ff5fe7b3d44d7c2136ce981e40e453d/wrapt-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:9e971000347f61271725e801ef44fa5d01b52720e59737f0d96280bffb98c5d1", size = 58727, upload-time = "2026-01-31T23:26:53.222Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/b414826a5aaf2fdcfe73c2e649cbeb2e098fef4820d1217554ee64f45666/wrapt-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:875a10a6f3b667f90a39010af26acf684ba831d9b18a86b242899d57c74550fa", size = 61155, upload-time = "2026-01-31T23:26:24.462Z" }, + { url = "https://files.pythonhosted.org/packages/58/9e/8b21ea776bf2a3c858e3377ecde4b348893ec44dc1726baaf583ca22c56e/wrapt-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e00f8559ceac0fb45091daad5f15d37f2c22bdc28ed71521d47ff01aad8fff3d", size = 61747, upload-time = "2026-01-31T23:25:53.987Z" }, + { url = "https://files.pythonhosted.org/packages/da/ec/48cd2470ad09557dfe6fccfe9de98698cc0df3786a6d4d97e8edd574d67a/wrapt-2.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ce0cf4c79c19904aaf2e822af280d7b3c23ad902f57e31c5a19433bc86e5d36d", size = 121342, upload-time = "2026-01-31T23:26:32.156Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4e/e8447b31be27b6057cdfc904a38632a765c3407fb4d10d11e5c1d0c203d5/wrapt-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3dd4f8c2256fcde1a85037a1837afc52e8d32d086fd669ae469455fd9a988d6", size = 122951, upload-time = "2026-01-31T23:25:08.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b6/73a6c9277e844ffe11f3002ad27a84ff5418248def33af9435d24dfe6c5b/wrapt-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:737e1e491473047cb66944b8b8fd23f3f542019afd6cf0569d1356d18a7ea6d5", size = 121373, upload-time = "2026-01-31T23:26:18.322Z" }, + { url = "https://files.pythonhosted.org/packages/85/04/869384435fecf829dc05621ffa02dab0f2f830be5d42fa8d8ac7b0b4c9fa/wrapt-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38de19e30e266c15d542ceb0603e657db4e82c53e7f47fd70674ae5da2b41180", size = 120468, upload-time = "2026-01-31T23:25:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/80/ac/42a5378d9b5b486122ae0572c46ae8d69ab6486b9f13961e6b9706297ff5/wrapt-2.1.0-cp313-cp313-win32.whl", hash = "sha256:bc7d496b6e16bd2f77e37e8969b21a7b58d6954e46c6689986fb67b9078100e5", size = 58095, upload-time = "2026-01-31T23:26:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/86/de/538fcef30f70a1aaadab4cab7d0396037518d7ec2b064557171147ce297f/wrapt-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:57df799e67b011847ef7ac64b05ed4633e56b64e7e7cab5eb83dc9689dbe0acf", size = 60344, upload-time = "2026-01-31T23:25:10.615Z" }, + { url = "https://files.pythonhosted.org/packages/08/13/27884668b21e9f0a625c13ebd6a8d70ad8371250ec8519881858404686bf/wrapt-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:01559d2961c29edc6263849fd9d32b29a20737da67648c7fd752a67bd96208c7", size = 58734, upload-time = "2026-01-31T23:26:00.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a3/e558c5b8f3a097aa1e942e2d75923adebfdfafb5a51ec425d1d062e49ab0/wrapt-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:66f588c8b3a44863156cfaccb516f946a64b3b03a6880822ab0b878135ca1f5c", size = 62972, upload-time = "2026-01-31T23:26:08.576Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/7157e98107099fad846f1e79308cc0954e26b25b01c03f1624ba7f57ec54/wrapt-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:355779ff720c11a2a5cffd03332dbce1005cb4747dca65b0fc8cdd5f8bf1037e", size = 63610, upload-time = "2026-01-31T23:26:39.9Z" }, + { url = "https://files.pythonhosted.org/packages/e4/8e/b8992671e4b4d3ce2a53af930588c204bf37b66eb212bd1722f2a5a8cf62/wrapt-2.1.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a0471df3fb4e85a9ff62f7142cdb169e31172467cdb79a713f9b1319c555903", size = 152538, upload-time = "2026-01-31T23:26:27.696Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f6/79f9fd4b3c0a8715e651fff1cc1182a983fd971376d5688a06fa94e31acd/wrapt-2.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bacf063143fa86f15b00a21259a81c95c527a18d504b8c820835366d361c879", size = 158702, upload-time = "2026-01-31T23:25:11.848Z" }, + { url = "https://files.pythonhosted.org/packages/9e/46/f88b52beb813eeb830d9134bc6eaf3e53cde4e3cfa1804e383754d4104fe/wrapt-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c87cd4f61a3b7cd65113e74006e1cd6352b74807fcc65d440e8342f001f8de5e", size = 155564, upload-time = "2026-01-31T23:25:15.033Z" }, + { url = "https://files.pythonhosted.org/packages/93/31/97145ea71e3e5a1b419af5c410b07b258155dc7cc1a6302791a93e991c83/wrapt-2.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2893498fe898719ac8fb6b4fe36ca86892bec1e2480d94e3bd1bc592c00527ad", size = 150165, upload-time = "2026-01-31T23:26:09.848Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/f33551d5bfbb0ddab81296cffc15570570039a973c0f99bba474be0fadf2/wrapt-2.1.0-cp313-cp313t-win32.whl", hash = "sha256:cbc07f101f5f1e7c23ec06a07e45715f459de992108eeb381b21b76d94dbaf4f", size = 59785, upload-time = "2026-01-31T23:25:52.23Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3a/9a76be7a36442f43841bb6336e262e09a915b2fb5dfc2822ffce1fb903d2/wrapt-2.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2ccc89cd504fc29c32f0b24046e8edf3ef0fcbc5d5efe8c91b303c099863d2c8", size = 63085, upload-time = "2026-01-31T23:26:05.363Z" }, + { url = "https://files.pythonhosted.org/packages/7a/35/65a13c2df008d189ebca5fec534011c5dd69ab4f47e6923b403321816fbf/wrapt-2.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:0b660be1c9cdfb4c711baab4ccbd0e9d1b65a0480d38729ec8cdbf3b29cb7f15", size = 60254, upload-time = "2026-01-31T23:25:06.052Z" }, + { url = "https://files.pythonhosted.org/packages/6f/eb/7c9eb1ea9b10ea98d9983a147c877a2ae927acb4a86e2dc4a0b548f05ad1/wrapt-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f7bf95bae7ac5f2bbcb307464b3b0ff70569dd3b036a87b1cf7efb2c76e66e5", size = 61316, upload-time = "2026-01-31T23:25:20.739Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c2/1c3d16d6b644f688913a00e2dc10f59adca817b5b3ee034ce4e9a692ab63/wrapt-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:be2f541a242818829526e5d08c716b6730970ed0dc1b76ba962a546947d0f005", size = 61813, upload-time = "2026-01-31T23:25:49.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/51/b6170084b6b771cc62374d924e328df2e81f687399a835f003497cad1110/wrapt-2.1.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad3aa174d06a14b4758d5a1678b9adde8b8e657c6695de9a3d4c223f4fcbbcce", size = 120309, upload-time = "2026-01-31T23:25:16.866Z" }, + { url = "https://files.pythonhosted.org/packages/f8/34/467829f0dd79f50878b2e67b67c67c816a6326a27d252d4192ef815b4a09/wrapt-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bffa584240d41bc3127510e07a752f94223d73bb1283ac2e99ac44235762efd2", size = 122690, upload-time = "2026-01-31T23:26:16.914Z" }, + { url = "https://files.pythonhosted.org/packages/df/5b/244c61a65e0bc9d4a18cfa2a2b3b05f8065290284fc60436a7ea5047ee10/wrapt-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9b2da9c8f1723994b335dbf9f496fbfabc76bcdd001f73772b8eb2118a714cea", size = 121115, upload-time = "2026-01-31T23:26:44.518Z" }, + { url = "https://files.pythonhosted.org/packages/86/7d/f9b5e103d3caf23a72c04a1baf2b61c4a14d1feb440d3c98c26725b4503a/wrapt-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:eabe95ea5fbe1524a53c0f3fc535c99f2aa376ec1451b0b79d943d2240d80e36", size = 119487, upload-time = "2026-01-31T23:25:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/b61fdc4680dd5cd6828977341b9fd729e2c623338bfe65647f5c0ff8195e/wrapt-2.1.0-cp314-cp314-win32.whl", hash = "sha256:2cd647097df1df78f027ac7d5d663f05daa1a117b69cf7f476cb299f90557747", size = 58519, upload-time = "2026-01-31T23:25:04.426Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4f/42ab43e496d0d19caed9f69366d0f28f7f08c139297e78b17dab6ecbb6d5/wrapt-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0fc3e388a14ef8101c685dc80b4d2932924a639a03e5c44b5ffabbda2f1f2dc", size = 60767, upload-time = "2026-01-31T23:25:21.954Z" }, + { url = "https://files.pythonhosted.org/packages/ef/15/0337768ac97a8758bc0fc1afdf5f656075a7facf198f62bbe8a22b789277/wrapt-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7c06653908a23a85c4b2455b9d37c085f9756c09058df87b4a2fce2b2f8d58c2", size = 59056, upload-time = "2026-01-31T23:26:25.814Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f1/58f4674d1db44912003a51b34e8d9823a832fbbb39162e9dbe06e5f6424e/wrapt-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c70b4829c6f2f4af4cdaa16442032fcaf882063304160555e4a19b43fd2c6c9d", size = 63061, upload-time = "2026-01-31T23:26:06.601Z" }, + { url = "https://files.pythonhosted.org/packages/02/c1/07f6bf6619285f39cd616314217170c6160da99a46ad6ae4a60044f6ab5a/wrapt-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7fd4c4ee51ebdf245549d54a7c2181a4f39caac97c9dc8a050b5ba814067a29", size = 63620, upload-time = "2026-01-31T23:25:30.326Z" }, + { url = "https://files.pythonhosted.org/packages/46/82/f7df1648762260f60c4e22c066a17d95f20267c94bfe653fab4f08e2c297/wrapt-2.1.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7b158558438874e5fd5cb505b5a635bd08c84857bc937973d9e12e1166cdf3b", size = 152546, upload-time = "2026-01-31T23:25:02.102Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/d953336e09bac13a9ffa9073e167c5dec8aaa4a717a8551bf64cb4683590/wrapt-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e2e156fe2d41700b837be9b1d8d80ebab44e9891589bc7c41578ef110184e29", size = 158704, upload-time = "2026-01-31T23:25:43.269Z" }, + { url = "https://files.pythonhosted.org/packages/39/a1/2ed57e46b30af2a5a750c85a9dd30d2244ef10e2f8db150560126d8cbd24/wrapt-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f1e9bac6a6c1ba65e0ac50e32c575266734a07b6c17e718c4babd91e2faa69b", size = 155563, upload-time = "2026-01-31T23:25:39.17Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8c/4f54f7ea5addf208be44459393185aaa193bd2d0b8ecf4683b159fcc5238/wrapt-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:12687e6271df7ae5706bee44cc1f77fecb7805976ec9f14f58381b30ae2aceb5", size = 150189, upload-time = "2026-01-31T23:25:44.654Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cc/e8290a1cd94297fbc1e9fbad06481b5a7c918f2db6645c550f05ee47f359/wrapt-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:38bbe336ee32f67eb99f886bd4f040d91310b7e660061bb03b9083d26e8cf915", size = 60431, upload-time = "2026-01-31T23:25:48.34Z" }, + { url = "https://files.pythonhosted.org/packages/d0/df/af5d244938853e3adb1251ca1397e9fa78d3e92adc808a0af0a8547585d3/wrapt-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0fa64a9a07df7f85b352adc42b43e7f44085fb11191b8f5b9b77219f7aaf7e17", size = 63859, upload-time = "2026-01-31T23:26:23.2Z" }, + { url = "https://files.pythonhosted.org/packages/39/c4/28b6f2804e8bc05d17114dfed03a80bce5b83ca2113fd44eecbef12275d1/wrapt-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:da379cbdf3b7d97ace33a69a391b7a7e2130b1aca94dc447246217994233974c", size = 60446, upload-time = "2026-01-31T23:25:41.001Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/70983b75d4abd6f85cffc6df79c623220ec5a579ceaacabac35c904b7b52/wrapt-2.1.0-py3-none-any.whl", hash = "sha256:e035693a0d25ea5bf5826df3e203dff7d091b0d5442aaefec9ca8f2bab38417f", size = 43886, upload-time = "2026-01-31T23:25:07.22Z" }, +] From 9830c238332e897bccc952dd7fe1cd61c89ed4cb Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 11:58:43 +0000 Subject: [PATCH 14/16] fix: / emoticon mouth regex no escape --- biberplus/tagger/biber_plus_tagger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biberplus/tagger/biber_plus_tagger.py b/biberplus/tagger/biber_plus_tagger.py index c9830da..3cf7ca1 100644 --- a/biberplus/tagger/biber_plus_tagger.py +++ b/biberplus/tagger/biber_plus_tagger.py @@ -1226,7 +1226,7 @@ def tag_emoji(self, word, previous_words, next_words): def tag_emoticon(self, word, previous_words, next_words): if self.helper.is_punctuation(word): - emoticon_pattern = re.compile("[:;=](?:-)?[)DPp\/]") + emoticon_pattern = re.compile("[:;=](?:-)?[)DPp/]") if re.search(emoticon_pattern, word["text"]): return "EMOT" From 16c79343448c2b629d8f7da6679c22d5d21aa164 Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 12:06:45 +0000 Subject: [PATCH 15/16] fix: remove unnecessary URL regex escaping --- biberplus/tagger/biber_plus_tagger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biberplus/tagger/biber_plus_tagger.py b/biberplus/tagger/biber_plus_tagger.py index 3cf7ca1..9a6d85f 100644 --- a/biberplus/tagger/biber_plus_tagger.py +++ b/biberplus/tagger/biber_plus_tagger.py @@ -1284,7 +1284,7 @@ def tag_subject_pronouns(self, word, previous_words, next_words): return "SBJP" def tag_url(self, word, previous_words, next_words): - url_pattern = "(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})" + url_pattern = r"(https?://(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?://(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})" if re.search(url_pattern, word["text"]): return "URL" From b8c46720b2a377e66aa86011b481eb10e879347b Mon Sep 17 00:00:00 2001 From: Louis Maddox Date: Sun, 1 Feb 2026 13:14:53 +0000 Subject: [PATCH 16/16] fix: remove duplicate non-functional test for UH interjections --- tests/tagger/test_additional_features.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/tagger/test_additional_features.py b/tests/tagger/test_additional_features.py index fbf7442..4e651d1 100644 --- a/tests/tagger/test_additional_features.py +++ b/tests/tagger/test_additional_features.py @@ -1,6 +1,7 @@ import unittest import spacy + from biberplus.tagger import tag_text @@ -81,16 +82,11 @@ def test_interjections_middle(self): print(tagged_words) self.assertIn("UH", tagged_words[2]["tags"]) - def test_interjections_end1(self): + def test_interjections_end(self): text = "That's a bad idea, gosh" tagged_words = tag_text(text, pipeline=self.pipeline) self.assertIn("UH", tagged_words[-1]["tags"]) - def test_interjections_end2(self): - text = "That's a great idea, right?" - tagged_words = tag_text(text, pipeline=self.pipeline) - self.assertIn("UH", tagged_words[-2]["tags"]) - def test_numerals(self): text = "I have three dogs and two cats." tagged_words = tag_text(text, pipeline=self.pipeline)