From 4879544ff59d96e9b33a0767543c5020b82f167a Mon Sep 17 00:00:00 2001 From: Philipp Heinrich Date: Wed, 23 Nov 2022 13:38:07 +0100 Subject: [PATCH 1/4] introduce corpus.quick_conc() --- ccc/concordances.py | 5 +- ccc/cwb.py | 116 ++++++++++++++++++++++++- ccc/discoursemes.py | 143 ++----------------------------- ccc/utils.py | 161 ++++++++++++++++++++++++++++++++++- tests/conftest.py | 23 +++++ tests/test_05_concordance.py | 57 +++++++++++++ 6 files changed, 362 insertions(+), 143 deletions(-) diff --git a/ccc/concordances.py b/ccc/concordances.py index bcb2175..1574e4e 100644 --- a/ccc/concordances.py +++ b/ccc/concordances.py @@ -7,7 +7,7 @@ """ import itertools import logging -from random import sample +from random import sample, seed # requirements from pandas import DataFrame @@ -287,6 +287,9 @@ def lines(self, form='simple', p_show=['word'], s_show=[], if (cut_off is None) or (len(matches) < cut_off): cut_off = len(matches) # order + if isinstance(order, int): + seed(order) + order = 'random' if order == 'random': matches = sample(list(matches), cut_off) elif order == 'first': diff --git a/ccc/cwb.py b/ccc/cwb.py index 07473b3..70ba5cc 100644 --- a/ccc/cwb.py +++ b/ccc/cwb.py @@ -18,16 +18,21 @@ # part of module from .cache import Cache, generate_idx, generate_library_idx from .cl import Corpus as Attributes +from .concordances import Concordance from .counts import Counts, cwb_scan_corpus from .cqp import CQP from .dumps import Dump -from .utils import chunk_anchors, correct_anchors, preprocess_query +from .utils import (chunk_anchors, correct_anchors, dump_left_join, + format_roles, group_lines, preprocess_query) from .version import __version__ logger = logging.getLogger(__name__) def decode(text): + """savely decode a string catching common errors + + """ try: text = text.decode('utf-8') except (UnicodeDecodeError, AttributeError): @@ -1101,8 +1106,7 @@ def query_cqp(self, cqp_query, context=20, context_left=None, ) # if dump has been retrieved from cache, NQR might not exist - if self.show_nqr().empty or \ - name not in self.show_nqr()['subcorpus'].values: + if save and (self.show_nqr().empty or name not in self.show_nqr()['subcorpus'].values): # undump the dump and save to disk cqp = self.start_cqp() cqp.nqr_from_dump(df_dump, name) @@ -1160,3 +1164,109 @@ def query(self, cqp_query=None, context=20, context_left=None, else: raise NotImplementedError() + + def quick_query(self, topic_query, s_context, filter_queries=[], match_strategy='longest'): + """makes sure query result is defined as subcorpus. + + finds all s_context spans that contain topic_query and all filter_queries + + :return: identifier (name of NQR on disk) + :rtype: str + + """ + + # IDENTIFY + topic_identifier = generate_idx([self.subcorpus, topic_query, s_context, match_strategy], prefix='Query') + filter_identifier = generate_idx([self.subcorpus, topic_query, s_context, match_strategy, filter_queries], prefix='Query') + + # CHECK CQP + cqp = self.start_cqp() + cqp.Exec(f'set MatchingStrategy "{match_strategy}";') + size = int(cqp.Exec(f'size {filter_identifier};')) + + if size == 0: + + # TODO: avoid saving twice if there's no filter + size = int(cqp.Exec(f'size {topic_identifier};')) + + logger.info(f'topic query: {topic_query}') + if size == 0: + # TOPIC + cqp.Query(f'{topic_identifier} = {topic_query} expand to {s_context};') + logger.info(f'.. saving {topic_identifier} in CWB binary format') + cqp.Exec(f'save {topic_identifier};') + logger.info('.. size: ' + cqp.Exec(f'size {topic_identifier};')) + + # FILTER + cqp.Exec(f'{filter_identifier} = {topic_identifier};') + for query in filter_queries: + logger.info(f'filter query: {query}') + cqp.Exec(f'{filter_identifier};') + cqp.Query(f'{filter_identifier} = {query} expand to {s_context};') + logger.info('.. size: ' + cqp.Exec(f'size {filter_identifier};')) + + # SAVE + logger.info(f'.. saving {filter_identifier} in CWB binary format') + cqp.Exec(f'save {filter_identifier};') + + cqp.__kill__() + + return filter_identifier + + def quick_conc(self, topic_query, s_context, window, order=42, + cut_off=100, highlight_queries=dict(), + filter_queries=dict(), p_show=['word'], s_show=[], + match_strategy='longest'): + """ + + :return: concordance lines, each one a dict + :rtype: list(dict) + """ + + # CHECK CQP + identifier = self.quick_query(topic_query, s_context, filter_queries.values(), match_strategy) + cqp = self.start_cqp() + + # init CONTEXT (TextConstellation) + cqp.Exec(f'{identifier};') + df_context = cqp.Dump(f'{identifier};') + dump_context = Dump(self.copy(), df_context, None) + dump_context = dump_context.set_context(window, s_context) + df_context = dump_context.df[['contextid', 'context', 'contextend']] + + # index by TOPIC MATCHES + cqp.Exec(f'Temp = {topic_query};') + df_query = cqp.Dump('Temp;') + dump_query = Dump(self.copy(), df_query, None) + dump_query = dump_query.set_context(window, s_context) + df_context = dump_left_join(df_context, dump_query.df, 'topic', drop=True, window=window) + df_context = df_context.set_index(['match_topic', 'matchend_topic']) + df_context.index.names = ['match', 'matchend'] + + # FILTER according to window size + for name, query in filter_queries.items(): + cqp.Exec(f'Temp = {query};') + df_query = cqp.Dump('Temp;') + dump_query = Dump(self.copy(), df_query, None) + dump_query = dump_query.set_context(window, s_context) + df_context = dump_left_join(df_context, dump_query.df, name, drop=True, window=window) + df_context = df_context.drop([c + "_" + name for c in ['match', 'matchend', 'offset']], axis=1) + + # HIGHLIGHT + for name, query in highlight_queries.items(): + cqp.Exec(f'Temp = {query};') + df_query = cqp.Dump('Temp;') + dump_query = Dump(self.copy(), df_query, None) + dump_query = dump_query.set_context(window, s_context) + df_context = dump_left_join(df_context, dump_query.df, name, drop=False, window=window) + + cqp.__kill__() + + # ACTUAL CONCORDANCING + hkeys = list(highlight_queries.keys()) + df = group_lines(df_context, hkeys) + conc = Concordance(self.copy(), df) + lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, order=order, cut_off=cut_off) + output = lines.apply(lambda row: format_roles(row, hkeys, s_show, window, htmlify_meta=True), axis=1) + + return list(output) diff --git a/ccc/discoursemes.py b/ccc/discoursemes.py index c211a94..7618242 100644 --- a/ccc/discoursemes.py +++ b/ccc/discoursemes.py @@ -9,14 +9,14 @@ # requirements from association_measures.measures import score -from pandas import NA, DataFrame, concat +from pandas import DataFrame, concat # part of module from . import Corpus from .collocates import Collocates, dump2cooc from .concordances import Concordance from .dumps import Dump -from .utils import format_cqp_query +from .utils import dump_left_join, format_cqp_query, format_roles logger = logging.getLogger(__name__) @@ -115,57 +115,6 @@ def __init__(self): pass -def constellation_left_join(df1, df2, name, drop=True, window=None): - """join an additional dump df2 to an existing constellation - - :param DataFrame df1: constellation dump === (m, me) ci c ce m_t* me_t* o_t* m_1* me_1* o_1* ... == - :param DataFrame df2: additional dump === (m, me) ci c ce == - :param str name: name for additional discourseme - :param bool drop: drop all rows that do not contain all discoursemes within topic context? - :return: constellation dump including additional dump === (m, me) ci c ce m_t me_t o_t m_1 me_1 o_1 ... m_name me_name o_name == - :rtype: DataFrame - """ - - # merge dumps via contextid ### - df1 = df1.reset_index() - df2 = df2.reset_index()[['contextid', 'match', 'matchend']].astype("Int64") - m = df1.merge(df2, on='contextid', how='left') - - # calculate offset ### - m['offset_y'] = 0 # init as overlap - # y .. x - m.loc[m['match_x'] > m['matchend_y'], 'offset_y'] = m['matchend_y'] - m['match_x'] - # x .. y - m.loc[m['matchend_x'] < m['match_y'], 'offset_y'] = m['match_y'] - m['matchend_x'] - # missing y - m.loc[m['match_y'].isna(), 'offset_y'] = NA - - # restrict to complete constellation ### - if drop: - m = m.dropna() - if window is None: - # only keep co-occurrences that are within context - m = m.loc[ - (m['matchend_y'] >= m['context']) & (m['match_y'] < m['contextend']) - ] - else: - m = m.loc[abs(m['offset_y']) <= window] - - # rename columns ### - m = m.rename(columns={ - 'match_x': 'match', - 'matchend_x': 'matchend', - 'match_y': 'match_' + name, - 'matchend_y': 'matchend_' + name, - 'offset_y': 'offset_' + name - }) - - # set index ### - m = m.set_index(['match', 'matchend']) - - return m - - def aggregate_matches(df, name, context_col='contextid', match_cols=['match', 'matchend']): @@ -185,88 +134,6 @@ def aggregate_matches(df, name, context_col='contextid', return table -def format_roles(row, names, s_show, window, htmlify_meta=False): - """Take a row of a dataframe indexed by match, matchend of the node, - columns for each discourseme with sets of tuples indicating discourseme positions, - columns for each s in s_show, - and a column 'dict' containing the pre-formatted concordance line. - - creates a list (aligned with other lists) of lists of roles; roles are: - - 'node' (if cpos in index) - - 'out_of_window' (if offset of cpos from node > window) - - discourseme names - - :return: concordance line for MMDA frontend - :rtype: dict - - """ - - # TODO directly create relevant objects, no need for frontend to take care of it - # init - d = row['dict'] - roles = list() - - # 'out_of_window' | None | 'node' - role = ['out_of_window' if abs(t) > window else None for t in d['offset']] - for i in range(d['cpos'].index(row.name[0]), d['cpos'].index(row.name[1]) + 1): - role[i] = 'node' - roles.append(role) - - # discourseme names - for name in names: - - role = [None] * len(d['offset']) - - if not isinstance(row[name], float): - for t in row[name]: - - # check match information - if len(t) == 2: - # lazy definition without offset - start = 0 - end = 1 - elif len(t) == 3: - # with offset - start = 1 - end = 2 - else: - continue - - # skip NAs - if not isinstance(t[start], int): - continue - - # skip the ones too far away - try: - start = d['cpos'].index(t[start]) - end = d['cpos'].index(t[end]) + 1 - except ValueError: - continue - - for i in range(start, end): - role[i] = name - - roles.append(role) - - # combine individual roles into one list of lists - d['role'] = [[a for a in set(r) if a is not None] for r in list(zip(*roles))] - - # add s-attributes - if htmlify_meta: - meta = {key: row[key] for key in s_show if not key.startswith("BOOL")} - d['meta'] = DataFrame.from_dict( - meta, orient='index' - ).to_html(bold_rows=False, header=False) - for s in s_show: - if s.startswith("BOOL"): - d[s] = row[s] - else: - for s in s_show: - d[s] = row[s] - - return d - - class Constellation: def __init__(self, dump, name='topic'): @@ -309,8 +176,8 @@ def add_discourseme(self, dump, name='discourseme', drop=True, window=None): return self.discoursemes[name] = dump - self.df = constellation_left_join(self.df, dump.df, name, - drop=drop, window=window) + self.df = dump_left_join(self.df, dump.df, name, + drop=drop, window=window) def group_lines(self): """ @@ -371,7 +238,7 @@ def concordance(self, window=5, elif order == 'first': df = df.head(cut_off) elif order == 'last': - df = df.head(cut_off) + df = df.tail(cut_off) # TODO else: raise NotImplementedError diff --git a/ccc/utils.py b/ccc/utils.py index 15a2523..c0f5056 100644 --- a/ccc/utils.py +++ b/ccc/utils.py @@ -13,7 +13,7 @@ # requirements import numpy as np -from pandas import NA +from pandas import NA, DataFrame from unidecode import unidecode logger = logging.getLogger(__name__) @@ -351,6 +351,165 @@ def fold_df(df, flags="%cd"): def filter_df(df, path): + data = pkgutil.get_data(__name__, path) items = set(data.decode().split("\n")) return df.loc[~df.index.isin(items)] + + +########################## +# concordance dataframes # +########################## +def dump_left_join(df1, df2, name, drop=True, window=None): + """join an additional dump df2 to an existing constellation + + :param DataFrame df1: constellation dump === (m, me) ci c ce m_t* me_t* o_t* m_1* me_1* o_1* ... == + :param DataFrame df2: additional dump === (m, me) ci c ce == + :param str name: name for additional discourseme + :param bool drop: drop all rows that do not contain all discoursemes within topic context? + :return: constellation dump including additional dump === (m, me) ci c ce m_t me_t o_t m_1 me_1 o_1 ... m_name me_name o_name == + :rtype: DataFrame + """ + + # merge dumps via contextid ### + df1 = df1.reset_index() + df2 = df2.reset_index()[['contextid', 'match', 'matchend']].astype("Int64") + m = df1.merge(df2, on='contextid', how='left') + + # calculate offset ### + m['offset_y'] = 0 # init as overlap + # y .. x + m.loc[m['match_x'] > m['matchend_y'], 'offset_y'] = m['matchend_y'] - m['match_x'] + # x .. y + m.loc[m['matchend_x'] < m['match_y'], 'offset_y'] = m['match_y'] - m['matchend_x'] + # missing y + m.loc[m['match_y'].isna(), 'offset_y'] = NA + + # restrict to complete constellation ### + if drop: + m = m.dropna() + # only keep co-occurrences that are within context + m = m.loc[(m['matchend_y'] >= m['context']) & (m['match_y'] < m['contextend'])] + if window: + # only keep co-occurrences that are within window + m = m.loc[abs(m['offset_y']) <= window] + + # rename columns ### + m = m.rename(columns={'match_x': 'match', + 'matchend_x': 'matchend', + 'match_y': 'match_' + name, + 'matchend_y': 'matchend_' + name, + 'offset_y': 'offset_' + name}) + + # set index ### + m = m.set_index(['match', 'matchend']) + + return m + + +def group_lines(df, names): + """ + convert dataframe: + === (m, me) ci c ce m0 m0e o0 m1 me1 o1 m2 me2 o2 === + with duplicate indices to + === (m, me) ci c ce m0 m1 m2 === + without duplicate indices + where + m0 = {(o0, m0, m0e), (o0, m0, m0e), ...} + m1 = {(o1, m1, m1e), ...} + + """ + + df = df.copy() + # TODO: deduplication necessary? + df_reduced = df[~df.index.duplicated(keep='first')][['contextid', 'context', 'contextend']] + for name in names: + columns = [m + "_" + name for m in ['offset', 'match', 'matchend']] + df[name] = df[columns].values.tolist() + df[name] = df[name].apply(tuple) + df = df.drop(columns, axis=1) + df_reduced[name] = df.groupby(level=['match', 'matchend'], group_keys=False)[name].apply(set) + + return df_reduced + + +def format_roles(row, names, s_show, window, htmlify_meta=False): + """Take a row of a dataframe indexed by match, matchend of the node, + columns for each discourseme with sets of tuples indicating discourseme positions, + columns for each s in s_show, + and a column 'dict' containing the pre-formatted concordance line. + + creates a list (aligned with other lists) of lists of roles; roles are: + - 'node' (if cpos in index) + - 'out_of_window' (if offset of cpos from node > window) + - discourseme names + + :return: concordance line for MMDA frontend + :rtype: dict + + """ + + # TODO directly create relevant objects, no need for frontend to take care of it + # init + d = row['dict'] + roles = list() + + # 'out_of_window' | None | 'node' + role = ['out_of_window' if abs(t) > window else None for t in d['offset']] + for i in range(d['cpos'].index(row.name[0]), d['cpos'].index(row.name[1]) + 1): + role[i] = 'node' + roles.append(role) + + # discourseme names + for name in names: + + role = [None] * len(d['offset']) + + if not isinstance(row[name], float): + for t in row[name]: + + # check match information + if len(t) == 2: + # lazy definition without offset + start = 0 + end = 1 + elif len(t) == 3: + # with offset + start = 1 + end = 2 + else: + continue + + # skip NAs + if not isinstance(t[start], int): + continue + + # skip the ones too far away + try: + start = d['cpos'].index(t[start]) + end = d['cpos'].index(t[end]) + 1 + except ValueError: + continue + + for i in range(start, end): + role[i] = name + + roles.append(role) + + # combine individual roles into one list of lists + d['role'] = [[a for a in set(r) if a is not None] for r in list(zip(*roles))] + + # add s-attributes + if htmlify_meta: + meta = {key: row[key] for key in s_show if not key.startswith("BOOL")} + d['meta'] = DataFrame.from_dict( + meta, orient='index' + ).to_html(bold_rows=False, header=False) + for s in s_show: + if s.startswith("BOOL"): + d[s] = row[s] + else: + for s in s_show: + d[s] = row[s] + + return d diff --git a/tests/conftest.py b/tests/conftest.py index f20ee3d..52b9c26 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -82,6 +82,29 @@ def germaparl(): } +@pytest.fixture +def germaparl_full(): + """ settings for local germaparl corpus """ + + corpus_name = "GERMAPARL-1949-2021" + registry_path = "/usr/local/share/cwb/registry" + lib_path = None + + context = 50 + s_context = 'speaker_node' + + s_query = 's' + + return { + 'registry_path': registry_path, + 'corpus_name': corpus_name, + 'context': context, + 's_context': s_context, + 's_query': s_query, + 'lib_path': lib_path, + } + + @pytest.fixture def discoursemes(): """ discoursemes """ diff --git a/tests/test_05_concordance.py b/tests/test_05_concordance.py index 0e83b61..fa268fd 100644 --- a/tests/test_05_concordance.py +++ b/tests/test_05_concordance.py @@ -17,6 +17,63 @@ def get_corpus(corpus_settings, data_path=DATA_PATH): ) +############### +# CONCORDANCE # +############### +@pytest.mark.concordance +def test_ccc_quick_conc(germaparl): + + corpus = get_corpus(germaparl) + + topic_query = '[lemma="die"]' + s_context = 's' + window = 5 + filter_queries = highlight_queries = { + 'CSU': '[lemma="CSU"]', + 'CDU': '[lemma="CDU"]' + } + + conc = corpus.quick_conc( + topic_query=topic_query, + filter_queries=filter_queries, + highlight_queries=highlight_queries, + window=window, + s_context=s_context, + cut_off=100, + order='first' + ) + + assert len(conc) == 100 + assert 'cpos' in conc[0] + + +@pytest.mark.concordance +def test_ccc_quick_conc_random(germaparl): + + corpus = get_corpus(germaparl) + + topic_query = '[lemma="die"]' + s_context = 's' + window = 5 + filter_queries = highlight_queries = { + 'CSU': '[lemma="CSU"]', + 'CDU': '[lemma="CDU"]' + } + + conc = corpus.quick_conc( + topic_query=topic_query, + filter_queries=filter_queries, + highlight_queries=highlight_queries, + window=window, + s_context=s_context, + cut_off=100, + order=50 + ) + + assert len(conc) == 100 + assert 'cpos' in conc[0] + + @pytest.mark.raw def test_concordance_simple(germaparl): corpus = get_corpus(germaparl) From c3e25062db6949cfed88e5618f25ee543479f339 Mon Sep 17 00:00:00 2001 From: Philipp Heinrich Date: Fri, 25 Nov 2022 13:25:42 +0100 Subject: [PATCH 2/4] simplify quick_query and quick_conc; integrate into discoursemes --- ccc/cwb.py | 150 +++++++++++++++++++------- ccc/discoursemes.py | 194 +++++++--------------------------- ccc/utils.py | 37 ++++++- tests/test_04_cwb.py | 41 +++++++ tests/test_05_concordance.py | 26 +++++ tests/test_11_discoursemes.py | 5 +- 6 files changed, 248 insertions(+), 205 deletions(-) diff --git a/ccc/cwb.py b/ccc/cwb.py index 70ba5cc..887243c 100644 --- a/ccc/cwb.py +++ b/ccc/cwb.py @@ -23,7 +23,7 @@ from .cqp import CQP from .dumps import Dump from .utils import (chunk_anchors, correct_anchors, dump_left_join, - format_roles, group_lines, preprocess_query) + format_roles, group_lines, preprocess_query, aggregate_matches) from .version import __version__ logger = logging.getLogger(__name__) @@ -1165,16 +1165,35 @@ def query(self, cqp_query=None, context=20, context_left=None, else: raise NotImplementedError() - def quick_query(self, topic_query, s_context, filter_queries=[], match_strategy='longest'): + def quick_query(self, s_context, topic_query="", filter_queries=[], match_strategy='longest'): """makes sure query result is defined as subcorpus. - finds all s_context spans that contain topic_query and all filter_queries + without topic query: + - finds all s_context spans that contain at least one filter_query + with topic query: + - finds all s_context spans that contain topic_query and all filter_queries :return: identifier (name of NQR on disk) :rtype: str """ + if len(topic_query) == 0: + identifier = generate_idx([self.subcorpus, filter_queries, s_context, match_strategy], prefix='Query') + + cqp = self.start_cqp() + cqp.Exec(f'set MatchingStrategy "{match_strategy}";') + size = int(cqp.Exec(f'size {identifier};')) + + if size == 0: + disjunction = " | ".join(['(' + q + ')' for q in filter_queries]) + logger.info(f'disjunction query: {disjunction}') + cqp.Query(f'{identifier} = {disjunction} within {s_context} expand to {s_context};') + logger.info(f'.. saving {identifier} in CWB binary format') + cqp.Exec(f'save {identifier};') + + return identifier + # IDENTIFY topic_identifier = generate_idx([self.subcorpus, topic_query, s_context, match_strategy], prefix='Query') filter_identifier = generate_idx([self.subcorpus, topic_query, s_context, match_strategy, filter_queries], prefix='Query') @@ -1223,50 +1242,99 @@ def quick_conc(self, topic_query, s_context, window, order=42, :rtype: list(dict) """ - # CHECK CQP - identifier = self.quick_query(topic_query, s_context, filter_queries.values(), match_strategy) - cqp = self.start_cqp() + if len(topic_query) == 0: - # init CONTEXT (TextConstellation) - cqp.Exec(f'{identifier};') - df_context = cqp.Dump(f'{identifier};') - dump_context = Dump(self.copy(), df_context, None) - dump_context = dump_context.set_context(window, s_context) - df_context = dump_context.df[['contextid', 'context', 'contextend']] - - # index by TOPIC MATCHES - cqp.Exec(f'Temp = {topic_query};') - df_query = cqp.Dump('Temp;') - dump_query = Dump(self.copy(), df_query, None) - dump_query = dump_query.set_context(window, s_context) - df_context = dump_left_join(df_context, dump_query.df, 'topic', drop=True, window=window) - df_context = df_context.set_index(['match_topic', 'matchend_topic']) - df_context.index.names = ['match', 'matchend'] - - # FILTER according to window size - for name, query in filter_queries.items(): - cqp.Exec(f'Temp = {query};') - df_query = cqp.Dump('Temp;') - dump_query = Dump(self.copy(), df_query, None) - dump_query = dump_query.set_context(window, s_context) - df_context = dump_left_join(df_context, dump_query.df, name, drop=True, window=window) - df_context = df_context.drop([c + "_" + name for c in ['match', 'matchend', 'offset']], axis=1) + queries = {**highlight_queries, **filter_queries} + + # INIT CQP + identifier = self.quick_query(s_context, topic_query="", filter_queries=queries.values(), match_strategy=match_strategy) + cqp = self.start_cqp() - # HIGHLIGHT - for name, query in highlight_queries.items(): - cqp.Exec(f'Temp = {query};') + # init CONTEXT (TextConstellation) + cqp.Exec(f'cut {identifier} {cut_off};') + df_context = cqp.Dump(f'{identifier};') + dump_context = Dump(self.copy(), df_context, None) + dump_context = dump_context.set_context(context_break=s_context) + df_context = dump_context.df[['contextid']] + df_context = df_context.reset_index().set_index('contextid') + + # HIGHLIGHT + cqp.Exec(f'{identifier};') + for name, query in queries.items(): + cqp.Exec(f'Temp = {query};') + df_query = cqp.Dump('Temp;') + if len(df_query) > 0: + dump_query = Dump(self.copy(), df_query, None) + dump_query = dump_query.set_context(context_break=s_context) + df_query = dump_query.df[['contextid']] + df_agg = aggregate_matches(df_query, name) + df_context = df_context.join(df_agg) + else: + df_context[name] = None + df_context[name + '_BOOL'] = False + df_context[name + '_COUNTS'] = 0 + cqp.__kill__() + + # index by CONTEXT MATCHES + df = df_context.set_index(['match', 'matchend']) + names = list(queries.keys()) + names_bool = [n + '_BOOL' for n in names] + names_count = [n + '_COUNTS' for n in names] + for b, c in zip(names_bool, names_count): + df[b] = df[b].fillna(False) + df[c] = df[c].fillna(0) + + # ACTUAL CONCORDANCING + conc = Concordance(self.copy(), df) + lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, order=order, cut_off=cut_off) + output = lines.apply(lambda row: format_roles(row, names, s_show=names_bool+s_show, window=0, htmlify_meta=True), axis=1) + + else: + + # INIT CQP + identifier = self.quick_query(s_context, topic_query, filter_queries.values(), match_strategy) + cqp = self.start_cqp() + + # init CONTEXT (TextConstellation) + cqp.Exec(f'{identifier};') + df_context = cqp.Dump(f'{identifier};') + dump_context = Dump(self.copy(), df_context, None) + dump_context = dump_context.set_context(window, s_context) + df_context = dump_context.df[['contextid', 'context', 'contextend']] + + # index by TOPIC MATCHES + cqp.Exec(f'Temp = {topic_query};') df_query = cqp.Dump('Temp;') dump_query = Dump(self.copy(), df_query, None) dump_query = dump_query.set_context(window, s_context) - df_context = dump_left_join(df_context, dump_query.df, name, drop=False, window=window) + df_context = dump_left_join(df_context, dump_query.df, 'topic', drop=True, window=window) + df_context = df_context.set_index(['match_topic', 'matchend_topic']) + df_context.index.names = ['match', 'matchend'] + + # FILTER according to window size + for name, query in filter_queries.items(): + cqp.Exec(f'Temp = {query};') + df_query = cqp.Dump('Temp;') + dump_query = Dump(self.copy(), df_query, None) + dump_query = dump_query.set_context(window, s_context) + df_context = dump_left_join(df_context, dump_query.df, name, drop=True, window=window) + df_context = df_context.drop([c + "_" + name for c in ['match', 'matchend', 'offset']], axis=1) + + # HIGHLIGHT + for name, query in highlight_queries.items(): + cqp.Exec(f'Temp = {query};') + df_query = cqp.Dump('Temp;') + dump_query = Dump(self.copy(), df_query, None) + dump_query = dump_query.set_context(window, s_context) + df_context = dump_left_join(df_context, dump_query.df, name, drop=False, window=window) - cqp.__kill__() + cqp.__kill__() - # ACTUAL CONCORDANCING - hkeys = list(highlight_queries.keys()) - df = group_lines(df_context, hkeys) - conc = Concordance(self.copy(), df) - lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, order=order, cut_off=cut_off) - output = lines.apply(lambda row: format_roles(row, hkeys, s_show, window, htmlify_meta=True), axis=1) + # ACTUAL CONCORDANCING + hkeys = list(highlight_queries.keys()) + df = group_lines(df_context, hkeys) + conc = Concordance(self.copy(), df) + lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, order=order, cut_off=cut_off) + output = lines.apply(lambda row: format_roles(row, hkeys, s_show, window, htmlify_meta=True), axis=1) return list(output) diff --git a/ccc/discoursemes.py b/ccc/discoursemes.py index 7618242..ab54477 100644 --- a/ccc/discoursemes.py +++ b/ccc/discoursemes.py @@ -16,7 +16,7 @@ from .collocates import Collocates, dump2cooc from .concordances import Concordance from .dumps import Dump -from .utils import dump_left_join, format_cqp_query, format_roles +from .utils import dump_left_join, format_cqp_query, aggregate_matches, group_lines, format_roles logger = logging.getLogger(__name__) @@ -24,7 +24,6 @@ ######################################################## # (1) FOCUS ON TOPIC: DIRECTED (IE INDEXED) DATAFRAMES # ######################################################## - # init(dump, name='topic') # input : dump.df: === (m, me) ci c ce == # output: self.df: === (m, me) ci c ce m_topic me_topic o_topic === @@ -38,27 +37,9 @@ # 1202 1202 36 1195 1218 1202 1202 0 # ... ... ... ... ... ... ... ... -# #### 1 occurrence -# ... die [CDU]:m-me+m_t-me_t will eigentlich ... - -# #### 2 occurrences -# ... die [CDU]:m-me+m_t-me_t und [CSU] gehen da ... 0 -# ... die [CDU]:m-me und [CSU]:m_t-me_t gehen da ... 2 -# ... die [CDU]:m_t-me_t und [CSU]:m-me gehen da ... -2 -# ... die [CDU] und [CSU]:m-me+m_t-me_t gehen da ... 0 - -# #### 3 occurrences -# ... die [Union]:0+1, d.h. die [CDU] und [CSU] gehen da ... -# ... die [Union]:0, d.h. die [CDU]:1 und [CSU] gehen da ... -# ... die [Union]:0, d.h. die [CDU] und [CSU]:1 gehen da ... -# ... die [Union]:1, d.h. die [CDU]:0 und [CSU] gehen da ... -# ... die [Union], d.h. die [CDU]:0+1 und [CSU] gehen da ... -# ... die [Union], d.h. die [CDU]:0 und [CSU]:1 gehen da ... -# ... die [Union]:1, d.h. die [CDU] und [CSU]:0 gehen da ... -# ... die [Union], d.h. die [CDU]:1 und [CSU]:0 gehen da ... -# ... die [Union], d.h. die [CDU] und [CSU]:0+1 gehen da ... - +########################################################## # add_discourseme(dump, name, drop=True) +########################################################## # input: === (m, me) ci c ce m_t me_t o_t == # === (m_d, me_d) ci == # output: === (m, me) ci c ce m_t me_t o_t m_d me_d o_d == @@ -71,19 +52,12 @@ # 1202 1202 36 1195 1218 1202 1202 0 1205 1205 3 # ... ... ... ... ... ... ... ... ... ... ... -# #### t . d -# #### d . t -# ... die [CDU]:0+1 [will]:2 eigentlich ... - -# #### t . t . d -# #### t . d . t -# #### d . t . t -# ... die [CDU]:0+1 und [CSU] [will]:2 eigentlich ... -# ... die [CDU]:0 und [CSU]:1 [will]:2 eigentlich ... -# ... die [CDU]:1 und [CSU]:0 [will]:2 eigentlich ... -# ... die [CDU] und [CSU]:0+1 [will]:2 eigentlich ... - +########################################################## # add_discourseme(dump, name, drop=False) +########################################################## +# input: === (m, me) ci c ce m_t me_t o_t == +# === (m_d, me_d) ci == +# output: === (m, me) ci c ce m_t me_t o_t m_d me_d o_d == # (m me) ci c ce m_t me_t o_t m_d me_d o_d # 638 638 18 629 672 638 638 0 636 636 -2 @@ -95,7 +69,9 @@ # 149292 149292 7317 149286 149306 149290 149290 -2 # 149292 7317 149286 149306 149292 149292 0 +########################################################## # group_lines() +########################################################## # === (m, me) ci c ce m0 m0e o0 m1 me1 o1 m2 me2 o2 === # with duplicate indices to # === (m, me) ci c ce d0 d1 d2 === @@ -115,25 +91,6 @@ def __init__(self): pass -def aggregate_matches(df, name, context_col='contextid', - match_cols=['match', 'matchend']): - - # counts - counts = DataFrame(df[context_col].value_counts()).astype("Int64") - counts.columns = ['COUNTS_' + name] - - # matches - matches = df.reset_index() - matches['MATCHES_' + name] = matches[match_cols].values.tolist() - matches['MATCHES_' + name] = matches['MATCHES_' + name].apply(tuple) - matches = matches.groupby('contextid', group_keys=True)['MATCHES_' + name].apply(set) - - # combine - table = counts.join(matches) - - return table - - class Constellation: def __init__(self, dump, name='topic'): @@ -179,32 +136,6 @@ def add_discourseme(self, dump, name='discourseme', drop=True, window=None): self.df = dump_left_join(self.df, dump.df, name, drop=drop, window=window) - def group_lines(self): - """ - convert dataframe: - === (m, me) ci c ce m0 m0e o0 m1 me1 o1 m2 me2 o2 === - with duplicate indices to - === (m, me) ci c ce m0 m1 m2 === - without duplicate indices - where - m0 = {(o0, m0, m0e), (o0, m0, m0e), ...} - m1 = {(o1, m1, m1e), ...} - - """ - - df = self.df.copy() - df_reduced = df[~df.index.duplicated(keep='first')][ - ['contextid', 'context', 'contextend'] - ] - for name in self.discoursemes.keys(): - columns = [m + "_" + name for m in ['offset', 'match', 'matchend']] - df[name] = df[columns].values.tolist() - df[name] = df[name].apply(tuple) - df = df.drop(columns, axis=1) - df_reduced[name] = df.groupby(level=['match', 'matchend'])[name].apply(set) - - return df_reduced - def breakdown(self, p_atts=['word'], flags=""): breakdowns = list() @@ -229,30 +160,11 @@ def concordance(self, window=5, """ # convert dataframe - df = self.group_lines() - - # cut off and sampling (done here to be able to use random_seed) - cut_off = len(df) if cut_off is None or cut_off > len(df) else cut_off - if order == 'random': - df = df.sample(cut_off, random_state=random_seed) - elif order == 'first': - df = df.head(cut_off) - elif order == 'last': - df = df.tail(cut_off) # TODO - else: - raise NotImplementedError - - # retrieve concordance lines + hkeys = list(self.discoursemes.keys()) + df = group_lines(self.df, hkeys) conc = Concordance(self.corpus.copy(), df) - lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, - order=order, cut_off=cut_off) - - # map roles - output = lines.apply( - lambda row: format_roles( - row, self.discoursemes.keys(), s_show, window, htmlify_meta - ), axis=1 - ) + lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, order=order, cut_off=cut_off) + output = lines.apply(lambda row: format_roles(row, hkeys, s_show, window, htmlify_meta=True), axis=1) return list(output) @@ -366,79 +278,49 @@ def breakdown(self, p_atts=['word'], flags=""): return breakdown - def concordance(self, window=0, - p_show=['word', 'lemma'], s_show=[], - order='random', cut_off=100, random_seed=42, - htmlify_meta=False, form='list'): - - # cut off and sampling - cut_off = len(self.df) if cut_off is None or cut_off > len(self.df) else cut_off - if order == 'random': - df = self.df.sample(cut_off, random_state=random_seed) - elif order == 'first': - df = self.df.head(cut_off) - elif order == 'last': - df = self.df.head(cut_off) - else: - raise NotImplementedError + def concordance(self, window=0, p_show=['word', 'lemma'], + s_show=[], order='random', cut_off=100, + random_seed=42, htmlify_meta=False): # join context..contextend contexts = self.corpus.dump_from_s_att(self.s_context, annotation=False) contexts.columns = ['contextid'] contexts = contexts.reset_index().set_index('contextid') - df = df.join(contexts).set_index(['match', 'matchend']) - - # retrieve concordance lines + df = self.df.join(contexts) + + # index by CONTEXT MATCHES + df = df.set_index(['match', 'matchend']) + names = list(self.discoursemes.keys()) + names_bool = [n + '_BOOL' for n in names] + names_count = [n + '_COUNTS' for n in names] + for b, c in zip(names_bool, names_count): + df[b] = df[b].fillna(False) + df[c] = df[c].fillna(0) + + # ACTUAL CONCORDANCING conc = Concordance(self.corpus.copy(), df) - lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, - order=order, cut_off=cut_off) - - # get boolean columns for each discourseme - names_bool = list() - for name in [c for c in df.columns if c.startswith("COUNTS_")]: - name_bool = '_'.join(['BOOL', name.split("COUNTS_")[-1]]) - names_bool.append(name_bool) - lines[name_bool] = (lines[name] > 0) - lines[name_bool] = lines[name_bool].fillna(False) - - # format roles - match_cols = [c for c in lines.columns if c.startswith("MATCHES_")] - match_names = [c.split("MATCHES_")[-1] for c in match_cols] - col_mapper = dict(zip(match_cols, match_names)) - lines = lines.rename(columns=col_mapper) - - if form == 'table': - return lines - elif form == 'list': - output = lines.apply( - lambda row: format_roles( - row, match_names, s_show=names_bool+s_show, window=window, htmlify_meta=htmlify_meta - ), axis=1 - ) - return list(output) - else: - raise NotImplementedError() + lines = conc.lines(form='dict', p_show=p_show, s_show=s_show, order=order, cut_off=cut_off) + output = lines.apply(lambda row: format_roles(row, names, s_show=names_bool+s_show, window=0, htmlify_meta=True), axis=1) + + return list(output) def associations(self, ams=None, frequencies=True, min_freq=2, order='log_likelihood', cut_off=None): - counts = self.df[[c for c in self.df.columns if c.startswith("COUNTS_")]] - counts.columns = [c.split("COUNTS_")[-1] for c in counts.columns] - cooc = counts > 0 + counts = self.df[[c for c in self.df.columns if c.endswith("_BOOL")]] + counts.columns = [c.split("_BOOL")[0] for c in counts.columns] + cooc = counts.fillna(False) # TODO triangular matrix tables = DataFrame() for name in counts.columns: - table = round(textual_associations( - cooc, self.N, name - ).reset_index(), 2) + table = round(textual_associations(cooc, self.N, name).reset_index(), 2) table['node'] = name tables = concat([tables, table]) tables = tables[ - ['node', 'candidate'] + - [d for d in tables.columns if d not in ['node', 'candidate']] + ['node', 'candidate'] + [d for d in tables.columns if d not in ['node', 'candidate']] ] return tables diff --git a/ccc/utils.py b/ccc/utils.py index c0f5056..50166d6 100644 --- a/ccc/utils.py +++ b/ccc/utils.py @@ -433,6 +433,35 @@ def group_lines(df, names): return df_reduced +def aggregate_matches(df, name, context_col='contextid', + match_cols=['match', 'matchend']): + """ + convert dataframe: + === (m, me) ci === + to + === (ci) {name} COUNTS_{name} BOOL_{name} === + + """ + # counts + counts = DataFrame(df[context_col].value_counts()).astype("Int64") + counts.columns = [name + '_COUNTS'] + + # matches + matches = df.reset_index() + matches[name] = matches[match_cols].values.tolist() + matches[name] = matches[name].apply(tuple) + matches = matches.groupby('contextid', group_keys=True)[name].apply(set) + + # combine + table = counts.join(matches) + + # bool + table[name + '_BOOL'] = table[name + '_COUNTS'] > 0 + table[name + '_BOOL'] = table[name + '_BOOL'].fillna(False) + + return table + + def format_roles(row, names, s_show, window, htmlify_meta=False): """Take a row of a dataframe indexed by match, matchend of the node, columns for each discourseme with sets of tuples indicating discourseme positions, @@ -501,12 +530,10 @@ def format_roles(row, names, s_show, window, htmlify_meta=False): # add s-attributes if htmlify_meta: - meta = {key: row[key] for key in s_show if not key.startswith("BOOL")} - d['meta'] = DataFrame.from_dict( - meta, orient='index' - ).to_html(bold_rows=False, header=False) + meta = {key: row[key] for key in s_show if not key.endswith("_BOOL")} + d['meta'] = DataFrame.from_dict(meta, orient='index').to_html(bold_rows=False, header=False) for s in s_show: - if s.startswith("BOOL"): + if s.endswith("_BOOL"): d[s] = row[s] else: for s in s_show: diff --git a/tests/test_04_cwb.py b/tests/test_04_cwb.py index 79bfffc..e6ceece 100644 --- a/tests/test_04_cwb.py +++ b/tests/test_04_cwb.py @@ -517,3 +517,44 @@ def test_query_anchor(germaparl): corrections={2: 1} ) assert (d.df[2] == d.df['contextend']).all() + + +@pytest.mark.now +def test_ccc_quick_query(germaparl): + + corpus = get_corpus(germaparl) + + topic_query = '[lemma="die"]' + s_context = 's' + filter_queries = { + 'CSU': '[lemma="CSU"]', + 'CDU': '[lemma="CDU"]' + } + + identifier = corpus.quick_query( + s_context=s_context, + topic_query=topic_query, + filter_queries=filter_queries.values() + ) + + assert identifier in corpus.show_nqr()['subcorpus'].values + + +@pytest.mark.now +def test_ccc_quick_query_2(germaparl): + + corpus = get_corpus(germaparl) + + s_context = 's' + queries = { + 'Angela': '[lemma="Angela"]', + 'CSU': '[lemma="CSU"]', + 'CDU': '[lemma="CDU"]' + } + + identifier = corpus.quick_query( + s_context=s_context, + filter_queries=queries.values() + ) + + assert identifier in corpus.show_nqr()['subcorpus'].values diff --git a/tests/test_05_concordance.py b/tests/test_05_concordance.py index fa268fd..4a6e60d 100644 --- a/tests/test_05_concordance.py +++ b/tests/test_05_concordance.py @@ -21,6 +21,7 @@ def get_corpus(corpus_settings, data_path=DATA_PATH): # CONCORDANCE # ############### @pytest.mark.concordance +@pytest.mark.now def test_ccc_quick_conc(germaparl): corpus = get_corpus(germaparl) @@ -47,6 +48,31 @@ def test_ccc_quick_conc(germaparl): assert 'cpos' in conc[0] +@pytest.mark.concordance +def test_ccc_quick_conc_2(germaparl): + + corpus = get_corpus(germaparl) + + s_context = 's' + queries = { + 'Herr': '[lemma="Herr"]', + 'CSU': '[lemma="CSU"]', + 'CDU': '[lemma="CDU"]' + } + + conc = corpus.quick_conc( + s_context=s_context, + topic_query={}, + window=0, + filter_queries=queries, + cut_off=100, + order='first' + ) + + assert len(conc) == 100 + assert 'cpos' in conc[0] + + @pytest.mark.concordance def test_ccc_quick_conc_random(germaparl): diff --git a/tests/test_11_discoursemes.py b/tests/test_11_discoursemes.py index b6bce93..af02646 100644 --- a/tests/test_11_discoursemes.py +++ b/tests/test_11_discoursemes.py @@ -537,7 +537,7 @@ def test_textual_constellation(germaparl, discoursemes): s_context=discoursemes['parameters']['s_context'] ) assert len(const.df) == 624 - assert 'MATCHES_topic' in const.df.columns + assert 'topic' in const.df.columns def test_textual_constellation_add(germaparl, discoursemes): @@ -580,7 +580,7 @@ def test_textual_constellation_add(germaparl, discoursemes): ) assert len(const.df) == 2156 - assert 'MATCHES_discourseme' in const.df.columns + assert 'discourseme' in const.df.columns def test_textual_constellation_association(germaparl, discoursemes): @@ -730,7 +730,6 @@ def test_textual_constellation_concordance(germaparl, discoursemes): assert len(lines) == 2198 -@pytest.mark.now def test_textual_constellation_breakdown(germaparl, discoursemes): corpus_name = germaparl['corpus_name'] From 4cc7b23cacc66061559a30af2b7325cb55fe733d Mon Sep 17 00:00:00 2001 From: Philipp Heinrich Date: Fri, 25 Nov 2022 13:28:22 +0100 Subject: [PATCH 3/4] update pytest to fix vulnerability issue --- Pipfile | 2 +- Pipfile.lock | 446 +++++++++++++++++++++++++-------------------------- 2 files changed, 222 insertions(+), 226 deletions(-) diff --git a/Pipfile b/Pipfile index b82c0f9..faea78c 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,7 @@ verify_ssl = true [dev-packages] cython = "==0.29.30" -pytest = "==7.0.1" +pytest = "==7.2.0" pylint = "==2.13.9" pytest-cov = "==3.0.0" tabulate = "==0.8.9" diff --git a/Pipfile.lock b/Pipfile.lock index 13ef03b..8cf1757 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d26e6bff5573b47b197bfafb3f0fdba42137ab7421374b6071268a87f878d39b" + "sha256": "a3c7ad0e282b40813900650a31138b3da99db3b4d2b1de0e8f01bcf824d57a67" }, "pipfile-spec": 6, "requires": {}, @@ -65,67 +65,73 @@ }, "numexpr": { "hashes": [ - "sha256:052ec3a55cc1ccc447580ee5b828b2bd0bc14fea0756ddb81d9617b5472c77b5", - "sha256:08d8f8e31647815d979185eb455cb5b4d845e20ff808bd6f7f4edf5e0a35e2f6", - "sha256:1575a35190d650bf64d2efd8590a8ef3ca564ef20b9f8727428b57759712becb", - "sha256:19cd7563421862de85404bd5de06bee8a3ebff4fc9f718de09cc704bc3348f08", - "sha256:33be3bbbad71d97d14a39d84957c2bcc368fec775369664d0c24be030c50c359", - "sha256:3a1ce79b7d32c55cce334566e3c6716f7b646f6eceb2ace38adaa795848f3583", - "sha256:4ddc46c1e5d726b57d008169b75074ab66869e1827098614ebafa45d152f81b7", - "sha256:4f291f0df7b25d9530991f880cc232a644a7a722d130c61b43e593b98fb6523f", - "sha256:5532bd7164eb8a05410771faf94a661fc69e5ca72deb8612f1bedc26311ed3c8", - "sha256:5b014d1c426c444102fb9eea6438052ee86c82684d27dd20b531caf2c60bc4c9", - "sha256:5c660dea90935b963db9529d963493c40fabb2343684b52083fb86b2547d60c8", - "sha256:828926f5d4dc9ace2bebd2eec56bee852518afa31e6df175d1706e6631dfd1a2", - "sha256:854541cf4214d747ab2f87229e9dde052fddc52c379f59047d64f9b7e2f4d578", - "sha256:99b9a91811de8cd24bd7d7fbc1883653dad6485e8c683d85b1007a13868713f6", - "sha256:a6954d65d7140864d9bb2302b7580c60c88c4d12e00c59a0a53f1660573e922b", - "sha256:b127b0d0e1665b94adcc658c5f9d688ac4903ef81da5d8f4e956c995cf69d5c7", - "sha256:ba9aa03f4213f4e0c0d964afd6a920c9000e73d22b88c72c46b151d292ee5581", - "sha256:be9b8309a4a2f785197b1a29f7767a5ff217ea505e5a751b237336f3b50b7e48", - "sha256:c35669268f602ac6412c8c6244b256ebb4f31ffc926b936ca0d9cffda251db8a", - "sha256:cb647c9d9c785dae0759bf6c875cde2bec472b5c3f7a6015734b161ae766d141", - "sha256:cbd75ac287923bd0c5b95143915648c62d97f994b06dacd770bd205da014f6bd", - "sha256:eba7fad925e3063a0434844a667fbdea30b53fe1344efef73475b32d33aa0fec", - "sha256:f29b882a21b5381c0e472bc66e8d1c519b8920edc2522b8b4ede79e314d31d20", - "sha256:fe6b49631c3bf54e92b0fb334c8e59694685924492d80c325e1b44ecbbc0f22d" + "sha256:059546e8f6283ccdb47c683101a890844f667fa6d56258d48ae2ecf1b3875957", + "sha256:17ac9cfe6d0078c5fc06ba1c1bbd20b8783f28c6f475bbabd3cad53683075cab", + "sha256:3f039321d1c17962c33079987b675fb251b273dbec0f51aac0934e932446ccc3", + "sha256:5538b30199bfc68886d2be18fcef3abd11d9271767a7a69ff3688defe782800a", + "sha256:655d84eb09adfee3c09ecf4a89a512225da153fdb7de13c447404b7d0523a9a7", + "sha256:6931b1e9d4f629f43c14b21d44f3f77997298bea43790cfcdb4dd98804f90783", + "sha256:6c368aa35ae9b18840e78b05f929d3a7b3abccdba9630a878c7db74ca2368339", + "sha256:6ee9db7598dd4001138b482342b96d78110dd77cefc051ec75af3295604dde6a", + "sha256:77898fdf3da6bb96aa8a4759a8231d763a75d848b2f2e5c5279dad0b243c8dfe", + "sha256:7bca95f4473b444428061d4cda8e59ac564dc7dc6a1dea3015af9805c6bc2946", + "sha256:7d71add384adc9119568d7e9ffa8a35b195decae81e0abf54a2b7779852f0637", + "sha256:845a6aa0ed3e2a53239b89c1ebfa8cf052d3cc6e053c72805e8153300078c0b1", + "sha256:90f12cc851240f7911a47c91aaf223dba753e98e46dff3017282e633602e76a7", + "sha256:9400781553541f414f82eac056f2b4c965373650df9694286b9bd7e8d413f8d8", + "sha256:9e34931089a6bafc77aaae21f37ad6594b98aa1085bb8b45d5b3cd038c3c17d9", + "sha256:9f096d707290a6a00b6ffdaf581ee37331109fb7b6c8744e9ded7c779a48e517", + "sha256:a38664e699526cb1687aefd9069e2b5b9387da7feac4545de446141f1ef86f46", + "sha256:a6d2d7740ae83ba5f3531e83afc4b626daa71df1ef903970947903345c37bd03", + "sha256:a75967d46b6bd56455dd32da6285e5ffabe155d0ee61eef685bbfb8dafb2e484", + "sha256:b076db98ca65eeaf9bd224576e3ac84c05e451c0bd85b13664b7e5f7b62e2c70", + "sha256:b318541bf3d8326682ebada087ba0050549a16d8b3fa260dd2585d73a83d20a7", + "sha256:b96334fc1748e9ec4f93d5fadb1044089d73fb08208fdb8382ed77c893f0be01", + "sha256:c867cc36cf815a3ec9122029874e00d8fbcef65035c4a5901e9b120dd5d626a2", + "sha256:d5432537418d18691b9115d615d6daa17ee8275baef3edf1afbbf8bc69806147", + "sha256:db93cf1842f068247de631bfc8af20118bf1f9447cd929b531595a5e0efc9346", + "sha256:df35324666b693f13a016bc7957de7cc4d8801b746b81060b671bf78a52b9037", + "sha256:df3a1f6b24214a1ab826e9c1c99edf1686c8e307547a9aef33910d586f626d01", + "sha256:eaec59e9bf70ff05615c34a8b8d6c7bd042bd9f55465d7b495ea5436f45319d0", + "sha256:f3a920bfac2645017110b87ddbe364c9c7a742870a4d2f6120b8786c25dc6db3", + "sha256:ff5835e8af9a212e8480003d731aad1727aaea909926fd009e8ae6a1cba7f141" ], "index": "pypi", - "version": "==2.8.3" + "version": "==2.8.4" }, "numpy": { "hashes": [ - "sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8", - "sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735", - "sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd", - "sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810", - "sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db", - "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962", - "sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79", - "sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911", - "sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d", - "sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488", - "sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5", - "sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0", - "sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f", - "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f", - "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2", - "sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0", - "sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68", - "sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3", - "sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6", - "sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71", - "sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894", - "sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f", - "sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329", - "sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba", - "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c", - "sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e", - "sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef", - "sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7" + "sha256:01dd17cbb340bf0fc23981e52e1d18a9d4050792e8fb8363cecbf066a84b827d", + "sha256:06005a2ef6014e9956c09ba07654f9837d9e26696a0470e42beedadb78c11b07", + "sha256:09b7847f7e83ca37c6e627682f145856de331049013853f344f37b0c9690e3df", + "sha256:0aaee12d8883552fadfc41e96b4c82ee7d794949e2a7c3b3a7201e968c7ecab9", + "sha256:0cbe9848fad08baf71de1a39e12d1b6310f1d5b2d0ea4de051058e6e1076852d", + "sha256:1b1766d6f397c18153d40015ddfc79ddb715cabadc04d2d228d4e5a8bc4ded1a", + "sha256:33161613d2269025873025b33e879825ec7b1d831317e68f4f2f0f84ed14c719", + "sha256:5039f55555e1eab31124a5768898c9e22c25a65c1e0037f4d7c495a45778c9f2", + "sha256:522e26bbf6377e4d76403826ed689c295b0b238f46c28a7251ab94716da0b280", + "sha256:56e454c7833e94ec9769fa0f86e6ff8e42ee38ce0ce1fa4cbb747ea7e06d56aa", + "sha256:58f545efd1108e647604a1b5aa809591ccd2540f468a880bedb97247e72db387", + "sha256:5e05b1c973a9f858c74367553e236f287e749465f773328c8ef31abe18f691e1", + "sha256:7903ba8ab592b82014713c491f6c5d3a1cde5b4a3bf116404e08f5b52f6daf43", + "sha256:8969bfd28e85c81f3f94eb4a66bc2cf1dbdc5c18efc320af34bffc54d6b1e38f", + "sha256:92c8c1e89a1f5028a4c6d9e3ccbe311b6ba53694811269b992c0b224269e2398", + "sha256:9c88793f78fca17da0145455f0d7826bcb9f37da4764af27ac945488116efe63", + "sha256:a7ac231a08bb37f852849bbb387a20a57574a97cfc7b6cabb488a4fc8be176de", + "sha256:abdde9f795cf292fb9651ed48185503a2ff29be87770c3b8e2a14b0cd7aa16f8", + "sha256:af1da88f6bc3d2338ebbf0e22fe487821ea4d8e89053e25fa59d1d79786e7481", + "sha256:b2a9ab7c279c91974f756c84c365a669a887efa287365a8e2c418f8b3ba73fb0", + "sha256:bf837dc63ba5c06dc8797c398db1e223a466c7ece27a1f7b5232ba3466aafe3d", + "sha256:ca51fcfcc5f9354c45f400059e88bc09215fb71a48d3768fb80e357f3b457e1e", + "sha256:ce571367b6dfe60af04e04a1834ca2dc5f46004ac1cc756fb95319f64c095a96", + "sha256:d208a0f8729f3fb790ed18a003f3a57895b989b40ea4dce4717e9cf4af62c6bb", + "sha256:dbee87b469018961d1ad79b1a5d50c0ae850000b639bcb1b694e9981083243b6", + "sha256:e9f4c4e51567b616be64e05d517c79a8a22f3606499941d97bb76f2ca59f982d", + "sha256:f063b69b090c9d918f9df0a12116029e274daf0181df392839661c4c7ec9018a", + "sha256:f9a909a8bae284d46bbfdefbdd4a262ba19d3bc9921b1e76126b1d21c3c34135" ], "markers": "python_version >= '3.8'", - "version": "==1.23.4" + "version": "==1.23.5" }, "packaging": { "hashes": [ @@ -137,36 +143,36 @@ }, "pandas": { "hashes": [ - "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484", - "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc", - "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8", - "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b", - "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989", - "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8", - "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488", - "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f", - "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977", - "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f", - "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761", - "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c", - "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38", - "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515", - "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a", - "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a", - "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec", - "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3", - "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac", - "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b", - "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be", - "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d", - "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9", - "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8", - "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060", - "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009", - "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9" + "sha256:0183cb04a057cc38fde5244909fca9826d5d57c4a5b7390c0cc3fa7acd9fa883", + "sha256:1fc87eac0541a7d24648a001d553406f4256e744d92df1df8ebe41829a915028", + "sha256:220b98d15cee0b2cd839a6358bd1f273d0356bf964c1a1aeb32d47db0215488b", + "sha256:2552bffc808641c6eb471e55aa6899fa002ac94e4eebfa9ec058649122db5824", + "sha256:315e19a3e5c2ab47a67467fc0362cb36c7c60a93b6457f675d7d9615edad2ebe", + "sha256:344021ed3e639e017b452aa8f5f6bf38a8806f5852e217a7594417fb9bbfa00e", + "sha256:375262829c8c700c3e7cbb336810b94367b9c4889818bbd910d0ecb4e45dc261", + "sha256:457d8c3d42314ff47cc2d6c54f8fc0d23954b47977b2caed09cd9635cb75388b", + "sha256:4aed257c7484d01c9a194d9a94758b37d3d751849c05a0050c087a358c41ad1f", + "sha256:530948945e7b6c95e6fa7aa4be2be25764af53fba93fe76d912e35d1c9ee46f5", + "sha256:5ae7e989f12628f41e804847a8cc2943d362440132919a69429d4dea1f164da0", + "sha256:71f510b0efe1629bf2f7c0eadb1ff0b9cf611e87b73cd017e6b7d6adb40e2b3a", + "sha256:73f219fdc1777cf3c45fde7f0708732ec6950dfc598afc50588d0d285fddaefc", + "sha256:8092a368d3eb7116e270525329a3e5c15ae796ccdf7ccb17839a73b4f5084a39", + "sha256:82ae615826da838a8e5d4d630eb70c993ab8636f0eff13cb28aafc4291b632b5", + "sha256:9608000a5a45f663be6af5c70c3cbe634fa19243e720eb380c0d378666bc7702", + "sha256:a40dd1e9f22e01e66ed534d6a965eb99546b41d4d52dbdb66565608fde48203f", + "sha256:b4f5a82afa4f1ff482ab8ded2ae8a453a2cdfde2001567b3ca24a4c5c5ca0db3", + "sha256:c009a92e81ce836212ce7aa98b219db7961a8b95999b97af566b8dc8c33e9519", + "sha256:c218796d59d5abd8780170c937b812c9637e84c32f8271bbf9845970f8c1351f", + "sha256:cc3cd122bea268998b79adebbb8343b735a5511ec14efb70a39e7acbc11ccbdc", + "sha256:d0d8fd58df5d17ddb8c72a5075d87cd80d71b542571b5f78178fb067fa4e9c72", + "sha256:e18bc3764cbb5e118be139b3b611bc3fbc5d3be42a7e827d1096f46087b395eb", + "sha256:e2b83abd292194f350bb04e188f9379d36b8dfac24dd445d5c87575f3beaf789", + "sha256:e7469271497960b6a781eaa930cba8af400dd59b62ec9ca2f4d31a19f2f91090", + "sha256:e9dbacd22555c2d47f262ef96bb4e30880e5956169741400af8b306bbb24a273", + "sha256:f6257b314fc14958f8122779e5a1557517b0f8e500cfb2bd53fa1f75a8ad0af2" ], "index": "pypi", - "version": "==1.5.0" + "version": "==1.5.2" }, "pyparsing": { "hashes": [ @@ -186,10 +192,10 @@ }, "pytz": { "hashes": [ - "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91", - "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174" + "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", + "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" ], - "version": "==2022.4" + "version": "==2022.6" }, "pyyaml": { "hashes": [ @@ -239,30 +245,30 @@ }, "scipy": { "hashes": [ - "sha256:0e9c83dccac06f3b9aa02df69577f239758d5d0d0c069673fb0b47ecb971983d", - "sha256:148cb6f53d9d10dafde848e9aeb1226bf2809d16dc3221b2fa568130b6f2e586", - "sha256:17be1a7c68ec4c49d8cd4eb1655d55d14a54ab63012296bdd5921c92dc485acd", - "sha256:1e3b23a82867018cd26255dc951789a7c567921622073e1113755866f1eae928", - "sha256:22380e076a162e81b659d53d75b02e9c75ad14ea2d53d9c645a12543414e2150", - "sha256:4012dbe540732311b8f4388b7e1482eb43a7cc0435bbf2b9916b3d6c38fb8d01", - "sha256:5994a8232cc6510a8e85899661df2d11198bf362f0ffe6fbd5c0aca17ab46ce3", - "sha256:61b95283529712101bfb7c87faf94cb86ed9e64de079509edfe107e5cfa55733", - "sha256:658fd31c6ad4eb9fa3fd460fcac779f70a6bc7480288a211b7658a25891cf01d", - "sha256:7b2608b3141c257d01ae772e23b3de9e04d27344e6b68a890883795229cb7191", - "sha256:82e8bfb352aa9dce9a0ffe81f4c369a2c87c85533519441686f59f21d8c09697", - "sha256:885b7ac56d7460544b2ef89ab9feafa30f4264c9825d975ef690608d07e6cc55", - "sha256:8c8c29703202c39d699b0d6b164bde5501c212005f20abf46ae322b9307c8a41", - "sha256:92c5e627a0635ca02e6494bbbdb74f98d93ac8730416209d61de3b70c8a821be", - "sha256:99e7720caefb8bca6ebf05c7d96078ed202881f61e0c68bd9e0f3e8097d6f794", - "sha256:a72297eb9702576bd8f626bb488fd32bb35349d3120fc4a5e733db137f06c9a6", - "sha256:aa270cc6080c987929335c4cb94e8054fee9a6058cecff22276fa5dbab9856fc", - "sha256:b6194da32e0ce9200b2eda4eb4edb89c5cb8b83d6deaf7c35f8ad3d5d7627d5c", - "sha256:bbed414fc25d64bd6d1613dc0286fbf91902219b8be63ad254525162235b67e9", - "sha256:d6cb1f92ded3fc48f7dbe94d20d7b9887e13b874e79043907de541c841563b4c", - "sha256:ee4ceed204f269da19f67f0115a85d3a2cd8547185037ad99a4025f9c61d02e9" + "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31", + "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108", + "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0", + "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b", + "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e", + "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e", + "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5", + "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840", + "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58", + "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523", + "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd", + "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab", + "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c", + "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb", + "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096", + "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0", + "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc", + "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9", + "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c", + "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95", + "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027" ], "markers": "python_version >= '3.8'", - "version": "==1.9.2" + "version": "==1.9.3" }, "six": { "hashes": [ @@ -282,11 +288,11 @@ }, "wheel": { "hashes": [ - "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a", - "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4" + "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac", + "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8" ], "index": "pypi", - "version": "==0.37.1" + "version": "==0.38.4" } }, "develop": { @@ -315,11 +321,11 @@ }, "babel": { "hashes": [ - "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", - "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" + "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe", + "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6" ], "markers": "python_version >= '3.6'", - "version": "==2.10.3" + "version": "==2.11.0" }, "bleach": { "hashes": [ @@ -416,11 +422,11 @@ }, "colorama": { "hashes": [ - "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", - "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.4.5" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" }, "coverage": { "extras": [ @@ -483,35 +489,35 @@ }, "cryptography": { "hashes": [ - "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", - "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", - "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", - "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", - "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", - "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", - "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", - "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", - "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", - "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", - "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", - "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", - "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", - "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", - "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", - "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", - "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", - "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", - "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", - "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", - "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", - "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", - "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", - "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", - "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", - "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" + "sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d", + "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd", + "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146", + "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7", + "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436", + "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0", + "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828", + "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b", + "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55", + "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36", + "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50", + "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2", + "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a", + "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8", + "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0", + "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548", + "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320", + "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748", + "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249", + "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959", + "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f", + "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0", + "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd", + "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220", + "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c", + "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722" ], "markers": "python_version >= '3.6'", - "version": "==38.0.1" + "version": "==38.0.3" }, "cython": { "hashes": [ @@ -561,11 +567,11 @@ }, "dill": { "hashes": [ - "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302", - "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86" + "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0", + "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.3.5.1" + "markers": "python_version >= '3.7'", + "version": "==0.3.6" }, "docutils": { "hashes": [ @@ -583,6 +589,14 @@ "index": "pypi", "version": "==0.7.1" }, + "exceptiongroup": { + "hashes": [ + "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828", + "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec" + ], + "markers": "python_version < '3.11'", + "version": "==1.0.4" + }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", @@ -601,11 +615,10 @@ }, "importlib-metadata": { "hashes": [ - "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab", - "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" + "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313" ], "markers": "python_version < '3.10'", - "version": "==5.0.0" + "version": "==5.1.0" }, "iniconfig": { "hashes": [ @@ -648,54 +661,36 @@ }, "keyring": { "hashes": [ - "sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0", - "sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5" + "sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e", + "sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361" ], "markers": "python_version >= '3.7'", - "version": "==23.9.3" + "version": "==23.11.0" }, "lazy-object-proxy": { "hashes": [ - "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", - "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", - "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", - "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", - "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", - "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", - "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", - "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", - "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", - "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", - "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", - "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", - "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", - "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", - "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", - "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", - "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", - "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", - "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", - "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", - "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", - "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", - "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", - "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", - "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", - "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", - "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", - "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", - "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", - "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", - "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", - "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", - "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", - "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", - "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", - "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", - "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" + "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada", + "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d", + "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7", + "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe", + "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd", + "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c", + "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858", + "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288", + "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec", + "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f", + "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891", + "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c", + "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25", + "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156", + "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8", + "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f", + "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e", + "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0", + "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b" ], - "markers": "python_version >= '3.6'", - "version": "==1.7.1" + "markers": "python_version >= '3.7'", + "version": "==1.8.0" }, "markupsafe": { "hashes": [ @@ -753,11 +748,11 @@ }, "more-itertools": { "hashes": [ - "sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2", - "sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750" + "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41", + "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab" ], - "markers": "python_version >= '3.5'", - "version": "==8.14.0" + "markers": "python_version >= '3.7'", + "version": "==9.0.0" }, "packaging": { "hashes": [ @@ -777,11 +772,11 @@ }, "platformdirs": { "hashes": [ - "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", - "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" + "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7", + "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10" ], "markers": "python_version >= '3.7'", - "version": "==2.5.2" + "version": "==2.5.4" }, "pluggy": { "hashes": [ @@ -801,9 +796,10 @@ }, "py-cpuinfo": { "hashes": [ - "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5" + "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", + "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5" ], - "version": "==8.0.0" + "version": "==9.0.0" }, "pycparser": { "hashes": [ @@ -845,19 +841,19 @@ }, "pytest": { "hashes": [ - "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db", - "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171" + "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71", + "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59" ], "index": "pypi", - "version": "==7.0.1" + "version": "==7.2.0" }, "pytest-benchmark": { "hashes": [ - "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809", - "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47" + "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1", + "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6" ], "index": "pypi", - "version": "==3.4.1" + "version": "==4.0.0" }, "pytest-cov": { "hashes": [ @@ -869,18 +865,18 @@ }, "pytz": { "hashes": [ - "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91", - "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174" + "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", + "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" ], - "version": "==2022.4" + "version": "==2022.6" }, "readme-renderer": { "hashes": [ - "sha256:d3f06a69e8c40fca9ab3174eca48f96d9771eddb43517b17d96583418427b106", - "sha256:e8ad25293c98f781dbc2c5a36a309929390009f902f99e1798c761aaf04a7923" + "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273", + "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343" ], "markers": "python_version >= '3.7'", - "version": "==37.2" + "version": "==37.3" }, "requests": { "hashes": [ @@ -892,11 +888,11 @@ }, "requests-toolbelt": { "hashes": [ - "sha256:64c6b8c51b515d123f9f708a29743f44eb70c4479440641ed2df8c4dea56d985", - "sha256:f695d6207931200b46c8ef6addbc8a921fb5d77cc4cd209c2e7d39293fcd2b30" + "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7", + "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.0" + "version": "==0.10.1" }, "rfc3986": { "hashes": [ @@ -1035,11 +1031,11 @@ }, "urllib3": { "hashes": [ - "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", - "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", + "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", - "version": "==1.26.12" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.13" }, "webencodings": { "hashes": [ @@ -1120,11 +1116,11 @@ }, "zipp": { "hashes": [ - "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb", - "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" + "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1", + "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8" ], "markers": "python_version >= '3.7'", - "version": "==3.9.0" + "version": "==3.10.0" } } } From 8523bbe08ed8769b234f6e8301d781d4c892738b Mon Sep 17 00:00:00 2001 From: Philipp Heinrich Date: Fri, 25 Nov 2022 13:33:00 +0100 Subject: [PATCH 4/4] add citation file --- CITATION.cff | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..bd9f9e0 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,10 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: +- family-names: "Heinrich" + given-names: "Philipp" + orcid: "https://orcid.org/0000-0002-4785-9205" +title: "cwb-ccc" +version: 0.11.2 +date-released: 2022-11-25 +url: "https://github.com/ausgerechnet/cwb-ccc"