fuse4dbricks 0.7.1__tar.gz → 0.7.2__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 (42) hide show
  1. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/.github/workflows/test.yml +1 -1
  2. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/CHANGELOG.md +11 -0
  3. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/PKG-INFO +1 -1
  4. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/metadata_manager.py +12 -0
  5. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/pyproject.toml +1 -1
  6. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_metadata_manager.py +20 -0
  7. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/uv.lock +1 -1
  8. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/.env.example +0 -0
  9. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/.gitignore +0 -0
  10. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/README.md +0 -0
  11. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/__init__.py +0 -0
  12. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/api/__init__.py +0 -0
  13. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/api/errors.py +0 -0
  14. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/api/uc_client.py +0 -0
  15. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/auth/__init__.py +0 -0
  16. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/auth/provider.py +0 -0
  17. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/__init__.py +0 -0
  18. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/auth_manager.py +0 -0
  19. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/data_manager.py +0 -0
  20. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/inode_manager.py +0 -0
  21. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/operations.py +0 -0
  22. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/ram_cache.py +0 -0
  23. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/securable_filter.py +0 -0
  24. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/utils.py +0 -0
  25. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/fs/write_buffer.py +0 -0
  26. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/main.py +0 -0
  27. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/storage/__init__.py +0 -0
  28. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/fuse4dbricks/storage/persistence.py +0 -0
  29. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_auth_manager.py +0 -0
  30. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_auth_provider.py +0 -0
  31. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_data_manager.py +0 -0
  32. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_e2e_mount.py +0 -0
  33. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_inode_manager.py +0 -0
  34. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_main.py +0 -0
  35. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_operations.py +0 -0
  36. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_persistence.py +0 -0
  37. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_securable_filter.py +0 -0
  38. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_uc_client.py +0 -0
  39. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_uc_client_consistency.py +0 -0
  40. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_uc_client_live.py +0 -0
  41. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_utils.py +0 -0
  42. {fuse4dbricks-0.7.1 → fuse4dbricks-0.7.2}/tests/test_write_buffer.py +0 -0
@@ -54,7 +54,7 @@ jobs:
54
54
  - name: Build a binary wheel and a source tarball
55
55
  run: uv build
56
56
  - name: Store the distribution packages
57
- uses: actions/upload-artifact@v5
57
+ uses: actions/upload-artifact@v7
58
58
  with:
59
59
  name: python-package-distributions
60
60
  path: dist/
@@ -1,3 +1,14 @@
1
+ # 0.7.2 (2026-06-19)
2
+
3
+ - Fix a just-created file or directory being reported as nonexistent for up to
4
+ the negative-cache TTL (~5s) when its path had been stat'd before it existed.
5
+ Probing a not-yet-existing path records a negative (not-found) cache entry,
6
+ and `invalidate()` — called on every mutating op (create/write, `mkdir`,
7
+ `unlink`/`rmdir`, `rename`, truncate) — cleared the attribute and directory
8
+ caches but left that negative entry in place, so a subsequent `stat` kept
9
+ returning `ENOENT` until the entry expired. `invalidate()` now also evicts the
10
+ path's negative-cache entries (for every principal).
11
+
1
12
  # 0.7.1 (2026-06-17)
2
13
 
3
14
  - Unmount cleanly on `SIGTERM`/`SIGHUP` (e.g. `systemctl stop`). trio only turns
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuse4dbricks
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: FUSE driver for Databricks Unity Catalog Volumes.
5
5
  Project-URL: Homepage, https://github.com/zeehio/fuse4dbricks
6
6
  Author-email: Sergio Oller <sergioller@gmail.com>
@@ -480,6 +480,18 @@ class MetadataManager:
480
480
  if attr_cache_key in self._attr_cache:
481
481
  del self._attr_cache[attr_cache_key]
482
482
 
483
+ # A mutating op (create/write/mkdir/rename/truncate/delete) just
484
+ # changed this path's presence, so any cached negative is now stale.
485
+ # Drop it for every principal: invalidate() is synchronous and has
486
+ # no principal in hand, and a just-created path must be immediately
487
+ # visible on this mount rather than hidden until the negative TTL
488
+ # expires. Build the key list first, then delete -- there is no
489
+ # await between, so this stays atomic under trio (mirrors the
490
+ # lock-free contract of the rest of this method).
491
+ stale_negatives = [k for k in self._negative_cache if k[1] == fs_path]
492
+ for key in stale_negatives:
493
+ del self._negative_cache[key]
494
+
483
495
  parent_path = "/".join(fs_path.rstrip("/").split("/")[:-1]) or "/"
484
496
  if parent_path in self._dir_cache:
485
497
  del self._dir_cache[parent_path]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fuse4dbricks"
7
- version = "0.7.1"
7
+ version = "0.7.2"
8
8
  description = "FUSE driver for Databricks Unity Catalog Volumes."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -453,6 +453,26 @@ def test_invalidate_nonexistent_path_does_not_raise(manager):
453
453
  manager.invalidate("/nonexistent/path/file.txt", is_dir=False) # Must not raise
454
454
 
455
455
 
456
+ def test_invalidate_clears_negative_cache_for_all_principals(manager, ctx):
457
+ """A mutating op (create/write/mkdir/...) routes through invalidate(); it
458
+ must drop the path's negative entry for EVERY principal so a just-created
459
+ file is immediately visible instead of hidden until the negative TTL
460
+ expires. This is the negative-cache poisoning bug: stat a path before it
461
+ exists, create it, then stat again -> stale ENOENT for ~ttl_negative."""
462
+ future = time.time() + 100
463
+ fs_path = "/cat/sch/vol/file.txt"
464
+ manager._negative_cache[("alice@example.com", fs_path)] = future
465
+ manager._negative_cache[("bob@example.com", fs_path)] = future
466
+ # A negative for a *different* path must survive.
467
+ manager._negative_cache[("alice@example.com", "/cat/sch/vol/other.txt")] = future
468
+
469
+ manager.invalidate(fs_path, is_dir=False)
470
+
471
+ assert ("alice@example.com", fs_path) not in manager._negative_cache
472
+ assert ("bob@example.com", fs_path) not in manager._negative_cache
473
+ assert ("alice@example.com", "/cat/sch/vol/other.txt") in manager._negative_cache
474
+
475
+
456
476
  # ---------------------------------------------------------------------------
457
477
  # reauthorize (token change -> refresh the uid -> principal mapping)
458
478
  # ---------------------------------------------------------------------------
@@ -353,7 +353,7 @@ wheels = [
353
353
 
354
354
  [[package]]
355
355
  name = "fuse4dbricks"
356
- version = "0.7.1"
356
+ version = "0.7.2"
357
357
  source = { editable = "." }
358
358
  dependencies = [
359
359
  { name = "databricks-sdk" },
File without changes
File without changes
File without changes