linkml-store 0.3.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.
- linkml_store/__init__.py +7 -0
- linkml_store/api/__init__.py +8 -0
- linkml_store/api/client.py +414 -0
- linkml_store/api/collection.py +1280 -0
- linkml_store/api/config.py +187 -0
- linkml_store/api/database.py +862 -0
- linkml_store/api/queries.py +69 -0
- linkml_store/api/stores/__init__.py +0 -0
- linkml_store/api/stores/chromadb/__init__.py +7 -0
- linkml_store/api/stores/chromadb/chromadb_collection.py +121 -0
- linkml_store/api/stores/chromadb/chromadb_database.py +89 -0
- linkml_store/api/stores/dremio/__init__.py +10 -0
- linkml_store/api/stores/dremio/dremio_collection.py +555 -0
- linkml_store/api/stores/dremio/dremio_database.py +1052 -0
- linkml_store/api/stores/dremio/mappings.py +105 -0
- linkml_store/api/stores/dremio_rest/__init__.py +11 -0
- linkml_store/api/stores/dremio_rest/dremio_rest_collection.py +502 -0
- linkml_store/api/stores/dremio_rest/dremio_rest_database.py +1023 -0
- linkml_store/api/stores/duckdb/__init__.py +16 -0
- linkml_store/api/stores/duckdb/duckdb_collection.py +339 -0
- linkml_store/api/stores/duckdb/duckdb_database.py +283 -0
- linkml_store/api/stores/duckdb/mappings.py +8 -0
- linkml_store/api/stores/filesystem/__init__.py +15 -0
- linkml_store/api/stores/filesystem/filesystem_collection.py +186 -0
- linkml_store/api/stores/filesystem/filesystem_database.py +81 -0
- linkml_store/api/stores/hdf5/__init__.py +7 -0
- linkml_store/api/stores/hdf5/hdf5_collection.py +104 -0
- linkml_store/api/stores/hdf5/hdf5_database.py +79 -0
- linkml_store/api/stores/ibis/__init__.py +5 -0
- linkml_store/api/stores/ibis/ibis_collection.py +488 -0
- linkml_store/api/stores/ibis/ibis_database.py +328 -0
- linkml_store/api/stores/mongodb/__init__.py +25 -0
- linkml_store/api/stores/mongodb/mongodb_collection.py +379 -0
- linkml_store/api/stores/mongodb/mongodb_database.py +114 -0
- linkml_store/api/stores/neo4j/__init__.py +0 -0
- linkml_store/api/stores/neo4j/neo4j_collection.py +429 -0
- linkml_store/api/stores/neo4j/neo4j_database.py +154 -0
- linkml_store/api/stores/solr/__init__.py +3 -0
- linkml_store/api/stores/solr/solr_collection.py +224 -0
- linkml_store/api/stores/solr/solr_database.py +83 -0
- linkml_store/api/stores/solr/solr_utils.py +0 -0
- linkml_store/api/types.py +4 -0
- linkml_store/cli.py +1147 -0
- linkml_store/constants.py +7 -0
- linkml_store/graphs/__init__.py +0 -0
- linkml_store/graphs/graph_map.py +24 -0
- linkml_store/index/__init__.py +53 -0
- linkml_store/index/implementations/__init__.py +0 -0
- linkml_store/index/implementations/llm_indexer.py +174 -0
- linkml_store/index/implementations/simple_indexer.py +43 -0
- linkml_store/index/indexer.py +211 -0
- linkml_store/inference/__init__.py +13 -0
- linkml_store/inference/evaluation.py +195 -0
- linkml_store/inference/implementations/__init__.py +0 -0
- linkml_store/inference/implementations/llm_inference_engine.py +154 -0
- linkml_store/inference/implementations/rag_inference_engine.py +276 -0
- linkml_store/inference/implementations/rule_based_inference_engine.py +169 -0
- linkml_store/inference/implementations/sklearn_inference_engine.py +314 -0
- linkml_store/inference/inference_config.py +66 -0
- linkml_store/inference/inference_engine.py +209 -0
- linkml_store/inference/inference_engine_registry.py +74 -0
- linkml_store/plotting/__init__.py +5 -0
- linkml_store/plotting/cli.py +826 -0
- linkml_store/plotting/dimensionality_reduction.py +453 -0
- linkml_store/plotting/embedding_plot.py +489 -0
- linkml_store/plotting/facet_chart.py +73 -0
- linkml_store/plotting/heatmap.py +383 -0
- linkml_store/utils/__init__.py +0 -0
- linkml_store/utils/change_utils.py +17 -0
- linkml_store/utils/dat_parser.py +95 -0
- linkml_store/utils/embedding_matcher.py +424 -0
- linkml_store/utils/embedding_utils.py +299 -0
- linkml_store/utils/enrichment_analyzer.py +217 -0
- linkml_store/utils/file_utils.py +37 -0
- linkml_store/utils/format_utils.py +550 -0
- linkml_store/utils/io.py +38 -0
- linkml_store/utils/llm_utils.py +122 -0
- linkml_store/utils/mongodb_utils.py +145 -0
- linkml_store/utils/neo4j_utils.py +42 -0
- linkml_store/utils/object_utils.py +190 -0
- linkml_store/utils/pandas_utils.py +93 -0
- linkml_store/utils/patch_utils.py +126 -0
- linkml_store/utils/query_utils.py +89 -0
- linkml_store/utils/schema_utils.py +23 -0
- linkml_store/utils/sklearn_utils.py +193 -0
- linkml_store/utils/sql_utils.py +177 -0
- linkml_store/utils/stats_utils.py +53 -0
- linkml_store/utils/vector_utils.py +158 -0
- linkml_store/webapi/__init__.py +0 -0
- linkml_store/webapi/html/__init__.py +3 -0
- linkml_store/webapi/html/base.html.j2 +24 -0
- linkml_store/webapi/html/collection_details.html.j2 +15 -0
- linkml_store/webapi/html/database_details.html.j2 +16 -0
- linkml_store/webapi/html/databases.html.j2 +14 -0
- linkml_store/webapi/html/generic.html.j2 +43 -0
- linkml_store/webapi/main.py +855 -0
- linkml_store-0.3.0.dist-info/METADATA +226 -0
- linkml_store-0.3.0.dist-info/RECORD +101 -0
- linkml_store-0.3.0.dist-info/WHEEL +4 -0
- linkml_store-0.3.0.dist-info/entry_points.txt +3 -0
- linkml_store-0.3.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from linkml_store.api import Collection
|
|
6
|
+
from linkml_store.api.collection import DEFAULT_FACET_LIMIT, OBJECT
|
|
7
|
+
from linkml_store.api.queries import Query, QueryResult
|
|
8
|
+
from linkml_store.api.types import DatabaseType
|
|
9
|
+
from linkml_store.utils.query_utils import mongo_query_to_match_function
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileSystemCollection(Collection[DatabaseType]):
|
|
15
|
+
path: Optional[Path] = None
|
|
16
|
+
file_format: Optional[str] = None
|
|
17
|
+
encoding: Optional[str] = None
|
|
18
|
+
_objects_list: List[OBJECT] = None
|
|
19
|
+
_object_map: Dict[str, OBJECT] = None
|
|
20
|
+
|
|
21
|
+
def __init__(self, **kwargs):
|
|
22
|
+
super().__init__(**kwargs)
|
|
23
|
+
parent: DatabaseType = self.parent
|
|
24
|
+
if not self.path:
|
|
25
|
+
if self.parent:
|
|
26
|
+
self.path = Path(parent.directory_path)
|
|
27
|
+
self._objects_list = []
|
|
28
|
+
self._object_map = {}
|
|
29
|
+
if not self.file_format:
|
|
30
|
+
self.file_format = "json"
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def path_to_file(self):
|
|
34
|
+
return Path(self.parent.directory_path) / f"{self.alias}.{self.file_format}"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def objects_as_list(self) -> List[OBJECT]:
|
|
38
|
+
if self._object_map:
|
|
39
|
+
return list(self._object_map.values())
|
|
40
|
+
else:
|
|
41
|
+
return self._objects_list
|
|
42
|
+
|
|
43
|
+
def _set_objects(self, objs: List[OBJECT]):
|
|
44
|
+
pk = self.identifier_attribute_name
|
|
45
|
+
if pk:
|
|
46
|
+
self._object_map = {obj[pk]: obj for obj in objs}
|
|
47
|
+
self._objects_list = []
|
|
48
|
+
else:
|
|
49
|
+
self._objects_list = objs
|
|
50
|
+
self._object_map = {}
|
|
51
|
+
|
|
52
|
+
def commit(self):
|
|
53
|
+
path = self.path_to_file
|
|
54
|
+
if not path:
|
|
55
|
+
raise ValueError("Path not set")
|
|
56
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
self._save(path)
|
|
58
|
+
|
|
59
|
+
def _save(self, path: Path):
|
|
60
|
+
encoding = self.encoding or "utf-8"
|
|
61
|
+
fmt = self.file_format or "json"
|
|
62
|
+
mode = "w"
|
|
63
|
+
if fmt == "parquet":
|
|
64
|
+
mode = "wb"
|
|
65
|
+
encoding = None
|
|
66
|
+
with open(path, mode, encoding=encoding) as stream:
|
|
67
|
+
if fmt == "json":
|
|
68
|
+
import json
|
|
69
|
+
|
|
70
|
+
json.dump(self.objects_as_list, stream, indent=2)
|
|
71
|
+
elif fmt == "jsonl":
|
|
72
|
+
import jsonlines
|
|
73
|
+
|
|
74
|
+
writer = jsonlines.Writer(stream)
|
|
75
|
+
writer.write_all(self.objects_as_list)
|
|
76
|
+
elif fmt == "yaml":
|
|
77
|
+
import yaml
|
|
78
|
+
|
|
79
|
+
yaml.dump_all(self.objects_as_list, stream)
|
|
80
|
+
elif fmt == "parquet":
|
|
81
|
+
import pandas as pd
|
|
82
|
+
import pyarrow
|
|
83
|
+
import pyarrow.parquet as pq
|
|
84
|
+
|
|
85
|
+
df = pd.DataFrame(self.objects_as_list)
|
|
86
|
+
table = pyarrow.Table.from_pandas(df)
|
|
87
|
+
pq.write_table(table, stream)
|
|
88
|
+
elif fmt in {"csv", "tsv"}:
|
|
89
|
+
import csv
|
|
90
|
+
|
|
91
|
+
delimiter = "\t" if fmt == "tsv" else ","
|
|
92
|
+
fieldnames = list(self.objects_as_list[0].keys())
|
|
93
|
+
for obj in self.objects_as_list[1:]:
|
|
94
|
+
fieldnames.extend([k for k in obj.keys() if k not in fieldnames])
|
|
95
|
+
writer = csv.DictWriter(stream, fieldnames=fieldnames, delimiter=delimiter)
|
|
96
|
+
writer.writeheader()
|
|
97
|
+
for obj in self.objects_as_list:
|
|
98
|
+
writer.writerow(obj)
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError(f"Unsupported file format: {fmt}")
|
|
101
|
+
|
|
102
|
+
def insert(self, objs: Union[OBJECT, List[OBJECT]], **kwargs):
|
|
103
|
+
if not isinstance(objs, list):
|
|
104
|
+
objs = [objs]
|
|
105
|
+
if not objs:
|
|
106
|
+
return
|
|
107
|
+
pk = self.identifier_attribute_name
|
|
108
|
+
if pk:
|
|
109
|
+
for obj in objs:
|
|
110
|
+
if pk not in obj:
|
|
111
|
+
raise ValueError(f"Primary key {pk} not found in object {obj}")
|
|
112
|
+
pk_val = obj[pk]
|
|
113
|
+
self._object_map[pk_val] = obj
|
|
114
|
+
else:
|
|
115
|
+
self._objects_list.extend(objs)
|
|
116
|
+
|
|
117
|
+
def delete(self, objs: Union[OBJECT, List[OBJECT]], **kwargs) -> Optional[int]:
|
|
118
|
+
if not isinstance(objs, list):
|
|
119
|
+
objs = [objs]
|
|
120
|
+
if not objs:
|
|
121
|
+
return 0
|
|
122
|
+
pk = self.identifier_attribute_name
|
|
123
|
+
n = 0
|
|
124
|
+
if pk:
|
|
125
|
+
for obj in objs:
|
|
126
|
+
pk_val = obj[pk]
|
|
127
|
+
if pk_val in self._object_map:
|
|
128
|
+
del self._object_map[pk_val]
|
|
129
|
+
n += 1
|
|
130
|
+
else:
|
|
131
|
+
n = len(objs)
|
|
132
|
+
self._objects_list = [o for o in self._objects_list if o not in objs]
|
|
133
|
+
n = n - len(objs)
|
|
134
|
+
return n
|
|
135
|
+
|
|
136
|
+
def delete_where(self, where: Optional[Dict[str, Any]] = None, missing_ok=True, **kwargs) -> Optional[int]:
|
|
137
|
+
logger.info(f"Deleting from {self.target_class_name} where: {where}")
|
|
138
|
+
if where is None:
|
|
139
|
+
where = {}
|
|
140
|
+
|
|
141
|
+
def matches(obj: OBJECT):
|
|
142
|
+
for k, v in where.items():
|
|
143
|
+
if obj.get(k) != v:
|
|
144
|
+
return False
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
print(type(self))
|
|
148
|
+
print(self)
|
|
149
|
+
print(vars(self))
|
|
150
|
+
curr_objects = [o for o in self.objects_as_list if not matches(o)]
|
|
151
|
+
self._set_objects(curr_objects)
|
|
152
|
+
|
|
153
|
+
def query(self, query: Query, limit: Optional[int] = None, offset: Optional[int] = None, **kwargs) -> QueryResult:
|
|
154
|
+
limit = limit or query.limit
|
|
155
|
+
offset = offset or query.offset
|
|
156
|
+
if offset is None:
|
|
157
|
+
offset = 0
|
|
158
|
+
where = query.where_clause or {}
|
|
159
|
+
match = mongo_query_to_match_function(where)
|
|
160
|
+
rows = [o for o in self.objects_as_list if match(o)]
|
|
161
|
+
count = len(rows)
|
|
162
|
+
if limit is None or limit < 0:
|
|
163
|
+
limit = count
|
|
164
|
+
# TODO: avoid recalculating
|
|
165
|
+
returned_row = rows[offset : offset + limit]
|
|
166
|
+
return QueryResult(query=query, num_rows=count, rows=returned_row)
|
|
167
|
+
|
|
168
|
+
def query_facets(
|
|
169
|
+
self, where: Dict = None, facet_columns: List[str] = None, facet_limit=DEFAULT_FACET_LIMIT, **kwargs
|
|
170
|
+
) -> Dict[str, Dict[str, int]]:
|
|
171
|
+
match = mongo_query_to_match_function(where)
|
|
172
|
+
rows = [o for o in self.objects_as_list if match(o)]
|
|
173
|
+
if not facet_columns:
|
|
174
|
+
facet_columns = self.class_definition().attributes.keys()
|
|
175
|
+
facet_results = {c: {} for c in facet_columns}
|
|
176
|
+
for row in rows:
|
|
177
|
+
for fc in facet_columns:
|
|
178
|
+
if fc in row:
|
|
179
|
+
v = row[fc]
|
|
180
|
+
if not isinstance(v, str):
|
|
181
|
+
v = str(v)
|
|
182
|
+
if v not in facet_results[fc]:
|
|
183
|
+
facet_results[fc][v] = 1
|
|
184
|
+
else:
|
|
185
|
+
facet_results[fc][v] += 1
|
|
186
|
+
return {fc: list(facet_results[fc].items()) for fc in facet_results}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from linkml_runtime import SchemaView
|
|
7
|
+
from linkml_runtime.utils.schema_builder import SchemaBuilder
|
|
8
|
+
|
|
9
|
+
from linkml_store.api import Database
|
|
10
|
+
from linkml_store.api.config import DatabaseConfig
|
|
11
|
+
from linkml_store.api.stores.filesystem.filesystem_collection import FileSystemCollection
|
|
12
|
+
from linkml_store.utils.file_utils import safe_remove_directory
|
|
13
|
+
from linkml_store.utils.format_utils import Format, load_objects
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FileSystemDatabase(Database):
|
|
19
|
+
collection_class = FileSystemCollection
|
|
20
|
+
|
|
21
|
+
directory_path: Optional[Path] = None
|
|
22
|
+
default_file_format: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
no_backup_on_drop: bool = False
|
|
25
|
+
|
|
26
|
+
def __init__(self, handle: Optional[str] = None, **kwargs):
|
|
27
|
+
handle = handle.replace("file:", "")
|
|
28
|
+
if handle.startswith("//"):
|
|
29
|
+
handle = handle[2:]
|
|
30
|
+
self.directory_path = Path(handle)
|
|
31
|
+
self.load_metadata()
|
|
32
|
+
super().__init__(handle=handle, **kwargs)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def metadata_path(self) -> Path:
|
|
36
|
+
return self.directory_path / ".linkml_metadata.yaml"
|
|
37
|
+
|
|
38
|
+
def load_metadata(self):
|
|
39
|
+
if self.metadata_path.exists():
|
|
40
|
+
md_dict = yaml.safe_load(open(self.metadata_path))
|
|
41
|
+
metadata = DatabaseConfig(**md_dict)
|
|
42
|
+
else:
|
|
43
|
+
metadata = DatabaseConfig()
|
|
44
|
+
self.metadata = metadata
|
|
45
|
+
|
|
46
|
+
def close(self, **kwargs):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def drop(self, no_backup=False, **kwargs):
|
|
50
|
+
self.close()
|
|
51
|
+
path = self.directory_path
|
|
52
|
+
if path.exists():
|
|
53
|
+
safe_remove_directory(path, no_backup=self.no_backup_on_drop or no_backup)
|
|
54
|
+
|
|
55
|
+
def init_collections(self):
|
|
56
|
+
metadata = self.metadata
|
|
57
|
+
if self._collections is None:
|
|
58
|
+
self._collections = {}
|
|
59
|
+
for name, collection_config in metadata.collections.items():
|
|
60
|
+
collection = FileSystemCollection(parent=self, **collection_config.dict())
|
|
61
|
+
self._collections[name] = collection
|
|
62
|
+
path = self.directory_path
|
|
63
|
+
if path.exists():
|
|
64
|
+
for fmt in Format:
|
|
65
|
+
suffix = fmt.value
|
|
66
|
+
logger.info(f"Looking for {suffix} files in {path}")
|
|
67
|
+
for f in path.glob(f"*.{suffix}"):
|
|
68
|
+
logger.info(f"Found {f}")
|
|
69
|
+
n = f.stem
|
|
70
|
+
objs = load_objects(f, suffix, expected_type=list)
|
|
71
|
+
collection = FileSystemCollection(parent=self, name=n)
|
|
72
|
+
self._collections[n] = collection
|
|
73
|
+
collection._set_objects(objs)
|
|
74
|
+
|
|
75
|
+
def xxxinduce_schema_view(self) -> SchemaView:
|
|
76
|
+
logger.info(f"Inducing schema view for {self.handle}")
|
|
77
|
+
sb = SchemaBuilder()
|
|
78
|
+
|
|
79
|
+
for collection_name in self.list_collection_names():
|
|
80
|
+
sb.add_class(collection_name)
|
|
81
|
+
return SchemaView(sb.schema)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import h5py
|
|
6
|
+
|
|
7
|
+
from linkml_store.api import Collection
|
|
8
|
+
from linkml_store.api.collection import DEFAULT_FACET_LIMIT, OBJECT
|
|
9
|
+
from linkml_store.api.queries import Query, QueryResult
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HDF5Collection(Collection):
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def hdf5_group(self) -> h5py.Group:
|
|
18
|
+
return self.parent.file[self.name]
|
|
19
|
+
|
|
20
|
+
def insert(self, objs: Union[OBJECT, List[OBJECT]], **kwargs):
|
|
21
|
+
if not isinstance(objs, list):
|
|
22
|
+
objs = [objs]
|
|
23
|
+
|
|
24
|
+
for obj in objs:
|
|
25
|
+
if "id" not in obj:
|
|
26
|
+
raise ValueError("Each object must have an 'id' field.")
|
|
27
|
+
obj_id = str(obj["id"])
|
|
28
|
+
for key, value in obj.items():
|
|
29
|
+
if key == "id":
|
|
30
|
+
continue
|
|
31
|
+
if isinstance(value, (dict, list)):
|
|
32
|
+
value = json.dumps(value)
|
|
33
|
+
self.hdf5_group.create_dataset(f"{obj_id}/{key}", data=value)
|
|
34
|
+
|
|
35
|
+
def delete(self, objs: Union[OBJECT, List[OBJECT]], **kwargs) -> int:
|
|
36
|
+
if not isinstance(objs, list):
|
|
37
|
+
objs = [objs]
|
|
38
|
+
count = 0
|
|
39
|
+
for obj in objs:
|
|
40
|
+
if "id" not in obj:
|
|
41
|
+
raise ValueError("Each object must have an 'id' field.")
|
|
42
|
+
obj_id = str(obj["id"])
|
|
43
|
+
if obj_id in self.hdf5_group:
|
|
44
|
+
del self.hdf5_group[obj_id]
|
|
45
|
+
count += 1
|
|
46
|
+
return count
|
|
47
|
+
|
|
48
|
+
def delete_where(self, where: Optional[Dict[str, Any]] = None, missing_ok=True, **kwargs) -> int:
|
|
49
|
+
logger.info(f"Deleting from {self.target_class_name} where: {where}")
|
|
50
|
+
if where is None:
|
|
51
|
+
where = {}
|
|
52
|
+
results = self.query(Query(where_clause=where)).rows
|
|
53
|
+
count = self.delete(results)
|
|
54
|
+
return count
|
|
55
|
+
|
|
56
|
+
def query(self, query: Query, **kwargs) -> QueryResult:
|
|
57
|
+
results = []
|
|
58
|
+
for obj_id in self.hdf5_group:
|
|
59
|
+
obj = {"id": obj_id}
|
|
60
|
+
for key, value in self.hdf5_group[obj_id].items():
|
|
61
|
+
try:
|
|
62
|
+
obj[key] = json.loads(value[()])
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
obj[key] = value[()]
|
|
65
|
+
if self._match_where_clause(obj, query.where_clause):
|
|
66
|
+
results.append(obj)
|
|
67
|
+
|
|
68
|
+
count = len(results)
|
|
69
|
+
if query.limit:
|
|
70
|
+
results = results[: query.limit]
|
|
71
|
+
return QueryResult(query=query, num_rows=count, rows=results)
|
|
72
|
+
|
|
73
|
+
def query_facets(
|
|
74
|
+
self, where: Dict = None, facet_columns: List[str] = None, facet_limit=DEFAULT_FACET_LIMIT, **kwargs
|
|
75
|
+
) -> Dict[str, List[Tuple[Any, int]]]:
|
|
76
|
+
results = {}
|
|
77
|
+
if not facet_columns:
|
|
78
|
+
facet_columns = list(self.class_definition().attributes.keys())
|
|
79
|
+
|
|
80
|
+
for col in facet_columns:
|
|
81
|
+
logger.debug(f"Faceting on {col}")
|
|
82
|
+
facet_counts = {}
|
|
83
|
+
for obj in self.query(Query(where_clause=where)).rows:
|
|
84
|
+
if col in obj:
|
|
85
|
+
value = obj[col]
|
|
86
|
+
if isinstance(value, list):
|
|
87
|
+
for v in value:
|
|
88
|
+
facet_counts[v] = facet_counts.get(v, 0) + 1
|
|
89
|
+
else:
|
|
90
|
+
facet_counts[value] = facet_counts.get(value, 0) + 1
|
|
91
|
+
facet_counts = sorted(facet_counts.items(), key=lambda x: x[1], reverse=True)[:facet_limit]
|
|
92
|
+
results[col] = facet_counts
|
|
93
|
+
|
|
94
|
+
return results
|
|
95
|
+
|
|
96
|
+
def _match_where_clause(self, obj: Dict[str, Any], where_clause: Optional[Dict[str, Any]]) -> bool:
|
|
97
|
+
if where_clause is None:
|
|
98
|
+
return True
|
|
99
|
+
for key, value in where_clause.items():
|
|
100
|
+
if key not in obj:
|
|
101
|
+
return False
|
|
102
|
+
if obj[key] != value:
|
|
103
|
+
return False
|
|
104
|
+
return True
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# hdf5_database.py
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import h5py
|
|
7
|
+
from linkml_runtime import SchemaView
|
|
8
|
+
from linkml_runtime.linkml_model import ClassDefinition, SlotDefinition
|
|
9
|
+
from linkml_runtime.utils.schema_builder import SchemaBuilder
|
|
10
|
+
|
|
11
|
+
from linkml_store.api import Database
|
|
12
|
+
from linkml_store.api.queries import Query, QueryResult
|
|
13
|
+
from linkml_store.api.stores.hdf5.hdf5_collection import HDF5Collection
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HDF5Database(Database):
|
|
19
|
+
_file: h5py.File = None
|
|
20
|
+
collection_class = HDF5Collection
|
|
21
|
+
|
|
22
|
+
def __init__(self, handle: Optional[str] = None, **kwargs):
|
|
23
|
+
if handle is None:
|
|
24
|
+
handle = "linkml_store.h5"
|
|
25
|
+
super().__init__(handle=handle, **kwargs)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def file(self) -> h5py.File:
|
|
29
|
+
if self._file is None:
|
|
30
|
+
self._file = h5py.File(self.handle, "a")
|
|
31
|
+
return self._file
|
|
32
|
+
|
|
33
|
+
def commit(self, **kwargs):
|
|
34
|
+
self.file.flush()
|
|
35
|
+
|
|
36
|
+
def close(self, **kwargs):
|
|
37
|
+
if self._file:
|
|
38
|
+
self._file.close()
|
|
39
|
+
|
|
40
|
+
def query(self, query: Query, **kwargs) -> QueryResult:
|
|
41
|
+
if query.from_table:
|
|
42
|
+
collection = self.get_collection(query.from_table)
|
|
43
|
+
return collection.query(query, **kwargs)
|
|
44
|
+
|
|
45
|
+
def init_collections(self):
|
|
46
|
+
if self._collections is None:
|
|
47
|
+
self._collections = {}
|
|
48
|
+
|
|
49
|
+
for collection_name in self.file:
|
|
50
|
+
if collection_name not in self._collections:
|
|
51
|
+
collection = HDF5Collection(name=collection_name, parent=self)
|
|
52
|
+
self._collections[collection_name] = collection
|
|
53
|
+
|
|
54
|
+
def induce_schema_view(self) -> SchemaView:
|
|
55
|
+
logger.info(f"Inducing schema view for {self.handle}")
|
|
56
|
+
sb = SchemaBuilder()
|
|
57
|
+
schema = sb.schema
|
|
58
|
+
|
|
59
|
+
for collection_name in self.file:
|
|
60
|
+
sb.add_class(collection_name)
|
|
61
|
+
hdf5_group = self.file[collection_name]
|
|
62
|
+
for field in hdf5_group:
|
|
63
|
+
if field == "_id":
|
|
64
|
+
continue
|
|
65
|
+
sd = SlotDefinition(field)
|
|
66
|
+
if isinstance(hdf5_group[field][()], list):
|
|
67
|
+
sd.multivalued = True
|
|
68
|
+
sb.schema.classes[collection_name].attributes[sd.name] = sd
|
|
69
|
+
|
|
70
|
+
sb.add_defaults()
|
|
71
|
+
for cls_name in schema.classes:
|
|
72
|
+
if cls_name in self.metadata.collections:
|
|
73
|
+
collection_metadata = self.metadata.collections[cls_name]
|
|
74
|
+
if collection_metadata.attributes:
|
|
75
|
+
del schema.classes[cls_name]
|
|
76
|
+
cls = ClassDefinition(name=collection_metadata.type, attributes=collection_metadata.attributes)
|
|
77
|
+
schema.classes[cls.name] = cls
|
|
78
|
+
|
|
79
|
+
return SchemaView(schema)
|