latticedb 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include src/latticedb/lib *.so *.dylib *.dll
4
+ include src/latticedb/py.typed
@@ -0,0 +1,353 @@
1
+ Metadata-Version: 2.4
2
+ Name: latticedb
3
+ Version: 0.2.0
4
+ Summary: Embedded knowledge graph database for AI and RAG applications
5
+ Author-email: Jeff Hajewski <jeff@latticedb.dev>
6
+ Maintainer-email: Jeff Hajewski <jeff@latticedb.dev>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/jeffhajewski/latticedb
9
+ Project-URL: Documentation, https://latticedb.dev
10
+ Project-URL: Repository, https://github.com/jeffhajewski/latticedb
11
+ Project-URL: Issues, https://github.com/jeffhajewski/latticedb/issues
12
+ Keywords: database,graph,vector,embeddings,rag,ai,knowledge-graph
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Database
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: numpy>=1.20.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
30
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
32
+
33
+ # Lattice Python Bindings
34
+
35
+ Python bindings for [LatticeDB](https://github.com/latticedb/latticedb), an embedded knowledge graph database for AI/RAG applications.
36
+
37
+ ## Installation
38
+
39
+ ### From Source
40
+
41
+ ```bash
42
+ # Build the native library first
43
+ cd /path/to/latticedb
44
+ zig build shared
45
+
46
+ # Install the Python package
47
+ cd bindings/python
48
+ pip install -e .
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ from lattice import Database
55
+
56
+ # Create a new database
57
+ with Database("mydb.ltdb", create=True) as db:
58
+ # Write transaction
59
+ with db.write() as txn:
60
+ # Create nodes with properties
61
+ alice = txn.create_node(
62
+ labels=["Person"],
63
+ properties={"name": "Alice", "age": 30}
64
+ )
65
+ bob = txn.create_node(
66
+ labels=["Person"],
67
+ properties={"name": "Bob", "age": 25}
68
+ )
69
+
70
+ # Create relationships
71
+ txn.create_edge(alice.id, bob.id, "KNOWS")
72
+
73
+ txn.commit()
74
+
75
+ # Query with Cypher
76
+ result = db.query("MATCH (n:Person) RETURN n.name")
77
+ for row in result:
78
+ print(row) # {'n': 'Alice'}, {'n': 'Bob'}
79
+
80
+ # Query with parameters (safe from injection)
81
+ result = db.query(
82
+ "MATCH (n:Person) WHERE n.name = $name RETURN n",
83
+ parameters={"name": "Alice"}
84
+ )
85
+ ```
86
+
87
+ ## API Reference
88
+
89
+ ### Database
90
+
91
+ ```python
92
+ Database(
93
+ path: str | Path,
94
+ *,
95
+ create: bool = False, # Create if doesn't exist
96
+ read_only: bool = False, # Open in read-only mode
97
+ cache_size_mb: int = 100, # Page cache size
98
+ enable_vector: bool = False, # Enable vector storage
99
+ vector_dimensions: int = 128 # Vector dimensions
100
+ )
101
+ ```
102
+
103
+ #### Methods
104
+
105
+ - `open()` - Open the database connection
106
+ - `close()` - Close the database connection
107
+ - `read()` - Start a read-only transaction (context manager)
108
+ - `write()` - Start a read-write transaction (context manager)
109
+ - `query(cypher: str, parameters: dict = None)` - Execute a Cypher query with optional parameters
110
+
111
+ ### Transaction
112
+
113
+ #### Read Operations
114
+
115
+ - `is_read_only` - True if read-only transaction
116
+ - `is_active` - True if transaction is still active
117
+ - `get_node(node_id: int)` - Get a node by ID, returns `Node` or `None`
118
+ - `get_property(node_id: int, key: str)` - Get a property value, returns value or `None`
119
+ - `node_exists(node_id: int)` - Check if a node exists, returns `True` or `False`
120
+
121
+ #### Write Operations
122
+
123
+ - `create_node(labels: list[str], properties: dict = None)` - Create a node with labels and optional properties
124
+ - `delete_node(node_id: int)` - Delete a node
125
+ - `set_property(node_id: int, key: str, value)` - Set a property on a node
126
+ - `set_vector(node_id: int, key: str, vector: np.ndarray)` - Set a vector embedding
127
+ - `create_edge(source_id: int, target_id: int, edge_type: str)` - Create an edge
128
+ - `delete_edge(source_id: int, target_id: int, edge_type: str)` - Delete an edge
129
+ - `commit()` - Commit the transaction
130
+ - `rollback()` - Rollback the transaction
131
+
132
+ ### Querying
133
+
134
+ #### Basic Queries
135
+
136
+ ```python
137
+ # Simple match
138
+ result = db.query("MATCH (n:Person) RETURN n")
139
+
140
+ # Return properties
141
+ result = db.query("MATCH (n:Person) RETURN n.name")
142
+
143
+ # With WHERE clause
144
+ result = db.query("MATCH (n:Person) WHERE n.age > 25 RETURN n.name")
145
+ ```
146
+
147
+ #### Data Mutation
148
+
149
+ ```python
150
+ # Create nodes and relationships
151
+ db.query("CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})")
152
+
153
+ # Update properties
154
+ db.query("MATCH (n:Person {name: 'Alice'}) SET n.age = 31, n.city = 'NYC'")
155
+
156
+ # Add labels
157
+ db.query("MATCH (n:Person {name: 'Alice'}) SET n:Admin:Verified")
158
+
159
+ # Remove properties and labels
160
+ db.query("MATCH (n:Person {name: 'Alice'}) REMOVE n.city, n:Verified")
161
+
162
+ # Delete nodes (DETACH removes connected edges)
163
+ db.query("MATCH (n:Person {name: 'Bob'}) DETACH DELETE n")
164
+ ```
165
+
166
+ #### Parameterized Queries
167
+
168
+ Use parameters to safely pass values into queries:
169
+
170
+ ```python
171
+ # String parameter
172
+ result = db.query(
173
+ "MATCH (n:Person) WHERE n.name = $name RETURN n",
174
+ parameters={"name": "Alice"}
175
+ )
176
+
177
+ # Integer parameter
178
+ result = db.query(
179
+ "MATCH (n:Person) WHERE n.age = $age RETURN n.name",
180
+ parameters={"age": 30}
181
+ )
182
+
183
+ # Multiple parameters
184
+ result = db.query(
185
+ "MATCH (n:Person) WHERE n.name = $name AND n.age > $min_age RETURN n",
186
+ parameters={"name": "Alice", "min_age": 20}
187
+ )
188
+
189
+ # Vector parameter (requires numpy)
190
+ import numpy as np
191
+ query_vec = np.random.rand(384).astype(np.float32)
192
+ result = db.query(
193
+ "MATCH (n:Document) WHERE n.embedding <=> $vec < 0.5 RETURN n",
194
+ parameters={"vec": query_vec}
195
+ )
196
+ ```
197
+
198
+ #### Working with Results
199
+
200
+ ```python
201
+ result = db.query("MATCH (n:Person) RETURN n.name")
202
+
203
+ # Get column names
204
+ print(result.columns) # ['n']
205
+
206
+ # Iterate rows
207
+ for row in result:
208
+ print(row) # {'n': 'Alice'}
209
+
210
+ # Get all rows as list
211
+ rows = list(result)
212
+
213
+ # Get row count
214
+ print(len(result))
215
+ ```
216
+
217
+ ### Reading Node Data
218
+
219
+ ```python
220
+ with db.read() as txn:
221
+ # Get a node by ID
222
+ node = txn.get_node(node_id)
223
+ if node:
224
+ print(f"ID: {node.id}")
225
+ print(f"Labels: {node.labels}")
226
+
227
+ # Get individual properties
228
+ name = txn.get_property(node_id, "name")
229
+ age = txn.get_property(node_id, "age")
230
+
231
+ # Returns None if property doesn't exist
232
+ unknown = txn.get_property(node_id, "nonexistent") # None
233
+ ```
234
+
235
+ ### Vector Operations
236
+
237
+ To use vector embeddings, enable vector storage when opening the database:
238
+
239
+ ```python
240
+ import numpy as np
241
+
242
+ with Database("mydb.ltdb", create=True, enable_vector=True, vector_dimensions=384) as db:
243
+ # Store vectors
244
+ with db.write() as txn:
245
+ node1 = txn.create_node(labels=["Document"])
246
+ txn.set_property(node1.id, "title", "Introduction to ML")
247
+ embedding1 = np.random.rand(384).astype(np.float32)
248
+ txn.set_vector(node1.id, "embedding", embedding1)
249
+
250
+ node2 = txn.create_node(labels=["Document"])
251
+ txn.set_property(node2.id, "title", "Deep Learning Guide")
252
+ embedding2 = np.random.rand(384).astype(np.float32)
253
+ txn.set_vector(node2.id, "embedding", embedding2)
254
+
255
+ txn.commit()
256
+
257
+ # Search for similar vectors (HNSW approximate nearest neighbor)
258
+ query_vector = np.random.rand(384).astype(np.float32)
259
+ results = db.vector_search(query_vector, k=10, ef_search=64)
260
+
261
+ for result in results:
262
+ print(f"Node {result.node_id}: distance={result.distance:.4f}")
263
+ ```
264
+
265
+ #### Vector Search Parameters
266
+
267
+ - `vector`: Query vector (numpy array of float32)
268
+ - `k`: Number of nearest neighbors to return (default: 10)
269
+ - `ef_search`: HNSW exploration factor - higher values are slower but more accurate (default: 64)
270
+
271
+ ### Full-Text Search
272
+
273
+ Index text content and search with BM25 scoring:
274
+
275
+ ```python
276
+ with Database("mydb.ltdb", create=True) as db:
277
+ # Index documents
278
+ with db.write() as txn:
279
+ doc1 = txn.create_node(labels=["Document"])
280
+ txn.set_property(doc1.id, "title", "Introduction to ML")
281
+ txn.fts_index(doc1.id, "Machine learning is a subset of artificial intelligence")
282
+
283
+ doc2 = txn.create_node(labels=["Document"])
284
+ txn.set_property(doc2.id, "title", "Deep Learning Guide")
285
+ txn.fts_index(doc2.id, "Deep learning uses neural networks")
286
+
287
+ txn.commit()
288
+
289
+ # Search for documents
290
+ results = db.fts_search("machine learning", limit=10)
291
+
292
+ for result in results:
293
+ print(f"Node {result.node_id}: score={result.score:.4f}")
294
+ ```
295
+
296
+ #### FTS Search Parameters
297
+
298
+ - `query`: Search query text
299
+ - `limit`: Maximum number of results to return (default: 10)
300
+
301
+ ## Supported Property Types
302
+
303
+ - `None` - Null value
304
+ - `bool` - Boolean
305
+ - `int` - 64-bit integer
306
+ - `float` - 64-bit float
307
+ - `str` - UTF-8 string
308
+ - `bytes` - Binary data
309
+
310
+ ## Error Handling
311
+
312
+ The library raises typed exceptions:
313
+
314
+ ```python
315
+ from lattice import LatticeError, LatticeNotFoundError, LatticeIOError
316
+
317
+ try:
318
+ with Database("nonexistent.ltdb") as db:
319
+ pass
320
+ except LatticeNotFoundError:
321
+ print("Database not found")
322
+ except LatticeIOError:
323
+ print("I/O error")
324
+ except LatticeError as e:
325
+ print(f"Error: {e}")
326
+ ```
327
+
328
+ ## Utilities
329
+
330
+ ```python
331
+ from lattice import version, library_available
332
+
333
+ # Check if the native library is available
334
+ if library_available():
335
+ print("Library found")
336
+
337
+ # Get the native library version
338
+ print(f"Lattice version: {version()}")
339
+ ```
340
+
341
+ ## Requirements
342
+
343
+ - Python 3.9+
344
+ - NumPy (optional, for vector operations)
345
+ - The native LatticeDB library (`liblattice.dylib` / `liblattice.so`)
346
+
347
+ ## Building from Source
348
+
349
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for build instructions.
350
+
351
+ ## License
352
+
353
+ Same license as LatticeDB.
@@ -0,0 +1,321 @@
1
+ # Lattice Python Bindings
2
+
3
+ Python bindings for [LatticeDB](https://github.com/latticedb/latticedb), an embedded knowledge graph database for AI/RAG applications.
4
+
5
+ ## Installation
6
+
7
+ ### From Source
8
+
9
+ ```bash
10
+ # Build the native library first
11
+ cd /path/to/latticedb
12
+ zig build shared
13
+
14
+ # Install the Python package
15
+ cd bindings/python
16
+ pip install -e .
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ from lattice import Database
23
+
24
+ # Create a new database
25
+ with Database("mydb.ltdb", create=True) as db:
26
+ # Write transaction
27
+ with db.write() as txn:
28
+ # Create nodes with properties
29
+ alice = txn.create_node(
30
+ labels=["Person"],
31
+ properties={"name": "Alice", "age": 30}
32
+ )
33
+ bob = txn.create_node(
34
+ labels=["Person"],
35
+ properties={"name": "Bob", "age": 25}
36
+ )
37
+
38
+ # Create relationships
39
+ txn.create_edge(alice.id, bob.id, "KNOWS")
40
+
41
+ txn.commit()
42
+
43
+ # Query with Cypher
44
+ result = db.query("MATCH (n:Person) RETURN n.name")
45
+ for row in result:
46
+ print(row) # {'n': 'Alice'}, {'n': 'Bob'}
47
+
48
+ # Query with parameters (safe from injection)
49
+ result = db.query(
50
+ "MATCH (n:Person) WHERE n.name = $name RETURN n",
51
+ parameters={"name": "Alice"}
52
+ )
53
+ ```
54
+
55
+ ## API Reference
56
+
57
+ ### Database
58
+
59
+ ```python
60
+ Database(
61
+ path: str | Path,
62
+ *,
63
+ create: bool = False, # Create if doesn't exist
64
+ read_only: bool = False, # Open in read-only mode
65
+ cache_size_mb: int = 100, # Page cache size
66
+ enable_vector: bool = False, # Enable vector storage
67
+ vector_dimensions: int = 128 # Vector dimensions
68
+ )
69
+ ```
70
+
71
+ #### Methods
72
+
73
+ - `open()` - Open the database connection
74
+ - `close()` - Close the database connection
75
+ - `read()` - Start a read-only transaction (context manager)
76
+ - `write()` - Start a read-write transaction (context manager)
77
+ - `query(cypher: str, parameters: dict = None)` - Execute a Cypher query with optional parameters
78
+
79
+ ### Transaction
80
+
81
+ #### Read Operations
82
+
83
+ - `is_read_only` - True if read-only transaction
84
+ - `is_active` - True if transaction is still active
85
+ - `get_node(node_id: int)` - Get a node by ID, returns `Node` or `None`
86
+ - `get_property(node_id: int, key: str)` - Get a property value, returns value or `None`
87
+ - `node_exists(node_id: int)` - Check if a node exists, returns `True` or `False`
88
+
89
+ #### Write Operations
90
+
91
+ - `create_node(labels: list[str], properties: dict = None)` - Create a node with labels and optional properties
92
+ - `delete_node(node_id: int)` - Delete a node
93
+ - `set_property(node_id: int, key: str, value)` - Set a property on a node
94
+ - `set_vector(node_id: int, key: str, vector: np.ndarray)` - Set a vector embedding
95
+ - `create_edge(source_id: int, target_id: int, edge_type: str)` - Create an edge
96
+ - `delete_edge(source_id: int, target_id: int, edge_type: str)` - Delete an edge
97
+ - `commit()` - Commit the transaction
98
+ - `rollback()` - Rollback the transaction
99
+
100
+ ### Querying
101
+
102
+ #### Basic Queries
103
+
104
+ ```python
105
+ # Simple match
106
+ result = db.query("MATCH (n:Person) RETURN n")
107
+
108
+ # Return properties
109
+ result = db.query("MATCH (n:Person) RETURN n.name")
110
+
111
+ # With WHERE clause
112
+ result = db.query("MATCH (n:Person) WHERE n.age > 25 RETURN n.name")
113
+ ```
114
+
115
+ #### Data Mutation
116
+
117
+ ```python
118
+ # Create nodes and relationships
119
+ db.query("CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})")
120
+
121
+ # Update properties
122
+ db.query("MATCH (n:Person {name: 'Alice'}) SET n.age = 31, n.city = 'NYC'")
123
+
124
+ # Add labels
125
+ db.query("MATCH (n:Person {name: 'Alice'}) SET n:Admin:Verified")
126
+
127
+ # Remove properties and labels
128
+ db.query("MATCH (n:Person {name: 'Alice'}) REMOVE n.city, n:Verified")
129
+
130
+ # Delete nodes (DETACH removes connected edges)
131
+ db.query("MATCH (n:Person {name: 'Bob'}) DETACH DELETE n")
132
+ ```
133
+
134
+ #### Parameterized Queries
135
+
136
+ Use parameters to safely pass values into queries:
137
+
138
+ ```python
139
+ # String parameter
140
+ result = db.query(
141
+ "MATCH (n:Person) WHERE n.name = $name RETURN n",
142
+ parameters={"name": "Alice"}
143
+ )
144
+
145
+ # Integer parameter
146
+ result = db.query(
147
+ "MATCH (n:Person) WHERE n.age = $age RETURN n.name",
148
+ parameters={"age": 30}
149
+ )
150
+
151
+ # Multiple parameters
152
+ result = db.query(
153
+ "MATCH (n:Person) WHERE n.name = $name AND n.age > $min_age RETURN n",
154
+ parameters={"name": "Alice", "min_age": 20}
155
+ )
156
+
157
+ # Vector parameter (requires numpy)
158
+ import numpy as np
159
+ query_vec = np.random.rand(384).astype(np.float32)
160
+ result = db.query(
161
+ "MATCH (n:Document) WHERE n.embedding <=> $vec < 0.5 RETURN n",
162
+ parameters={"vec": query_vec}
163
+ )
164
+ ```
165
+
166
+ #### Working with Results
167
+
168
+ ```python
169
+ result = db.query("MATCH (n:Person) RETURN n.name")
170
+
171
+ # Get column names
172
+ print(result.columns) # ['n']
173
+
174
+ # Iterate rows
175
+ for row in result:
176
+ print(row) # {'n': 'Alice'}
177
+
178
+ # Get all rows as list
179
+ rows = list(result)
180
+
181
+ # Get row count
182
+ print(len(result))
183
+ ```
184
+
185
+ ### Reading Node Data
186
+
187
+ ```python
188
+ with db.read() as txn:
189
+ # Get a node by ID
190
+ node = txn.get_node(node_id)
191
+ if node:
192
+ print(f"ID: {node.id}")
193
+ print(f"Labels: {node.labels}")
194
+
195
+ # Get individual properties
196
+ name = txn.get_property(node_id, "name")
197
+ age = txn.get_property(node_id, "age")
198
+
199
+ # Returns None if property doesn't exist
200
+ unknown = txn.get_property(node_id, "nonexistent") # None
201
+ ```
202
+
203
+ ### Vector Operations
204
+
205
+ To use vector embeddings, enable vector storage when opening the database:
206
+
207
+ ```python
208
+ import numpy as np
209
+
210
+ with Database("mydb.ltdb", create=True, enable_vector=True, vector_dimensions=384) as db:
211
+ # Store vectors
212
+ with db.write() as txn:
213
+ node1 = txn.create_node(labels=["Document"])
214
+ txn.set_property(node1.id, "title", "Introduction to ML")
215
+ embedding1 = np.random.rand(384).astype(np.float32)
216
+ txn.set_vector(node1.id, "embedding", embedding1)
217
+
218
+ node2 = txn.create_node(labels=["Document"])
219
+ txn.set_property(node2.id, "title", "Deep Learning Guide")
220
+ embedding2 = np.random.rand(384).astype(np.float32)
221
+ txn.set_vector(node2.id, "embedding", embedding2)
222
+
223
+ txn.commit()
224
+
225
+ # Search for similar vectors (HNSW approximate nearest neighbor)
226
+ query_vector = np.random.rand(384).astype(np.float32)
227
+ results = db.vector_search(query_vector, k=10, ef_search=64)
228
+
229
+ for result in results:
230
+ print(f"Node {result.node_id}: distance={result.distance:.4f}")
231
+ ```
232
+
233
+ #### Vector Search Parameters
234
+
235
+ - `vector`: Query vector (numpy array of float32)
236
+ - `k`: Number of nearest neighbors to return (default: 10)
237
+ - `ef_search`: HNSW exploration factor - higher values are slower but more accurate (default: 64)
238
+
239
+ ### Full-Text Search
240
+
241
+ Index text content and search with BM25 scoring:
242
+
243
+ ```python
244
+ with Database("mydb.ltdb", create=True) as db:
245
+ # Index documents
246
+ with db.write() as txn:
247
+ doc1 = txn.create_node(labels=["Document"])
248
+ txn.set_property(doc1.id, "title", "Introduction to ML")
249
+ txn.fts_index(doc1.id, "Machine learning is a subset of artificial intelligence")
250
+
251
+ doc2 = txn.create_node(labels=["Document"])
252
+ txn.set_property(doc2.id, "title", "Deep Learning Guide")
253
+ txn.fts_index(doc2.id, "Deep learning uses neural networks")
254
+
255
+ txn.commit()
256
+
257
+ # Search for documents
258
+ results = db.fts_search("machine learning", limit=10)
259
+
260
+ for result in results:
261
+ print(f"Node {result.node_id}: score={result.score:.4f}")
262
+ ```
263
+
264
+ #### FTS Search Parameters
265
+
266
+ - `query`: Search query text
267
+ - `limit`: Maximum number of results to return (default: 10)
268
+
269
+ ## Supported Property Types
270
+
271
+ - `None` - Null value
272
+ - `bool` - Boolean
273
+ - `int` - 64-bit integer
274
+ - `float` - 64-bit float
275
+ - `str` - UTF-8 string
276
+ - `bytes` - Binary data
277
+
278
+ ## Error Handling
279
+
280
+ The library raises typed exceptions:
281
+
282
+ ```python
283
+ from lattice import LatticeError, LatticeNotFoundError, LatticeIOError
284
+
285
+ try:
286
+ with Database("nonexistent.ltdb") as db:
287
+ pass
288
+ except LatticeNotFoundError:
289
+ print("Database not found")
290
+ except LatticeIOError:
291
+ print("I/O error")
292
+ except LatticeError as e:
293
+ print(f"Error: {e}")
294
+ ```
295
+
296
+ ## Utilities
297
+
298
+ ```python
299
+ from lattice import version, library_available
300
+
301
+ # Check if the native library is available
302
+ if library_available():
303
+ print("Library found")
304
+
305
+ # Get the native library version
306
+ print(f"Lattice version: {version()}")
307
+ ```
308
+
309
+ ## Requirements
310
+
311
+ - Python 3.9+
312
+ - NumPy (optional, for vector operations)
313
+ - The native LatticeDB library (`liblattice.dylib` / `liblattice.so`)
314
+
315
+ ## Building from Source
316
+
317
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for build instructions.
318
+
319
+ ## License
320
+
321
+ Same license as LatticeDB.