dirsql 0.3.71__tar.gz → 0.3.72__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 (136) hide show
  1. {dirsql-0.3.71 → dirsql-0.3.72}/Cargo.lock +1 -1
  2. {dirsql-0.3.71 → dirsql-0.3.72}/PKG-INFO +1 -1
  3. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/_async.py +7 -10
  4. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/main.py +1 -1
  5. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/resolve_config_extensions.py +7 -9
  6. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/resolve_config_extensions.py +8 -12
  7. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/resolve_extension.py +9 -18
  8. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/Cargo.toml +1 -1
  9. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/conftest.py +4 -10
  10. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/e2e-attestation.json +2 -2
  11. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/src/lib.rs +11 -26
  12. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/conftest.py +0 -1
  13. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/differ_bench.rs +0 -3
  14. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/scanner_bench.rs +0 -2
  15. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/bin/dirsql.rs +5 -10
  16. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/execute.rs +1 -1
  17. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/init.rs +5 -9
  18. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/mod.rs +8 -27
  19. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/serialize.rs +1 -9
  20. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/server.rs +2 -7
  21. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/command.rs +5 -19
  22. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/config.rs +14 -58
  23. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/db.rs +58 -195
  24. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/differ.rs +3 -40
  25. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/lib.rs +115 -435
  26. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/matcher.rs +1 -15
  27. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/persist.rs +18 -57
  28. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/scanner.rs +3 -13
  29. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/watcher.rs +7 -11
  30. {dirsql-0.3.71 → dirsql-0.3.72}/Cargo.toml +0 -0
  31. {dirsql-0.3.71 → dirsql-0.3.72}/README.md +0 -0
  32. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/__init__.py +0 -0
  33. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/_dirsql.pyi +0 -0
  34. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/__init__.py +0 -0
  35. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/binary_path.py +0 -0
  36. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/is_windows.py +0 -0
  37. {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/py.typed +0 -0
  38. {dirsql-0.3.71 → dirsql-0.3.72}/docs/.claude/CLAUDE.md +0 -0
  39. {dirsql-0.3.71 → dirsql-0.3.72}/docs/.vitepress/config.ts +0 -0
  40. {dirsql-0.3.71 → dirsql-0.3.72}/docs/.vitepress/theme/index.ts +0 -0
  41. {dirsql-0.3.71 → dirsql-0.3.72}/docs/.vitepress/theme/lang.ts +0 -0
  42. {dirsql-0.3.71 → dirsql-0.3.72}/docs/AGENTS.md +0 -0
  43. {dirsql-0.3.71 → dirsql-0.3.72}/docs/explanation.md +0 -0
  44. {dirsql-0.3.71 → dirsql-0.3.72}/docs/getting-started.md +0 -0
  45. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/columns-from-paths.md +0 -0
  46. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/define-tables.md +0 -0
  47. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/embed.md +0 -0
  48. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/extract-from-contents.md +0 -0
  49. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/load-extension.md +0 -0
  50. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/persist.md +0 -0
  51. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/react-to-changes.md +0 -0
  52. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/search-by-meaning.md +0 -0
  53. {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/skip-files.md +0 -0
  54. {dirsql-0.3.71 → dirsql-0.3.72}/docs/index.md +0 -0
  55. {dirsql-0.3.71 → dirsql-0.3.72}/docs/migrations.md +0 -0
  56. {dirsql-0.3.71 → dirsql-0.3.72}/docs/package.json +0 -0
  57. {dirsql-0.3.71 → dirsql-0.3.72}/docs/playwright.config.ts +0 -0
  58. {dirsql-0.3.71 → dirsql-0.3.72}/docs/pnpm-lock.yaml +0 -0
  59. {dirsql-0.3.71 → dirsql-0.3.72}/docs/pnpm-workspace.yaml +0 -0
  60. {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/cli.md +0 -0
  61. {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/columns.md +0 -0
  62. {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/config.md +0 -0
  63. {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/hooks.md +0 -0
  64. {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/http-api.md +0 -0
  65. {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/sdk.md +0 -0
  66. {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/integration/home.spec.ts +0 -0
  67. {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/integration/language-flag.spec.ts +0 -0
  68. {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/integration/sidebar.spec.ts +0 -0
  69. {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/unit/config.test.ts +0 -0
  70. {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/unit/lang.test.ts +0 -0
  71. {dirsql-0.3.71 → dirsql-0.3.72}/docs/vitest.config.ts +0 -0
  72. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/README.md +0 -0
  73. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.claude/CLAUDE.md +0 -0
  74. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.vitepress/config.ts +0 -0
  75. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  76. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  77. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/AGENTS.md +0 -0
  78. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/explanation.md +0 -0
  79. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/getting-started.md +0 -0
  80. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/columns-from-paths.md +0 -0
  81. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/define-tables.md +0 -0
  82. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/embed.md +0 -0
  83. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/extract-from-contents.md +0 -0
  84. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/load-extension.md +0 -0
  85. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/persist.md +0 -0
  86. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/react-to-changes.md +0 -0
  87. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/search-by-meaning.md +0 -0
  88. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/skip-files.md +0 -0
  89. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/index.md +0 -0
  90. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/migrations.md +0 -0
  91. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/package.json +0 -0
  92. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/playwright.config.ts +0 -0
  93. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/pnpm-lock.yaml +0 -0
  94. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/pnpm-workspace.yaml +0 -0
  95. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/cli.md +0 -0
  96. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/columns.md +0 -0
  97. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/config.md +0 -0
  98. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/hooks.md +0 -0
  99. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/http-api.md +0 -0
  100. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/sdk.md +0 -0
  101. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  102. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  103. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
  104. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/unit/config.test.ts +0 -0
  105. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  106. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/vitest.config.ts +0 -0
  107. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/__init__.py +0 -0
  108. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/binding/__init__.py +0 -0
  109. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/e2e/__init__.py +0 -0
  110. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/integration/__init__.py +0 -0
  111. {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/smoke/__init__.py +0 -0
  112. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/Cargo.toml +0 -0
  113. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/README.md +0 -0
  114. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/db_bench.rs +0 -0
  115. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/matcher_bench.rs +0 -0
  116. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/explanation.md +0 -0
  117. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/getting-started.md +0 -0
  118. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  119. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/define-tables.md +0 -0
  120. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/embed.md +0 -0
  121. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  122. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/load-extension.md +0 -0
  123. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/persist.md +0 -0
  124. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/react-to-changes.md +0 -0
  125. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  126. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/skip-files.md +0 -0
  127. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/index.md +0 -0
  128. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/migrations.md +0 -0
  129. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/cli.md +0 -0
  130. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/columns.md +0 -0
  131. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/config.md +0 -0
  132. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/hooks.md +0 -0
  133. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/http-api.md +0 -0
  134. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/sdk.md +0 -0
  135. {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/router.rs +0 -0
  136. {dirsql-0.3.71 → dirsql-0.3.72}/pyproject.toml +0 -0
@@ -500,7 +500,7 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "dirsql-py-ext"
503
- version = "0.3.71"
503
+ version = "0.3.72"
504
504
  dependencies = [
505
505
  "dirsql",
506
506
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.71
3
+ Version: 0.3.72
4
4
  Summary: Ephemeral SQL index over a local directory
5
5
  Keywords: sql,filesystem,directory,sqlite,index
6
6
  Author: Kevin Scott
@@ -59,7 +59,7 @@ class DirSQL:
59
59
  at startup. Any ``[[dirsql.extension]]`` entries in a ``config`` file are
60
60
  appended after the programmatic ones. A ``path`` (programmatic or
61
61
  config-file) may be a bare **package name**, resolved from the installed
62
- package in the runtime env (#298 / #313).
62
+ package in the runtime env.
63
63
  """
64
64
 
65
65
  def __init__(
@@ -98,15 +98,13 @@ class DirSQL:
98
98
  """Resolve extensions and construct the Rust-backed instance.
99
99
 
100
100
  Runs on a worker thread (via ``asyncio.to_thread``): both the
101
- package-name resolution (``importlib`` + filesystem globs) and the
102
- core's initial scan are blocking work.
101
+ package-name resolution and the core's initial scan are blocking.
103
102
 
104
103
  When the ``config`` file names an extension by bare package name, the
105
104
  SDK resolves every one of the config's ``[[dirsql.extension]]`` entries
106
- itself (#313) -- appended after the programmatic ones, matching the
107
- core's ordering -- and suppresses the core's own config-extension
108
- loading so the entries are not loaded a second time (and the core never
109
- sees the unresolvable bare name).
105
+ itself -- appended after the programmatic ones -- and suppresses the
106
+ core's own config-extension loading so the entries are not loaded a
107
+ second time (the core cannot resolve a bare name).
110
108
  """
111
109
  extensions = self._resolved_extensions()
112
110
  suppress = False
@@ -129,9 +127,8 @@ class DirSQL:
129
127
  def _resolved_extensions(self):
130
128
  """Resolve each programmatic extension's ``path`` to a loadable file.
131
129
 
132
- A bare package name is resolved to the loadable installed in the runtime
133
- env (#298); path-looking values are passed through verbatim (mirroring
134
- the Rust builder, which takes programmatic paths as-is). Config-file
130
+ A bare package name is resolved to the loadable installed in the
131
+ runtime env; path-looking values pass through verbatim. Config-file
135
132
  ``[[dirsql.extension]]`` entries are handled by ``_build_db``.
136
133
  """
137
134
  if not self._extensions:
@@ -24,7 +24,7 @@ def main(argv: list[str] | None = None) -> int:
24
24
  return 1
25
25
 
26
26
  # Resolve any package-name extensions in a TOML config here (the binary
27
- # can't) and pass them as `--extension` flags; a no-op otherwise (#227).
27
+ # can't) and pass them as `--extension` flags; a no-op otherwise.
28
28
  try:
29
29
  argv = with_resolved_extensions(argv)
30
30
  except Exception as exc:
@@ -1,14 +1,12 @@
1
1
  """Launcher-side resolution of a TOML config's ``[[dirsql.extension]]`` entries.
2
2
 
3
- Mirrors the TypeScript launcher. The compiled ``dirsql`` binary reads a
4
- ``.dirsql.toml`` itself and loads its extensions literally -- it has no
5
- ``importlib``, so it cannot resolve a bare **package name** (#227). This
6
- launcher can. When a TOML config names an extension by package name, the
7
- shared SDK resolver (:mod:`dirsql.resolve_config_extensions`, #313) resolves
8
- every one of its extensions and this launcher passes the resolved literal
9
- paths to the binary via repeatable ``--extension`` flags; the binary then
10
- loads those and ignores the config's own extension entries (the Rust
11
- ``--extension`` flag / ``suppress_config_extensions``).
3
+ The compiled ``dirsql`` binary loads a config's extensions literally -- it
4
+ has no ``importlib``, so it cannot resolve a bare **package name**. When a
5
+ TOML config names an extension by package name, the shared SDK resolver
6
+ (:mod:`dirsql.resolve_config_extensions`) resolves every one of its
7
+ extensions and this launcher passes the resolved literal paths to the binary
8
+ via repeatable ``--extension`` flags; the binary then loads those and ignores
9
+ the config's own extension entries.
12
10
 
13
11
  Native-language configs (``.py`` / ``.js`` / ``.mjs`` / ``.cjs``) are untouched:
14
12
  the binary dispatches those to ``dirsql interpret``, whose handshake already
@@ -1,16 +1,14 @@
1
1
  """SDK-side resolution of a TOML config's ``[[dirsql.extension]]`` entries.
2
2
 
3
- The Rust core parses a ``.dirsql.toml`` itself and loads its extensions
4
- literally -- it has no ``importlib``, so it cannot resolve a bare **package
5
- name** (#227). The SDK can (#313). When a TOML config names an extension by
6
- package name, the SDK resolves every one of its extensions here, hands the
7
- core the resolved literal paths, and suppresses the core's own config-extension
8
- loading (the Rust ``suppress_config_extensions`` builder toggle) so the
9
- config's entries are not loaded a second time.
3
+ The Rust core loads a config's extensions literally -- it has no
4
+ ``importlib``, so it cannot resolve a bare **package name**. When a TOML
5
+ config names an extension by package name, the SDK resolves every one of its
6
+ extensions here, hands the core the resolved literal paths, and suppresses
7
+ the core's own config-extension loading (``suppress_config_extensions``) so
8
+ the config's entries are not loaded a second time.
10
9
 
11
10
  Shared by the ``DirSQL`` constructor (``config=`` path) and the CLI launcher
12
- (``dirsql.cli.resolve_config_extensions``, which converts the resolved specs
13
- into ``--extension`` flags for the binary).
11
+ (which converts the resolved specs into ``--extension`` flags).
14
12
  """
15
13
 
16
14
  from __future__ import annotations
@@ -45,9 +43,7 @@ def resolve_config_extension_specs(config_path):
45
43
  entries = cfg.get("extension")
46
44
  if not isinstance(entries, list):
47
45
  return None
48
- # Only intervene when at least one path is a bare package name; a config
49
- # with only literal paths (or no entries at all) keeps the core's existing
50
- # behavior untouched.
46
+ # Only intervene when at least one path is a bare package name.
51
47
  if not any(
52
48
  isinstance(e, dict)
53
49
  and isinstance(e.get("path"), str)
@@ -1,23 +1,17 @@
1
1
  """Resolve an extension entry's ``path`` to a concrete loadable file.
2
2
 
3
- #225 supports only literal file paths. #298 adds resolving a bare **package
4
- name**: when ``path`` carries no path separator and no loadable-file suffix, it
5
- names a package installed in the runtime env, and dirsql discovers the loadable
6
- file *inside* that package.
7
-
8
- Resolution is an ordered probe (file-first, then package), so every literal
9
- path from #225 keeps its old behavior and only a bare name reaches the package
10
- machinery:
3
+ Resolution is an ordered probe (file-first, then package), so only a bare
4
+ package name reaches the package machinery:
11
5
 
12
6
  1. **Path-looking** (contains a separator, or ends in ``.so`` / ``.dylib`` /
13
7
  ``.dll`` / ``.pyd``) -- returned as a file path: made absolute against
14
8
  ``base`` when ``resolve_relative`` is set (config-file entries), else
15
- verbatim (programmatic entries, mirroring the Rust builder).
9
+ verbatim (programmatic entries).
16
10
  2. **Bare name** -- a same-named local file under ``base`` *shadows* the
17
- package (parity with #225's file-first probe); otherwise the package dir is
18
- located via :func:`importlib.util.find_spec` and the current platform's
19
- loadable is globbed from inside it. Zero matches and multiple matches are
20
- both hard errors -- the caller must disambiguate with a literal path.
11
+ package; otherwise the package dir is located via
12
+ :func:`importlib.util.find_spec` and the current platform's loadable is
13
+ globbed from inside it. Zero matches and multiple matches are both hard
14
+ errors -- the caller must disambiguate with a literal path.
21
15
  """
22
16
 
23
17
  import glob as _glob
@@ -25,9 +19,8 @@ import importlib.util
25
19
  import os
26
20
  import sys
27
21
 
28
- # Suffixes that mark a value as "already a file path" (so package resolution is
29
- # never attempted) and, per platform, the globs used to find a loadable inside
30
- # a package directory.
22
+ # Suffixes that mark a value as "already a file path", so package resolution
23
+ # is never attempted.
31
24
  _LOADABLE_SUFFIXES = (".so", ".dylib", ".dll", ".pyd")
32
25
 
33
26
 
@@ -73,8 +66,6 @@ def _resolve_package(name):
73
66
  matches.update(_glob.glob(os.path.join(d, "**", pat), recursive=True))
74
67
  found = sorted(matches)
75
68
 
76
- # Exactly one loadable resolves; unpacking (rather than a guarded
77
- # subscript) makes the zero- and multiple-match failures each observable.
78
69
  try:
79
70
  (single,) = found
80
71
  except ValueError:
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
4
4
  # pypi/maturin handler can rewrite it via `write-version` before
5
5
  # `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
6
6
  # and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
7
- version = "0.3.71"
7
+ version = "0.3.72"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -1,15 +1,9 @@
1
1
  """Stub the compiled `_dirsql` extension for unit tests that don't need it.
2
2
 
3
- `cli_test.py` exercises the pure-Python launcher; it must be runnable
4
- without `maturin develop` having built the PyO3 extension. We stub the
5
- missing module here (rather than in the test file) so the stub is in
6
- place before pytest imports `dirsql.__init__`, which transitively
7
- imports real types from `dirsql._dirsql`.
8
-
9
- The stub is installed ONLY when the real extension can't be imported.
10
- When `maturin develop` has been run (as in CI), the real extension wins
11
- and tests that depend on it — `_async_test.py`, the integration suites —
12
- see the real bindings.
3
+ Must live here (not in a test file) so the stub is in place before pytest
4
+ imports `dirsql.__init__`, which imports real types from `dirsql._dirsql`.
5
+ Installed only when the real extension can't be imported; when `maturin
6
+ develop` has been run, the real bindings win.
13
7
  """
14
8
 
15
9
  from __future__ import annotations
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "command": "uv run python -m pytest tests/e2e/ -x -q",
3
- "ran_at": 1783389455,
3
+ "ran_at": 1783420501,
4
4
  "exit_code": 0,
5
- "commit": "edd5f78fd484ce51829ff3f8387e720b989fb8fb"
5
+ "commit": "fe9d09208be0b3c6084d9b916a41403d5822d096"
6
6
  }
@@ -20,8 +20,6 @@ mod python {
20
20
  use std::path::PathBuf;
21
21
  use std::time::Duration;
22
22
 
23
- // -- Public PyO3 classes ------------------------------------------------
24
-
25
23
  /// A table definition. Mirrors `dirsql::Table` but holds a Python
26
24
  /// callable for `extract`.
27
25
  #[pyclass(name = "Table", frozen)]
@@ -34,15 +32,11 @@ mod python {
34
32
  extract: Py<PyAny>,
35
33
  #[pyo3(get)]
36
34
  strict: bool,
37
- /// Parsed table name (from `ddl`), or `None` if the DDL doesn't
38
- /// match `CREATE TABLE <name> (...)`. Computed once at construction
39
- /// via `dirsql::db::parse_table_name` so the `dirsql interpret`
40
- /// dispatcher (#196) and other consumers share the core's single
41
- /// source of truth instead of re-parsing DDL. We return `None`
42
- /// rather than raising at construction so `DirSQL.ready()` keeps
43
- /// surfacing malformed DDLs as the loud failure path (the Rust
44
- /// core's `DirSqlError::Ddl`); callers that care about the name
45
- /// before that point can check `t.name is None` themselves.
35
+ /// Parsed table name (from `ddl`) via `dirsql::db::parse_table_name`,
36
+ /// or `None` if the DDL doesn't match `CREATE TABLE <name> (...)`.
37
+ /// `None` rather than a construction error so `DirSQL.ready()` keeps
38
+ /// surfacing malformed DDLs as the loud failure path (the core's
39
+ /// `DirSqlError::Ddl`).
46
40
  #[pyo3(get)]
47
41
  name: Option<String>,
48
42
  }
@@ -64,11 +58,9 @@ mod python {
64
58
  }
65
59
 
66
60
  /// Marshals a Python `{"path": str, "entrypoint"?: str}` mapping from the
67
- /// `extensions=` constructor argument into a [`dirsql::Extension`]. Mirrors
68
- /// the `[[dirsql.extension]]` config-file fields and the Rust builder's
69
- /// `Extension { path, entrypoint }`. Paths are taken verbatim (the
70
- /// programmatic surface does not resolve relative paths), matching
71
- /// `DirSQLBuilder::extensions`.
61
+ /// `extensions=` constructor argument into a [`dirsql::Extension`]. Paths
62
+ /// are taken verbatim; the programmatic surface does not resolve relative
63
+ /// paths.
72
64
  #[derive(FromPyObject)]
73
65
  struct PyExtensionSpec {
74
66
  #[pyo3(item)]
@@ -117,8 +109,8 @@ mod python {
117
109
  /// `suppress_config_extensions` skips the core's own loading of the
118
110
  /// config's `[[dirsql.extension]]` entries; the SDK sets it after
119
111
  /// resolving those entries itself (package names need `importlib`,
120
- /// which the core lacks -- #313) and passing the resolved literal
121
- /// paths via `extensions`, so the entries are not loaded twice.
112
+ /// which the core lacks) and passing the resolved literal paths via
113
+ /// `extensions`, so the entries are not loaded twice.
122
114
  #[new]
123
115
  #[pyo3(signature = (root=None, *, tables=None, ignore=None, config=None, persist=false, persist_path=None, extensions=None, suppress_config_extensions=false))]
124
116
  fn new(
@@ -205,8 +197,6 @@ mod python {
205
197
  }
206
198
  }
207
199
 
208
- // -- Helpers ------------------------------------------------------------
209
-
210
200
  fn build_table(py: Python<'_>, t: &PyTable) -> Table {
211
201
  let extract_ref = t.extract.clone_ref(py);
212
202
  let mut table = Table::try_new(
@@ -260,10 +250,7 @@ mod python {
260
250
  /// Pure, GIL-free intermediate for a row event. [`row_event_to_plain`]
261
251
  /// builds it from a core [`RowEvent`] (unit-testable without a Python
262
252
  /// interpreter); [`row_event_to_py`] then marshals it into the
263
- /// Python-facing [`PyRowEvent`] (the GIL step). Splitting the two keeps the
264
- /// variant -> action / field-selection mapping testable at the unit tier,
265
- /// mirroring the napi binding's pure `row_event_to_js`. The value-level
266
- /// `Row -> PyDict` conversion stays GIL-bound (binding-tier covered).
253
+ /// Python-facing [`PyRowEvent`] (the GIL step).
267
254
  struct PlainRowEvent {
268
255
  table: Option<String>,
269
256
  action: &'static str,
@@ -406,8 +393,6 @@ mod python {
406
393
  }
407
394
  }
408
395
 
409
- // -- Module registration ------------------------------------------------
410
-
411
396
  #[pymodule]
412
397
  #[pyo3(name = "_dirsql")]
413
398
  fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
@@ -15,7 +15,6 @@ def tmp_dir():
15
15
  @pytest.fixture
16
16
  def jsonl_dir(tmp_dir):
17
17
  """Create a temp dir with JSONL files for testing."""
18
- # Create a simple JSONL file
19
18
  os.makedirs(os.path.join(tmp_dir, "comments", "abc"), exist_ok=True)
20
19
  os.makedirs(os.path.join(tmp_dir, "comments", "def"), exist_ok=True)
21
20
 
@@ -46,7 +46,6 @@ fn bench_diff_single_line_change(c: &mut Criterion) {
46
46
  for count in [10, 100, 1000] {
47
47
  let old = make_rows(count);
48
48
  let mut new = old.clone();
49
- // Change one row in the middle
50
49
  new[count / 2].insert("name".to_string(), Value::Text("CHANGED".to_string()));
51
50
  group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, _| {
52
51
  b.iter(|| {
@@ -62,7 +61,6 @@ fn bench_diff_append(c: &mut Criterion) {
62
61
  for count in [10, 100, 1000] {
63
62
  let old = make_rows(count);
64
63
  let mut new = old.clone();
65
- // Append 10% more rows
66
64
  let extra = make_rows(count + count / 10);
67
65
  new.extend_from_slice(&extra[count..]);
68
66
  group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, _| {
@@ -78,7 +76,6 @@ fn bench_diff_full_replace(c: &mut Criterion) {
78
76
  let mut group = c.benchmark_group("differ/full_replace");
79
77
  for count in [10, 100, 1000] {
80
78
  let old = make_rows(count);
81
- // Completely different rows trigger full replace
82
79
  let new: Vec<HashMap<String, Value>> = (0..count)
83
80
  .map(|i| {
84
81
  HashMap::from([
@@ -7,7 +7,6 @@ use tempfile::TempDir;
7
7
  fn create_temp_tree(file_count: usize) -> TempDir {
8
8
  let dir = TempDir::new().unwrap();
9
9
  for i in 0..file_count {
10
- // Distribute files across subdirectories
11
10
  let subdir = dir.path().join(format!("dir_{}", i % 10));
12
11
  fs::create_dir_all(&subdir).unwrap();
13
12
  fs::write(
@@ -16,7 +15,6 @@ fn create_temp_tree(file_count: usize) -> TempDir {
16
15
  )
17
16
  .unwrap();
18
17
  }
19
- // Add some non-matching files
20
18
  for i in 0..file_count / 5 {
21
19
  fs::write(dir.path().join(format!("readme_{i}.md")), "# not matched").unwrap();
22
20
  }
@@ -53,8 +53,8 @@ struct Cli {
53
53
  ///
54
54
  /// Intended for the language launcher (pip/npm), not end users: the
55
55
  /// launcher resolves config extensions — including bare **package names**,
56
- /// which need an interpreter this compiled binary lacks (see #227) and
57
- /// passes the resolved literal paths here. When any are present, the TOML
56
+ /// which need an interpreter this compiled binary lacks and passes the
57
+ /// resolved literal paths here. When any are present, the TOML
58
58
  /// config's own extension entries are not loaded (the launcher already
59
59
  /// merged and resolved them). Used by server mode and by the `query`
60
60
  /// subcommand.
@@ -198,7 +198,6 @@ async fn run_server(cli: Cli) -> ExitCode {
198
198
  // Echo back the user-facing hostname (not the resolved IP SocketAddr).
199
199
  println!("Running at {host}:{}", handle.local_addr().port());
200
200
 
201
- // Await ctrl-c / SIGTERM; then drain.
202
201
  if let Err(err) = wait_for_shutdown().await {
203
202
  eprintln!("dirsql: signal handler error: {err}");
204
203
  }
@@ -233,9 +232,9 @@ fn load_state(cli: &Cli) -> AppState {
233
232
 
234
233
  // Launcher-resolved extensions (`--extension`) override the TOML config's
235
234
  // own `[[dirsql.extension]]` entries: the launcher has already merged and
236
- // resolved them (including package names the compiled binary can't resolve;
237
- // #227), so build from the config but suppress its extension loading and
238
- // supply the resolved literal paths instead.
235
+ // resolved them (including package names the compiled binary can't
236
+ // resolve), so suppress the config's extension loading and supply the
237
+ // resolved literal paths instead.
239
238
  let build = if cli.extension.is_empty() {
240
239
  DirSQL::from_config_path(&resolved)
241
240
  } else {
@@ -428,10 +427,6 @@ mod tests {
428
427
 
429
428
  #[test]
430
429
  fn default_files_table_declares_filesystem_fact_columns_over_recursive_glob() {
431
- // The zero-config fallback table is pure data: a fixed DDL naming only
432
- // the auto-injected filesystem-fact columns and a `**/*` glob that
433
- // matches every file at any depth. The extract closure is never
434
- // invoked here, so this stays a pure unit test.
435
430
  let table = default_files_table();
436
431
  assert_eq!(table.glob, "**/*");
437
432
  assert!(table.ddl.starts_with("CREATE TABLE files ("));
@@ -4,7 +4,7 @@
4
4
  //! the `pre-query` hook, the query timeout, [`DirSQL::query`], row
5
5
  //! serialization, the `post-query` hook, and error classification —
6
6
  //! lives here exactly once. The HTTP handler and the one-shot
7
- //! `dirsql query` subcommand (#399) are thin transport adapters over
7
+ //! `dirsql query` subcommand are thin transport adapters over
8
8
  //! [`execute_query`], so the two surfaces cannot drift behaviorally:
9
9
  //! per-surface code only maps [`QueryFailure`] to a status code or an
10
10
  //! exit code.
@@ -9,7 +9,7 @@
9
9
  //!
10
10
  //! Failure modes:
11
11
  //! - Output already exists and `--force` was not passed: bail before
12
- //! spawning the agent (no point burning a paid LLM call).
12
+ //! spawning the agent (never burn a paid LLM call first).
13
13
  //! - `claude` is not on PATH: surface a descriptive error pointing at
14
14
  //! the install docs.
15
15
  //! - `claude` exits non-zero: surface its stderr; do not write any
@@ -150,8 +150,7 @@ mod tests {
150
150
  assert!(msg.contains("claude"));
151
151
  }
152
152
 
153
- /// `run` bails before spawning the agent when the output already exists and
154
- /// `--force` was not passed (the temp dir itself is an existing path).
153
+ /// The temp dir itself serves as the already-existing output path.
155
154
  #[test]
156
155
  fn run_bails_when_output_exists_without_force() {
157
156
  let dir = tempfile::tempdir().unwrap();
@@ -167,10 +166,9 @@ mod tests {
167
166
  );
168
167
  }
169
168
 
170
- // The spawn / non-zero-exit / write failure arms of `run` require actually
171
- // invoking `claude` and touching the filesystem, so they are exercised at
172
- // the e2e tier. Their `Display` renderings, however, are pure and covered
173
- // here (the values are constructed inline, no process or fs I/O).
169
+ // The spawn / non-zero-exit / write failure arms of `run` need a real
170
+ // `claude` and filesystem, so they live at the e2e tier; only their pure
171
+ // `Display` renderings are covered here.
174
172
 
175
173
  #[test]
176
174
  fn spawn_error_display_mentions_spawn() {
@@ -196,8 +194,6 @@ mod tests {
196
194
 
197
195
  #[test]
198
196
  fn invalid_utf8_error_display_mentions_utf8() {
199
- // A non-UTF8 byte sequence yields the `FromUtf8Error` that `#[from]`
200
- // converts into `InitError::InvalidUtf8`.
201
197
  let utf8_err = String::from_utf8(vec![0xff, 0xfe]).unwrap_err();
202
198
  let err: InitError = utf8_err.into();
203
199
  let msg = format!("{err}");
@@ -39,10 +39,6 @@ pub mod server;
39
39
 
40
40
  pub use server::{serve, serve_with_state};
41
41
 
42
- // ---------------------------------------------------------------------------
43
- // Public types
44
- // ---------------------------------------------------------------------------
45
-
46
42
  /// A server-wide `pre-query` command hook, carrying the command template plus
47
43
  /// the directory it runs in (the config file's parent). When set on a
48
44
  /// [`ServerConfig`], the server passes each `POST /query` request body to the
@@ -57,7 +53,7 @@ pub struct PreQuery {
57
53
  pub config_dir: PathBuf,
58
54
  /// Per-run timeout. Defaults to the shared 30-second
59
55
  /// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
60
- /// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
56
+ /// (the CLI wires the global `[dirsql].hook-timeout` here).
61
57
  pub timeout: Duration,
62
58
  }
63
59
 
@@ -95,7 +91,7 @@ pub struct PostQuery {
95
91
  pub config_dir: PathBuf,
96
92
  /// Per-run timeout. Defaults to the shared 30-second
97
93
  /// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
98
- /// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
94
+ /// (the CLI wires the global `[dirsql].hook-timeout` here).
99
95
  pub timeout: Duration,
100
96
  }
101
97
 
@@ -202,10 +198,8 @@ impl From<DirSQL> for AppState {
202
198
  }
203
199
  }
204
200
 
205
- /// Symmetric construction for the degraded arm. Lets call sites build the
206
- /// degraded state with the same `.into()` ergonomics as the ready arm
207
- /// instead of typing the variant name. The string is the diagnostic that
208
- /// `/query` and `/events` echo back as a 503 body.
201
+ /// The string is the diagnostic that `/query` and `/events` echo back as a
202
+ /// 503 body.
209
203
  impl From<String> for AppState {
210
204
  fn from(reason: String) -> Self {
211
205
  Self::Unavailable(reason)
@@ -266,14 +260,9 @@ pub enum ServerError {
266
260
  mod tests {
267
261
  use super::*;
268
262
 
269
- // `From<DirSQL> for AppState` produces the `Ready` arm -- this is
270
- // verified at the integration tier by `from_dirsql_yields_ready_state`
271
- // in `tests/cli_integration.rs`, which builds a real `DirSQL` over a
272
- // temp directory (so the initial scan runs) and asserts the public
273
- // `AppState::Ready` variant. It lived here once but needed
274
- // `std::fs::write` to populate the scanned directory, which the
275
- // `testing-conventions` `unit lint` isolation rule forbids in a unit
276
- // test (effectful std). The pure config-default test below stays inline.
263
+ // `From<DirSQL> for AppState` needs a real scanned directory, so its
264
+ // `Ready`-arm test lives in `tests/cli_integration.rs` (unit-lint
265
+ // isolation forbids the fs setup here).
277
266
 
278
267
  #[test]
279
268
  fn default_config_binds_localhost_7117_with_30s_timeout() {
@@ -287,8 +276,6 @@ mod tests {
287
276
 
288
277
  #[test]
289
278
  fn pre_query_constructor_carries_command_and_dir() {
290
- // `PreQuery::new` is pure data plumbing: the command template and the
291
- // working directory it will run in, with the shared default timeout.
292
279
  let pq = PreQuery::new("to_sql.py {args}", "/proj");
293
280
  assert_eq!(pq.command, "to_sql.py {args}");
294
281
  assert_eq!(pq.config_dir, PathBuf::from("/proj"));
@@ -311,8 +298,6 @@ mod tests {
311
298
 
312
299
  #[test]
313
300
  fn post_query_constructor_carries_command_and_dir() {
314
- // `PostQuery::new` is pure data plumbing: the command template and the
315
- // working directory it will run in, with the shared default timeout.
316
301
  let pq = PostQuery::new("jq '{results: .}'", "/proj");
317
302
  assert_eq!(pq.command, "jq '{results: .}'");
318
303
  assert_eq!(pq.config_dir, PathBuf::from("/proj"));
@@ -336,8 +321,6 @@ mod tests {
336
321
 
337
322
  #[test]
338
323
  fn with_query_timeout_overrides_the_default() {
339
- // The builder replaces the 30s default with the supplied timeout and
340
- // leaves the rest of the config untouched.
341
324
  let cfg = ServerConfig::bind("127.0.0.1", 8080).with_query_timeout(Duration::from_secs(5));
342
325
  assert_eq!(cfg.query_timeout, Duration::from_secs(5));
343
326
  assert_eq!(cfg.host, "127.0.0.1");
@@ -346,9 +329,7 @@ mod tests {
346
329
 
347
330
  #[test]
348
331
  fn app_state_from_string_builds_the_unavailable_arm() {
349
- // `String -> AppState` yields the degraded arm carrying the diagnostic
350
- // verbatim (the `/query`/`/events` 503 body). `AppState` isn't `Debug`,
351
- // so match rather than assert on a rendering.
332
+ // `AppState` isn't `Debug`, so match instead of asserting on a rendering.
352
333
  let state: AppState = "config failed to load".to_string().into();
353
334
  match state {
354
335
  AppState::Unavailable(reason) => assert_eq!(reason, "config failed to load"),
@@ -30,8 +30,7 @@ fn cell_to_json(value: &CellValue) -> Value {
30
30
  }
31
31
  }
32
32
 
33
- /// Hex-encode a byte slice. Used for `BLOB` SQLite values in JSON
34
- /// output. Lightweight; pulls in no dep just to emit hex.
33
+ /// Hex-encode a byte slice, for `BLOB` SQLite values in JSON output.
35
34
  fn hex_encode(bytes: &[u8]) -> String {
36
35
  let mut out = String::with_capacity(bytes.len() * 2);
37
36
  for b in bytes {
@@ -93,10 +92,6 @@ fn event_to_value(event: &RowEvent) -> Value {
93
92
  }
94
93
  }
95
94
 
96
- // ---------------------------------------------------------------------------
97
- // Unit tests
98
- // ---------------------------------------------------------------------------
99
-
100
95
  #[cfg(test)]
101
96
  mod tests {
102
97
  use super::*;
@@ -130,7 +125,6 @@ mod tests {
130
125
  Some("posts/a.json"),
131
126
  );
132
127
  assert!(parsed.get("old_row").unwrap().is_null());
133
- // event_to_json is the string form of the value it builds.
134
128
  assert_eq!(event_to_json(&event), parsed.to_string());
135
129
  }
136
130
 
@@ -202,8 +196,6 @@ mod tests {
202
196
 
203
197
  #[test]
204
198
  fn blob_cell_becomes_hex_string() {
205
- // The Blob arm of `cell_to_json` hex-encodes the bytes into a JSON
206
- // string (BLOB columns aren't representable as JSON literals).
207
199
  let json = cell_to_json(&CellValue::Blob(vec![0xde, 0xad, 0xbe, 0xef]));
208
200
  assert_eq!(json.as_str(), Some("deadbeef"));
209
201
  }
@@ -96,9 +96,8 @@ fn start_watch_task(db: DirSQL, tx: broadcast::Sender<String>) {
96
96
  mod tests {
97
97
  use super::*;
98
98
 
99
- // Binding with an `Unavailable` state needs no DB/filesystem, so this
100
- // exercises the real bind / local-addr / graceful-shutdown plumbing
101
- // without standing up a full index. Port 0 lets the OS pick a free port.
99
+ // An `Unavailable` state needs no DB/filesystem, so the real bind /
100
+ // graceful-shutdown plumbing runs without standing up an index.
102
101
  #[tokio::test]
103
102
  async fn serve_with_state_binds_an_ephemeral_port_then_shuts_down() {
104
103
  let config = ServerConfig::bind("127.0.0.1".to_string(), 0);
@@ -115,10 +114,6 @@ mod tests {
115
114
  handle.shutdown().await.expect("graceful shutdown");
116
115
  }
117
116
 
118
- // A `Ready` state drives the `serve` convenience wrapper plus
119
- // `start_watch_task` (which attaches `DirSQL::watch` and pumps its stream
120
- // into the broadcast channel). Built over an empty temp dir so the scan
121
- // touches nothing; the port-0 bind and graceful shutdown are real.
122
117
  #[tokio::test]
123
118
  async fn serve_with_a_ready_db_attaches_the_watcher_then_shuts_down() {
124
119
  let dir = tempfile::tempdir().unwrap();