mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-27 03:44:19 +00:00
397 lines
15 KiB
Python
397 lines
15 KiB
Python
from collections import Counter
|
|
from cqi.models.corpora import Corpus as CQiCorpus
|
|
from cqi.models.subcorpora import Subcorpus as CQiSubcorpus
|
|
from cqi.status import StatusOk as CQiStatusOk
|
|
from flask import current_app
|
|
import gzip
|
|
import json
|
|
import math
|
|
from app import db
|
|
from app.models import Corpus
|
|
from .utils import SessionManager
|
|
|
|
|
|
def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
|
|
corpus_id = SessionManager.get_corpus_id()
|
|
cqi_client = SessionManager.get_cqi_client()
|
|
db_corpus = Corpus.query.get(corpus_id)
|
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
|
db_corpus.num_tokens = cqi_corpus.size
|
|
db.session.commit()
|
|
return CQiStatusOk()
|
|
|
|
|
|
def ext_corpus_static_data(corpus: str) -> dict:
|
|
corpus_id = SessionManager.get_corpus_id()
|
|
db_corpus = Corpus.query.get(corpus_id)
|
|
|
|
static_data_file_path = db_corpus.path / 'cwb' / 'static.json.gz'
|
|
if static_data_file_path.exists():
|
|
with static_data_file_path.open('rb') as f:
|
|
return f.read()
|
|
|
|
cqi_client = SessionManager.get_cqi_client()
|
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
|
cqi_p_attrs = cqi_corpus.positional_attributes.list()
|
|
cqi_s_attrs = cqi_corpus.structural_attributes.list()
|
|
|
|
static_data = {
|
|
'corpus': {
|
|
'bounds': [0, cqi_corpus.size - 1],
|
|
'freqs': {}
|
|
},
|
|
'p_attrs': {},
|
|
's_attrs': {},
|
|
'values': {'p_attrs': {}, 's_attrs': {}}
|
|
}
|
|
|
|
for p_attr in cqi_p_attrs:
|
|
current_app.logger.info(f'corpus.freqs.{p_attr.name}')
|
|
static_data['corpus']['freqs'][p_attr.name] = []
|
|
p_attr_id_list = list(range(p_attr.lexicon_size))
|
|
static_data['corpus']['freqs'][p_attr.name].extend(p_attr.freqs_by_ids(p_attr_id_list))
|
|
del p_attr_id_list
|
|
|
|
current_app.logger.info(f'p_attrs.{p_attr.name}')
|
|
static_data['p_attrs'][p_attr.name] = []
|
|
cpos_list = list(range(cqi_corpus.size))
|
|
static_data['p_attrs'][p_attr.name].extend(p_attr.ids_by_cpos(cpos_list))
|
|
del cpos_list
|
|
|
|
current_app.logger.info(f'values.p_attrs.{p_attr.name}')
|
|
static_data['values']['p_attrs'][p_attr.name] = []
|
|
p_attr_id_list = list(range(p_attr.lexicon_size))
|
|
static_data['values']['p_attrs'][p_attr.name].extend(p_attr.values_by_ids(p_attr_id_list))
|
|
del p_attr_id_list
|
|
|
|
for s_attr in cqi_s_attrs:
|
|
if s_attr.has_values:
|
|
continue
|
|
|
|
static_data['s_attrs'][s_attr.name] = {'lexicon': [], 'values': None}
|
|
|
|
if s_attr.name in ['s', 'ent']:
|
|
##############################################################
|
|
# A faster way to get cpos boundaries for smaller s_attrs #
|
|
# Note: Needs more testing, don't use it in production #
|
|
##############################################################
|
|
cqi_corpus.query('Last', f'<{s_attr.name}> []* </{s_attr.name}>;')
|
|
cqi_subcorpus = cqi_corpus.subcorpora.get('Last')
|
|
first_match = 0
|
|
last_match = cqi_subcorpus.size - 1
|
|
match_boundaries = zip(
|
|
range(first_match, last_match + 1),
|
|
cqi_subcorpus.dump(
|
|
cqi_subcorpus.fields['match'],
|
|
first_match,
|
|
last_match
|
|
),
|
|
cqi_subcorpus.dump(
|
|
cqi_subcorpus.fields['matchend'],
|
|
first_match,
|
|
last_match
|
|
)
|
|
)
|
|
cqi_subcorpus.drop()
|
|
del cqi_subcorpus, first_match, last_match
|
|
for id, lbound, rbound in match_boundaries:
|
|
static_data['s_attrs'][s_attr.name]['lexicon'].append({})
|
|
current_app.logger.info(f's_attrs.{s_attr.name}.lexicon.{id}.bounds')
|
|
static_data['s_attrs'][s_attr.name]['lexicon'][id]['bounds'] = [lbound, rbound]
|
|
del match_boundaries
|
|
|
|
if s_attr.name != 'text':
|
|
continue
|
|
|
|
for id in range(0, s_attr.size):
|
|
static_data['s_attrs'][s_attr.name]['lexicon'].append({})
|
|
# This is a very slow operation, thats why we only use it for
|
|
# the text attribute
|
|
lbound, rbound = s_attr.cpos_by_id(id)
|
|
current_app.logger.info(f's_attrs.{s_attr.name}.lexicon.{id}.bounds')
|
|
static_data['s_attrs'][s_attr.name]['lexicon'][id]['bounds'] = [lbound, rbound]
|
|
static_data['s_attrs'][s_attr.name]['lexicon'][id]['freqs'] = {}
|
|
cpos_list = list(range(lbound, rbound + 1))
|
|
for p_attr in cqi_p_attrs:
|
|
p_attr_ids = []
|
|
p_attr_ids.extend(p_attr.ids_by_cpos(cpos_list))
|
|
current_app.logger.info(f's_attrs.{s_attr.name}.lexicon.{id}.freqs.{p_attr.name}')
|
|
static_data['s_attrs'][s_attr.name]['lexicon'][id]['freqs'][p_attr.name] = dict(Counter(p_attr_ids))
|
|
del p_attr_ids
|
|
del cpos_list
|
|
|
|
sub_s_attrs = cqi_corpus.structural_attributes.list(filters={'part_of': s_attr})
|
|
current_app.logger.info(f's_attrs.{s_attr.name}.values')
|
|
static_data['s_attrs'][s_attr.name]['values'] = [
|
|
sub_s_attr.name[(len(s_attr.name) + 1):]
|
|
for sub_s_attr in sub_s_attrs
|
|
]
|
|
s_attr_id_list = list(range(s_attr.size))
|
|
sub_s_attr_values = []
|
|
for sub_s_attr in sub_s_attrs:
|
|
tmp = []
|
|
tmp.extend(sub_s_attr.values_by_ids(s_attr_id_list))
|
|
sub_s_attr_values.append(tmp)
|
|
del tmp
|
|
del s_attr_id_list
|
|
current_app.logger.info(f'values.s_attrs.{s_attr.name}')
|
|
static_data['values']['s_attrs'][s_attr.name] = [
|
|
{
|
|
s_attr_value_name: sub_s_attr_values[s_attr_value_name_idx][s_attr_id]
|
|
for s_attr_value_name_idx, s_attr_value_name in enumerate(
|
|
static_data['s_attrs'][s_attr.name]['values']
|
|
)
|
|
} for s_attr_id in range(0, s_attr.size)
|
|
]
|
|
del sub_s_attr_values
|
|
current_app.logger.info('Saving static data to file')
|
|
with gzip.open(static_data_file_path, 'wt') as f:
|
|
json.dump(static_data, f)
|
|
del static_data
|
|
current_app.logger.info('Sending static data to client')
|
|
with open(static_data_file_path, 'rb') as f:
|
|
return f.read()
|
|
|
|
|
|
def ext_corpus_paginate_corpus(
|
|
corpus: str,
|
|
page: int = 1,
|
|
per_page: int = 20
|
|
) -> dict:
|
|
cqi_client = SessionManager.get_cqi_client()
|
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
|
# Sanity checks
|
|
if (
|
|
per_page < 1
|
|
or page < 1
|
|
or (
|
|
cqi_corpus.size > 0
|
|
and page > math.ceil(cqi_corpus.size / per_page)
|
|
)
|
|
):
|
|
return {'code': 416, 'msg': 'Range Not Satisfiable'}
|
|
first_cpos = (page - 1) * per_page
|
|
last_cpos = min(cqi_corpus.size, first_cpos + per_page)
|
|
cpos_list = [*range(first_cpos, last_cpos)]
|
|
lookups = _lookups_by_cpos(cqi_corpus, cpos_list)
|
|
payload = {}
|
|
# the items for the current page
|
|
payload['items'] = [cpos_list]
|
|
# the lookups for the items
|
|
payload['lookups'] = lookups
|
|
# the total number of items matching the query
|
|
payload['total'] = cqi_corpus.size
|
|
# the number of items to be displayed on a page.
|
|
payload['per_page'] = per_page
|
|
# The total number of pages
|
|
payload['pages'] = math.ceil(payload['total'] / payload['per_page'])
|
|
# the current page number (1 indexed)
|
|
payload['page'] = page if payload['pages'] > 0 else None
|
|
# True if a previous page exists
|
|
payload['has_prev'] = payload['page'] > 1 if payload['page'] else False
|
|
# True if a next page exists.
|
|
payload['has_next'] = payload['page'] < payload['pages'] if payload['page'] else False # noqa
|
|
# Number of the previous page.
|
|
payload['prev_num'] = payload['page'] - 1 if payload['has_prev'] else None
|
|
# Number of the next page
|
|
payload['next_num'] = payload['page'] + 1 if payload['has_next'] else None
|
|
return payload
|
|
|
|
|
|
def ext_cqp_paginate_subcorpus(
|
|
subcorpus: str,
|
|
context: int = 50,
|
|
page: int = 1,
|
|
per_page: int = 20
|
|
) -> dict:
|
|
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
|
cqi_client = SessionManager.get_cqi_client()
|
|
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
|
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
|
# Sanity checks
|
|
if (
|
|
per_page < 1
|
|
or page < 1
|
|
or (
|
|
cqi_subcorpus.size > 0
|
|
and page > math.ceil(cqi_subcorpus.size / per_page)
|
|
)
|
|
):
|
|
return {'code': 416, 'msg': 'Range Not Satisfiable'}
|
|
offset = (page - 1) * per_page
|
|
cutoff = per_page
|
|
cqi_results_export = _export_subcorpus(
|
|
cqi_subcorpus, context=context, cutoff=cutoff, offset=offset)
|
|
payload = {}
|
|
# the items for the current page
|
|
payload['items'] = cqi_results_export.pop('matches')
|
|
# the lookups for the items
|
|
payload['lookups'] = cqi_results_export
|
|
# the total number of items matching the query
|
|
payload['total'] = cqi_subcorpus.size
|
|
# the number of items to be displayed on a page.
|
|
payload['per_page'] = per_page
|
|
# The total number of pages
|
|
payload['pages'] = math.ceil(payload['total'] / payload['per_page'])
|
|
# the current page number (1 indexed)
|
|
payload['page'] = page if payload['pages'] > 0 else None
|
|
# True if a previous page exists
|
|
payload['has_prev'] = payload['page'] > 1 if payload['page'] else False
|
|
# True if a next page exists.
|
|
payload['has_next'] = payload['page'] < payload['pages'] if payload['page'] else False # noqa
|
|
# Number of the previous page.
|
|
payload['prev_num'] = payload['page'] - 1 if payload['has_prev'] else None
|
|
# Number of the next page
|
|
payload['next_num'] = payload['page'] + 1 if payload['has_next'] else None
|
|
return payload
|
|
|
|
|
|
def ext_cqp_partial_export_subcorpus(
|
|
subcorpus: str,
|
|
match_id_list: list,
|
|
context: int = 50
|
|
) -> dict:
|
|
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
|
cqi_client = SessionManager.get_cqi_client()
|
|
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
|
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
|
cqi_subcorpus_partial_export = _partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
|
|
return cqi_subcorpus_partial_export
|
|
|
|
|
|
def ext_cqp_export_subcorpus(subcorpus: str, context: int = 50) -> dict:
|
|
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
|
cqi_client = SessionManager.get_cqi_client()
|
|
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
|
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
|
cqi_subcorpus_export = _export_subcorpus(cqi_subcorpus, context=context)
|
|
return cqi_subcorpus_export
|
|
|
|
|
|
def _lookups_by_cpos(corpus: CQiCorpus, cpos_list: list[int]) -> dict:
|
|
lookups = {}
|
|
lookups['cpos_lookup'] = {cpos: {} for cpos in cpos_list}
|
|
for attr in corpus.positional_attributes.list():
|
|
cpos_attr_values = attr.values_by_cpos(cpos_list)
|
|
for i, cpos in enumerate(cpos_list):
|
|
lookups['cpos_lookup'][cpos][attr.name] = cpos_attr_values[i]
|
|
for attr in corpus.structural_attributes.list():
|
|
# We only want to iterate over non subattributes, identifiable by
|
|
# attr.has_values == False
|
|
if attr.has_values:
|
|
continue
|
|
cpos_attr_ids = attr.ids_by_cpos(cpos_list)
|
|
for i, cpos in enumerate(cpos_list):
|
|
if cpos_attr_ids[i] == -1:
|
|
continue
|
|
lookups['cpos_lookup'][cpos][attr.name] = cpos_attr_ids[i]
|
|
occured_attr_ids = [x for x in set(cpos_attr_ids) if x != -1]
|
|
if len(occured_attr_ids) == 0:
|
|
continue
|
|
subattrs = corpus.structural_attributes.list(filters={'part_of': attr})
|
|
if len(subattrs) == 0:
|
|
continue
|
|
lookup_name = f'{attr.name}_lookup'
|
|
lookups[lookup_name] = {}
|
|
for attr_id in occured_attr_ids:
|
|
lookups[lookup_name][attr_id] = {}
|
|
for subattr in subattrs:
|
|
subattr_name = subattr.name[(len(attr.name) + 1):] # noqa
|
|
for i, subattr_value in enumerate(subattr.values_by_ids(occured_attr_ids)): # noqa
|
|
lookups[lookup_name][occured_attr_ids[i]][subattr_name] = subattr_value # noqa
|
|
return lookups
|
|
|
|
|
|
def _partial_export_subcorpus(
|
|
subcorpus: CQiSubcorpus,
|
|
match_id_list: list[int],
|
|
context: int = 25
|
|
) -> dict:
|
|
if subcorpus.size == 0:
|
|
return {'matches': []}
|
|
match_boundaries = []
|
|
for match_id in match_id_list:
|
|
if match_id < 0 or match_id >= subcorpus.size:
|
|
continue
|
|
match_boundaries.append(
|
|
(
|
|
match_id,
|
|
subcorpus.dump(subcorpus.fields['match'], match_id, match_id)[0],
|
|
subcorpus.dump(subcorpus.fields['matchend'], match_id, match_id)[0]
|
|
)
|
|
)
|
|
cpos_set = set()
|
|
matches = []
|
|
for match_boundary in match_boundaries:
|
|
match_num, match_start, match_end = match_boundary
|
|
c = (match_start, match_end)
|
|
if match_start == 0 or context == 0:
|
|
lc = None
|
|
cpos_list_lbound = match_start
|
|
else:
|
|
lc_lbound = max(0, (match_start - context))
|
|
lc_rbound = match_start - 1
|
|
lc = (lc_lbound, lc_rbound)
|
|
cpos_list_lbound = lc_lbound
|
|
if match_end == (subcorpus.collection.corpus.size - 1) or context == 0:
|
|
rc = None
|
|
cpos_list_rbound = match_end
|
|
else:
|
|
rc_lbound = match_end + 1
|
|
rc_rbound = min(
|
|
(match_end + context),
|
|
(subcorpus.collection.corpus.size - 1)
|
|
)
|
|
rc = (rc_lbound, rc_rbound)
|
|
cpos_list_rbound = rc_rbound
|
|
match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
|
|
matches.append(match)
|
|
cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
|
|
lookups = _lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
|
|
return {'matches': matches, **lookups}
|
|
|
|
|
|
def _export_subcorpus(
|
|
subcorpus: CQiSubcorpus,
|
|
context: int = 25,
|
|
cutoff: float = float('inf'),
|
|
offset: int = 0
|
|
) -> dict:
|
|
if subcorpus.size == 0:
|
|
return {'matches': []}
|
|
first_match = max(0, offset)
|
|
last_match = min((offset + cutoff - 1), (subcorpus.size - 1))
|
|
match_boundaries = zip(
|
|
range(first_match, last_match + 1),
|
|
subcorpus.dump(subcorpus.fields['match'], first_match, last_match),
|
|
subcorpus.dump(subcorpus.fields['matchend'], first_match, last_match)
|
|
)
|
|
cpos_set = set()
|
|
matches = []
|
|
for match_num, match_start, match_end in match_boundaries:
|
|
c = (match_start, match_end)
|
|
if match_start == 0 or context == 0:
|
|
lc = None
|
|
cpos_list_lbound = match_start
|
|
else:
|
|
lc_lbound = max(0, (match_start - context))
|
|
lc_rbound = match_start - 1
|
|
lc = (lc_lbound, lc_rbound)
|
|
cpos_list_lbound = lc_lbound
|
|
if match_end == (subcorpus.collection.corpus.size - 1) or context == 0:
|
|
rc = None
|
|
cpos_list_rbound = match_end
|
|
else:
|
|
rc_lbound = match_end + 1
|
|
rc_rbound = min(
|
|
(match_end + context),
|
|
(subcorpus.collection.corpus.size - 1)
|
|
)
|
|
rc = (rc_lbound, rc_rbound)
|
|
cpos_list_rbound = rc_rbound
|
|
match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
|
|
matches.append(match)
|
|
cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
|
|
lookups = _lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
|
|
return {'matches': matches, **lookups}
|