-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
552 lines (494 loc) · 112 KB
/
Copy pathindex.html
File metadata and controls
552 lines (494 loc) · 112 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/7d4881bb7e1bf84d-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" as="image" href="/inputlayer_logo_long_dark.png"/><link rel="stylesheet" href="/_next/static/css/508410e5e895cb66.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-6b4fdc854b138615.js"/><script src="/_next/static/chunks/4bd1b696-92152b0f5947070d.js" async=""></script><script src="/_next/static/chunks/794-3b4e831e04fcc275.js" async=""></script><script src="/_next/static/chunks/main-app-72862d37dc80227e.js" async=""></script><script src="/_next/static/chunks/app/layout-51b94f561cc33957.js" async=""></script><script src="/_next/static/chunks/661-63cf87d99b552766.js" async=""></script><script src="/_next/static/chunks/121-aece4f809b101dc1.js" async=""></script><script src="/_next/static/chunks/502-7fd593d8b6507a0f.js" async=""></script><script src="/_next/static/chunks/689-85ea892b64182e04.js" async=""></script><script src="/_next/static/chunks/app/docs/%5B%5B...slug%5D%5D/page-0b234b25687989d5.js" async=""></script><title>Python SDK - InputLayer Docs</title><meta name="description" content="InputLayer documentation - Python SDK."/><meta property="og:title" content="Python SDK - InputLayer Docs"/><meta property="og:description" content="InputLayer documentation - Python SDK."/><meta property="og:type" content="article"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:title" content="Python SDK - InputLayer Docs"/><meta name="twitter:description" content="InputLayer documentation - Python SDK."/><link rel="icon" href="/favicon.ico" sizes="any"/><link rel="icon" href="/favicon_32.png" sizes="32x32" type="image/png"/><link rel="apple-touch-icon" href="/favicon_180.png" sizes="180x180" type="image/png"/><meta name="next-size-adjust" content=""/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_246ccd __variable_c29908 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><script>((a,b,c,d,e,f,g,h)=>{let i=document.documentElement,j=["light","dark"];function k(b){var c;(Array.isArray(a)?a:[a]).forEach(a=>{let c="class"===a,d=c&&f?e.map(a=>f[a]||a):e;c?(i.classList.remove(...d),i.classList.add(f&&f[b]?f[b]:b)):i.setAttribute(a,b)}),c=b,h&&j.includes(c)&&(i.style.colorScheme=c)}if(d)k(d);else try{let a=localStorage.getItem(b)||c,d=g&&"system"===a?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":a;k(d)}catch(a){}})("class","theme","dark",null,["light","dark"],null,true,true)</script><div class="flex flex-col h-dvh"><header class="sticky top-0 z-50 w-full border-b border-border/50 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"><div class="flex h-14 items-center px-6"><a class="mr-8" href="/"><img alt="InputLayer" width="123" height="28" decoding="async" data-nimg="1" class="flex-shrink-0" style="color:transparent" src="/inputlayer_logo_long_dark.png"/></a><nav class="hidden md:flex items-center gap-6 text-sm"><a class="text-muted-foreground transition-colors hover:text-foreground" href="/docs/">Docs</a><a class="text-muted-foreground transition-colors hover:text-foreground" href="/blog/">Blog</a><a href="https://demo.inputlayer.ai" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1 text-muted-foreground transition-colors hover:text-foreground">Demo<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-external-link h-3 w-3"><path d="M15 3h6v6"></path><path d="M10 14 21 3"></path><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path></svg></a></nav><div class="ml-auto flex items-center gap-3"><div class="inline-flex items-center gap-1.5"><a href="https://github.com/inputlayer/inputlayer" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 rounded-md border border-border bg-secondary/50 px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground hover:bg-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path></svg><span>Star</span></a><a href="https://github.com/inputlayer/inputlayer/fork" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 rounded-md border border-border bg-secondary/50 px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground hover:bg-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-git-fork h-3.5 w-3.5"><circle cx="12" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><circle cx="18" cy="6" r="3"></circle><path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9"></path><path d="M12 12v3"></path></svg><span>Fork</span></a></div><button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 size-9 h-9 w-9"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun h-4 w-4"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></svg></button><div class="md:hidden"><button class="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors" aria-label="Open menu"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu h-5 w-5"><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg></button></div></div></div></header><div class="flex flex-1 overflow-hidden"><div class="w-64 shrink-0 border-r border-border/50 overflow-y-auto"><div class="flex flex-col h-full"><div class="p-3 border-b border-border/50"><div class="relative"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg><input type="text" placeholder="Search docs..." class="w-full h-8 pl-8 pr-3 text-sm rounded-md border border-border bg-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring" value=""/></div></div><nav class="flex-1 overflow-y-auto p-2 space-y-0.5"><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:12px" href="/docs/index/">Overview</a><div data-state="open" data-slot="collapsible"><button class="w-full flex items-center w-full py-1.5 px-3 text-sm rounded-md transition-colors text-foreground font-medium" style="padding-left:12px" type="button" aria-controls="radix-_R_j6av5ubtb_" aria-expanded="true" data-state="open" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-3.5 w-3.5 mr-1 shrink-0 transition-transform rotate-90"><path d="m9 18 6-6-6-6"></path></svg>Guides</button><div data-state="open" id="radix-_R_j6av5ubtb_" data-slot="collapsible-content"><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/quickstart/">Quick Start</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/installation/">Installation</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/first-program/">Your First Program</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/verified-completions/">Verified Completions</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/core-concepts/">Data Modeling</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/repl/">REPL Guide</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/web-ui/">Web UI</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/recursion/">Recursion</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/explainability/">Explainability</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/query-profiling/">Query Profiling</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/vectors/">Vector Search</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/indexing/">Indexing</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/temporal/">Temporal Reasoning</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/persistence/">Persistence</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/configuration/">Configuration</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/rest-api/">REST API</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors bg-primary/10 text-primary font-medium" style="padding-left:24px" href="/docs/guides/python-sdk/">Python SDK</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/langchain/">LangChain Integration</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/langgraph/">LangGraph Integration</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/js-sdk/">JavaScript / TypeScript SDK</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/deployment/">Deployment</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/authentication/">Authentication</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/websocket-api/">WebSocket API</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/migrations/">Migrations</a><a class="block py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:24px" href="/docs/guides/troubleshooting/">Troubleshooting</a></div></div><div data-state="closed" data-slot="collapsible"><button class="w-full flex items-center w-full py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:12px" type="button" aria-controls="radix-_R_r6av5ubtb_" aria-expanded="false" data-state="closed" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-3.5 w-3.5 mr-1 shrink-0 transition-transform"><path d="m9 18 6-6-6-6"></path></svg>Reference</button><div data-state="closed" id="radix-_R_r6av5ubtb_" hidden="" data-slot="collapsible-content"></div></div><div data-state="closed" data-slot="collapsible"><button class="w-full flex items-center w-full py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:12px" type="button" aria-controls="radix-_R_136av5ubtb_" aria-expanded="false" data-state="closed" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-3.5 w-3.5 mr-1 shrink-0 transition-transform"><path d="m9 18 6-6-6-6"></path></svg>Specification</button><div data-state="closed" id="radix-_R_136av5ubtb_" hidden="" data-slot="collapsible-content"></div></div><div data-state="closed" data-slot="collapsible"><button class="w-full flex items-center w-full py-1.5 px-3 text-sm rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground" style="padding-left:12px" type="button" aria-controls="radix-_R_1b6av5ubtb_" aria-expanded="false" data-state="closed" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-3.5 w-3.5 mr-1 shrink-0 transition-transform"><path d="m9 18 6-6-6-6"></path></svg>Internals</button><div data-state="closed" id="radix-_R_1b6av5ubtb_" hidden="" data-slot="collapsible-content"></div></div></nav></div></div><div class="flex flex-1 flex-col overflow-hidden"><div class="flex flex-1 overflow-hidden"><div class="flex-1 overflow-y-auto px-8 py-6 max-w-4xl mx-auto"><article class="docs-prose"><h1 id="python-sdk" node="[object Object]">Python SDK</h1>
<p>InputLayer's Python SDK is an Object-Logic Mapper (OLM) that lets you work with your knowledge graph using plain Python classes. You define schemas as typed dataclasses, build queries with lambdas and operators, and the SDK compiles everything into InputLayer Query Language (IQL) behind the scenes. You never have to write IQL by hand for normal application code.</p>
<p>The SDK connects over WebSocket, so you get persistent connections, real-time notifications, and session-scoped state out of the box. For LangChain users, see the dedicated <a href="langchain" target="_blank" rel="noopener noreferrer" node="[object Object]">LangChain Integration</a> guide.</p>
<h2 id="installation" node="[object Object]">Installation</h2>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-bash text-sm"><span class="syn-builtin">pip</span> install inputlayer<span class="syn-meta">-client-dev</span>
<span class="syn-comment"># With pandas DataFrame support</span>
<span class="syn-builtin">pip</span> install inputlayer<span class="syn-meta">-client-dev</span>[pandas]</code></pre>
<p>Requirements: Python 3.10+ and a running InputLayer server.</p>
<p>The package also installs the <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">il migration</code> commands for managing schema migrations. See <a href="migrations" target="_blank" rel="noopener noreferrer" node="[object Object]">Migrations</a> for the full guide.</p>
<h2 id="connecting" node="[object Object]">Connecting</h2>
<p>The <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">InputLayer</code> client manages a WebSocket connection with automatic reconnection. You can authenticate with username/password or API keys.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">
<span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> InputLayer
<span class="syn-keyword">async</span> <span class="syn-keyword">def</span> main():
<span class="syn-keyword">async</span> <span class="syn-keyword">with</span> InputLayer(<span class="syn-string">"ws://localhost:8080/ws"</span>, username=<span class="syn-string">"admin"</span>, password=<span class="syn-string">"admin"</span>) <span class="syn-keyword">as</span> il:
<span class="syn-builtin">print</span>(<span class="syn-string">f"Connected to InputLayer {il.server_version}"</span>)
asyncio.run(main())</code></pre>
<p>If you prefer API key authentication:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">async</span> <span class="syn-keyword">with</span> InputLayer(<span class="syn-string">"ws://localhost:8080/ws"</span>, api_key=<span class="syn-string">"il_key_abc123"</span>) <span class="syn-keyword">as</span> il:
...</code></pre>
<p>The client accepts a few optional parameters for connection resilience:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">il = InputLayer(
<span class="syn-string">"ws://localhost:8080/ws"</span>,
username=<span class="syn-string">"admin"</span>,
password=<span class="syn-string">"admin"</span>,
auto_reconnect=<span class="syn-number">True</span>, <span class="syn-comment"># reconnect on disconnect (default: True)</span>
reconnect_delay=<span class="syn-number">1.0</span>, <span class="syn-comment"># seconds between attempts (default: 1.0)</span>
max_reconnect_attempts=<span class="syn-number">10</span>, <span class="syn-comment"># give up after N failures (default: 10)</span>
)
<span class="syn-keyword">await</span> il.connect()
<span class="syn-comment"># ... use the client ...</span>
<span class="syn-keyword">await</span> il.close()</code></pre>
<h3 id="sync-client" node="[object Object]">Sync Client</h3>
<p>If you're working in a script, notebook, or anywhere async isn't practical, use <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">InputLayerSync</code> instead. It wraps the async client and exposes the same API, just without <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">await</code>:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> InputLayerSync
<span class="syn-keyword">with</span> InputLayerSync(<span class="syn-string">"ws://localhost:8080/ws"</span>, username=<span class="syn-string">"admin"</span>, password=<span class="syn-string">"admin"</span>) <span class="syn-keyword">as</span> il:
kg = il.knowledge_graph(<span class="syn-string">"myapp"</span>)
kg.define(Employee)
result = kg.query(Employee)
<span class="syn-keyword">for</span> emp <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(emp.name)</code></pre>
<p>Every method shown in this guide works the same way on the sync client - just drop the <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">await</code>.</p>
<h2 id="knowledge-graphs" node="[object Object]">Knowledge Graphs</h2>
<p>Once connected, you work within a knowledge graph. Think of it as a namespace or database that holds your relations, rules, and indexes.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Get or create a knowledge graph</span>
kg = il.knowledge_graph(<span class="syn-string">"myapp"</span>)
<span class="syn-comment"># List all knowledge graphs on the server</span>
graphs = <span class="syn-keyword">await</span> il.list_knowledge_graphs()
<span class="syn-comment"># Drop a knowledge graph and all its data</span>
<span class="syn-keyword">await</span> il.drop_knowledge_graph(<span class="syn-string">"myapp"</span>)</code></pre>
<h2 id="defining-schemas" node="[object Object]">Defining Schemas</h2>
<p>Schemas are defined as Python classes that extend <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">Relation</code>. Each field becomes a typed column in the knowledge graph.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> Relation, Vector, Timestamp
<span class="syn-keyword">class</span> Employee(Relation):
id: <span class="syn-builtin">int</span>
name: <span class="syn-builtin">str</span>
department: <span class="syn-builtin">str</span>
salary: <span class="syn-builtin">float</span>
active: <span class="syn-builtin">bool</span>
<span class="syn-keyword">class</span> Document(Relation):
id: <span class="syn-builtin">int</span>
title: <span class="syn-builtin">str</span>
content: <span class="syn-builtin">str</span>
embedding: Vector[<span class="syn-number">384</span>]
created_at: Timestamp</code></pre>
<p>Deploy your schema to the server with <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">define()</code>. This is idempotent - calling it multiple times is safe and won't duplicate anything.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">kg = il.knowledge_graph(<span class="syn-string">"myapp"</span>)
<span class="syn-keyword">await</span> kg.define(Employee, Document)</code></pre>
<p>You can inspect what's been deployed:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># List all relations</span>
relations = <span class="syn-keyword">await</span> kg.relations()
<span class="syn-keyword">for</span> r <span class="syn-keyword">in</span> relations:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{r.name}: {r.row_count} rows"</span>)
<span class="syn-comment"># Describe a specific relation's schema</span>
desc = <span class="syn-keyword">await</span> kg.describe(Employee)
<span class="syn-keyword">for</span> col <span class="syn-keyword">in</span> desc.columns:
<span class="syn-builtin">print</span>(<span class="syn-string">f" {col.name}: {col.type}"</span>)</code></pre>
<h3 id="supported-types" node="[object Object]">Supported Types</h3>
<div class="my-4 overflow-x-auto"><table class="w-full border-collapse" node="[object Object]"><thead><tr><th class="border px-4 py-2 text-left font-semibold bg-muted/50" node="[object Object]">Python Type</th><th class="border px-4 py-2 text-left font-semibold bg-muted/50" node="[object Object]">InputLayer Type</th><th class="border px-4 py-2 text-left font-semibold bg-muted/50" node="[object Object]">Description</th></tr></thead><tbody><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">int</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">int</code></td><td class="border px-4 py-2" node="[object Object]">64-bit integer</td></tr><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">float</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">float</code></td><td class="border px-4 py-2" node="[object Object]">64-bit floating point</td></tr><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">str</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">string</code></td><td class="border px-4 py-2" node="[object Object]">UTF-8 string</td></tr><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">bool</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">bool</code></td><td class="border px-4 py-2" node="[object Object]">Boolean</td></tr><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">Vector[N]</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">vector(N)</code></td><td class="border px-4 py-2" node="[object Object]">N-dimensional float32 vector</td></tr><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">VectorInt8[N]</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">vector_int8(N)</code></td><td class="border px-4 py-2" node="[object Object]">N-dimensional int8 quantized vector</td></tr><tr><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">Timestamp</code></td><td class="border px-4 py-2" node="[object Object]"><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">timestamp</code></td><td class="border px-4 py-2" node="[object Object]">Unix epoch milliseconds</td></tr></tbody></table></div>
<h3 id="custom-relation-names" node="[object Object]">Custom Relation Names</h3>
<p>By default, the SDK converts your class name to snake_case (e.g., <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">SensorReading</code> becomes <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">sensor_reading</code>). You can override this:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">class</span> SensorReading(Relation):
__relation_name__ = <span class="syn-string">"readings"</span>
sensor_id: <span class="syn-builtin">int</span>
value: <span class="syn-builtin">float</span></code></pre>
<h2 id="inserting-data" node="[object Object]">Inserting Data</h2>
<p>You can insert data in several ways, depending on what's most convenient.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># A single fact</span>
<span class="syn-keyword">await</span> kg.insert(Employee(id=<span class="syn-number">1</span>, name=<span class="syn-string">"Alice"</span>, department=<span class="syn-string">"eng"</span>, salary=<span class="syn-number">120000.0</span>, active=<span class="syn-number">True</span>))
<span class="syn-comment"># A batch of facts</span>
<span class="syn-keyword">await</span> kg.insert([
Employee(id=<span class="syn-number">2</span>, name=<span class="syn-string">"Bob"</span>, department=<span class="syn-string">"hr"</span>, salary=<span class="syn-number">90000.0</span>, active=<span class="syn-number">True</span>),
Employee(id=<span class="syn-number">3</span>, name=<span class="syn-string">"Charlie"</span>, department=<span class="syn-string">"eng"</span>, salary=<span class="syn-number">110000.0</span>, active=<span class="syn-number">False</span>),
])
<span class="syn-comment"># From a dictionary</span>
<span class="syn-keyword">await</span> kg.insert(Employee, data={<span class="syn-string">"id"</span>: <span class="syn-number">4</span>, <span class="syn-string">"name"</span>: <span class="syn-string">"Diana"</span>, <span class="syn-string">"department"</span>: <span class="syn-string">"eng"</span>, <span class="syn-string">"salary"</span>: <span class="syn-number">105000.0</span>, <span class="syn-string">"active"</span>: <span class="syn-number">True</span>})
<span class="syn-comment"># From a list of dictionaries</span>
<span class="syn-keyword">await</span> kg.insert(Employee, data=[
{<span class="syn-string">"id"</span>: <span class="syn-number">5</span>, <span class="syn-string">"name"</span>: <span class="syn-string">"Eve"</span>, <span class="syn-string">"department"</span>: <span class="syn-string">"sales"</span>, <span class="syn-string">"salary"</span>: <span class="syn-number">95000.0</span>, <span class="syn-string">"active"</span>: <span class="syn-number">True</span>},
{<span class="syn-string">"id"</span>: <span class="syn-number">6</span>, <span class="syn-string">"name"</span>: <span class="syn-string">"Frank"</span>, <span class="syn-string">"department"</span>: <span class="syn-string">"sales"</span>, <span class="syn-string">"salary"</span>: <span class="syn-number">88000.0</span>, <span class="syn-string">"active"</span>: <span class="syn-number">True</span>},
])</code></pre>
<h3 id="inserting-from-pandas-dataframes" node="[object Object]">Inserting from pandas DataFrames</h3>
<p>If you installed with <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">pip install inputlayer-client-dev[pandas]</code>, you can load data directly from DataFrames:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">
df = pd.DataFrame({
<span class="syn-string">"id"</span>: [<span class="syn-number">7</span>, <span class="syn-number">8</span>, <span class="syn-number">9</span>],
<span class="syn-string">"name"</span>: [<span class="syn-string">"Grace"</span>, <span class="syn-string">"Hank"</span>, <span class="syn-string">"Ivy"</span>],
<span class="syn-string">"department"</span>: [<span class="syn-string">"eng"</span>, <span class="syn-string">"hr"</span>, <span class="syn-string">"eng"</span>],
<span class="syn-string">"salary"</span>: [<span class="syn-number">115000.0</span>, <span class="syn-number">92000.0</span>, <span class="syn-number">108000.0</span>],
<span class="syn-string">"active"</span>: [<span class="syn-number">True</span>, <span class="syn-number">True</span>, <span class="syn-number">False</span>],
})
result = <span class="syn-keyword">await</span> kg.insert(Employee, data=df)
<span class="syn-builtin">print</span>(<span class="syn-string">f"Inserted {result.count} rows"</span>)</code></pre>
<h2 id="deleting-data" node="[object Object]">Deleting Data</h2>
<p>Delete specific facts or use a filter to remove matching rows:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Delete a specific fact</span>
<span class="syn-keyword">await</span> kg.delete(Employee(id=<span class="syn-number">1</span>, name=<span class="syn-string">"Alice"</span>, department=<span class="syn-string">"eng"</span>, salary=<span class="syn-number">120000.0</span>, active=<span class="syn-number">True</span>))
<span class="syn-comment"># Delete by condition</span>
result = <span class="syn-keyword">await</span> kg.delete(Employee, where=<span class="syn-keyword">lambda</span> e: e.active == <span class="syn-number">False</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f"Deleted {result.count} rows"</span>)</code></pre>
<h2 id="querying" node="[object Object]">Querying</h2>
<p>Queries are built with Python expressions. The SDK compiles your lambdas, column references, and aggregation calls into the right query language behind the scenes.</p>
<h3 id="basic-queries" node="[object Object]">Basic Queries</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># All rows from a relation</span>
result = <span class="syn-keyword">await</span> kg.query(Employee)
<span class="syn-keyword">for</span> emp <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{emp.name} - {emp.department}"</span>)
<span class="syn-comment"># With a filter</span>
engineers = <span class="syn-keyword">await</span> kg.query(
Employee,
where=<span class="syn-keyword">lambda</span> e: (e.department == <span class="syn-string">"eng"</span>) & (e.active == <span class="syn-number">True</span>),
)</code></pre>
<h3 id="selecting-specific-columns" node="[object Object]">Selecting Specific Columns</h3>
<p>Instead of fetching full rows, you can select just the columns you need:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">result = <span class="syn-keyword">await</span> kg.query(
Employee.name, Employee.salary,
join=[Employee],
where=<span class="syn-keyword">lambda</span> e: e.department == <span class="syn-string">"eng"</span>,
)
<span class="syn-keyword">for</span> row <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{row.name}: ${row.salary}"</span>)</code></pre>
<h3 id="joins" node="[object Object]">Joins</h3>
<p>When your query spans multiple relations, use <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">join</code> and <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">on</code> to combine them:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">class</span> Department(Relation):
name: <span class="syn-builtin">str</span>
budget: <span class="syn-builtin">float</span>
result = <span class="syn-keyword">await</span> kg.query(
Employee.name, Department.budget,
join=[Employee, Department],
on=<span class="syn-keyword">lambda</span> e, d: e.department == d.name,
)</code></pre>
<h3 id="self-joins" node="[object Object]">Self-Joins</h3>
<p>For queries that need to compare rows within the same relation, use <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">refs()</code> to create aliased references:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">e1, e2 = Employee.refs(<span class="syn-number">2</span>)
result = <span class="syn-keyword">await</span> kg.query(
e1.name, e2.name,
join=[e1, e2],
on=<span class="syn-keyword">lambda</span> a, b: (a.department == b.department) & (a.id != b.id),
)</code></pre>
<h3 id="computed-columns" node="[object Object]">Computed Columns</h3>
<p>You can define computed values inline using keyword arguments:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">result = <span class="syn-keyword">await</span> kg.query(
Employee.name,
join=[Employee],
bonus=Employee.salary * <span class="syn-number">0.1</span>,
)
<span class="syn-keyword">for</span> row <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{row.name}: bonus = ${row.bonus}"</span>)</code></pre>
<h3 id="ordering-and-pagination" node="[object Object]">Ordering and Pagination</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Top 10 highest paid</span>
result = <span class="syn-keyword">await</span> kg.query(
Employee,
order_by=Employee.salary.desc(),
limit=<span class="syn-number">10</span>,
)
<span class="syn-comment"># Second page</span>
result = <span class="syn-keyword">await</span> kg.query(
Employee,
order_by=Employee.name.asc(),
limit=<span class="syn-number">10</span>,
offset=<span class="syn-number">10</span>,
)</code></pre>
<h3 id="aggregations" node="[object Object]">Aggregations</h3>
<p>The SDK includes standard aggregation functions that you can use in queries:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> count, count_distinct, sum_, avg, min_, max_
<span class="syn-comment"># Group by department with stats</span>
result = <span class="syn-keyword">await</span> kg.query(
Employee.department,
count(Employee.id),
avg(Employee.salary),
max_(Employee.salary),
join=[Employee],
)
<span class="syn-keyword">for</span> row <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{row.department}: {row.count} employees, avg ${row.avg}"</span>)</code></pre>
<p>For more specialized aggregation, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">top_k</code> lets you find the top entries per group:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> top_k
<span class="syn-comment"># Top 3 highest-paid employees per department</span>
result = <span class="syn-keyword">await</span> kg.query(
Employee.department, Employee.name, Employee.salary,
join=[Employee],
top_k(<span class="syn-number">3</span>, Employee.department, order_by=Employee.salary, desc=<span class="syn-number">True</span>),
)</code></pre>
<h3 id="working-with-results" node="[object Object]">Working with Results</h3>
<p>Every query returns a <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">ResultSet</code> with several ways to access the data:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">result = <span class="syn-keyword">await</span> kg.query(Employee)
<span class="syn-comment"># Iterate as typed objects</span>
<span class="syn-keyword">for</span> emp <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(emp.name)
<span class="syn-comment"># Check result metadata</span>
<span class="syn-builtin">print</span>(<span class="syn-string">f"Rows: {len(result)}, Total: {result.total_count}"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f"Execution time: {result.execution_time_ms}ms"</span>)
<span class="syn-comment"># Get the first row (or None if empty)</span>
first = result.first()
<span class="syn-comment"># Get a single scalar value</span>
total = (<span class="syn-keyword">await</span> kg.query(count(Employee.id), join=[Employee])).scalar()
<span class="syn-comment"># Convert to different formats</span>
dicts = result.to_dicts() <span class="syn-comment"># list[dict]</span>
tuples = result.to_tuples() <span class="syn-comment"># list[tuple]</span>
df = result.to_df() <span class="syn-comment"># pandas DataFrame (requires pandas)</span></code></pre>
<h3 id="query-plans" node="[object Object]">Query Plans</h3>
<p>To understand how a query will execute without running it, use <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">debug()</code>:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">plan = <span class="syn-keyword">await</span> kg.debug(
Employee,
where=<span class="syn-keyword">lambda</span> e: e.department == <span class="syn-string">"eng"</span>,
)
<span class="syn-builtin">print</span>(plan.iql) <span class="syn-comment"># compiled IQL string</span>
<span class="syn-builtin">print</span>(plan.plan) <span class="syn-comment"># execution plan</span></code></pre>
<h3 id="raw-iql" node="[object Object]">Raw IQL</h3>
<p>If you need to drop down to raw InputLayer Query Language for something the OLM doesn't cover:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">result = <span class="syn-keyword">await</span> kg.execute(<span class="syn-string">"?employee(Id, Name, D, Salary, A), Salary > 100000"</span>)</code></pre>
<p>The query body is whatever follows <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">?</code>. Variable names must be capitalized (lowercase identifiers parse as constants). For agent-style or chain-style use, prefer the safe parameter binding helpers from the <a href="langchain" target="_blank" rel="noopener noreferrer" node="[object Object]">LangChain Integration</a> guide rather than building IQL strings with <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">f"..."</code>.</p>
<h2 id="derived-relations-rules" node="[object Object]">Derived Relations (Rules)</h2>
<p>Derived relations are computed views that InputLayer keeps up to date automatically. When the underlying data changes, derived results are recomputed incrementally - you never need to manually refresh them.</p>
<p>Define them using <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">Derived</code> with the <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">From(...).where(...).select(...)</code> builder:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> typing <span class="syn-keyword">import</span> ClassVar
<span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> Derived, From
<span class="syn-keyword">class</span> HighEarner(Derived):
name: <span class="syn-builtin">str</span>
salary: <span class="syn-builtin">float</span>
rules: ClassVar[<span class="syn-builtin">list</span>] = [
From(Employee)
.where(<span class="syn-keyword">lambda</span> e: e.salary > <span class="syn-number">100000</span>)
.select(name=Employee.name, salary=Employee.salary),
]</code></pre>
<h3 id="recursive-rules" node="[object Object]">Recursive Rules</h3>
<p>One of InputLayer's most powerful features is native support for recursive logic. You define it naturally - a base case and a recursive case:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">class</span> Edge(Relation):
src: <span class="syn-builtin">int</span>
dst: <span class="syn-builtin">int</span>
<span class="syn-keyword">class</span> Reachable(Derived):
src: <span class="syn-builtin">int</span>
dst: <span class="syn-builtin">int</span>
rules: ClassVar[<span class="syn-builtin">list</span>] = []
Reachable.rules = [
<span class="syn-comment"># Base case: direct edges are reachable</span>
From(Edge).select(src=Edge.src, dst=Edge.dst),
<span class="syn-comment"># Recursive case: if A reaches B and B reaches C, then A reaches C</span>
From(Reachable, Edge)
.where(<span class="syn-keyword">lambda</span> r, e: r.dst == e.src)
.select(src=Reachable.src, dst=Edge.dst),
]</code></pre>
<p>Deploy and query rules just like regular relations:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Deploy the rule (persistent - survives restarts)</span>
<span class="syn-keyword">await</span> kg.define_rules(Reachable)
<span class="syn-comment"># Query it</span>
result = <span class="syn-keyword">await</span> kg.query(Reachable, where=<span class="syn-keyword">lambda</span> r: r.src == <span class="syn-number">1</span>)
<span class="syn-keyword">for</span> row <span class="syn-keyword">in</span> result:
<span class="syn-builtin">print</span>(<span class="syn-string">f"1 can reach {row.dst}"</span>)</code></pre>
<h3 id="managing-rules" node="[object Object]">Managing Rules</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># List all deployed rules</span>
rules = <span class="syn-keyword">await</span> kg.list_rules()
<span class="syn-keyword">for</span> r <span class="syn-keyword">in</span> rules:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{r.name}: {r.clause_count} clause(s)"</span>)
<span class="syn-comment"># View a rule's compiled IQL definition</span>
clauses = <span class="syn-keyword">await</span> kg.rule_definition(<span class="syn-string">"reachable"</span>)
<span class="syn-keyword">for</span> clause <span class="syn-keyword">in</span> clauses:
<span class="syn-builtin">print</span>(clause)
<span class="syn-comment"># Drop a specific rule</span>
<span class="syn-keyword">await</span> kg.drop_rule(<span class="syn-string">"high_earner"</span>)
<span class="syn-comment"># Clear a rule's materialized data (rule stays, data recomputes)</span>
<span class="syn-keyword">await</span> kg.clear_rule(<span class="syn-string">"reachable"</span>)</code></pre>
<h2 id="vector-search" node="[object Object]">Vector Search</h2>
<p>InputLayer supports HNSW indexes for approximate nearest-neighbor search over vector columns.</p>
<h3 id="creating-an-index" node="[object Object]">Creating an Index</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> HnswIndex
index = HnswIndex(
name=<span class="syn-string">"doc_emb_idx"</span>,
relation=Document,
column=<span class="syn-string">"embedding"</span>,
metric=<span class="syn-string">"cosine"</span>, <span class="syn-comment"># cosine, euclidean, manhattan, dot_product</span>
m=<span class="syn-number">32</span>, <span class="syn-comment"># connections per node (higher = more accurate, more memory)</span>
ef_construction=<span class="syn-number">200</span>, <span class="syn-comment"># build-time search width</span>
ef_search=<span class="syn-number">50</span>, <span class="syn-comment"># query-time search width</span>
)
<span class="syn-keyword">await</span> kg.create_index(index)</code></pre>
<h3 id="searching" node="[object Object]">Searching</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">query_embedding = [<span class="syn-number">0.1</span>, <span class="syn-number">0.2</span>, ...] <span class="syn-comment"># your query vector</span>
<span class="syn-comment"># Top-k nearest neighbors</span>
result = <span class="syn-keyword">await</span> kg.vector_search(
Document,
query_vec=query_embedding,
k=<span class="syn-number">10</span>,
metric=<span class="syn-string">"cosine"</span>,
)
<span class="syn-comment"># Radius-based search (all vectors within distance)</span>
result = <span class="syn-keyword">await</span> kg.vector_search(
Document,
query_vec=query_embedding,
radius=<span class="syn-number">0.3</span>,
metric=<span class="syn-string">"cosine"</span>,
)
<span class="syn-comment"># Combined with a filter</span>
result = <span class="syn-keyword">await</span> kg.vector_search(
Document,
query_vec=query_embedding,
k=<span class="syn-number">10</span>,
metric=<span class="syn-string">"cosine"</span>,
where=<span class="syn-keyword">lambda</span> d: d.created_at > Timestamp.from_datetime(cutoff_date),
)</code></pre>
<h3 id="managing-indexes" node="[object Object]">Managing Indexes</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># List all indexes</span>
indexes = <span class="syn-keyword">await</span> kg.list_indexes()
<span class="syn-keyword">for</span> idx <span class="syn-keyword">in</span> indexes:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{idx.name}: {idx.row_count} vectors, metric={idx.metric}"</span>)
<span class="syn-comment"># Get detailed stats</span>
stats = <span class="syn-keyword">await</span> kg.index_stats(<span class="syn-string">"doc_emb_idx"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f"Layers: {stats.layers}, Memory: {stats.memory_bytes} bytes"</span>)
<span class="syn-comment"># Rebuild after large data changes</span>
<span class="syn-keyword">await</span> kg.rebuild_index(<span class="syn-string">"doc_emb_idx"</span>)
<span class="syn-comment"># Drop an index</span>
<span class="syn-keyword">await</span> kg.drop_index(<span class="syn-string">"doc_emb_idx"</span>)</code></pre>
<h2 id="sessions" node="[object Object]">Sessions</h2>
<p>Sessions let you inject ephemeral facts and rules that exist only for the lifetime of your WebSocket connection. They're useful for user-specific context, A/B testing, or temporary views that shouldn't persist.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Insert session-scoped facts (only visible to this connection)</span>
<span class="syn-keyword">await</span> kg.session.insert([
Employee(id=<span class="syn-number">999</span>, name=<span class="syn-string">"Temp"</span>, department=<span class="syn-string">"eng"</span>, salary=<span class="syn-number">0.0</span>, active=<span class="syn-number">True</span>),
])
<span class="syn-comment"># Define session-scoped rules</span>
<span class="syn-keyword">await</span> kg.session.define_rules(MyTempView)
<span class="syn-comment"># Query as normal - session facts mix with persistent data</span>
result = <span class="syn-keyword">await</span> kg.query(MyTempView)
<span class="syn-comment"># List session rules</span>
session_rules = <span class="syn-keyword">await</span> kg.session.list_rules()
<span class="syn-comment"># Clean up (or just disconnect - session state is automatically cleared)</span>
<span class="syn-keyword">await</span> kg.session.clear()</code></pre>
<h2 id="notifications" node="[object Object]">Notifications</h2>
<p>Subscribe to real-time events as data changes in the knowledge graph. This is useful for building reactive pipelines, dashboards, or audit logs.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Register a callback for a specific relation</span>
<span class="syn-meta">@il</span>.on(<span class="syn-string">"persistent_update"</span>, relation=<span class="syn-string">"sensor_reading"</span>)
<span class="syn-keyword">def</span> on_sensor_update(event):
<span class="syn-builtin">print</span>(<span class="syn-string">f"[{event.relation}] {event.operation}: {event.count} rows"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f" sequence: {event.seq}, timestamp: {event.timestamp_ms}"</span>)
<span class="syn-comment"># Listen for any knowledge graph change</span>
<span class="syn-meta">@il</span>.on(<span class="syn-string">"kg_change"</span>)
<span class="syn-keyword">def</span> on_kg_change(event):
<span class="syn-builtin">print</span>(<span class="syn-string">f"KG {event.knowledge_graph} changed"</span>)
<span class="syn-comment"># You can also iterate over events</span>
<span class="syn-keyword">async</span> <span class="syn-keyword">for</span> event <span class="syn-keyword">in</span> il.notifications():
<span class="syn-builtin">print</span>(<span class="syn-string">f"Event: {event.type} seq={event.seq}"</span>)</code></pre>
<p>Event types include <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">persistent_update</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">rule_change</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">kg_change</code>, and <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">schema_change</code>. You can filter by relation or knowledge graph.</p>
<h2 id="built-in-functions" node="[object Object]">Built-in Functions</h2>
<p>The SDK exposes InputLayer's built-in function library for use in queries and rules. Import them from <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">inputlayer.functions</code>:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> functions <span class="syn-keyword">as</span> fn</code></pre>
<h3 id="distance-functions" node="[object Object]">Distance Functions</h3>
<p>For computing vector similarity outside of index-based search:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm">result = <span class="syn-keyword">await</span> kg.query(
Document.title,
join=[Document],
distance=fn.cosine(Document.embedding, query_vec),
)</code></pre>
<p>Available: <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.cosine</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.euclidean</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.manhattan</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.dot</code></p>
<p>Int8 variants: <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.cosine_int8</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.euclidean_int8</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.manhattan_int8</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.dot_int8</code></p>
<h3 id="vector-operations" node="[object Object]">Vector Operations</h3>
<p><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.normalize</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.vec_dim</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.vec_add</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.vec_scale</code></p>
<h3 id="temporal-functions" node="[object Object]">Temporal Functions</h3>
<p>For working with <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">Timestamp</code> columns:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> Timestamp
<span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> functions <span class="syn-keyword">as</span> fn
<span class="syn-comment"># Rows from the last hour</span>
result = <span class="syn-keyword">await</span> kg.query(
SensorReading,
where=<span class="syn-keyword">lambda</span> r: fn.within_last(r.timestamp, fn.time_now(), <span class="syn-number">3600000</span>),
)
<span class="syn-comment"># Time-decayed scoring</span>
result = <span class="syn-keyword">await</span> kg.query(
Article.title,
join=[Article],
score=fn.time_decay(Article.published_at, fn.time_now(), <span class="syn-number">86400000</span>),
)</code></pre>
<p>Available: <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_now</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_diff</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_add</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_sub</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_decay</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_decay_linear</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_before</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_after</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.time_between</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.within_last</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.intervals_overlap</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.interval_contains</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.interval_duration</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.point_in_interval</code></p>
<h3 id="math-functions" node="[object Object]">Math Functions</h3>
<p><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.abs_</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.sqrt</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.pow_</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.log</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.exp</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.sin</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.cos</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.tan</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.floor</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.ceil</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.sign</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.min_val</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.max_val</code></p>
<h3 id="string-functions" node="[object Object]">String Functions</h3>
<p><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.len_</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.upper</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.lower</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.trim</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.substr</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.replace</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.concat</code></p>
<h3 id="type-conversion" node="[object Object]">Type Conversion</h3>
<p><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.to_int</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">fn.to_float</code></p>
<h2 id="user-and-access-management" node="[object Object]">User and Access Management</h2>
<p>The SDK provides methods for managing users, API keys, and per-knowledge-graph access control.</p>
<h3 id="user-management" node="[object Object]">User Management</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Create a new user</span>
<span class="syn-keyword">await</span> il.create_user(<span class="syn-string">"alice"</span>, <span class="syn-string">"securepassword"</span>, role=<span class="syn-string">"editor"</span>)
<span class="syn-comment"># List users</span>
users = <span class="syn-keyword">await</span> il.list_users()
<span class="syn-keyword">for</span> u <span class="syn-keyword">in</span> users:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{u.username}: {u.role}"</span>)
<span class="syn-comment"># Change a user's role</span>
<span class="syn-keyword">await</span> il.set_role(<span class="syn-string">"alice"</span>, <span class="syn-string">"admin"</span>)
<span class="syn-comment"># Change a user's password</span>
<span class="syn-keyword">await</span> il.set_password(<span class="syn-string">"alice"</span>, <span class="syn-string">"newpassword"</span>)
<span class="syn-comment"># Remove a user</span>
<span class="syn-keyword">await</span> il.drop_user(<span class="syn-string">"alice"</span>)</code></pre>
<h3 id="api-keys" node="[object Object]">API Keys</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Create an API key</span>
key = <span class="syn-keyword">await</span> il.create_api_key(<span class="syn-string">"my-service"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f"Store this key securely: {key}"</span>)
<span class="syn-comment"># List active keys</span>
keys = <span class="syn-keyword">await</span> il.list_api_keys()
<span class="syn-keyword">for</span> k <span class="syn-keyword">in</span> keys:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{k.label} (created: {k.created_at})"</span>)
<span class="syn-comment"># Revoke a key</span>
<span class="syn-keyword">await</span> il.revoke_api_key(<span class="syn-string">"my-service"</span>)</code></pre>
<h3 id="per-knowledge-graph-access-control" node="[object Object]">Per-Knowledge-Graph Access Control</h3>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-comment"># Grant a user access to a specific knowledge graph</span>
<span class="syn-keyword">await</span> kg.grant_access(<span class="syn-string">"alice"</span>, <span class="syn-string">"editor"</span>)
<span class="syn-comment"># List access control entries</span>
acl = <span class="syn-keyword">await</span> kg.list_acl()
<span class="syn-keyword">for</span> entry <span class="syn-keyword">in</span> acl:
<span class="syn-builtin">print</span>(<span class="syn-string">f"{entry.username}: {entry.role}"</span>)
<span class="syn-comment"># Revoke access</span>
<span class="syn-keyword">await</span> kg.revoke_access(<span class="syn-string">"alice"</span>)</code></pre>
<h2 id="error-handling" node="[object Object]">Error Handling</h2>
<p>The SDK uses a hierarchy of typed exceptions so you can handle specific failure modes:</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-python text-sm"><span class="syn-keyword">from</span> inputlayer <span class="syn-keyword">import</span> (
InputLayerError, <span class="syn-comment"># base class for all errors</span>
ConnectionError, <span class="syn-comment"># network/connection issues</span>
AuthenticationError, <span class="syn-comment"># bad credentials</span>
SchemaConflictError, <span class="syn-comment"># schema mismatch on define()</span>
ValidationError, <span class="syn-comment"># invalid data</span>
QueryError, <span class="syn-comment"># engine rejected a query (parse/type/unsafe rule)</span>
QueryTimeoutError, <span class="syn-comment"># query took too long</span>
PermissionError, <span class="syn-comment"># insufficient permissions</span>
KnowledgeGraphNotFoundError,
KnowledgeGraphExistsError,
RelationNotFoundError,
RuleNotFoundError,
IndexNotFoundError,
InternalError, <span class="syn-comment"># unexpected server error</span>
)
<span class="syn-keyword">try</span>:
<span class="syn-keyword">await</span> kg.define(Employee)
<span class="syn-keyword">except</span> AuthenticationError:
<span class="syn-builtin">print</span>(<span class="syn-string">"Check your credentials"</span>)
<span class="syn-keyword">except</span> SchemaConflictError <span class="syn-keyword">as</span> e:
<span class="syn-builtin">print</span>(<span class="syn-string">f"Schema conflict: {e.conflicts}"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f" Existing: {e.existing_schema}"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f" Proposed: {e.proposed_schema}"</span>)
<span class="syn-keyword">except</span> QueryError <span class="syn-keyword">as</span> e:
<span class="syn-comment"># The engine rejected the query - parse error, type error, unsafe</span>
<span class="syn-comment"># rule, etc. The full IQL string is attached for debugging.</span>
<span class="syn-builtin">print</span>(<span class="syn-string">f"Query rejected: {e}"</span>)
<span class="syn-builtin">print</span>(<span class="syn-string">f" query: {e.query}"</span>)
<span class="syn-keyword">except</span> InputLayerError <span class="syn-keyword">as</span> e:
<span class="syn-builtin">print</span>(<span class="syn-string">f"InputLayer error: {e}"</span>)</code></pre>
<h2 id="migrations" node="[object Object]">Migrations</h2>
<p>For production deployments, the SDK includes a Django-style migration system that tracks schema changes, generates versioned migration files, and supports rollbacks. The <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">il</code> CLI is installed automatically with the package.</p>
<pre class="rounded-lg bg-[var(--code-bg)] p-4 overflow-x-auto mb-4 text-sm font-mono" node="[object Object]"><code class="language-bash text-sm"><span class="syn-comment"># Generate a migration from your current models</span>
il migration generate <span class="syn-meta">--models</span> myapp.models
<span class="syn-comment"># Apply pending migrations to a server</span>
il migration apply <span class="syn-meta">--url</span> ws://localhost:8080/ws <span class="syn-meta">--kg</span> production
<span class="syn-comment"># Check what's applied</span>
il migration status <span class="syn-meta">--url</span> ws://localhost:8080/ws <span class="syn-meta">--kg</span> production</code></pre>
<p>This is a large topic with its own dedicated guide. See <a href="migrations" target="_blank" rel="noopener noreferrer" node="[object Object]">Migrations</a> for the full walkthrough covering the CLI, operations, workflows, and CI/CD integration.</p>
<h2 id="known-limitations" node="[object Object]">Known Limitations</h2>
<p>A few rough edges to be aware of in the current pre-alpha SDK:</p>
<ul>
<li>
<p><strong>Aggregates are session rules under the hood</strong>. <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">kg.query(count(Employee.id))</code> works by registering a temporary session rule and querying it. The rule is best-effort dropped after the call, so if your process dies between rule registration and cleanup the rule lingers until the WebSocket session ends. This is invisible to application code but worth knowing if you read server logs.</p>
</li>
<li>
<p><strong><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">order_by</code> and <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">offset</code> are applied client-side</strong> for plain queries. IQL only supports ordering inside aggregate heads (<code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">top_k</code>, <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">within_radius</code>, etc.), so the SDK sorts and slices the result rows in Python after they come back. This is fine for typical result sizes but is not appropriate for streaming over millions of rows.</p>
</li>
<li>
<p><strong><code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">kg.relations()</code> only sees relations after data exists</strong>. The server's <code class="bg-muted rounded px-1.5 py-0.5 font-mono text-sm" node="[object Object]">.rel</code> listing surfaces a relation only after at least one row has been inserted; defining the schema is not enough. If you need a registry of declared schemas that's independent of data, track them in your application code.</p>
</li>
</ul>
<h2 id="next-steps" node="[object Object]">Next Steps</h2>
<ul>
<li><a href="langchain" target="_blank" rel="noopener noreferrer" node="[object Object]">LangChain Integration</a> - Vector store, retriever, structured agent tools</li>
<li><a href="migrations" target="_blank" rel="noopener noreferrer" node="[object Object]">Migrations</a> - Schema versioning for production deployments</li>
<li><a href="vectors" target="_blank" rel="noopener noreferrer" node="[object Object]">Vector Search</a> - Deep dive into vector indexing and search</li>
<li><a href="authentication" target="_blank" rel="noopener noreferrer" node="[object Object]">Authentication</a> - Server-level auth setup</li>
<li><a href="rest-api" target="_blank" rel="noopener noreferrer" node="[object Object]">REST API</a> - HTTP interface alongside WebSocket</li>
</ul></article></div><div class="hidden lg:block w-48 shrink-0 border-l border-border/50"><div class="sticky top-0 p-4"><p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">On this page</p><nav class="space-y-1"><a href="#installation" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Installation</a><a href="#connecting" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Connecting</a><a href="#sync-client" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Sync Client</a><a href="#knowledge-graphs" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Knowledge Graphs</a><a href="#defining-schemas" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Defining Schemas</a><a href="#supported-types" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Supported Types</a><a href="#custom-relation-names" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Custom Relation Names</a><a href="#inserting-data" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Inserting Data</a><a href="#inserting-from-pandas-dataframes" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Inserting from pandas DataFrames</a><a href="#deleting-data" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Deleting Data</a><a href="#querying" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Querying</a><a href="#basic-queries" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Basic Queries</a><a href="#selecting-specific-columns" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Selecting Specific Columns</a><a href="#joins" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Joins</a><a href="#self-joins" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Self-Joins</a><a href="#computed-columns" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Computed Columns</a><a href="#ordering-and-pagination" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Ordering and Pagination</a><a href="#aggregations" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Aggregations</a><a href="#working-with-results" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Working with Results</a><a href="#query-plans" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Query Plans</a><a href="#raw-iql" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Raw IQL</a><a href="#derived-relations-rules" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Derived Relations (Rules)</a><a href="#recursive-rules" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Recursive Rules</a><a href="#managing-rules" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Managing Rules</a><a href="#vector-search" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Vector Search</a><a href="#creating-an-index" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Creating an Index</a><a href="#searching" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Searching</a><a href="#managing-indexes" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Managing Indexes</a><a href="#sessions" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Sessions</a><a href="#notifications" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Notifications</a><a href="#built-in-functions" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Built-in Functions</a><a href="#distance-functions" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Distance Functions</a><a href="#vector-operations" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Vector Operations</a><a href="#temporal-functions" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Temporal Functions</a><a href="#math-functions" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Math Functions</a><a href="#string-functions" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">String Functions</a><a href="#type-conversion" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Type Conversion</a><a href="#user-and-access-management" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">User and Access Management</a><a href="#user-management" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">User Management</a><a href="#api-keys" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">API Keys</a><a href="#per-knowledge-graph-access-control" class="block text-xs py-0.5 transition-colors hover:text-foreground pl-3 text-muted-foreground">Per-Knowledge-Graph Access Control</a><a href="#error-handling" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Error Handling</a><a href="#migrations" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Migrations</a><a href="#known-limitations" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Known Limitations</a><a href="#next-steps" class="block text-xs py-0.5 transition-colors hover:text-foreground text-muted-foreground">Next Steps</a></nav></div></div></div></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-6b4fdc854b138615.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[1942,[\"177\",\"static/chunks/app/layout-51b94f561cc33957.js\"],\"ThemeProvider\"]\n3:I[7121,[],\"\"]\n4:I[4581,[],\"\"]\n6:I[484,[],\"OutletBoundary\"]\n7:\"$Sreact.suspense\"\na:I[484,[],\"ViewportBoundary\"]\nc:I[484,[],\"MetadataBoundary\"]\ne:I[7123,[],\"default\",1]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/7d4881bb7e1bf84d-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/508410e5e895cb66.css\",\"style\"]\n9:X\n0:{\"P\":null,\"c\":[\"\",\"docs\",\"guides\",\"python-sdk\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"docs\",{\"children\":[[\"slug\",\"guides/python-sdk\",\"oc\",[]],{\"children\":[\"__PAGE__\",{},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4624],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/508410e5e895cb66.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"suppressHydrationWarning\":true,\"children\":[\"$\",\"body\",null,{\"className\":\"__variable_246ccd __variable_c29908 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"attribute\":\"class\",\"defaultTheme\":\"dark\",\"enableSystem\":true,\"disableTransitionOnChange\":true,\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[\"$L5\",null,[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$7\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@8\"}]}]]}],{},null,false,null]},null,false,\"$9\"]},null,false,\"$9\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$La\",null,{\"children\":\"$Lb\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Lc\",null,{\"children\":[\"$\",\"$7\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Ld\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$e\",[]],\"S\":true,\"h\":null,\"r\":\"$undefined\",\"s\":\"$undefined\",\"a\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"-VCeFxnnhBZOsl7_J-k7I\"}\nf:I[8428,[\"661\",\"static/chunks/661-63cf87d99b552766.js\",\"121\",\"static/chunks/121-aece4f809b101dc1.js\",\"502\",\"static/chunks/502-7fd593d8b6507a0f.js\",\"689\",\"static/chunks/689-85ea892b64182e04.js\",\"870\",\"static/chunks/app/docs/%5B%5B...slug%5D%5D/page-0b234b25687989d5.js\"],\"DocsPageClient\"]\n9:C\n10:T5629,# Python SDK\n\nInputLayer's Python SDK is an Object-Logic Mapper (OLM) that lets you work with your knowledge graph using plain Python classes. You define schemas as typed dataclasses, build queries with lambdas and operators, and the SDK compiles everything into InputLayer Query Language (IQL) behind the scenes. You never have to write IQL by hand for normal application code.\n\nThe SDK connects over WebSocket, so you get persistent connections, real-time notifications, and session-scoped state out of the box. For LangChain users, see the dedicated [LangChain Integration](langchain) guide.\n\n## Installation\n\n```bash\npip install inputlayer-client-dev\n\n# With pandas DataFrame support\npip install inputlayer-client-dev[pandas]\n```\n\nRequirements: Python 3.10+ and a running InputLayer server.\n\nThe package also installs the `il migration` commands for managing schema migrations. See [Migrations](migrations) for the full guide.\n\n## Connecting\n\nThe `InputLayer` client manages a WebSocket connection with automatic reconnection. You can authenticate with username/password or API keys.\n\n```python\n\nfrom inputlayer import InputLayer\n\nasync def main():\n async with InputLayer(\"ws://localhost:8080/ws\", username=\"admin\", password=\"admin\") as il:\n print(f\"Connected to InputLayer {il.server_version}\")\n\nasyncio.run(main())\n```\n\nIf you prefer API key authentication:\n\n```python\nasync with InputLayer(\"ws://localhost:8080/ws\", api_key=\"il_key_abc123\") as il:\n ...\n```\n\nThe client accepts a few optional parameters for connection resilience:\n\n```python\nil = InputLayer(\n \"ws://localhost:8080/ws\",\n username=\"admin\",\n password=\"admin\",\n auto_reconnect=True, # reconnect on disconnect (default: True)\n reconnect_delay=1.0, # seconds between attempts (default: 1.0)\n max_reconnect_attempts=10, # give up after N failures (default: 10)\n)\nawait il.connect()\n# ... use the client ...\nawait il.close()\n```\n\n### Sync Client\n\nIf you're working in a script, notebook, or anywhere async isn't practical, use `InputLayerSync` instead. It wraps the async client and exposes the same API, just without `await`:\n\n```python\nfrom inputlayer import InputLayerSync\n\nwith InputLayerSync(\"ws://localhost:8080/ws\", username=\"admin\", password=\"admin\") as il:\n kg = il.knowledge_graph(\"myapp\")\n kg.define(Employee)\n result = kg.query(Employee)\n for emp in result:\n print(emp.name)\n```\n\nEvery method shown in this guide works the same way on the sync client - just drop the `await`.\n\n## Knowledge Graphs\n\nOnce connected, you work within a knowledge graph. Think of it as a namespace or database that holds your relations, rules, and indexes.\n\n```python\n# Get or create a knowledge graph\nkg = il.knowledge_graph(\"myapp\")\n\n# List all knowledge graphs on the server\ngraphs = await il.list_knowledge_graphs()\n\n# Drop a knowledge graph and all its data\nawait il.drop_knowledge_graph(\"myapp\")\n```\n\n## Defining Schemas\n\nSchemas are defined as Python classes that extend `Relation`. Each field becomes a typed column in the knowledge graph.\n\n```python\nfrom inputlayer import Relation, Vector, Timestamp\n\nclass Employee(Relation):\n id: int\n name: str\n department: str\n salary: float\n active: bool\n\nclass Document(Relation):\n id: int\n title: str\n content: str\n embedding: Vector[384]\n created_at: Timestamp\n```\n\nDeploy your schema to the server with `define()`. This is idempotent - calling it multiple times is safe and won't duplicate anything.\n\n```python\nkg = il.knowledge_graph(\"myapp\")\nawait kg.define(Employee, Document)\n```\n\nYou can inspect what's been deployed:\n\n```python\n# List all relations\nrelations = await kg.relations()\nfor r in relations:\n print(f\"{r.name}: {r.row_count} rows\")\n\n# Describe a specific relation's schema\ndesc = await kg.describe(Employee)\nfor col in desc.columns:\n print(f\" {col.name}: {col.type}\")\n```\n\n### Supported Types\n\n| Python Type | InputLayer Type | Description |\n|-------------|-----------------|-------------|\n| `int` | `int` | 64-bit integer |\n| `float` | `float` | 64-bit floating point |\n| `str` | `string` | UTF-8 string |\n| `bool` | `bool` | Boolean |\n| `Vector[N]` | `vector(N)` | N-dimensional float32 vector |\n| `VectorInt8[N]` | `vector_int8(N)` | N-dimensional int8 quantized vector |\n| `Timestamp` | `timestamp` | Unix epoch milliseconds |\n\n### Custom Relation Names\n\nBy default, the SDK converts your class name to snake_case (e.g., `SensorReading` becomes `sensor_reading`). You can override this:\n\n```python\nclass SensorReading(Relation):\n __relation_name__ = \"readings\"\n sensor_id: int\n value: float\n```\n\n## Inserting Data\n\nYou can insert data in several ways, depending on what's most convenient.\n\n```python\n# A single fact\nawait kg.insert(Employee(id=1, name=\"Alice\", department=\"eng\", salary=120000.0, active=True))\n\n# A batch of facts\nawait kg.insert([\n Employee(id=2, name=\"Bob\", department=\"hr\", salary=90000.0, active=True),\n Employee(id=3, name=\"Charlie\", department=\"eng\", salary=110000.0, active=False),\n])\n\n# From a dictionary\nawait kg.insert(Employee, data={\"id\": 4, \"name\": \"Diana\", \"department\": \"eng\", \"salary\": 105000.0, \"active\": True})\n\n# From a list of dictionaries\nawait kg.insert(Employee, data=[\n {\"id\": 5, \"name\": \"Eve\", \"department\": \"sales\", \"salary\": 95000.0, \"active\": True},\n {\"id\": 6, \"name\": \"Frank\", \"department\": \"sales\", \"salary\": 88000.0, \"active\": True},\n])\n```\n\n### Inserting from pandas DataFrames\n\nIf you installed with `pip install inputlayer-client-dev[pandas]`, you can load data directly from DataFrames:\n\n```python\n\n\ndf = pd.DataFrame({\n \"id\": [7, 8, 9],\n \"name\": [\"Grace\", \"Hank\", \"Ivy\"],\n \"department\": [\"eng\", \"hr\", \"eng\"],\n \"salary\": [115000.0, 92000.0, 108000.0],\n \"active\": [True, True, False],\n})\nresult = await kg.insert(Employee, data=df)\nprint(f\"Inserted {result.count} rows\")\n```\n\n## Deleting Data\n\nDelete specific facts or use a filter to remove matching rows:\n\n```python\n# Delete a specific fact\nawait kg.delete(Employee(id=1, name=\"Alice\", department=\"eng\", salary=120000.0, active=True))\n\n# Delete by condition\nresult = await kg.delete(Employee, where=lambda e: e.active == False)\nprint(f\"Deleted {result.count} rows\")\n```\n\n## Querying\n\nQueries are built with Python expressions. The SDK compiles your lambdas, column references, and aggregation calls into the right query language behind the scenes.\n\n### Basic Queries\n\n```python\n# All rows from a relation\nresult = await kg.query(Employee)\nfor emp in result:\n print(f\"{emp.name} - {emp.department}\")\n\n# With a filter\nengineers = await kg.query(\n Employee,\n where=lambda e: (e.department == \"eng\") \u0026 (e.active == True),\n)\n```\n\n### Selecting Specific Columns\n\nInstead of fetching full rows, you can select just the columns you need:\n\n```python\nresult = await kg.query(\n Employee.name, Employee.salary,\n join=[Employee],\n where=lambda e: e.department == \"eng\",\n)\nfor row in result:\n print(f\"{row.name}: ${row.salary}\")\n```\n\n### Joins\n\nWhen your query spans multiple relations, use `join` and `on` to combine them:\n\n```python\nclass Department(Relation):\n name: str\n budget: float\n\nresult = await kg.query(\n Employee.name, Department.budget,\n join=[Employee, Department],\n on=lambda e, d: e.department == d.name,\n)\n```\n\n### Self-Joins\n\nFor queries that need to compare rows within the same relation, use `refs()` to create aliased references:\n\n```python\ne1, e2 = Employee.refs(2)\n\nresult = await kg.query(\n e1.name, e2.name,\n join=[e1, e2],\n on=lambda a, b: (a.department == b.department) \u0026 (a.id != b.id),\n)\n```\n\n### Computed Columns\n\nYou can define computed values inline using keyword arguments:\n\n```python\nresult = await kg.query(\n Employee.name,\n join=[Employee],\n bonus=Employee.salary * 0.1,\n)\nfor row in result:\n print(f\"{row.name}: bonus = ${row.bonus}\")\n```\n\n### Ordering and Pagination\n\n```python\n# Top 10 highest paid\nresult = await kg.query(\n Employee,\n order_by=Employee.salary.desc(),\n limit=10,\n)\n\n# Second page\nresult = await kg.query(\n Employee,\n order_by=Employee.name.asc(),\n limit=10,\n offset=10,\n)\n```\n\n### Aggregations\n\nThe SDK includes standard aggregation functions that you can use in queries:\n\n```python\nfrom inputlayer import count, count_distinct, sum_, avg, min_, max_\n\n# Group by department with stats\nresult = await kg.query(\n Employee.department,\n count(Employee.id),\n avg(Employee.salary),\n max_(Employee.salary),\n join=[Employee],\n)\nfor row in result:\n print(f\"{row.department}: {row.count} employees, avg ${row.avg}\")\n```\n\nFor more specialized aggregation, `top_k` lets you find the top entries per group:\n\n```python\nfrom inputlayer import top_k\n\n# Top 3 highest-paid employees per department\nresult = await kg.query(\n Employee.department, Employee.name, Employee.salary,\n join=[Employee],\n top_k(3, Employee.department, order_by=Employee.salary, desc=True),\n)\n```\n\n### Working with Results\n\nEvery query returns a `ResultSet` with several ways to access the data:\n\n```python\nresult = await kg.query(Employee)\n\n# Iterate as typed objects\nfor emp in result:\n print(emp.name)\n\n# Check result metadata\nprint(f\"Rows: {len(result)}, Total: {result.total_count}\")\nprint(f\"Execution time: {result.execution_time_ms}ms\")\n\n# Get the first row (or None if empty)\nfirst = result.first()\n\n# Get a single scalar value\ntotal = (await kg.query(count(Employee.id), join=[Employee])).scalar()\n\n# Convert to different formats\ndicts = result.to_dicts() # list[dict]\ntuples = result.to_tuples() # list[tuple]\ndf = result.to_df() # pandas DataFrame (requires pandas)\n```\n\n### Query Plans\n\nTo understand how a query will execute without running it, use `debug()`:\n\n```python\nplan = await kg.debug(\n Employee,\n where=lambda e: e.department == \"eng\",\n)\nprint(plan.iql) # compiled IQL string\nprint(plan.plan) # execution plan\n```\n\n### Raw IQL\n\nIf you need to drop down to raw InputLayer Query Language for something the OLM doesn't cover:\n\n```python\nresult = await kg.execute(\"?employee(Id, Name, D, Salary, A), Salary \u003e 100000\")\n```\n\nThe query body is whatever follows `?`. Variable names must be capitalized (lowercase identifiers parse as constants). For agent-style or chain-style use, prefer the safe parameter binding helpers from the [LangChain Integration](langchain) guide rather than building IQL strings with `f\"...\"`.\n\n## Derived Relations (Rules)\n\nDerived relations are computed views that InputLayer keeps up to date automatically. When the underlying data changes, derived results are recomputed incrementally - you never need to manually refresh them.\n\nDefine them using `Derived` with the `From(...).where(...).select(...)` builder:\n\n```python\nfrom typing import ClassVar\nfrom inputlayer import Derived, From\n\nclass HighEarner(Derived):\n name: str\n salary: float\n rules: ClassVar[list] = [\n From(Employee)\n .where(lambda e: e.salary \u003e 100000)\n .select(name=Employee.name, salary=Employee.salary),\n ]\n```\n\n### Recursive Rules\n\nOne of InputLayer's most powerful features is native support for recursive logic. You define it naturally - a base case and a recursive case:\n\n```python\nclass Edge(Relation):\n src: int\n dst: int\n\nclass Reachable(Derived):\n src: int\n dst: int\n rules: ClassVar[list] = []\n\nReachable.rules = [\n # Base case: direct edges are reachable\n From(Edge).select(src=Edge.src, dst=Edge.dst),\n # Recursive case: if A reaches B and B reaches C, then A reaches C\n From(Reachable, Edge)\n .where(lambda r, e: r.dst == e.src)\n .select(src=Reachable.src, dst=Edge.dst),\n]\n```\n\nDeploy and query rules just like regular relations:\n\n```python\n# Deploy the rule (persistent - survives restarts)\nawait kg.define_rules(Reachable)\n\n# Query it\nresult = await kg.query(Reachable, where=lambda r: r.src == 1)\nfor row in result:\n print(f\"1 can reach {row.dst}\")\n```\n\n### Managing Rules\n\n```python\n# List all deployed rules\nrules = await kg.list_rules()\nfor r in rules:\n print(f\"{r.name}: {r.clause_count} clause(s)\")\n\n# View a rule's compiled IQL definition\nclauses = await kg.rule_definition(\"reachable\")\nfor clause in clauses:\n print(clause)\n\n# Drop a specific rule\nawait kg.drop_rule(\"high_earner\")\n\n# Clear a rule's materialized data (rule stays, data recomputes)\nawait kg.clear_rule(\"reachable\")\n```\n\n## Vector Search\n\nInputLayer supports HNSW indexes for approximate nearest-neighbor search over vector columns.\n\n### Creating an Index\n\n```python\nfrom inputlayer import HnswIndex\n\nindex = HnswIndex(\n name=\"doc_emb_idx\",\n relation=Document,\n column=\"embedding\",\n metric=\"cosine\", # cosine, euclidean, manhattan, dot_product\n m=32, # connections per node (higher = more accurate, more memory)\n ef_construction=200, # build-time search width\n ef_search=50, # query-time search width\n)\nawait kg.create_index(index)\n```\n\n### Searching\n\n```python\nquery_embedding = [0.1, 0.2, ...] # your query vector\n\n# Top-k nearest neighbors\nresult = await kg.vector_search(\n Document,\n query_vec=query_embedding,\n k=10,\n metric=\"cosine\",\n)\n\n# Radius-based search (all vectors within distance)\nresult = await kg.vector_search(\n Document,\n query_vec=query_embedding,\n radius=0.3,\n metric=\"cosine\",\n)\n\n# Combined with a filter\nresult = await kg.vector_search(\n Document,\n query_vec=query_embedding,\n k=10,\n metric=\"cosine\",\n where=lambda d: d.created_at \u003e Timestamp.from_datetime(cutoff_date),\n)\n```\n\n### Managing Indexes\n\n```python\n# List all indexes\nindexes = await kg.list_indexes()\nfor idx in indexes:\n print(f\"{idx.name}: {idx.row_count} vectors, metric={idx.metric}\")\n\n# Get detailed stats\nstats = await kg.index_stats(\"doc_emb_idx\")\nprint(f\"Layers: {stats.layers}, Memory: {stats.memory_bytes} bytes\")\n\n# Rebuild after large data changes\nawait kg.rebuild_index(\"doc_emb_idx\")\n\n# Drop an index\nawait kg.drop_index(\"doc_emb_idx\")\n```\n\n## Sessions\n\nSessions let you inject ephemeral facts and rules that exist only for the lifetime of your WebSocket connection. They're useful for user-specific context, A/B testing, or temporary views that shouldn't persist.\n\n```python\n# Insert session-scoped facts (only visible to this connection)\nawait kg.session.insert([\n Employee(id=999, name=\"Temp\", department=\"eng\", salary=0.0, active=True),\n])\n\n# Define session-scoped rules\nawait kg.session.define_rules(MyTempView)\n\n# Query as normal - session facts mix with persistent data\nresult = await kg.query(MyTempView)\n\n# List session rules\nsession_rules = await kg.session.list_rules()\n\n# Clean up (or just disconnect - session state is automatically cleared)\nawait kg.session.clear()\n```\n\n## Notifications\n\nSubscribe to real-time events as data changes in the knowledge graph. This is useful for building reactive pipelines, dashboards, or audit logs.\n\n```python\n# Register a callback for a specific relation\n@il.on(\"persistent_update\", relation=\"sensor_reading\")\ndef on_sensor_update(event):\n print(f\"[{event.relation}] {event.operation}: {event.count} rows\")\n print(f\" sequence: {event.seq}, timestamp: {event.timestamp_ms}\")\n\n# Listen for any knowledge graph change\n@il.on(\"kg_change\")\ndef on_kg_change(event):\n print(f\"KG {event.knowledge_graph} changed\")\n\n# You can also iterate over events\nasync for event in il.notifications():\n print(f\"Event: {event.type} seq={event.seq}\")\n```\n\nEvent types include `persistent_update`, `rule_change`, `kg_change`, and `schema_change`. You can filter by relation or knowledge graph.\n\n## Built-in Functions\n\nThe SDK exposes InputLayer's built-in function library for use in queries and rules. Import them from `inputlayer.functions`:\n\n```python\nfrom inputlayer import functions as fn\n```\n\n### Distance Functions\n\nFor computing vector similarity outside of index-based search:\n\n```python\nresult = await kg.query(\n Document.title,\n join=[Document],\n distance=fn.cosine(Document.embedding, query_vec),\n)\n```\n\nAvailable: `fn.cosine`, `fn.euclidean`, `fn.manhattan`, `fn.dot`\n\nInt8 variants: `fn.cosine_int8`, `fn.euclidean_int8`, `fn.manhattan_int8`, `fn.dot_int8`\n\n### Vector Operations\n\n`fn.normalize`, `fn.vec_dim`, `fn.vec_add`, `fn.vec_scale`\n\n### Temporal Functions\n\nFor working with `Timestamp` columns:\n\n```python\nfrom inputlayer import Timestamp\nfrom inputlayer import functions as fn\n\n# Rows from the last hour\nresult = await kg.query(\n SensorReading,\n where=lambda r: fn.within_last(r.timestamp, fn.time_now(), 3600000),\n)\n\n# Time-decayed scoring\nresult = await kg.query(\n Article.title,\n join=[Article],\n score=fn.time_decay(Article.published_at, fn.time_now(), 86400000),\n)\n```\n\nAvailable: `fn.time_now`, `fn.time_diff`, `fn.time_add`, `fn.time_sub`, `fn.time_decay`, `fn.time_decay_linear`, `fn.time_before`, `fn.time_after`, `fn.time_between`, `fn.within_last`, `fn.intervals_overlap`, `fn.interval_contains`, `fn.interval_duration`, `fn.point_in_interval`\n\n### Math Functions\n\n`fn.abs_`, `fn.sqrt`, `fn.pow_`, `fn.log`, `fn.exp`, `fn.sin`, `fn.cos`, `fn.tan`, `fn.floor`, `fn.ceil`, `fn.sign`, `fn.min_val`, `fn.max_val`\n\n### String Functions\n\n`fn.len_`, `fn.upper`, `fn.lower`, `fn.trim`, `fn.substr`, `fn.replace`, `fn.concat`\n\n### Type Conversion\n\n`fn.to_int`, `fn.to_float`\n\n## User and Access Management\n\nThe SDK provides methods for managing users, API keys, and per-knowledge-graph access control.\n\n### User Management\n\n```python\n# Create a new user\nawait il.create_user(\"alice\", \"securepassword\", role=\"editor\")\n\n# List users\nusers = await il.list_users()\nfor u in users:\n print(f\"{u.username}: {u.role}\")\n\n# Change a user's role\nawait il.set_role(\"alice\", \"admin\")\n\n# Change a user's password\nawait il.set_password(\"alice\", \"newpassword\")\n\n# Remove a user\nawait il.drop_user(\"alice\")\n```\n\n### API Keys\n\n```python\n# Create an API key\nkey = await il.create_api_key(\"my-service\")\nprint(f\"Store this key securely: {key}\")\n\n# List active keys\nkeys = await il.list_api_keys()\nfor k in keys:\n print(f\"{k.label} (created: {k.created_at})\")\n\n# Revoke a key\nawait il.revoke_api_key(\"my-service\")\n```\n\n### Per-Knowledge-Graph Access Control\n\n```python\n# Grant a user access to a specific knowledge graph\nawait kg.grant_access(\"alice\", \"editor\")\n\n# List access control entries\nacl = await kg.list_acl()\nfor entry in acl:\n print(f\"{entry.username}: {entry.role}\")\n\n# Revoke access\nawait kg.revoke_access(\"alice\")\n```\n\n## Error Handling\n\nThe SDK uses a hierarchy of typed exceptions so you can handle specific failure modes:\n\n```python\nfrom inputlayer import (\n InputLayerError, # base class for all errors\n ConnectionError, # network/connection issues\n AuthenticationError, # bad credentials\n SchemaConflictError, # schema mismatch on define()\n ValidationError, # invalid data\n QueryError, # engine rejected a query (parse/type/unsafe rule)\n QueryTimeoutError, # query took too long\n PermissionError, # insufficient permissions\n KnowledgeGraphNotFoundError,\n KnowledgeGraphExistsError,\n RelationNotFoundError,\n RuleNotFoundError,\n IndexNotFoundError,\n InternalError, # unexpected server error\n)\n\ntry:\n await kg.define(Employee)\nexcept AuthenticationError:\n print(\"Check your credentials\")\nexcept SchemaConflictError as e:\n print(f\"Schema conflict: {e.conflicts}\")\n print(f\" Existing: {e.existing_schema}\")\n print(f\" Proposed: {e.proposed_schema}\")\nexcept QueryError as e:\n # The engine rejected the query - parse error, type error, unsafe\n # rule, etc. The full IQL string is attached for debugging.\n print(f\"Query rejected: {e}\")\n print(f\" query: {e.query}\")\nexcept InputLayerError as e:\n print(f\"InputLayer error: {e}\")\n```\n\n## Migrations\n\nFor production deployments, the SDK includes a Django-style migration system that tracks schema changes, generates versioned migration files, and supports rollbacks. The `il` CLI is installed automatically with the package.\n\n```bash\n# Generate a migration from your current models\nil migration generate --models myapp.models\n\n# Apply pending migrations to a server\nil migration apply --url ws://localhost:8080/ws --kg production\n\n# Check what's applied\nil migration status --url ws://localhost:8080/ws --kg production\n```\n\nThis is a large topic with its own dedicated guide. See [Migrations](migrations) for the full walkthrough covering the CLI, operations, workflows, and CI/CD integration.\n\n## Known Limitations\n\nA few rough edges to be aware of in the current pre-alpha SDK:\n\n- **Aggregates are session rules under the hood**. `kg.query(count(Employee.id))` works by registering a temporary session rule and querying it. The rule is best-effort dropped after the call, so if your process dies between rule registration and cleanup the rule lingers until the WebSocket session ends. This is invisible to application code but worth knowing if you read server logs.\n\n- **`order_by` and `offset` are applied client-side** for plain queries. IQL only supports ordering inside aggregate heads (`top_k`, `within_radius`, etc.), so the SDK sorts and slices the result rows in Python after they come back. This is fine for typical result sizes but is not appropriate for streaming over millions of rows.\n\n- **`kg.relations()` only sees relations after data exists**. The server's `.rel` listing surfaces a relation only after at least one row has been inserted; defining the schema is not enough. If you need a registry of declared schemas that's independent of data, track them in your application code.\n\n## Next Steps\n\n- [LangChain Integration](langchain) - Vector store, retriever, structured agent tools\n- [Migrations](migrations) - Schema versioning for production deployments\n- [Vector Search](vectors) - Deep dive into vector indexing and search\n- [Authentication](authentication) - Server-level auth setup\n- [REST API](rest-api) - HTTP interface alongside WebSocket5:[\"$\",\"$Lf\",null,{\"page\":{\"title\":\"Python SDK\",\"content\":\"$10\",\"toc\":[{\"level\":2,\"text\":\"Installation\",\"id\":\"installation\"},{\"level\":2,\"text\":\"Connecting\",\"id\":\"connecting\"},{\"level\":3,\"text\":\"Sync Client\",\"id\":\"sync-client\"},{\"level\":2,\"text\":\"Knowledge Graphs\",\"id\":\"knowledge-graphs\"},{\"level\":2,\"text\":\"Defining Schemas\",\"id\":\"defining-schemas\"},{\"level\":3,\"text\":\"Supported Types\",\"id\":\"supported-types\"},{\"level\":3,\"text\":\"Custom Relation Names\",\"id\":\"custom-relation-names\"},{\"level\":2,\"text\":\"Inserting Data\",\"id\":\"inserting-data\"},{\"level\":3,\"text\":\"Inserting from pandas DataFrames\",\"id\":\"inserting-from-pandas-dataframes\"},{\"level\":2,\"text\":\"Deleting Data\",\"id\":\"deleting-data\"},{\"level\":2,\"text\":\"Querying\",\"id\":\"querying\"},{\"level\":3,\"text\":\"Basic Queries\",\"id\":\"basic-queries\"},{\"level\":3,\"text\":\"Selecting Specific Columns\",\"id\":\"selecting-specific-columns\"},{\"level\":3,\"text\":\"Joins\",\"id\":\"joins\"},{\"level\":3,\"text\":\"Self-Joins\",\"id\":\"self-joins\"},{\"level\":3,\"text\":\"Computed Columns\",\"id\":\"computed-columns\"},{\"level\":3,\"text\":\"Ordering and Pagination\",\"id\":\"ordering-and-pagination\"},{\"level\":3,\"text\":\"Aggregations\",\"id\":\"aggregations\"},{\"level\":3,\"text\":\"Working with Results\",\"id\":\"working-with-results\"},{\"level\":3,\"text\":\"Query Plans\",\"id\":\"query-plans\"},{\"level\":3,\"text\":\"Raw IQL\",\"id\":\"raw-iql\"},{\"level\":2,\"text\":\"Derived Relations (Rules)\",\"id\":\"derived-relations-rules\"},{\"level\":3,\"text\":\"Recursive Rules\",\"id\":\"recursive-rules\"},{\"level\":3,\"text\":\"Managing Rules\",\"id\":\"managing-rules\"},{\"level\":2,\"text\":\"Vector Search\",\"id\":\"vector-search\"},{\"level\":3,\"text\":\"Creating an Index\",\"id\":\"creating-an-index\"},{\"level\":3,\"text\":\"Searching\",\"id\":\"searching\"},{\"level\":3,\"text\":\"Managing Indexes\",\"id\":\"managing-indexes\"},{\"level\":2,\"text\":\"Sessions\",\"id\":\"sessions\"},{\"level\":2,\"text\":\"Notifications\",\"id\":\"notifications\"},{\"level\":2,\"text\":\"Built-in Functions\",\"id\":\"built-in-functions\"},{\"level\":3,\"text\":\"Distance Functions\",\"id\":\"distance-functions\"},{\"level\":3,\"text\":\"Vector Operations\",\"id\":\"vector-operations\"},{\"level\":3,\"text\":\"Temporal Functions\",\"id\":\"temporal-functions\"},{\"level\":3,\"text\":\"Math Functions\",\"id\":\"math-functions\"},{\"level\":3,\"text\":\"String Functions\",\"id\":\"string-functions\"},{\"level\":3,\"text\":\"Type Conversion\",\"id\":\"type-conversion\"},{\"level\":2,\"text\":\"User and Access Management\",\"id\":\"user-and-access-management\"},{\"level\":3,\"text\":\"User Management\",\"id\":\"user-management\"},{\"level\":3,\"text\":\"API Keys\",\"id\":\"api-keys\"},{\"level\":3,\"text\":\"Per-Knowledge-Graph Access Control\",\"id\":\"per-knowledge-graph-access-control\"},{\"level\":2,\"text\":\"Error Handling\",\"id\":\"error-handling\"},{\"level\":2,\"text\":\"Migrations\",\"id\":\"migrations\"},{\"level\":2,\"text\":\"Known Limitations\",\"id\":\"known-limitations\"},{\"level\":2,\"text\":\"Next Steps\",\"id\":\"next-steps\"}]},\"slugKey\":\"guides/python-sdk\"}]\nb:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n11:I[6869,[],\"IconMark\"]\n8:null\nd:[[\"$\",\"title\",\"0\",{\"children\":\"Python SDK - InputLayer Docs\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"InputLayer documentation - Python SDK.\"}],[\"$\",\"meta\",\"2\",{\"property\":\"og:title\",\"content\":\"Python SDK - InputLayer Docs\"}],[\"$\",\"meta\",\"3\",{\"property\":\"og:description\",\"content\":\"InputLayer documentation - Python SDK.\"}],[\"$\",\"meta\",\"4\",{\"property\":\"og:type\",\"content\":\"article\"}],[\"$\",\"meta\",\"5\",{\"name\":\"twitter:card\",\"content\":\"summary_large_image\"}],[\"$\",\"meta\",\"6\",{\"name\":\"twitter:title\",\"content\":\"Python SDK - InputLayer Docs\"}],[\"$\",\"meta\",\"7\",{\"name\":\"twitter:description\",\"content\":\"InputLayer documentation - Python SDK.\"}],[\"$\",\"link\",\"8\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"sizes\":\"any\"}],[\"$\",\"link\",\"9\",{\"rel\":\"icon\",\"href\":\"/favicon_32.png\",\"sizes\":\"32x32\",\"type\":\"image/png\"}],[\"$\",\"link\",\"10\",{\"rel\":\"apple-touch-icon\",\"href\":\"/favicon_180.png\",\"sizes\":\"180x180\",\"type\":\"image/png\"}],[\"$\",\"$L11\",\"11\",{}]]\n"])</script></body></html>