dirsql 0.1.2__tar.gz → 0.1.4__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.
Files changed (39) hide show
  1. {dirsql-0.1.2 → dirsql-0.1.4}/PKG-INFO +3 -1
  2. {dirsql-0.1.2/packages/python → dirsql-0.1.4}/README.md +2 -0
  3. {dirsql-0.1.2 → dirsql-0.1.4/packages/python}/README.md +2 -0
  4. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/src/lib.rs +14 -6
  5. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/test_async_dirsql.py +4 -0
  6. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/README.md +2 -0
  7. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/differ.rs +4 -0
  8. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/lib.rs +13 -11
  9. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/tests/sdk.rs +11 -1
  10. {dirsql-0.1.2 → dirsql-0.1.4}/pyproject.toml +1 -1
  11. {dirsql-0.1.2 → dirsql-0.1.4}/Cargo.lock +0 -0
  12. {dirsql-0.1.2 → dirsql-0.1.4}/Cargo.toml +0 -0
  13. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/Cargo.toml +0 -0
  14. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/__init__.py +0 -0
  15. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/conftest.py +0 -0
  16. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/__init__.py +0 -0
  17. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/test_binding.py +0 -0
  18. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/test_dirsql.py +0 -0
  19. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/test_docs_examples.py +0 -0
  20. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/test_docs_gaps.py +0 -0
  21. {dirsql-0.1.2 → dirsql-0.1.4}/packages/python/tests/integration/test_from_config.py +0 -0
  22. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/Cargo.toml +0 -0
  23. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/benches/db_bench.rs +0 -0
  24. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/benches/differ_bench.rs +0 -0
  25. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/benches/matcher_bench.rs +0 -0
  26. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/benches/scanner_bench.rs +0 -0
  27. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/config.rs +0 -0
  28. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/db.rs +0 -0
  29. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/matcher.rs +0 -0
  30. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/parser.rs +0 -0
  31. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/scanner.rs +0 -0
  32. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/src/watcher.rs +0 -0
  33. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/tests/async_sdk.rs +0 -0
  34. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/tests/docs_examples.rs +0 -0
  35. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/tests/docs_gaps.rs +0 -0
  36. {dirsql-0.1.2 → dirsql-0.1.4}/packages/rust/tests/from_config.rs +0 -0
  37. {dirsql-0.1.2 → dirsql-0.1.4}/python/dirsql/__init__.py +0 -0
  38. {dirsql-0.1.2 → dirsql-0.1.4}/python/dirsql/_async.py +0 -0
  39. {dirsql-0.1.2 → dirsql-0.1.4}/python/dirsql/test_async.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -19,6 +19,8 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
19
19
 
20
20
  Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an in-memory SQLite database, and exposes a SQL query interface. The database is purely in-memory -- the filesystem is always the source of truth.
21
21
 
