opencode-skills-collection 3.1.10 → 3.1.11
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.
- package/bundled-skills/.antigravity-install-manifest.json +8 -1
- package/bundled-skills/browser-testing-with-devtools/SKILL.md +334 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/sources/sources.md +4 -0
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/drizzle-migration-conflict/SKILL.md +179 -0
- package/bundled-skills/drizzle-migration-conflict/references/ci-policy.md +87 -0
- package/bundled-skills/drizzle-migration-conflict/references/conflict-resolution.md +163 -0
- package/bundled-skills/drizzle-migration-conflict/references/report-template.md +69 -0
- package/bundled-skills/drizzle-migration-conflict/references/sources.md +51 -0
- package/bundled-skills/drizzle-migration-conflict/scripts/check_drizzle_migrations.py +721 -0
- package/bundled-skills/frontend-lighthouse/SKILL.md +348 -0
- package/bundled-skills/pre-release-review/SKILL.md +198 -0
- package/bundled-skills/pre-release-review/references/checklist.md +104 -0
- package/bundled-skills/pre-release-review/references/report-template.md +91 -0
- package/bundled-skills/re-create/SKILL.md +251 -0
- package/bundled-skills/weaviate/SKILL.md +132 -0
- package/bundled-skills/weaviate/references/ask.md +36 -0
- package/bundled-skills/weaviate/references/create_collection.md +152 -0
- package/bundled-skills/weaviate/references/environment_requirements.md +34 -0
- package/bundled-skills/weaviate/references/example_data.md +24 -0
- package/bundled-skills/weaviate/references/explore_collection.md +50 -0
- package/bundled-skills/weaviate/references/fetch_filter.md +88 -0
- package/bundled-skills/weaviate/references/get_collection.md +32 -0
- package/bundled-skills/weaviate/references/hybrid_search.md +47 -0
- package/bundled-skills/weaviate/references/import_data.md +160 -0
- package/bundled-skills/weaviate/references/keyword_search.md +38 -0
- package/bundled-skills/weaviate/references/list_collections.md +31 -0
- package/bundled-skills/weaviate/references/query_search.md +38 -0
- package/bundled-skills/weaviate/references/semantic_search.md +46 -0
- package/bundled-skills/weaviate/scripts/ask.py +106 -0
- package/bundled-skills/weaviate/scripts/create_collection.py +359 -0
- package/bundled-skills/weaviate/scripts/example_data.py +945 -0
- package/bundled-skills/weaviate/scripts/explore_collection.py +295 -0
- package/bundled-skills/weaviate/scripts/fetch_filter.py +261 -0
- package/bundled-skills/weaviate/scripts/get_collection.py +122 -0
- package/bundled-skills/weaviate/scripts/hybrid_search.py +157 -0
- package/bundled-skills/weaviate/scripts/import.py +701 -0
- package/bundled-skills/weaviate/scripts/keyword_search.py +142 -0
- package/bundled-skills/weaviate/scripts/list_collections.py +77 -0
- package/bundled-skills/weaviate/scripts/query_search.py +135 -0
- package/bundled-skills/weaviate/scripts/semantic_search.py +139 -0
- package/bundled-skills/weaviate/scripts/weaviate_conn.py +231 -0
- package/bundled-skills/weaviate-cookbooks/SKILL.md +67 -0
- package/bundled-skills/weaviate-cookbooks/references/advanced_rag.md +274 -0
- package/bundled-skills/weaviate-cookbooks/references/agentic_rag.md +360 -0
- package/bundled-skills/weaviate-cookbooks/references/async_client.md +428 -0
- package/bundled-skills/weaviate-cookbooks/references/basic_agent.md +270 -0
- package/bundled-skills/weaviate-cookbooks/references/basic_rag.md +219 -0
- package/bundled-skills/weaviate-cookbooks/references/data_explorer.md +336 -0
- package/bundled-skills/weaviate-cookbooks/references/environment_requirements.md +78 -0
- package/bundled-skills/weaviate-cookbooks/references/frontend_interface.md +104 -0
- package/bundled-skills/weaviate-cookbooks/references/pdf_multimodal_rag.md +635 -0
- package/bundled-skills/weaviate-cookbooks/references/project_setup.md +75 -0
- package/bundled-skills/weaviate-cookbooks/references/query_agent_chatbot.md +163 -0
- package/package.json +1 -1
- package/skills_index.json +156 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# dependencies = [
|
|
4
|
+
# "weaviate-client==4.19.2",
|
|
5
|
+
# "typer==0.21.0",
|
|
6
|
+
# ]
|
|
7
|
+
# ///
|
|
8
|
+
"""
|
|
9
|
+
Explore a Weaviate collection's data: metrics, unique values (top occurrences), and sample objects.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
uv run explore_collection.py "CollectionName" [--limit 5] [--no-metrics] [--json]
|
|
13
|
+
|
|
14
|
+
Environment Variables:
|
|
15
|
+
WEAVIATE_URL: Weaviate Cloud cluster URL
|
|
16
|
+
WEAVIATE_API_KEY: API key for authentication
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
import weaviate
|
|
24
|
+
import weaviate.classes as wvc
|
|
25
|
+
from weaviate.classes.aggregate import Metrics
|
|
26
|
+
from weaviate.collections.classes.config import DataType
|
|
27
|
+
|
|
28
|
+
# Import shared connection utilities (local to this skill)
|
|
29
|
+
from weaviate_conn import get_client
|
|
30
|
+
|
|
31
|
+
app = typer.Typer()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_metrics_for_property(prop_name: str, data_type: DataType | str) -> Metrics:
|
|
35
|
+
"""
|
|
36
|
+
Return the appropriate Metrics object based on the property's data type.
|
|
37
|
+
"""
|
|
38
|
+
# Text
|
|
39
|
+
if data_type in [DataType.TEXT, DataType.TEXT_ARRAY]:
|
|
40
|
+
return Metrics(prop_name).text(
|
|
41
|
+
count=True,
|
|
42
|
+
top_occurrences_count=True,
|
|
43
|
+
top_occurrences_value=True,
|
|
44
|
+
limit=5,
|
|
45
|
+
)
|
|
46
|
+
# Integer
|
|
47
|
+
elif data_type in [DataType.INT, DataType.INT_ARRAY]:
|
|
48
|
+
return Metrics(prop_name).integer(
|
|
49
|
+
count=True,
|
|
50
|
+
minimum=True,
|
|
51
|
+
maximum=True,
|
|
52
|
+
mean=True,
|
|
53
|
+
median=True,
|
|
54
|
+
mode=True,
|
|
55
|
+
sum_=True,
|
|
56
|
+
)
|
|
57
|
+
# Number
|
|
58
|
+
elif data_type in [DataType.NUMBER, DataType.NUMBER_ARRAY]:
|
|
59
|
+
return Metrics(prop_name).number(
|
|
60
|
+
count=True,
|
|
61
|
+
minimum=True,
|
|
62
|
+
maximum=True,
|
|
63
|
+
mean=True,
|
|
64
|
+
median=True,
|
|
65
|
+
mode=True,
|
|
66
|
+
sum_=True,
|
|
67
|
+
)
|
|
68
|
+
# Boolean
|
|
69
|
+
elif data_type in [DataType.BOOL, DataType.BOOL_ARRAY]:
|
|
70
|
+
return Metrics(prop_name).boolean(
|
|
71
|
+
count=True,
|
|
72
|
+
percentage_true=True,
|
|
73
|
+
percentage_false=True,
|
|
74
|
+
total_true=True,
|
|
75
|
+
total_false=True,
|
|
76
|
+
)
|
|
77
|
+
# Date
|
|
78
|
+
elif data_type in [DataType.DATE, DataType.DATE_ARRAY]:
|
|
79
|
+
return Metrics(prop_name).date_(
|
|
80
|
+
count=True,
|
|
81
|
+
minimum=True,
|
|
82
|
+
maximum=True,
|
|
83
|
+
median=True,
|
|
84
|
+
mode=True,
|
|
85
|
+
)
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.command()
|
|
90
|
+
def main(
|
|
91
|
+
name: str = typer.Argument(..., help="Collection name"),
|
|
92
|
+
limit: int = typer.Option(
|
|
93
|
+
5, "--limit", "-l", help="Number of sample objects to show"
|
|
94
|
+
),
|
|
95
|
+
no_metrics: bool = typer.Option(
|
|
96
|
+
False, "--no-metrics", help="Skip calculating metrics (faster)"
|
|
97
|
+
),
|
|
98
|
+
json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
|
99
|
+
):
|
|
100
|
+
"""Explore data within a Weaviate collection."""
|
|
101
|
+
try:
|
|
102
|
+
with get_client() as client:
|
|
103
|
+
if not client.collections.exists(name):
|
|
104
|
+
print(f"Error: Collection '{name}' not found.", file=sys.stderr)
|
|
105
|
+
raise typer.Exit(1)
|
|
106
|
+
|
|
107
|
+
collection = client.collections.use(name)
|
|
108
|
+
config = collection.config.get()
|
|
109
|
+
|
|
110
|
+
# 1. Fetch Aggregation Metrics
|
|
111
|
+
metrics_data = {}
|
|
112
|
+
total_count = 0
|
|
113
|
+
|
|
114
|
+
if not no_metrics:
|
|
115
|
+
if not json_output:
|
|
116
|
+
print("Calculating metrics...", file=sys.stderr)
|
|
117
|
+
|
|
118
|
+
return_metrics = []
|
|
119
|
+
# Add metrics for each property based on type
|
|
120
|
+
for prop in config.properties:
|
|
121
|
+
m = get_metrics_for_property(prop.name, prop.data_type)
|
|
122
|
+
if m:
|
|
123
|
+
return_metrics.append(m)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
# Always ask for total_count
|
|
127
|
+
if return_metrics:
|
|
128
|
+
agg_response = collection.aggregate.over_all(
|
|
129
|
+
total_count=True, return_metrics=return_metrics
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
# Fallback if no properties to aggregate
|
|
133
|
+
agg_response = collection.aggregate.over_all(total_count=True)
|
|
134
|
+
|
|
135
|
+
total_count = agg_response.total_count
|
|
136
|
+
|
|
137
|
+
for prop_name, agg_res in agg_response.properties.items():
|
|
138
|
+
prop_metrics = {}
|
|
139
|
+
|
|
140
|
+
# Helpers to extract common fields safely
|
|
141
|
+
def extract_fields(obj, fields):
|
|
142
|
+
for f in fields:
|
|
143
|
+
val = getattr(obj, f, None)
|
|
144
|
+
if val is not None:
|
|
145
|
+
prop_metrics[f] = val
|
|
146
|
+
|
|
147
|
+
# Identify type of result by checking attributes
|
|
148
|
+
if hasattr(agg_res, "top_occurrences"):
|
|
149
|
+
# Text
|
|
150
|
+
extract_fields(agg_res, ["count"])
|
|
151
|
+
if agg_res.top_occurrences:
|
|
152
|
+
prop_metrics["top_occurrences"] = [
|
|
153
|
+
{"value": to.value, "count": to.count}
|
|
154
|
+
for to in agg_res.top_occurrences
|
|
155
|
+
]
|
|
156
|
+
elif hasattr(agg_res, "mean"):
|
|
157
|
+
# Number/Int
|
|
158
|
+
extract_fields(
|
|
159
|
+
agg_res,
|
|
160
|
+
[
|
|
161
|
+
"count",
|
|
162
|
+
"minimum",
|
|
163
|
+
"maximum",
|
|
164
|
+
"mean",
|
|
165
|
+
"median",
|
|
166
|
+
"mode",
|
|
167
|
+
"sum_",
|
|
168
|
+
],
|
|
169
|
+
)
|
|
170
|
+
elif hasattr(agg_res, "percentage_true"):
|
|
171
|
+
# Boolean
|
|
172
|
+
extract_fields(
|
|
173
|
+
agg_res,
|
|
174
|
+
[
|
|
175
|
+
"count",
|
|
176
|
+
"total_true",
|
|
177
|
+
"total_false",
|
|
178
|
+
"percentage_true",
|
|
179
|
+
"percentage_false",
|
|
180
|
+
],
|
|
181
|
+
)
|
|
182
|
+
elif hasattr(agg_res, "minimum") and not hasattr(
|
|
183
|
+
agg_res, "mean"
|
|
184
|
+
):
|
|
185
|
+
# Date (has min/max but no mean)
|
|
186
|
+
extract_fields(
|
|
187
|
+
agg_res,
|
|
188
|
+
["count", "minimum", "maximum", "median", "mode"],
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if prop_metrics:
|
|
192
|
+
metrics_data[prop_name] = prop_metrics
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
if not json_output:
|
|
196
|
+
print(f"Warning: Aggregation failed: {e}", file=sys.stderr)
|
|
197
|
+
metrics_data["error"] = str(e)
|
|
198
|
+
else:
|
|
199
|
+
# Just get total count if metrics skipped
|
|
200
|
+
try:
|
|
201
|
+
agg_response = collection.aggregate.over_all(total_count=True)
|
|
202
|
+
total_count = agg_response.total_count
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
# 2. Fetch Sample Objects
|
|
207
|
+
if limit > 0:
|
|
208
|
+
if not json_output:
|
|
209
|
+
print(f"Fetching {limit} sample objects...", file=sys.stderr)
|
|
210
|
+
# Fetch objects with all properties
|
|
211
|
+
objects_resp = collection.query.fetch_objects(limit=limit)
|
|
212
|
+
sample_objects = []
|
|
213
|
+
for obj in objects_resp.objects:
|
|
214
|
+
sample_objects.append(
|
|
215
|
+
{"uuid": str(obj.uuid), "properties": obj.properties}
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
sample_objects = []
|
|
219
|
+
|
|
220
|
+
# 3. Output
|
|
221
|
+
result = {
|
|
222
|
+
"collection": name,
|
|
223
|
+
"total_count": total_count,
|
|
224
|
+
"metrics": metrics_data,
|
|
225
|
+
"sample_objects": sample_objects,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if json_output:
|
|
229
|
+
print(json.dumps(result, indent=2, default=str))
|
|
230
|
+
else:
|
|
231
|
+
# Markdown Output
|
|
232
|
+
print(f"## Collection Explorer: {name}\n")
|
|
233
|
+
print(f"**Total Objects:** {total_count}")
|
|
234
|
+
|
|
235
|
+
if metrics_data:
|
|
236
|
+
print("\n### Property Metrics\n")
|
|
237
|
+
|
|
238
|
+
prop_types = {p.name: p.data_type.value for p in config.properties}
|
|
239
|
+
|
|
240
|
+
for prop_name, data in metrics_data.items():
|
|
241
|
+
p_type = prop_types.get(prop_name, "unknown")
|
|
242
|
+
print(f"**{prop_name}** ({p_type})")
|
|
243
|
+
for k, v in data.items():
|
|
244
|
+
if k == "top_occurrences":
|
|
245
|
+
print(f"- Top Values:")
|
|
246
|
+
for item in v:
|
|
247
|
+
# Escape pipes and newlines in values
|
|
248
|
+
val_str = (
|
|
249
|
+
str(item["value"])
|
|
250
|
+
.replace("\n", " ")
|
|
251
|
+
.replace("|", "\\|")
|
|
252
|
+
)
|
|
253
|
+
print(f" - {val_str} ({item['count']})")
|
|
254
|
+
else:
|
|
255
|
+
label = k.replace("_", " ").capitalize()
|
|
256
|
+
print(f"- {label}: {v}")
|
|
257
|
+
print("")
|
|
258
|
+
|
|
259
|
+
if sample_objects:
|
|
260
|
+
print(f"### Sample Objects (Limit: {limit})\n")
|
|
261
|
+
|
|
262
|
+
all_props = set()
|
|
263
|
+
for obj in sample_objects:
|
|
264
|
+
all_props.update(obj["properties"].keys())
|
|
265
|
+
sorted_props = sorted(list(all_props))
|
|
266
|
+
|
|
267
|
+
headers = ["#", "UUID"] + sorted_props
|
|
268
|
+
header_row = "| " + " | ".join(headers) + " |"
|
|
269
|
+
separator_row = "| " + " | ".join(["---"] * len(headers)) + " |"
|
|
270
|
+
|
|
271
|
+
print(header_row)
|
|
272
|
+
print(separator_row)
|
|
273
|
+
|
|
274
|
+
for idx, obj in enumerate(sample_objects, 1):
|
|
275
|
+
row_data = [str(idx), str(obj["uuid"])]
|
|
276
|
+
props = obj["properties"]
|
|
277
|
+
for prop in sorted_props:
|
|
278
|
+
val = props.get(prop, "-")
|
|
279
|
+
val_str = str(val).replace("\n", " ").replace("|", "\\|")
|
|
280
|
+
if len(val_str) > 100:
|
|
281
|
+
val_str = val_str[:97] + "..."
|
|
282
|
+
row_data.append(val_str)
|
|
283
|
+
print("| " + " | ".join(row_data) + " |")
|
|
284
|
+
print()
|
|
285
|
+
|
|
286
|
+
except weaviate.exceptions.WeaviateConnectionError as e:
|
|
287
|
+
print(f"Error: Connection failed - {e}", file=sys.stderr)
|
|
288
|
+
raise typer.Exit(1)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
291
|
+
raise typer.Exit(1)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
app()
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# dependencies = [
|
|
4
|
+
# "weaviate-client==4.19.2",
|
|
5
|
+
# "typer==0.21.0",
|
|
6
|
+
# ]
|
|
7
|
+
# ///
|
|
8
|
+
"""
|
|
9
|
+
Fetch and filter objects from a Weaviate collection.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
# Fetch random 10 objects
|
|
13
|
+
uv run fetch_filter.py "JeopardyQuestion"
|
|
14
|
+
|
|
15
|
+
# Fetch by ID
|
|
16
|
+
uv run fetch_filter.py "JeopardyQuestion" --id "uuid-string"
|
|
17
|
+
|
|
18
|
+
# Filter with simple JSON
|
|
19
|
+
uv run fetch_filter.py "JeopardyQuestion" --filters '[{"property": "round", "operator": "equal", "value": "Double Jeopardy!"}]'
|
|
20
|
+
|
|
21
|
+
Environment Variables:
|
|
22
|
+
WEAVIATE_URL: Weaviate Cloud cluster URL
|
|
23
|
+
WEAVIATE_API_KEY: API key for authentication
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import sys
|
|
28
|
+
from typing import Any, List, Optional
|
|
29
|
+
|
|
30
|
+
import typer
|
|
31
|
+
import weaviate
|
|
32
|
+
from weaviate.classes.query import Filter
|
|
33
|
+
|
|
34
|
+
# Import shared connection utilities (local to this skill)
|
|
35
|
+
from weaviate_conn import get_client
|
|
36
|
+
|
|
37
|
+
app = typer.Typer()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_filter_item(item: Any) -> Optional[Filter]:
|
|
41
|
+
"""
|
|
42
|
+
Recursively parse a single filter item (dict or list).
|
|
43
|
+
|
|
44
|
+
Supported structures:
|
|
45
|
+
1. List of filters (implicit AND): [filter1, filter2]
|
|
46
|
+
2. Explicit Logical Operators:
|
|
47
|
+
{"operator": "and", "filters": [...]}
|
|
48
|
+
{"operator": "or", "filters": [...]}
|
|
49
|
+
3. Property Filter:
|
|
50
|
+
{"property": "name", "operator": "equal", "value": "foo"}
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(item, list):
|
|
53
|
+
# Implicit AND for lists
|
|
54
|
+
sub_filters = [parse_filter_item(x) for x in item]
|
|
55
|
+
# Filter out Nones
|
|
56
|
+
sub_filters = [f for f in sub_filters if f is not None]
|
|
57
|
+
if not sub_filters:
|
|
58
|
+
return None
|
|
59
|
+
return Filter.all_of(sub_filters)
|
|
60
|
+
|
|
61
|
+
if not isinstance(item, dict):
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
# Check for logical operators
|
|
65
|
+
op = item.get("operator")
|
|
66
|
+
|
|
67
|
+
if op == "and":
|
|
68
|
+
sub_items = item.get("filters", [])
|
|
69
|
+
sub_filters = [parse_filter_item(x) for x in sub_items]
|
|
70
|
+
sub_filters = [f for f in sub_filters if f is not None]
|
|
71
|
+
return Filter.all_of(sub_filters) if sub_filters else None
|
|
72
|
+
|
|
73
|
+
if op == "or":
|
|
74
|
+
sub_items = item.get("filters", [])
|
|
75
|
+
sub_filters = [parse_filter_item(x) for x in sub_items]
|
|
76
|
+
sub_filters = [f for f in sub_filters if f is not None]
|
|
77
|
+
return Filter.any_of(sub_filters) if sub_filters else None
|
|
78
|
+
|
|
79
|
+
# Property Filter
|
|
80
|
+
prop = item.get("property")
|
|
81
|
+
val = item.get("value")
|
|
82
|
+
|
|
83
|
+
if not prop or not op:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
current_filter = Filter.by_property(prop)
|
|
87
|
+
|
|
88
|
+
# Map operator string to method
|
|
89
|
+
if op == "equal":
|
|
90
|
+
return current_filter.equal(val)
|
|
91
|
+
elif op == "not_equal":
|
|
92
|
+
return current_filter.not_equal(val)
|
|
93
|
+
elif op == "less_than":
|
|
94
|
+
return current_filter.less_than(val)
|
|
95
|
+
elif op == "less_or_equal":
|
|
96
|
+
return current_filter.less_or_equal(val)
|
|
97
|
+
elif op == "greater_than":
|
|
98
|
+
return current_filter.greater_than(val)
|
|
99
|
+
elif op == "greater_or_equal":
|
|
100
|
+
return current_filter.greater_or_equal(val)
|
|
101
|
+
elif op == "like":
|
|
102
|
+
return current_filter.like(val)
|
|
103
|
+
elif op == "contains_any":
|
|
104
|
+
if not isinstance(val, list):
|
|
105
|
+
print(
|
|
106
|
+
f"Error: Value for 'contains_any' must be a list, got {type(val)}",
|
|
107
|
+
file=sys.stderr,
|
|
108
|
+
)
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
return current_filter.contains_any(val)
|
|
111
|
+
elif op == "contains_all":
|
|
112
|
+
if not isinstance(val, list):
|
|
113
|
+
print(
|
|
114
|
+
f"Error: Value for 'contains_all' must be a list, got {type(val)}",
|
|
115
|
+
file=sys.stderr,
|
|
116
|
+
)
|
|
117
|
+
raise typer.Exit(1)
|
|
118
|
+
return current_filter.contains_all(val)
|
|
119
|
+
elif op == "is_none":
|
|
120
|
+
return current_filter.is_none(bool(val))
|
|
121
|
+
else:
|
|
122
|
+
print(
|
|
123
|
+
f"Warning: Unknown operator '{op}' for property '{prop}'. Skipping.",
|
|
124
|
+
file=sys.stderr,
|
|
125
|
+
)
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def parse_filters(filter_json: str) -> Optional[Filter]:
|
|
130
|
+
"""
|
|
131
|
+
Parse a JSON string of filters into a Weaviate Filter object.
|
|
132
|
+
Supports complex nesting with AND/OR.
|
|
133
|
+
"""
|
|
134
|
+
if not filter_json:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
data = json.loads(filter_json)
|
|
139
|
+
except json.JSONDecodeError as e:
|
|
140
|
+
print(f"Error parsing filters JSON: {e}", file=sys.stderr)
|
|
141
|
+
raise typer.Exit(1)
|
|
142
|
+
|
|
143
|
+
return parse_filter_item(data)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command()
|
|
147
|
+
def main(
|
|
148
|
+
collection_name: str = typer.Argument(..., help="Collection name"),
|
|
149
|
+
obj_id: str = typer.Option(None, "--id", help="Fetch specific object by UUID"),
|
|
150
|
+
filters: str = typer.Option(None, "--filters", "-f", help="JSON string of filters"),
|
|
151
|
+
limit: int = typer.Option(10, "--limit", "-l", help="Number of objects to fetch"),
|
|
152
|
+
properties: str = typer.Option(
|
|
153
|
+
None,
|
|
154
|
+
"--properties",
|
|
155
|
+
"-p",
|
|
156
|
+
help="Comma-separated properties to include (default: all)",
|
|
157
|
+
),
|
|
158
|
+
json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
|
159
|
+
):
|
|
160
|
+
"""Fetch objects with optional filtering."""
|
|
161
|
+
try:
|
|
162
|
+
with get_client() as client:
|
|
163
|
+
if not client.collections.exists(collection_name):
|
|
164
|
+
print(
|
|
165
|
+
f"Error: Collection '{collection_name}' not found.", file=sys.stderr
|
|
166
|
+
)
|
|
167
|
+
raise typer.Exit(1)
|
|
168
|
+
|
|
169
|
+
collection = client.collections.use(collection_name)
|
|
170
|
+
|
|
171
|
+
# Determine return properties
|
|
172
|
+
return_properties = None
|
|
173
|
+
if properties:
|
|
174
|
+
return_properties = [
|
|
175
|
+
p.strip() for p in properties.split(",") if p.strip()
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
results = []
|
|
179
|
+
|
|
180
|
+
if obj_id:
|
|
181
|
+
# Fetch single object by ID
|
|
182
|
+
if not json_output:
|
|
183
|
+
print(f"Fetching object {obj_id}...", file=sys.stderr)
|
|
184
|
+
|
|
185
|
+
obj = collection.query.fetch_object_by_id(obj_id)
|
|
186
|
+
|
|
187
|
+
if obj:
|
|
188
|
+
results.append(obj)
|
|
189
|
+
else:
|
|
190
|
+
print(f"Error: Object {obj_id} not found.", file=sys.stderr)
|
|
191
|
+
raise typer.Exit(1)
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
# Fetch multiple with filters
|
|
195
|
+
weaviate_filter = parse_filters(filters)
|
|
196
|
+
|
|
197
|
+
if not json_output:
|
|
198
|
+
print(
|
|
199
|
+
f"Fetching objects from '{collection_name}'...", file=sys.stderr
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
response = collection.query.fetch_objects(
|
|
203
|
+
filters=weaviate_filter,
|
|
204
|
+
limit=limit,
|
|
205
|
+
return_properties=return_properties,
|
|
206
|
+
)
|
|
207
|
+
results = list(response.objects)
|
|
208
|
+
|
|
209
|
+
# Output Formatting
|
|
210
|
+
output_data = []
|
|
211
|
+
for obj in results:
|
|
212
|
+
item = {
|
|
213
|
+
"uuid": str(obj.uuid),
|
|
214
|
+
"properties": obj.properties,
|
|
215
|
+
"metadata": {
|
|
216
|
+
"creation_time": str(obj.metadata.creation_time)
|
|
217
|
+
if obj.metadata.creation_time
|
|
218
|
+
else None,
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
output_data.append(item)
|
|
222
|
+
|
|
223
|
+
if json_output:
|
|
224
|
+
print(json.dumps(output_data, indent=2, default=str))
|
|
225
|
+
else:
|
|
226
|
+
if not results:
|
|
227
|
+
print("No objects found.")
|
|
228
|
+
else:
|
|
229
|
+
print(f"## Found {len(results)} Objects\n")
|
|
230
|
+
|
|
231
|
+
# Gather all property keys for the table headers
|
|
232
|
+
all_keys = set()
|
|
233
|
+
for item in output_data:
|
|
234
|
+
all_keys.update(item["properties"].keys())
|
|
235
|
+
sorted_keys = sorted(list(all_keys))
|
|
236
|
+
|
|
237
|
+
# Table Header
|
|
238
|
+
headers = ["UUID"] + sorted_keys
|
|
239
|
+
print("| " + " | ".join(headers) + " |")
|
|
240
|
+
print("| " + " | ".join(["---"] * len(headers)) + " |")
|
|
241
|
+
|
|
242
|
+
for item in output_data:
|
|
243
|
+
row = [str(item["uuid"])]
|
|
244
|
+
for k in sorted_keys:
|
|
245
|
+
val = item["properties"].get(k, "-")
|
|
246
|
+
val_str = str(val).replace("\n", " ").replace("|", "\\|")
|
|
247
|
+
if len(val_str) > 100:
|
|
248
|
+
val_str = val_str[:97] + "..."
|
|
249
|
+
row.append(val_str)
|
|
250
|
+
print("| " + " | ".join(row) + " |")
|
|
251
|
+
|
|
252
|
+
except weaviate.exceptions.WeaviateConnectionError as e:
|
|
253
|
+
print(f"Error: Connection failed - {e}", file=sys.stderr)
|
|
254
|
+
raise typer.Exit(1)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
257
|
+
raise typer.Exit(1)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == "__main__":
|
|
261
|
+
app()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# dependencies = [
|
|
4
|
+
# "weaviate-client==4.19.2",
|
|
5
|
+
# "typer==0.21.0",
|
|
6
|
+
# ]
|
|
7
|
+
# ///
|
|
8
|
+
"""
|
|
9
|
+
Get details of a specific Weaviate collection.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
uv run get_collection.py --name "CollectionName" [--json]
|
|
13
|
+
|
|
14
|
+
Environment Variables:
|
|
15
|
+
WEAVIATE_URL: Weaviate Cloud cluster URL
|
|
16
|
+
WEAVIATE_API_KEY: API key for authentication
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
import weaviate
|
|
24
|
+
|
|
25
|
+
# Import shared connection utilities (local to this skill)
|
|
26
|
+
from weaviate_conn import get_client
|
|
27
|
+
|
|
28
|
+
app = typer.Typer()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def main(
|
|
33
|
+
name: str = typer.Option(..., "--name", "-n", help="Collection name"),
|
|
34
|
+
json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
|
35
|
+
):
|
|
36
|
+
"""Get detailed configuration of a Weaviate collection."""
|
|
37
|
+
try:
|
|
38
|
+
with get_client() as client:
|
|
39
|
+
if not client.collections.exists(name):
|
|
40
|
+
print(f"Error: Collection '{name}' not found.", file=sys.stderr)
|
|
41
|
+
raise typer.Exit(1)
|
|
42
|
+
|
|
43
|
+
print("Fetching collection details...", file=sys.stderr)
|
|
44
|
+
collection = client.collections.use(name)
|
|
45
|
+
config = collection.config.get()
|
|
46
|
+
|
|
47
|
+
# Extract vectorizer config
|
|
48
|
+
vectorizer_config = None
|
|
49
|
+
if hasattr(config, "vectorizer_config") and config.vectorizer_config:
|
|
50
|
+
vc = config.vectorizer_config
|
|
51
|
+
if hasattr(vc, "vectorizer"):
|
|
52
|
+
vectorizer_config = {
|
|
53
|
+
"vectorizer": str(vc.vectorizer.value)
|
|
54
|
+
if hasattr(vc.vectorizer, "value")
|
|
55
|
+
else str(vc.vectorizer),
|
|
56
|
+
"model": getattr(vc, "model", None),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Extract properties
|
|
60
|
+
properties = []
|
|
61
|
+
if hasattr(config, "properties") and config.properties:
|
|
62
|
+
for p in config.properties:
|
|
63
|
+
prop_info = {
|
|
64
|
+
"name": p.name,
|
|
65
|
+
"data_type": str(p.data_type),
|
|
66
|
+
"description": getattr(p, "description", None),
|
|
67
|
+
}
|
|
68
|
+
properties.append(prop_info)
|
|
69
|
+
|
|
70
|
+
result = {
|
|
71
|
+
"name": name,
|
|
72
|
+
"description": config.description,
|
|
73
|
+
"vectorizer_config": vectorizer_config,
|
|
74
|
+
"properties": properties,
|
|
75
|
+
"replication_factor": getattr(config.replication_config, "factor", None)
|
|
76
|
+
if hasattr(config, "replication_config")
|
|
77
|
+
else None,
|
|
78
|
+
"multi_tenancy_enabled": getattr(
|
|
79
|
+
config.multi_tenancy_config, "enabled", False
|
|
80
|
+
)
|
|
81
|
+
if hasattr(config, "multi_tenancy_config")
|
|
82
|
+
else False,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if json_output:
|
|
86
|
+
print(json.dumps(result, indent=2, default=str))
|
|
87
|
+
else:
|
|
88
|
+
print(f"## Collection: {name}\n")
|
|
89
|
+
print(f"**Description:** {config.description or 'N/A'}")
|
|
90
|
+
|
|
91
|
+
if vectorizer_config:
|
|
92
|
+
print(
|
|
93
|
+
f"**Vectorizer:** {vectorizer_config.get('vectorizer', 'N/A')}"
|
|
94
|
+
)
|
|
95
|
+
if vectorizer_config.get("model"):
|
|
96
|
+
print(f"**Model:** {vectorizer_config['model']}")
|
|
97
|
+
|
|
98
|
+
print(
|
|
99
|
+
f"**Replication Factor:** {result['replication_factor'] or 'N/A'}"
|
|
100
|
+
)
|
|
101
|
+
print(
|
|
102
|
+
f"**Multi-Tenancy:** {'Enabled' if result['multi_tenancy_enabled'] else 'Disabled'}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if properties:
|
|
106
|
+
print(f"\n### Properties ({len(properties)})\n")
|
|
107
|
+
print("| Name | Data Type | Description |")
|
|
108
|
+
print("|------|-----------|-------------|")
|
|
109
|
+
for prop in properties:
|
|
110
|
+
desc = prop.get("description") or "-"
|
|
111
|
+
print(f"| {prop['name']} | {prop['data_type']} | {desc} |")
|
|
112
|
+
|
|
113
|
+
except weaviate.exceptions.WeaviateConnectionError as e:
|
|
114
|
+
print(f"Error: Connection failed - {e}", file=sys.stderr)
|
|
115
|
+
raise typer.Exit(1)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
app()
|