tablassert 7.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tablassert/__init__.py +0 -0
- tablassert/downloader.py +35 -0
- tablassert/enums.py +521 -0
- tablassert/fullmap.py +167 -0
- tablassert/ingests.py +43 -0
- tablassert/lib.py +602 -0
- tablassert/log.py +15 -0
- tablassert/models.py +131 -0
- tablassert/qc.py +124 -0
- tablassert/utils.py +43 -0
- tablassert-7.0.0.dist-info/METADATA +141 -0
- tablassert-7.0.0.dist-info/RECORD +15 -0
- tablassert-7.0.0.dist-info/WHEEL +4 -0
- tablassert-7.0.0.dist-info/entry_points.txt +2 -0
- tablassert-7.0.0.dist-info/licenses/LICENSE +201 -0
tablassert/fullmap.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from tablassert.enums import Categories
|
|
2
|
+
from tablassert.utils import samphash
|
|
3
|
+
from tablassert.log import logger
|
|
4
|
+
from tempfile import gettempdir
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from operator import add
|
|
8
|
+
import polars as pl
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def distinct(lf: pl.LazyFrame, l0: str, l1: str) -> pl.LazyFrame:
|
|
12
|
+
# ? Extract Unique Terms From Two Text Normalization Columns As LazyFrame
|
|
13
|
+
t0: pl.LazyFrame = lf.select(pl.col(l0).alias("term")).unique()
|
|
14
|
+
t0 = t0.with_columns(pl.lit(0).alias("nlp level"))
|
|
15
|
+
|
|
16
|
+
t1: pl.LazyFrame = lf.select(pl.col(l1).alias("term")).unique()
|
|
17
|
+
t1 = t1.with_columns(pl.lit(1).alias("nlp level"))
|
|
18
|
+
|
|
19
|
+
terms: pl.LazyFrame = pl.concat([t0, t1]).unique(subset=["term"])
|
|
20
|
+
|
|
21
|
+
bad: str = r"^\d+$|^(none|nan|na|null|unknown)$|^$"
|
|
22
|
+
return terms.filter(~pl.col("term").str.contains(bad))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def to_temp(lf: pl.LazyFrame, tmp: Path = Path(gettempdir())) -> Path:
|
|
26
|
+
# ? Writes LazyFrame To A Tempfile To Be Used In Fullmap
|
|
27
|
+
# ! Collection Point: samphash And write_parquet Require Eager
|
|
28
|
+
df: pl.DataFrame = lf.collect()
|
|
29
|
+
p: Path = tmp / samphash(df)
|
|
30
|
+
p = p.with_suffix(".parquet")
|
|
31
|
+
df.write_parquet(p)
|
|
32
|
+
return p
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def query_builder(
|
|
36
|
+
p: Path, prioritize: Optional[list[Categories]], avoid: Optional[list[Categories]], taxon: Optional[str]
|
|
37
|
+
) -> str:
|
|
38
|
+
# ? Build Query With UNION For Better Index Utilization
|
|
39
|
+
base: str = """
|
|
40
|
+
SELECT
|
|
41
|
+
PA.term,
|
|
42
|
+
CU.CURIE,
|
|
43
|
+
CU.PREFERRED_NAME,
|
|
44
|
+
CA.CATEGORY_NAME,
|
|
45
|
+
CU.TAXON_ID,
|
|
46
|
+
SO.SOURCE_NAME,
|
|
47
|
+
SO.SOURCE_VERSION,
|
|
48
|
+
PA."nlp level" AS NLP_LEVEL,
|
|
49
|
+
CASE
|
|
50
|
+
{priority_case}
|
|
51
|
+
ELSE 50
|
|
52
|
+
END AS PR
|
|
53
|
+
FROM SYNONYMS SY
|
|
54
|
+
JOIN SOURCES SO ON SY.SOURCE_ID = SO.SOURCE_ID
|
|
55
|
+
JOIN CURIES CU ON SY.CURIE_ID = CU.CURIE_ID
|
|
56
|
+
JOIN CATEGORIES CA ON CU.CATEGORY_ID = CA.CATEGORY_ID
|
|
57
|
+
{avoid_filter}
|
|
58
|
+
JOIN read_parquet('{parquet}') PA ON PA.term = SY.SYNONYM
|
|
59
|
+
{taxon_filter}
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
priority_case: str = (
|
|
63
|
+
f"WHEN CA.CATEGORY_NAME IN ({', '.join(f"'{x}'" for x in prioritize)}) THEN 1"
|
|
64
|
+
if prioritize
|
|
65
|
+
else "WHEN TRUE THEN 50"
|
|
66
|
+
)
|
|
67
|
+
avoid_filter: str = f"AND CA.CATEGORY_NAME NOT IN ({', '.join(f"'{x}'" for x in avoid)})" if avoid else ""
|
|
68
|
+
taxon_filter: str = f"WHERE CU.TAXON_ID = {taxon} OR CA.CATEGORY_NAME != 'Gene'" if taxon else ""
|
|
69
|
+
|
|
70
|
+
return base.format(priority_case=priority_case, avoid_filter=avoid_filter, taxon_filter=taxon_filter, parquet=p)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def query_distinct(
|
|
74
|
+
p: Path, conn: object, taxon: Optional[str], prioritize: Optional[list[Categories]], avoid: Optional[list[Categories]]
|
|
75
|
+
) -> pl.DataFrame:
|
|
76
|
+
# ? Query Database For Distinct Terms Only Using Persistent Connection
|
|
77
|
+
# * Added Column Prioritization Logic From 4.2.0
|
|
78
|
+
query: str = query_builder(p, prioritize, avoid, taxon)
|
|
79
|
+
results: pl.DataFrame = conn.execute(query).pl() # pyright: ignore
|
|
80
|
+
|
|
81
|
+
frequency: pl.DataFrame = results.group_by("CATEGORY_NAME").agg(pl.len().alias("FREQUENCY"))
|
|
82
|
+
results = results.join(frequency, on="CATEGORY_NAME", how="left")
|
|
83
|
+
|
|
84
|
+
results = results.sort(["term", "PR", "NLP_LEVEL", "FREQUENCY"], descending=[False, False, False, True])
|
|
85
|
+
results = results.unique(subset=["term"], keep="first")
|
|
86
|
+
|
|
87
|
+
p.unlink(missing_ok=True)
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def version4(
|
|
92
|
+
lf: pl.LazyFrame,
|
|
93
|
+
col: str,
|
|
94
|
+
conn: object,
|
|
95
|
+
taxon: Optional[str],
|
|
96
|
+
prioritize: Optional[list[Categories]],
|
|
97
|
+
avoid: Optional[list[Categories]],
|
|
98
|
+
section_hash: str,
|
|
99
|
+
config_file: str,
|
|
100
|
+
tag: str = " one",
|
|
101
|
+
) -> pl.LazyFrame:
|
|
102
|
+
# ? Case Dependant, Provenance Rich Name Entity Recognition
|
|
103
|
+
l0: str = col
|
|
104
|
+
l1: str = add(l0, tag)
|
|
105
|
+
|
|
106
|
+
terms: pl.LazyFrame = distinct(lf, l0, l1)
|
|
107
|
+
p: Path = to_temp(terms)
|
|
108
|
+
matches: pl.DataFrame = query_distinct(p, conn, taxon, prioritize, avoid)
|
|
109
|
+
|
|
110
|
+
# * Log Unmatched Entities
|
|
111
|
+
antimatches: pl.LazyFrame = terms.join(matches.lazy().select("term"), left_on="term", right_on="term", how="anti")
|
|
112
|
+
|
|
113
|
+
# ! Collection Point: Requires Eager
|
|
114
|
+
unnmatched: pl.DataFrame = antimatches.select("term").unique().collect()
|
|
115
|
+
if unnmatched.height > 0:
|
|
116
|
+
for term in unnmatched.get_column("term").to_list():
|
|
117
|
+
logger.info(f"FAILED FULLMAP | STORE: {section_hash} | CONFIG: {config_file} | COL: {col} | VALUE: {term!r}")
|
|
118
|
+
|
|
119
|
+
# ! Collection Point: Join After DuckDB Query, Then Re-Lazy
|
|
120
|
+
df: pl.DataFrame = lf.collect()
|
|
121
|
+
result: pl.DataFrame = df.join(
|
|
122
|
+
matches.filter(pl.col("NLP_LEVEL").eq(0)), left_on=l0, right_on="term", how="left", suffix=" l0"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
l1_matches: pl.DataFrame = matches.filter(pl.col("NLP_LEVEL").eq(1))
|
|
126
|
+
result = result.join(l1_matches, left_on=l1, right_on="term", how="left", suffix=" l1")
|
|
127
|
+
|
|
128
|
+
result = result.with_columns(
|
|
129
|
+
[
|
|
130
|
+
pl.when(pl.col("CURIE").is_not_null()).then(pl.col("CURIE")).otherwise(pl.col("CURIE l1")).alias(col),
|
|
131
|
+
pl.when(pl.col("PREFERRED_NAME").is_not_null())
|
|
132
|
+
.then(pl.col("PREFERRED_NAME"))
|
|
133
|
+
.otherwise(pl.col("PREFERRED_NAME l1"))
|
|
134
|
+
.alias(add(col, " name")),
|
|
135
|
+
pl.when(pl.col("CATEGORY_NAME").is_not_null())
|
|
136
|
+
.then(add(pl.lit("biolink:"), pl.col("CATEGORY_NAME")))
|
|
137
|
+
.otherwise(add(pl.lit("biolink:"), pl.col("CATEGORY_NAME l1")))
|
|
138
|
+
.alias(add(col, " category")),
|
|
139
|
+
pl.when(pl.col("TAXON_ID").is_not_null())
|
|
140
|
+
.then(add(pl.lit("NCBITaxon:"), pl.col("TAXON_ID").cast(pl.String)))
|
|
141
|
+
.otherwise(add(pl.lit("NCBITaxon:"), pl.col("TAXON_ID l1").cast(pl.String)))
|
|
142
|
+
.alias(add(col, " taxon")),
|
|
143
|
+
pl.when(pl.col("SOURCE_NAME").is_not_null())
|
|
144
|
+
.then(pl.col("SOURCE_NAME"))
|
|
145
|
+
.otherwise(pl.col("SOURCE_NAME l1"))
|
|
146
|
+
.alias(add(col, " source")),
|
|
147
|
+
pl.when(pl.col("SOURCE_VERSION").is_not_null())
|
|
148
|
+
.then(pl.col("SOURCE_VERSION"))
|
|
149
|
+
.otherwise(pl.col("SOURCE_VERSION l1"))
|
|
150
|
+
.alias(add(col, " source version")),
|
|
151
|
+
pl.when(pl.col("NLP_LEVEL").is_not_null())
|
|
152
|
+
.then(pl.col("NLP_LEVEL"))
|
|
153
|
+
.otherwise(pl.col("NLP_LEVEL l1"))
|
|
154
|
+
.alias(add(col, " nlp level")),
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
result = result.select(
|
|
159
|
+
pl.exclude(
|
|
160
|
+
r"^(CURIE|PREFERRED_NAME|CATEGORY_NAME|TAXON_ID|SOURCE_NAME|SOURCE_VERSION|NLP_LEVEL|PR|FREQUENCY)( l1)?$"
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
result = result.select(pl.exclude(add(col, " one")))
|
|
164
|
+
result = result.with_columns(pl.col(add(col, " taxon")).replace("NCBITaxon:0", None))
|
|
165
|
+
result = result.filter(pl.col(col).is_not_null())
|
|
166
|
+
|
|
167
|
+
return result.lazy()
|
tablassert/ingests.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from yaml import CLoader
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Union
|
|
5
|
+
from typing import Any
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def fastmerge(a: Union[list[Any], dict[str, Any]], b: Union[list[Any], dict[str, Any]]) -> Any:
|
|
10
|
+
# ? Streamlined (Fast) Implementation Of Deepmerge Config
|
|
11
|
+
if isinstance(a, dict) and isinstance(b, dict):
|
|
12
|
+
for k, v in b.items():
|
|
13
|
+
if k in a:
|
|
14
|
+
av: Any = a[k]
|
|
15
|
+
if isinstance(av, dict) and isinstance(v, dict):
|
|
16
|
+
fastmerge(av, v)
|
|
17
|
+
elif isinstance(av, list) and isinstance(v, list):
|
|
18
|
+
av.extend(v)
|
|
19
|
+
else:
|
|
20
|
+
a[k] = v
|
|
21
|
+
else:
|
|
22
|
+
a[k] = v
|
|
23
|
+
return a
|
|
24
|
+
|
|
25
|
+
elif isinstance(a, list) and isinstance(b, list):
|
|
26
|
+
a.extend(b)
|
|
27
|
+
return a
|
|
28
|
+
else:
|
|
29
|
+
return b
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def from_yaml(p: Path) -> object:
|
|
33
|
+
# ? Reads YAML Config To Dict
|
|
34
|
+
with p.open("r") as f:
|
|
35
|
+
return yaml.load(f, Loader=CLoader)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def to_sections(instructions: dict[str, Any], table: Path) -> list[list[dict[str, Any]]]:
|
|
39
|
+
# ? Converts Dict To Sections
|
|
40
|
+
template: dict[str, Any] = instructions.get("template", {})
|
|
41
|
+
template["config"] = table
|
|
42
|
+
sections: list[dict[str, Any]] = instructions.get("sections", [{}])
|
|
43
|
+
return [fastmerge(deepcopy(template), x) for x in sections]
|