22
+ [Documentation](https://thekevinscott.github.io/dirsql/?lang=python)
23
+
22
24
  ## Installation
23
25
 
24
26
  ```bash
@@ -2,6 +2,8 @@
2
2
 
3
3
  Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an in-memory SQLite database, and exposes a SQL query interface. The database is purely in-memory -- the filesystem is always the source of truth.
4
4
 
5
+ [Documentation](https://thekevinscott.github.io/dirsql/?lang=python)
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -2,6 +2,8 @@
2
2
 
3
3
  Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an in-memory SQLite database, and exposes a SQL query interface. The database is purely in-memory -- the filesystem is always the source of truth.
4
4
 
5
+ [Documentation](https://thekevinscott.github.io/dirsql/?lang=python)
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -49,10 +49,14 @@ mod python {
49
49
  }
50
50
 
51
51
  /// A row event produced by the watch loop.
52
+ ///
53
+ /// `table` is `Optional[str]` because error events may occur before a
54
+ /// file has been attributed to any table (e.g. a watch-channel failure).
55
+ /// For insert / update / delete events it is always set.
52
56
  #[pyclass(name = "RowEvent", frozen)]
53
57
  struct PyRowEvent {
54
58
  #[pyo3(get)]
55
- table: String,
59
+ table: Option<String>,
56
60
  #[pyo3(get)]
57
61
  action: String,
58
62
  #[pyo3(get)]
@@ -199,7 +203,7 @@ mod python {
199
203
  row,
200
204
  file_path,
201
205
  } => PyRowEvent {
202
- table: table.clone(),
206
+ table: Some(table.clone()),
203
207
  action: "insert".to_string(),
204
208
  row: Some(value_row_to_py_dict(py, row)?),
205
209
  old_row: None,
@@ -212,7 +216,7 @@ mod python {
212
216
  new_row,
213
217
  file_path,
214
218
  } => PyRowEvent {
215
- table: table.clone(),
219
+ table: Some(table.clone()),
216
220
  action: "update".to_string(),
217
221
  row: Some(value_row_to_py_dict(py, new_row)?),
218
222
  old_row: Some(value_row_to_py_dict(py, old_row)?),
@@ -224,15 +228,19 @@ mod python {
224
228
  row,
225
229
  file_path,
226
230
  } => PyRowEvent {
227
- table: table.clone(),
231
+ table: Some(table.clone()),
228
232
  action: "delete".to_string(),
229
233
  row: Some(value_row_to_py_dict(py, row)?),
230
234
  old_row: None,
231
235
  error: None,
232
236
  file_path: Some(file_path.clone()),
233
237
  },
234
- RowEvent::Error { file_path, error } => PyRowEvent {
235
- table: String::new(),
238
+ RowEvent::Error {
239
+ table,
240
+ file_path,
241
+ error,
242
+ } => PyRowEvent {
243
+ table: table.clone(),
236
244
  action: "error".to_string(),
237
245
  row: None,
238
246
  old_row: None,
@@ -341,6 +341,10 @@ def describe_DirSQL_async():
341
341
  assert len(events) >= 1
342
342
  assert events[0].action == "error"
343
343
  assert events[0].error is not None
344
+ # The failing file matched the `items` table's glob; the error
345
+ # event must carry that attribution so multi-table consumers can
346
+ # route the error to the right handler.
347
+ assert events[0].table == "items"
344
348
 
345
349
  @pytest.mark.asyncio
346
350
  async def it_updates_db_on_file_changes(tmp_dir):
@@ -2,6 +2,8 @@
2
2
 
3
3
  Rust SDK for `dirsql`. Ephemeral SQL index over a local directory.
4
4
 
5
+ [Documentation](https://thekevinscott.github.io/dirsql/?lang=rust)
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -26,6 +26,10 @@ pub enum RowEvent {
26
26
  file_path: String,
27
27
  },
28
28
  Error {
29
+ /// The table whose glob matched the failing file, when known.
30
+ /// `None` for errors that aren't tied to a specific table (e.g.
31
+ /// a watch-channel failure before file attribution).
32
+ table: Option<String>,
29
33
  file_path: PathBuf,
30
34
  error: String,
31
35
  },
@@ -341,18 +341,18 @@ impl DirSQL {
341
341
  fn handle_delete(&self, table: &str, rel_path: &str) -> Vec<RowEvent> {
342
342
  let old_rows = match self.inner.file_rows.lock() {
343
343
  Ok(mut file_rows) => file_rows.remove(rel_path).map(|(_, r)| r),
344
- Err(e) => return vec![error_event(rel_path, e.to_string())],
344
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
345
345
  };
346
346
 
347
347
  let row_events = differ::diff(table, old_rows.as_deref(), None, rel_path);
348
348
 
349
349
  let delete_result = match self.inner.db.lock() {
350
350
  Ok(db) => db.delete_rows_by_file(table, rel_path),
351
- Err(e) => return vec![error_event(rel_path, e.to_string())],
351
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
352
352
  };
353
353
 
354
354
  if let Err(e) = delete_result {
355
- return vec![error_event(rel_path, e.to_string())];
355
+ return vec![error_event(Some(table), rel_path, e.to_string())];
356
356
  }
357
357
 
358
358
  row_events
@@ -362,7 +362,7 @@ impl DirSQL {
362
362
  let content = match std::fs::read_to_string(abs_path) {
363
363
  Ok(c) => c,
364
364
  Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Vec::new(),
365
- Err(e) => return vec![error_event(rel_path, e.to_string())],
365
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
366
366
  };
367
367
 
368
368
  let extract = match self.inner.extract_map.get(table) {
@@ -372,7 +372,7 @@ impl DirSQL {
372
372
 
373
373
  let raw_rows = match extract(rel_path, &content) {
374
374
  Ok(r) => r,
375
- Err(e) => return vec![error_event(rel_path, e.to_string())],
375
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
376
376
  };
377
377
 
378
378
  let strict = *self.inner.strict_map.get(table).unwrap_or(&false);
@@ -380,13 +380,13 @@ impl DirSQL {
380
380
  let new_rows = {
381
381
  let db = match self.inner.db.lock() {
382
382
  Ok(g) => g,
383
- Err(e) => return vec![error_event(rel_path, e.to_string())],
383
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
384
384
  };
385
385
  let mut normalized = Vec::with_capacity(raw_rows.len());
386
386
  for raw in &raw_rows {
387
387
  match db.normalize_row(table, raw, strict) {
388
388
  Ok(row) => normalized.push(row),
389
- Err(e) => return vec![error_event(rel_path, e.to_string())],
389
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
390
390
  }
391
391
  }
392
392
  normalized
@@ -394,7 +394,7 @@ impl DirSQL {
394
394
 
395
395
  let old_rows = match self.inner.file_rows.lock() {
396
396
  Ok(guard) => guard.get(rel_path).map(|(_, r)| r.clone()),
397
- Err(e) => return vec![error_event(rel_path, e.to_string())],
397
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
398
398
  };
399
399
 
400
400
  let row_events = differ::diff(table, old_rows.as_deref(), Some(&new_rows), rel_path);
@@ -406,11 +406,11 @@ impl DirSQL {
406
406
  }
407
407
  Ok(())
408
408
  }),
409
- Err(e) => return vec![error_event(rel_path, e.to_string())],
409
+ Err(e) => return vec![error_event(Some(table), rel_path, e.to_string())],
410
410
  };
411
411
 
412
412
  if let Err(e) = db_result {
413
- return vec![error_event(rel_path, e.to_string())];
413
+ return vec![error_event(Some(table), rel_path, e.to_string())];
414
414
  }
415
415
 
416
416
  if let Ok(mut guard) = self.inner.file_rows.lock() {
@@ -491,8 +491,9 @@ impl DirSQL {
491
491
  }
492
492
  }
493
493
 
494
- fn error_event(rel_path: &str, error: String) -> RowEvent {
494
+ fn error_event(table: Option<&str>, rel_path: &str, error: String) -> RowEvent {
495
495
  RowEvent::Error {
496
+ table: table.map(str::to_string),
496
497
  file_path: PathBuf::from(rel_path),
497
498
  error,
498
499
  }
@@ -510,6 +511,7 @@ fn run_channel_loop(db: DirSQL, tx: UnboundedSender<RowEvent>) {
510
511
  }
511
512
  Err(e) => {
512
513
  let _ = tx.unbounded_send(RowEvent::Error {
514
+ table: None,
513
515
  file_path: db.inner.root.clone(),
514
516
  error: e.to_string(),
515
517
  });
@@ -382,8 +382,18 @@ fn it_streams_watch_error_events() {
382
382
 
383
383
  let event = block_on(stream.next()).expect("watch event");
384
384
  match event {
385
- dirsql::RowEvent::Error { error, .. } => {
385
+ dirsql::RowEvent::Error {
386
+ table,
387
+ error,
388
+ file_path,
389
+ } => {
386
390
  assert!(error.contains("intentional parse failure"));
391
+ assert_eq!(
392
+ table.as_deref(),
393
+ Some("items"),
394
+ "error event should attribute the failure to the matching table"
395
+ );
396
+ assert!(file_path.to_string_lossy().contains("bad.txt"));
387
397
  }
388
398
  other => panic!("expected error event, got: {other:?}"),
389
399
  }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "dirsql"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "Ephemeral SQL index over a local directory"
9
9
  license = "MIT"
10
10
  requires-python = ">=3.12"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes