linkml-store 0.1.11__py3-none-any.whl → 0.1.12__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.
Potentially problematic release.
This version of linkml-store might be problematic. Click here for more details.
- linkml_store/api/collection.py +17 -5
- linkml_store/api/config.py +2 -1
- linkml_store/api/database.py +32 -3
- linkml_store/api/stores/duckdb/duckdb_database.py +31 -3
- linkml_store/api/stores/mongodb/mongodb_database.py +31 -1
- linkml_store/cli.py +29 -2
- linkml_store/utils/format_utils.py +132 -14
- linkml_store/utils/mongodb_utils.py +145 -0
- linkml_store/utils/sql_utils.py +7 -2
- linkml_store/webapi/html/generic.html.j2 +25 -28
- linkml_store/webapi/main.py +346 -63
- {linkml_store-0.1.11.dist-info → linkml_store-0.1.12.dist-info}/METADATA +1 -1
- {linkml_store-0.1.11.dist-info → linkml_store-0.1.12.dist-info}/RECORD +16 -15
- {linkml_store-0.1.11.dist-info → linkml_store-0.1.12.dist-info}/LICENSE +0 -0
- {linkml_store-0.1.11.dist-info → linkml_store-0.1.12.dist-info}/WHEEL +0 -0
- {linkml_store-0.1.11.dist-info → linkml_store-0.1.12.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
{% extends "base.html.j2" %}
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
{% macro make_link(link) %}
|
|
5
|
+
{{ link.rel }} [
|
|
6
|
+
page: <a href="/pages{{ link.href }}">/pages{{ link.href }}</a> |
|
|
7
|
+
API: <a href="{{ link.href }}">{{ link.href }}</a> ]
|
|
8
|
+
<a href="{{ href }}">{{ rel }}</a>
|
|
9
|
+
{% endmacro %}
|
|
10
|
+
|
|
3
11
|
{% block title %}{meta.path}{% endblock %}
|
|
4
12
|
|
|
5
13
|
{% block content %}
|
|
@@ -9,38 +17,27 @@
|
|
|
9
17
|
</pre>
|
|
10
18
|
|
|
11
19
|
<h1>Links</h1>
|
|
12
|
-
|
|
13
|
-
{% for link in response.links %}
|
|
14
|
-
<li>
|
|
15
|
-
|
|
16
|
-
</li>
|
|
17
|
-
{% endfor %}
|
|
20
|
+
<ul>
|
|
21
|
+
{% for link in response.links %}
|
|
22
|
+
<li> {{ make_link(link) }} </li>
|
|
23
|
+
{% endfor %}
|
|
18
24
|
</ul>
|
|
19
|
-
</ul>
|
|
20
25
|
|
|
26
|
+
{% if response.items != None and response["items"] != None %}
|
|
27
|
+
<h1>Items</h1>
|
|
28
|
+
<ul>
|
|
29
|
+
{% for item in response["items"] %}
|
|
30
|
+
<li>
|
|
31
|
+
{{ item.name }}
|
|
32
|
+
{% for link in item.links %}
|
|
33
|
+
{{ make_link(link) }}
|
|
34
|
+
{% endfor %}
|
|
35
|
+
HTML: {{ item.html | safe }}
|
|
36
|
+
</li>
|
|
37
|
+
{% endfor %}
|
|
38
|
+
{% endif %}
|
|
21
39
|
<h1>Data</h1>
|
|
22
|
-
{% if data_html %}
|
|
23
|
-
<ul>
|
|
24
|
-
{% for e in data_html %}
|
|
25
|
-
<li>{{ e|safe }}</li>
|
|
26
|
-
{% endfor %}
|
|
27
|
-
</ul>
|
|
28
|
-
{% else %}
|
|
29
|
-
|
|
30
|
-
{% if "items" in response.data %}
|
|
31
|
-
<ul>
|
|
32
|
-
{% for item in response.data['items'] %}
|
|
33
|
-
<li>
|
|
34
|
-
{{ item.name }}
|
|
35
|
-
{% for link in item.links %}
|
|
36
|
-
<a href="/pages{{ link.href }}">{{ link.rel }}</a>
|
|
37
|
-
{% endfor %}
|
|
38
|
-
</li>
|
|
39
|
-
{% endfor %}
|
|
40
|
-
</ul>
|
|
41
|
-
{% endif %}
|
|
42
40
|
<pre>
|
|
43
41
|
{{ response.data }}
|
|
44
42
|
</pre>
|
|
45
|
-
{% endif %}
|
|
46
43
|
{% endblock %}
|
linkml_store/webapi/main.py
CHANGED
|
@@ -55,6 +55,19 @@ class Link(BaseModel):
|
|
|
55
55
|
href: str
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
class Item(BaseModel):
|
|
59
|
+
name: str
|
|
60
|
+
type: Optional[str] = None
|
|
61
|
+
links: List[Link]
|
|
62
|
+
data: Optional[Any] = None
|
|
63
|
+
html: Optional[str] = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ItemType(BaseModel):
|
|
67
|
+
name: str
|
|
68
|
+
description: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
|
|
58
71
|
class Meta(BaseModel):
|
|
59
72
|
path: Optional[str] = None
|
|
60
73
|
path_template: Optional[str] = None
|
|
@@ -62,6 +75,10 @@ class Meta(BaseModel):
|
|
|
62
75
|
timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
|
|
63
76
|
version: str = "1.0"
|
|
64
77
|
request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
78
|
+
item_count: Optional[int] = None
|
|
79
|
+
paged: bool = False
|
|
80
|
+
page: int = 1
|
|
81
|
+
page_size: Optional[int] = None
|
|
65
82
|
|
|
66
83
|
|
|
67
84
|
class Error(BaseModel):
|
|
@@ -71,8 +88,10 @@ class Error(BaseModel):
|
|
|
71
88
|
|
|
72
89
|
|
|
73
90
|
class APIResponse(BaseModel):
|
|
74
|
-
data: Optional[Any] = None
|
|
75
91
|
meta: Meta = Field(default_factory=Meta)
|
|
92
|
+
items: Optional[List[Item]] = None
|
|
93
|
+
item_type: Optional[ItemType] = None
|
|
94
|
+
data: Optional[Any] = None
|
|
76
95
|
links: Optional[List[Link]] = None
|
|
77
96
|
errors: Optional[List[Error]] = None
|
|
78
97
|
|
|
@@ -126,6 +145,7 @@ async def top(request: Request, client: Client = Depends(get_client)):
|
|
|
126
145
|
links = [
|
|
127
146
|
Link(rel="self", href="/"),
|
|
128
147
|
Link(rel="docs", href="/docs"),
|
|
148
|
+
Link(rel="pages", href="/pages"),
|
|
129
149
|
Link(rel="databases", href="/databases"),
|
|
130
150
|
Link(rel="config", href="/config"),
|
|
131
151
|
]
|
|
@@ -137,10 +157,8 @@ async def config(request: Request, client: Client = Depends(get_client)):
|
|
|
137
157
|
client = get_client()
|
|
138
158
|
data = client.metadata
|
|
139
159
|
links = [
|
|
140
|
-
Link(rel="self", href="/"),
|
|
141
|
-
Link(rel="
|
|
142
|
-
Link(rel="databases", href="/databases"),
|
|
143
|
-
Link(rel="config", href="/config"),
|
|
160
|
+
Link(rel="self", href="/config"),
|
|
161
|
+
Link(rel="parent", href="/"),
|
|
144
162
|
]
|
|
145
163
|
return APIResponse(data=data, links=links)
|
|
146
164
|
|
|
@@ -151,31 +169,24 @@ async def config(request: Request, client: Client = Depends(get_client)):
|
|
|
151
169
|
async def list_databases(request: Request, client: Client = Depends(get_client)):
|
|
152
170
|
databases = list(client.databases.keys())
|
|
153
171
|
|
|
154
|
-
database_links = [Link(rel="database", href=f"/databases/{db_name}") for db_name in databases]
|
|
172
|
+
# database_links = [Link(rel="database", href=f"/databases/{db_name}") for db_name in databases]
|
|
173
|
+
database_links = []
|
|
155
174
|
|
|
156
175
|
additional_links = [
|
|
157
176
|
Link(rel="self", href="/databases"),
|
|
177
|
+
Link(rel="parent", href="/"),
|
|
158
178
|
Link(rel="create_database", href="/database/create"),
|
|
159
179
|
]
|
|
160
180
|
|
|
161
|
-
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"type": "Database",
|
|
166
|
-
"links": [
|
|
167
|
-
{"rel": "self", "href": f"/databases/{db_name}"},
|
|
168
|
-
{"rel": "collections", "href": f"/databases/{db_name}/collections"},
|
|
169
|
-
{"rel": "schema", "href": f"/databases/{db_name}/schema"},
|
|
170
|
-
],
|
|
171
|
-
}
|
|
172
|
-
for db_name in databases
|
|
173
|
-
]
|
|
174
|
-
}
|
|
181
|
+
items = [
|
|
182
|
+
Item(name=db_name, type="Database", links=[Link(rel="self", href=f"/databases/{db_name}")], data={})
|
|
183
|
+
for db_name in databases
|
|
184
|
+
]
|
|
175
185
|
|
|
176
186
|
api_response = APIResponse(
|
|
177
187
|
meta=Meta(path=request.url.path, path_template="databases", params={}),
|
|
178
|
-
data=
|
|
188
|
+
data={},
|
|
189
|
+
items=items,
|
|
179
190
|
links=additional_links + database_links,
|
|
180
191
|
)
|
|
181
192
|
if request.headers.get("Accept") == "text/html":
|
|
@@ -186,6 +197,7 @@ async def list_databases(request: Request, client: Client = Depends(get_client))
|
|
|
186
197
|
|
|
187
198
|
@app.post("/database/create", response_model=APIResponse)
|
|
188
199
|
async def create_database(database: DatabaseCreate, client: Client = Depends(get_client)):
|
|
200
|
+
# TODO
|
|
189
201
|
db = client.attach_database(database.handle, alias=database.name)
|
|
190
202
|
return APIResponse(
|
|
191
203
|
data={"name": db.metadata.alias, "handle": db.metadata.handle},
|
|
@@ -202,53 +214,48 @@ async def get_database_details(
|
|
|
202
214
|
):
|
|
203
215
|
database = get_db(database_name)
|
|
204
216
|
collections = database.list_collections()
|
|
217
|
+
db_metadata = database.metadata.model_dump(exclude_none=True, exclude_defaults=True)
|
|
218
|
+
if "collections" in db_metadata:
|
|
219
|
+
# do not replicate information
|
|
220
|
+
del db_metadata["collections"]
|
|
205
221
|
return APIResponse(
|
|
206
222
|
meta=Meta(path=request.url.path, path_template="database_details", params={"database_name": database_name}),
|
|
207
223
|
data={
|
|
208
224
|
"name": database.metadata.alias,
|
|
209
225
|
"handle": database.metadata.handle,
|
|
226
|
+
"config": db_metadata,
|
|
210
227
|
"num_collections": len(collections),
|
|
211
|
-
"collections": [
|
|
212
|
-
{
|
|
213
|
-
"name": c.name,
|
|
214
|
-
"links": [
|
|
215
|
-
{"rel": "self", "href": f"/databases/{database_name}/collections/{c.name}"},
|
|
216
|
-
{"rel": "objects", "href": f"/databases/{database_name}/collections/{c.name}/objects"},
|
|
217
|
-
],
|
|
218
|
-
}
|
|
219
|
-
for c in collections
|
|
220
|
-
],
|
|
221
228
|
},
|
|
222
229
|
links=[
|
|
223
230
|
Link(rel="self", href=f"/databases/{database_name}"),
|
|
224
231
|
Link(rel="collections", href=f"/databases/{database_name}/collections"),
|
|
225
232
|
Link(rel="schema", href=f"/databases/{database_name}/schema"),
|
|
226
|
-
Link(rel="
|
|
233
|
+
Link(rel="parent", href="/databases"),
|
|
227
234
|
],
|
|
228
235
|
)
|
|
229
236
|
|
|
230
237
|
|
|
231
238
|
@app.get("/databases/{database_name}/collections", response_model=APIResponse)
|
|
232
|
-
async def
|
|
239
|
+
async def list_database_collections(
|
|
233
240
|
request: Request, database_name: str, get_db: Callable[[str], Database] = Depends(get_database)
|
|
234
241
|
):
|
|
235
242
|
database = get_db(database_name)
|
|
236
243
|
collections = database.list_collections()
|
|
244
|
+
items = [
|
|
245
|
+
Item(
|
|
246
|
+
name=c.alias,
|
|
247
|
+
type="Collection",
|
|
248
|
+
links=[
|
|
249
|
+
Link(rel="self", href=f"/databases/{database_name}/collections/{c.alias}"),
|
|
250
|
+
Link(rel="objects", href=f"/databases/{database_name}/collections/{c.alias}/objects"),
|
|
251
|
+
],
|
|
252
|
+
)
|
|
253
|
+
for c in collections
|
|
254
|
+
]
|
|
237
255
|
return APIResponse(
|
|
238
256
|
meta=Meta(path=request.url.path, path_template="collections", params={}),
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{
|
|
242
|
-
"name": c.name,
|
|
243
|
-
"type": "Collection",
|
|
244
|
-
"links": [
|
|
245
|
-
{"rel": "self", "href": f"/databases/{database_name}/collections/{c.name}"},
|
|
246
|
-
{"rel": "objects", "href": f"/databases/{database_name}/collections/{c.name}/objects"},
|
|
247
|
-
],
|
|
248
|
-
}
|
|
249
|
-
for c in collections
|
|
250
|
-
]
|
|
251
|
-
},
|
|
257
|
+
items=items,
|
|
258
|
+
data={},
|
|
252
259
|
links=[
|
|
253
260
|
Link(rel="self", href=f"/databases/{database_name}/collections"),
|
|
254
261
|
Link(rel="database", href=f"/databases/{database_name}"),
|
|
@@ -275,11 +282,15 @@ async def get_collection_details(
|
|
|
275
282
|
"collection_name": collection_name,
|
|
276
283
|
},
|
|
277
284
|
),
|
|
278
|
-
data={
|
|
285
|
+
data={
|
|
286
|
+
"target_class_name": collection.target_class_name,
|
|
287
|
+
"alias": collection.alias,
|
|
288
|
+
"num_objects": collection.find({}).num_rows,
|
|
289
|
+
},
|
|
279
290
|
links=[
|
|
280
291
|
Link(rel="self", href=f"/databases/{database_name}/collections/{collection_name}"),
|
|
281
292
|
Link(rel="objects", href=f"/databases/{database_name}/collections/{collection_name}/objects"),
|
|
282
|
-
Link(rel="
|
|
293
|
+
Link(rel="attributes", href=f"/databases/{database_name}/collections/{collection_name}/attributes"),
|
|
283
294
|
Link(rel="search", href=f"/databases/{database_name}/collections/{collection_name}/search/{{term}}"),
|
|
284
295
|
Link(rel="database", href=f"/databases/{database_name}"),
|
|
285
296
|
],
|
|
@@ -294,21 +305,21 @@ async def create_collection(
|
|
|
294
305
|
get_db: Callable[[str], Database] = Depends(get_database),
|
|
295
306
|
):
|
|
296
307
|
database = get_db(database_name)
|
|
297
|
-
new_collection = database.create_collection(collection.
|
|
308
|
+
new_collection = database.create_collection(collection.alias, alias=collection.alias)
|
|
298
309
|
return APIResponse(
|
|
299
310
|
meta=Meta(path=request.url.path, path_template="create_collection", params={"database_name": database_name}),
|
|
300
|
-
data={"name": new_collection.
|
|
311
|
+
data={"name": new_collection.alias, "alias": new_collection.alias},
|
|
301
312
|
links=[
|
|
302
|
-
Link(rel="self", href=f"/databases/{database_name}/collections/{new_collection.
|
|
313
|
+
Link(rel="self", href=f"/databases/{database_name}/collections/{new_collection.alias}"),
|
|
303
314
|
Link(rel="database", href=f"/databases/{database_name}"),
|
|
304
|
-
Link(rel="objects", href=f"/databases/{database_name}/collections/{new_collection.
|
|
305
|
-
Link(rel="objects", href=f"/databases/{database_name}/collections/{new_collection.
|
|
315
|
+
Link(rel="objects", href=f"/databases/{database_name}/collections/{new_collection.alias}/objects"),
|
|
316
|
+
Link(rel="objects", href=f"/databases/{database_name}/collections/{new_collection.alias}/facets"),
|
|
306
317
|
],
|
|
307
318
|
)
|
|
308
319
|
|
|
309
320
|
|
|
310
321
|
@app.get("/databases/{database_name}/collections/{collection_name}/objects", response_model=APIResponse)
|
|
311
|
-
async def
|
|
322
|
+
async def list_collection_objects(
|
|
312
323
|
request: Request,
|
|
313
324
|
database_name: str,
|
|
314
325
|
collection_name: str,
|
|
@@ -320,14 +331,28 @@ async def collection_list_objects(
|
|
|
320
331
|
database = get_db(database_name)
|
|
321
332
|
collection = database.get_collection(collection_name)
|
|
322
333
|
where_clause = load_objects(where) if where else None
|
|
323
|
-
query = StoreQuery(from_table=collection.
|
|
334
|
+
query = StoreQuery(from_table=collection.alias, where_clause=where_clause, limit=limit, offset=offset)
|
|
324
335
|
result = collection.query(query)
|
|
336
|
+
base_url = f"/databases/{database_name}/collections/{collection_name}/objects"
|
|
337
|
+
|
|
338
|
+
items = []
|
|
339
|
+
cd = collection.class_definition()
|
|
340
|
+
id_att_name = collection.identifier_attribute_name
|
|
341
|
+
for i, row in enumerate(result.rows):
|
|
342
|
+
if id_att_name:
|
|
343
|
+
name = row[id_att_name]
|
|
344
|
+
link = Link(rel="self", href=f"{base_url}/{name}")
|
|
345
|
+
else:
|
|
346
|
+
ix = offset + i
|
|
347
|
+
name = str(ix)
|
|
348
|
+
link = Link(rel="self", href=f"{base_url}_index/{ix}")
|
|
349
|
+
item = Item(name=name, data=row, links=[link])
|
|
350
|
+
items.append(item)
|
|
325
351
|
|
|
326
352
|
total_count = collection.find({}).num_rows
|
|
327
353
|
total_pages = (total_count + limit - 1) // limit
|
|
328
354
|
current_page = offset // limit + 1
|
|
329
355
|
|
|
330
|
-
base_url = f"/databases/{database_name}/collections/{collection_name}/objects"
|
|
331
356
|
links = [Link(rel="self", href=f"{base_url}?limit={limit}&offset={offset}")]
|
|
332
357
|
if current_page > 1:
|
|
333
358
|
links.append(Link(rel="prev", href=f"{base_url}?limit={limit}&offset={offset - limit}"))
|
|
@@ -338,6 +363,50 @@ async def collection_list_objects(
|
|
|
338
363
|
[
|
|
339
364
|
Link(rel="first", href=f"{base_url}?limit={limit}&offset=0"),
|
|
340
365
|
Link(rel="last", href=f"{base_url}?limit={limit}&offset={(total_pages - 1) * limit}"),
|
|
366
|
+
Link(rel="parent", href=f"/databases/{database_name}/collections/{collection_name}"),
|
|
367
|
+
]
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return APIResponse(
|
|
371
|
+
meta=Meta(
|
|
372
|
+
path=request.url.path,
|
|
373
|
+
path_template="objects",
|
|
374
|
+
params={
|
|
375
|
+
"database_name": database_name,
|
|
376
|
+
"collection_name": collection_name,
|
|
377
|
+
},
|
|
378
|
+
paged=True,
|
|
379
|
+
item_count=total_count,
|
|
380
|
+
page=current_page,
|
|
381
|
+
page_size=limit,
|
|
382
|
+
),
|
|
383
|
+
item_type=ItemType(
|
|
384
|
+
name=cd.name,
|
|
385
|
+
description=cd.description,
|
|
386
|
+
),
|
|
387
|
+
items=items,
|
|
388
|
+
data={},
|
|
389
|
+
links=links,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@app.get("/databases/{database_name}/collections/{collection_name}/objects/{id}", response_model=APIResponse)
|
|
394
|
+
async def get_object_details(
|
|
395
|
+
request: Request,
|
|
396
|
+
database_name: str,
|
|
397
|
+
collection_name: str,
|
|
398
|
+
id: str,
|
|
399
|
+
get_db: Callable[[str], Database] = Depends(get_database),
|
|
400
|
+
):
|
|
401
|
+
database = get_db(database_name)
|
|
402
|
+
collection = database.get_collection(collection_name)
|
|
403
|
+
ids = id.split("+")
|
|
404
|
+
result = collection.get(ids)
|
|
405
|
+
|
|
406
|
+
base_url = f"/databases/{database_name}/collections/{collection_name}/objects/{id}"
|
|
407
|
+
links = [Link(rel="self", href=base_url)]
|
|
408
|
+
links.extend(
|
|
409
|
+
[
|
|
341
410
|
Link(rel="collection", href=f"/databases/{database_name}/collections/{collection_name}"),
|
|
342
411
|
Link(rel="database", href=f"/databases/{database_name}"),
|
|
343
412
|
]
|
|
@@ -354,10 +423,6 @@ async def collection_list_objects(
|
|
|
354
423
|
),
|
|
355
424
|
data={
|
|
356
425
|
"domain_objects": result.rows,
|
|
357
|
-
"total_count": total_count,
|
|
358
|
-
"page": current_page,
|
|
359
|
-
"total_pages": total_pages,
|
|
360
|
-
"page_size": limit,
|
|
361
426
|
},
|
|
362
427
|
links=links,
|
|
363
428
|
)
|
|
@@ -419,7 +484,7 @@ async def search_objects(
|
|
|
419
484
|
|
|
420
485
|
|
|
421
486
|
@app.get("/databases/{database_name}/collections/{collection_name}/facets", response_model=APIResponse)
|
|
422
|
-
async def
|
|
487
|
+
async def list_collection_facets(
|
|
423
488
|
request: Request,
|
|
424
489
|
database_name: str,
|
|
425
490
|
collection_name: str,
|
|
@@ -428,6 +493,7 @@ async def objects_facets(
|
|
|
428
493
|
offset: int = Query(0, ge=0),
|
|
429
494
|
get_db: Callable[[str], Database] = Depends(get_database),
|
|
430
495
|
):
|
|
496
|
+
# DEPRECATED?
|
|
431
497
|
database = get_db(database_name)
|
|
432
498
|
collection = database.get_collection(collection_name)
|
|
433
499
|
where_clause = load_objects(where) if where else None
|
|
@@ -473,6 +539,173 @@ async def objects_facets(
|
|
|
473
539
|
)
|
|
474
540
|
|
|
475
541
|
|
|
542
|
+
@app.get("/databases/{database_name}/collections/{collection_name}/attributes", response_model=APIResponse)
|
|
543
|
+
async def list_collection_attributes(
|
|
544
|
+
request: Request,
|
|
545
|
+
database_name: str,
|
|
546
|
+
collection_name: str,
|
|
547
|
+
where: Optional[str] = None,
|
|
548
|
+
get_db: Callable[[str], Database] = Depends(get_database),
|
|
549
|
+
):
|
|
550
|
+
database = get_db(database_name)
|
|
551
|
+
collection = database.get_collection(collection_name)
|
|
552
|
+
where_clause = load_objects(where) if where else None
|
|
553
|
+
base_url = f"/databases/{database_name}/collections/{collection_name}/attributes"
|
|
554
|
+
results = collection.query_facets(where_clause)
|
|
555
|
+
items = [
|
|
556
|
+
Item(
|
|
557
|
+
name=facet_att,
|
|
558
|
+
type="Attribute",
|
|
559
|
+
links=[
|
|
560
|
+
Link(rel="self", href=f"{base_url}/{facet_att}"),
|
|
561
|
+
],
|
|
562
|
+
data=[{"value": v, "count": c} for v, c in data],
|
|
563
|
+
)
|
|
564
|
+
for facet_att, data in results.items()
|
|
565
|
+
]
|
|
566
|
+
|
|
567
|
+
links = [
|
|
568
|
+
Link(rel="self", href=base_url),
|
|
569
|
+
Link(rel="parent", href=f"/databases/{database_name}/collections/{collection_name}"),
|
|
570
|
+
Link(rel="grandparent", href=f"/databases/{database_name}"),
|
|
571
|
+
]
|
|
572
|
+
|
|
573
|
+
return APIResponse(
|
|
574
|
+
meta=Meta(
|
|
575
|
+
path=request.url.path,
|
|
576
|
+
path_template="facets",
|
|
577
|
+
params={
|
|
578
|
+
"database_name": database_name,
|
|
579
|
+
"collection_name": collection_name,
|
|
580
|
+
},
|
|
581
|
+
),
|
|
582
|
+
data={},
|
|
583
|
+
items=items,
|
|
584
|
+
links=links,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@app.get(
|
|
589
|
+
"/databases/{database_name}/collections/{collection_name}/attributes/{attribute_name}", response_model=APIResponse
|
|
590
|
+
)
|
|
591
|
+
async def get_attribute_details(
|
|
592
|
+
request: Request,
|
|
593
|
+
database_name: str,
|
|
594
|
+
collection_name: str,
|
|
595
|
+
attribute_name: str,
|
|
596
|
+
where: Optional[str] = None,
|
|
597
|
+
get_db: Callable[[str], Database] = Depends(get_database),
|
|
598
|
+
):
|
|
599
|
+
database = get_db(database_name)
|
|
600
|
+
collection = database.get_collection(collection_name)
|
|
601
|
+
where_clause = load_objects(where) if where else None
|
|
602
|
+
base_url = f"/databases/{database_name}/collections/{collection_name}/attributes/{attribute_name}"
|
|
603
|
+
count_tuples = collection.query_facets(where_clause, facet_columns=[attribute_name])[attribute_name]
|
|
604
|
+
_count_objs = [{"value": v, "count": c} for v, c in count_tuples]
|
|
605
|
+
cd = collection.class_definition()
|
|
606
|
+
att = cd.attributes[attribute_name]
|
|
607
|
+
att_dict = json_dumper.to_dict(att)
|
|
608
|
+
items = [
|
|
609
|
+
Item(
|
|
610
|
+
name=str(v),
|
|
611
|
+
type="Value",
|
|
612
|
+
links=[
|
|
613
|
+
Link(rel="self", href=f"{base_url}/equals/{v}"),
|
|
614
|
+
],
|
|
615
|
+
data={"count": c},
|
|
616
|
+
)
|
|
617
|
+
for v, c in count_tuples
|
|
618
|
+
]
|
|
619
|
+
|
|
620
|
+
links = [
|
|
621
|
+
Link(rel="self", href=base_url),
|
|
622
|
+
Link(rel="collection", href=f"/databases/{database_name}/collections/{collection_name}"),
|
|
623
|
+
Link(rel="database", href=f"/databases/{database_name}"),
|
|
624
|
+
]
|
|
625
|
+
|
|
626
|
+
return APIResponse(
|
|
627
|
+
meta=Meta(
|
|
628
|
+
path=request.url.path,
|
|
629
|
+
path_template="facets",
|
|
630
|
+
params={
|
|
631
|
+
"database_name": database_name,
|
|
632
|
+
"collection_name": collection_name,
|
|
633
|
+
},
|
|
634
|
+
),
|
|
635
|
+
data={
|
|
636
|
+
"attribute": att_dict,
|
|
637
|
+
},
|
|
638
|
+
items=items,
|
|
639
|
+
links=links,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@app.get(
|
|
644
|
+
"/databases/{database_name}/collections/{collection_name}/attributes/{attribute_name}/equals/{value}",
|
|
645
|
+
response_model=APIResponse,
|
|
646
|
+
)
|
|
647
|
+
async def query_by_attribute(
|
|
648
|
+
request: Request,
|
|
649
|
+
database_name: str,
|
|
650
|
+
collection_name: str,
|
|
651
|
+
attribute_name: str,
|
|
652
|
+
value: str,
|
|
653
|
+
where: Optional[str] = None,
|
|
654
|
+
limit: int = Query(10, ge=1, le=100),
|
|
655
|
+
offset: int = Query(0, ge=0),
|
|
656
|
+
get_db: Callable[[str], Database] = Depends(get_database),
|
|
657
|
+
):
|
|
658
|
+
database = get_db(database_name)
|
|
659
|
+
collection = database.get_collection(collection_name)
|
|
660
|
+
where_clause = {attribute_name: value}
|
|
661
|
+
query = StoreQuery(from_table=collection.alias, where_clause=where_clause, limit=limit, offset=offset)
|
|
662
|
+
result = collection.query(query)
|
|
663
|
+
items = []
|
|
664
|
+
for i, row in enumerate(result.rows):
|
|
665
|
+
item = Item(name=str(i), type="X", data=row, links=[])
|
|
666
|
+
items.append(item)
|
|
667
|
+
|
|
668
|
+
total_count = collection.find({}).num_rows
|
|
669
|
+
total_pages = (total_count + limit - 1) // limit
|
|
670
|
+
current_page = offset // limit + 1
|
|
671
|
+
|
|
672
|
+
base_url = f"/databases/{database_name}/collections/{collection_name}/attributes/{attribute_name}/equals/{value}"
|
|
673
|
+
links = [Link(rel="self", href=f"{base_url}?limit={limit}&offset={offset}")]
|
|
674
|
+
if current_page > 1:
|
|
675
|
+
links.append(Link(rel="prev", href=f"{base_url}?limit={limit}&offset={offset - limit}"))
|
|
676
|
+
if current_page < total_pages:
|
|
677
|
+
links.append(Link(rel="next", href=f"{base_url}?limit={limit}&offset={offset + limit}"))
|
|
678
|
+
|
|
679
|
+
links.extend(
|
|
680
|
+
[
|
|
681
|
+
Link(rel="first", href=f"{base_url}?limit={limit}&offset=0"),
|
|
682
|
+
Link(rel="last", href=f"{base_url}?limit={limit}&offset={(total_pages - 1) * limit}"),
|
|
683
|
+
Link(
|
|
684
|
+
rel="parent",
|
|
685
|
+
href=f"/databases/{database_name}/collections/{collection_name}/attributes/{attribute_name}",
|
|
686
|
+
),
|
|
687
|
+
]
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
return APIResponse(
|
|
691
|
+
meta=Meta(
|
|
692
|
+
path=request.url.path,
|
|
693
|
+
path_template="objects",
|
|
694
|
+
params={
|
|
695
|
+
"database_name": database_name,
|
|
696
|
+
"collection_name": collection_name,
|
|
697
|
+
},
|
|
698
|
+
paged=True,
|
|
699
|
+
item_count=total_count,
|
|
700
|
+
page=current_page,
|
|
701
|
+
page_size=limit,
|
|
702
|
+
),
|
|
703
|
+
items=items,
|
|
704
|
+
data={},
|
|
705
|
+
links=links,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
476
709
|
@app.post("/databases/{database_name}/collections/{collection_name}/objects", response_model=APIResponse)
|
|
477
710
|
async def insert_objects(
|
|
478
711
|
database_name: str,
|
|
@@ -509,8 +742,8 @@ async def get_database_schema(
|
|
|
509
742
|
)
|
|
510
743
|
|
|
511
744
|
|
|
512
|
-
@app.get("/
|
|
513
|
-
async def
|
|
745
|
+
@app.get("/xxxpages/{path:path}", response_class=HTMLResponse)
|
|
746
|
+
async def xxxgeneric_page(request: Request, path: str, get_db: Callable[[str], Database] = Depends(get_database)):
|
|
514
747
|
# Construct the API URL
|
|
515
748
|
api_url = f"{request.base_url}{path}"
|
|
516
749
|
query_params = dict(request.query_params)
|
|
@@ -524,6 +757,7 @@ async def generic_page(request: Request, path: str, get_db: Callable[[str], Data
|
|
|
524
757
|
|
|
525
758
|
# Parse the JSON response
|
|
526
759
|
api_data = response.json()
|
|
760
|
+
# api_response = APIResponse(**api_data)
|
|
527
761
|
|
|
528
762
|
# Use the template specified in the API response
|
|
529
763
|
meta = api_data["meta"]
|
|
@@ -557,6 +791,55 @@ async def generic_page(request: Request, path: str, get_db: Callable[[str], Data
|
|
|
557
791
|
)
|
|
558
792
|
|
|
559
793
|
|
|
794
|
+
@app.get("/pages/{path:path}", response_class=HTMLResponse)
|
|
795
|
+
async def generic_page(request: Request, path: str, get_db: Callable[[str], Database] = Depends(get_database)):
|
|
796
|
+
# Construct the API URL
|
|
797
|
+
api_url = f"{request.base_url}{path}"
|
|
798
|
+
query_params = dict(request.query_params)
|
|
799
|
+
|
|
800
|
+
# Make a request to the API
|
|
801
|
+
async with httpx.AsyncClient() as client:
|
|
802
|
+
response = await client.get(api_url, params=query_params)
|
|
803
|
+
|
|
804
|
+
if response.status_code != 200:
|
|
805
|
+
raise HTTPException(status_code=response.status_code, detail=response.text)
|
|
806
|
+
|
|
807
|
+
# Parse the JSON response
|
|
808
|
+
api_data = response.json()
|
|
809
|
+
payload = APIResponse(**api_data)
|
|
810
|
+
template_name = "generic.html.j2"
|
|
811
|
+
params = payload.meta.params
|
|
812
|
+
|
|
813
|
+
# data = payload.data
|
|
814
|
+
data_html = None
|
|
815
|
+
if "database_name" in params:
|
|
816
|
+
db = get_db(params["database_name"])
|
|
817
|
+
sv = db.schema_view
|
|
818
|
+
else:
|
|
819
|
+
sv = None
|
|
820
|
+
if not payload.items:
|
|
821
|
+
payload.items = []
|
|
822
|
+
if payload.item_type and payload.items:
|
|
823
|
+
cn = payload.item_type.name
|
|
824
|
+
style_engine = StyleEngine(schemaview=sv)
|
|
825
|
+
html_renderer.style_engine = style_engine
|
|
826
|
+
for item in payload.items:
|
|
827
|
+
if item.data:
|
|
828
|
+
item.html = html_renderer.render(item.data, schemaview=sv, source_element_name=cn)
|
|
829
|
+
|
|
830
|
+
# Render the appropriate template
|
|
831
|
+
return templates.TemplateResponse(
|
|
832
|
+
template_name,
|
|
833
|
+
{
|
|
834
|
+
"request": request,
|
|
835
|
+
"response": payload,
|
|
836
|
+
"current_path": f"/pages/{path}",
|
|
837
|
+
"data_html": data_html,
|
|
838
|
+
"params": params,
|
|
839
|
+
},
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
|
|
560
843
|
def run_server():
|
|
561
844
|
import uvicorn
|
|
562
845
|
|