dirsql 0.4.44__tar.gz → 0.4.46__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 (239) hide show
  1. {dirsql-0.4.44 → dirsql-0.4.46}/Cargo.lock +2 -2
  2. {dirsql-0.4.44 → dirsql-0.4.46}/PKG-INFO +1 -2
  3. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/_dirsql.pyi +19 -0
  4. dirsql-0.4.46/dirsql/plan_entry_path.py +20 -0
  5. dirsql-0.4.46/dirsql/read_config.py +15 -0
  6. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/resolve_configs_extension_specs.py +17 -15
  7. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/resolve_extension.py +2 -2
  8. dirsql-0.4.46/dirsql/resolve_package.py +37 -0
  9. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/Cargo.toml +1 -1
  10. dirsql-0.4.46/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +3 -0
  11. dirsql-0.4.46/packages/python/e2e-attestations/claude-1120-py-ext.json +7 -0
  12. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/src/lib.rs +211 -2
  13. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/Cargo.toml +1 -1
  14. {dirsql-0.4.44 → dirsql-0.4.46}/pyproject.toml +0 -4
  15. dirsql-0.4.44/dirsql/has_bare_name.py +0 -14
  16. dirsql-0.4.44/dirsql/is_bare_name.py +0 -14
  17. dirsql-0.4.44/dirsql/load_extension_entries.py +0 -28
  18. dirsql-0.4.44/dirsql/platform_patterns.py +0 -12
  19. dirsql-0.4.44/dirsql/resolve_config_extensions.py +0 -39
  20. dirsql-0.4.44/dirsql/resolve_entries.py +0 -20
  21. dirsql-0.4.44/dirsql/resolve_package.py +0 -53
  22. dirsql-0.4.44/dirsql/toml_module.py +0 -21
  23. {dirsql-0.4.44 → dirsql-0.4.46}/Cargo.toml +0 -0
  24. {dirsql-0.4.44 → dirsql-0.4.46}/README.md +0 -0
  25. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/__init__.py +0 -0
  26. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/_async.py +0 -0
  27. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/__init__.py +0 -0
  28. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/discover_plugins/__init__.py +0 -0
  29. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  30. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  31. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  32. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  33. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  34. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/main.py +0 -0
  35. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/cli/resolve_config_extensions.py +0 -0
  36. {dirsql-0.4.44 → dirsql-0.4.46}/dirsql/py.typed +0 -0
  37. {dirsql-0.4.44 → dirsql-0.4.46}/docs/.claude/CLAUDE.md +0 -0
  38. {dirsql-0.4.44 → dirsql-0.4.46}/docs/.vitepress/config.ts +0 -0
  39. {dirsql-0.4.44 → dirsql-0.4.46}/docs/.vitepress/theme/index.ts +0 -0
  40. {dirsql-0.4.44 → dirsql-0.4.46}/docs/.vitepress/theme/lang.ts +0 -0
  41. {dirsql-0.4.44 → dirsql-0.4.46}/docs/AGENTS.md +0 -0
  42. {dirsql-0.4.44 → dirsql-0.4.46}/docs/explanation.md +0 -0
  43. {dirsql-0.4.44 → dirsql-0.4.46}/docs/getting-started.md +0 -0
  44. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/columns-from-paths.md +0 -0
  45. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/define-tables.md +0 -0
  46. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/embed.md +0 -0
  47. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/extract-from-contents.md +0 -0
  48. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/load-extension.md +0 -0
  49. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/parse-files-into-columns.md +0 -0
  50. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/persist.md +0 -0
  51. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/query-json.md +0 -0
  52. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/query-without-config.md +0 -0
  53. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/react-to-changes.md +0 -0
  54. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/search-by-meaning.md +0 -0
  55. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/search-indexes.md +0 -0
  56. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/skip-files.md +0 -0
  57. {dirsql-0.4.44 → dirsql-0.4.46}/docs/howto/write-a-plugin.md +0 -0
  58. {dirsql-0.4.44 → dirsql-0.4.46}/docs/index.md +0 -0
  59. {dirsql-0.4.44 → dirsql-0.4.46}/docs/package.json +0 -0
  60. {dirsql-0.4.44 → dirsql-0.4.46}/docs/plugins.md +0 -0
  61. {dirsql-0.4.44 → dirsql-0.4.46}/docs/pnpm-lock.yaml +0 -0
  62. {dirsql-0.4.44 → dirsql-0.4.46}/docs/pnpm-workspace.yaml +0 -0
  63. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/cli.md +0 -0
  64. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/columns.md +0 -0
  65. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/config.md +0 -0
  66. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/hooks.md +0 -0
  67. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/http-api.md +0 -0
  68. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/path-tables.md +0 -0
  69. {dirsql-0.4.44 → dirsql-0.4.46}/docs/reference/sdk.md +0 -0
  70. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/CHANGELOG.md +0 -0
  71. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/MIGRATIONS.md +0 -0
  72. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/README.md +0 -0
  73. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  74. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  75. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  76. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  77. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  78. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  79. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  80. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  81. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  82. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  83. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  84. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  85. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  86. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  87. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  88. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  89. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  90. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  91. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  92. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
  93. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
  94. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
  95. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
  96. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
  97. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/changelog.d/README.md +0 -0
  98. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/conftest.py +0 -0
  99. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/.claude/CLAUDE.md +0 -0
  100. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/.vitepress/config.ts +0 -0
  101. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  102. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  103. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/AGENTS.md +0 -0
  104. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/explanation.md +0 -0
  105. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/getting-started.md +0 -0
  106. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/columns-from-paths.md +0 -0
  107. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/define-tables.md +0 -0
  108. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/embed.md +0 -0
  109. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/extract-from-contents.md +0 -0
  110. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/load-extension.md +0 -0
  111. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  112. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/persist.md +0 -0
  113. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/query-json.md +0 -0
  114. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/query-without-config.md +0 -0
  115. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/react-to-changes.md +0 -0
  116. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/search-by-meaning.md +0 -0
  117. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/search-indexes.md +0 -0
  118. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/skip-files.md +0 -0
  119. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/howto/write-a-plugin.md +0 -0
  120. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/index.md +0 -0
  121. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/package.json +0 -0
  122. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/plugins.md +0 -0
  123. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/pnpm-lock.yaml +0 -0
  124. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/pnpm-workspace.yaml +0 -0
  125. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/cli.md +0 -0
  126. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/columns.md +0 -0
  127. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/config.md +0 -0
  128. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/hooks.md +0 -0
  129. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/http-api.md +0 -0
  130. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/path-tables.md +0 -0
  131. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/docs/reference/sdk.md +0 -0
  132. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  133. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
  134. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
  135. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
  136. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
  137. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
  138. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
  139. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
  140. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
  141. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
  142. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  143. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  144. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  145. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  146. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  147. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  148. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  149. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  150. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  151. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
  152. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
  153. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  154. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
  155. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
  156. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
  157. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  158. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  159. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  160. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  161. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  162. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  163. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  164. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  165. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  166. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  167. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/migrations.d/README.md +0 -0
  168. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/testing-conventions.toml +0 -0
  169. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/__init__.py +0 -0
  170. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/conftest.py +0 -0
  171. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/e2e/__init__.py +0 -0
  172. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  173. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  174. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/integration/__init__.py +0 -0
  175. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/integration/binding/__init__.py +0 -0
  176. {dirsql-0.4.44 → dirsql-0.4.46}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  177. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/CHANGELOG.md +0 -0
  178. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/MIGRATIONS.md +0 -0
  179. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/README.md +0 -0
  180. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/benches/db_bench.rs +0 -0
  181. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/benches/differ_bench.rs +0 -0
  182. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/benches/matcher_bench.rs +0 -0
  183. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/benches/scanner_bench.rs +0 -0
  184. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/explanation.md +0 -0
  185. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/getting-started.md +0 -0
  186. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  187. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/define-tables.md +0 -0
  188. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/embed.md +0 -0
  189. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  190. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/load-extension.md +0 -0
  191. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  192. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/persist.md +0 -0
  193. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/query-json.md +0 -0
  194. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/query-without-config.md +0 -0
  195. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/react-to-changes.md +0 -0
  196. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  197. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/search-indexes.md +0 -0
  198. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/skip-files.md +0 -0
  199. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  200. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/index.md +0 -0
  201. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/plugins.md +0 -0
  202. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/cli.md +0 -0
  203. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/columns.md +0 -0
  204. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/config.md +0 -0
  205. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/hooks.md +0 -0
  206. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/http-api.md +0 -0
  207. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/path-tables.md +0 -0
  208. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/docs/reference/sdk.md +0 -0
  209. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/bin/dirsql.rs +0 -0
  210. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/execute.rs +0 -0
  211. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/init.rs +0 -0
  212. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/mod.rs +0 -0
  213. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/repl.rs +0 -0
  214. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/router.rs +0 -0
  215. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/run.rs +0 -0
  216. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/serialize.rs +0 -0
  217. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/server.rs +0 -0
  218. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/cli/table.rs +0 -0
  219. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/command.rs +0 -0
  220. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/config.rs +0 -0
  221. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/db.rs +0 -0
  222. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/default_config.toml +0 -0
  223. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/differ.rs +0 -0
  224. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/extension_resolution.rs +0 -0
  225. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/functions.rs +0 -0
  226. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/infer.rs +0 -0
  227. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/launcher.rs +0 -0
  228. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/lib.rs +0 -0
  229. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/matcher.rs +0 -0
  230. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/parsed_cache.rs +0 -0
  231. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/parsed_vtab.rs +0 -0
  232. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/path_table.rs +0 -0
  233. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/persist.rs +0 -0
  234. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/progress.rs +0 -0
  235. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/scanner.rs +0 -0
  236. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/sql_literal.rs +0 -0
  237. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/vtab.rs +0 -0
  238. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/vtab_scaffold.rs +0 -0
  239. {dirsql-0.4.44 → dirsql-0.4.46}/packages/rust/src/watcher.rs +0 -0
@@ -552,7 +552,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
552
552
 
553
553
  [[package]]
554
554
  name = "dirsql"
555
- version = "0.4.44"
555
+ version = "0.4.46"
556
556
  dependencies = [
557
557
  "assert_cmd",
558
558
  "axum",
@@ -597,7 +597,7 @@ dependencies = [
597
597
 
598
598
  [[package]]
599
599
  name = "dirsql-py-ext"
600
- version = "0.4.44"
600
+ version = "0.4.46"
601
601
  dependencies = [
602
602
  "dirsql",
603
603
  "pyo3",
@@ -1,7 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.44
4
- Requires-Dist: tomli>=2 ; python_full_version < '3.11'
3
+ Version: 0.4.46
5
4
  Requires-Dist: bin-shim>=0.1
6
5
  Summary: Ephemeral SQL index over a local directory
7
6
  Keywords: sql,filesystem,directory,sqlite,index
@@ -78,4 +78,23 @@ class DirSQL:
78
78
  def _start_watcher(self) -> None: ...
79
79
  def _poll_events(self, timeout_ms: int) -> list[RowEvent]: ...
80
80
 
81
+ class ExtensionPlanEntry:
82
+ """One planned ``[[dirsql.extension]]`` entry. Exactly one of ``path`` and
83
+ ``package`` is set: a ``path`` is ready to load, a ``package`` must be
84
+ located with ``importlib`` -- unless ``shadow`` names an existing file,
85
+ which takes precedence over the package."""
86
+
87
+ path: str | None
88
+ package: str | None
89
+ shadow: str | None
90
+ entrypoint: str | None
91
+
92
+ @override
93
+ def __repr__(self) -> str: ...
94
+
81
95
  def config_paths_from_argv(argv: list[str]) -> list[str]: ...
96
+ def plan_config_extensions(
97
+ configs: list[tuple[str, str | None]],
98
+ ) -> list[ExtensionPlanEntry] | None: ...
99
+ def select_loadable(name: str, dirs: list[str], candidates: list[str]) -> str: ...
100
+ def is_bare_name(path: str) -> bool: ...
@@ -0,0 +1,20 @@
1
+ """The loadable path one planned extension entry resolves to."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from .resolve_package import _resolve_package
8
+
9
+
10
+ def _plan_entry_path(entry):
11
+ """Resolve a planned entry to a concrete file.
12
+
13
+ A literal entry is already resolved. A package entry prefers a same-named
14
+ file shadowing it next to the config, then the installed package.
15
+ """
16
+ if entry.path is not None:
17
+ return entry.path
18
+ if os.path.isfile(entry.shadow):
19
+ return entry.shadow
20
+ return _resolve_package(entry.package)
@@ -0,0 +1,15 @@
1
+ """Reading one TOML config off disk for the core's extension planner."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def _read_config(config_path):
7
+ """Return the config's text, or ``None`` when it cannot be read.
8
+
9
+ A missing or unreadable config is left for the core to report.
10
+ """
11
+ try:
12
+ with open(config_path, encoding="utf-8") as f:
13
+ return f.read()
14
+ except OSError:
15
+ return None
@@ -1,15 +1,19 @@
1
1
  """Resolution of several TOML configs' ``[[dirsql.extension]]`` entries, in order.
2
2
 
3
- The plural counterpart to :func:`dirsql.resolve_config_extensions
4
- .resolve_config_extension_specs`, used by the SDK constructor and the CLI
5
- launcher when argv carries more than one ``--config``.
3
+ The planning -- which configs parse, whether any entry names a package rather
4
+ than a file, and what each literal path resolves to against its own config's
5
+ directory -- lives in the Rust core (``_dirsql.plan_config_extensions``). This
6
+ module supplies the two host-specific halves the core cannot have: reading the
7
+ files, and locating an installed package with ``importlib``.
6
8
  """
7
9
 
8
10
  from __future__ import annotations
9
11
 
10
- from .has_bare_name import _has_bare_name
11
- from .load_extension_entries import _load_extension_entries
12
- from .resolve_entries import _resolve_entries
12
+ import os
13
+
14
+ from ._dirsql import plan_config_extensions
15
+ from .plan_entry_path import _plan_entry_path
16
+ from .read_config import _read_config
13
17
 
14
18
 
15
19
  def resolve_configs_extension_specs(config_paths):
@@ -24,13 +28,11 @@ def resolve_configs_extension_specs(config_paths):
24
28
  the resolved list. Returns ``None`` when no config uses a package name,
25
29
  leaving every config's loading to the core.
26
30
  """
27
- loaded = [_load_extension_entries(p) for p in config_paths]
28
- if not any(item is not None and _has_bare_name(item[0]) for item in loaded):
31
+ sources = [(os.path.abspath(p), _read_config(p)) for p in config_paths]
32
+ plan = plan_config_extensions(sources)
33
+ if plan is None:
29
34
  return None
30
- specs = []
31
- for item in loaded:
32
- if item is None:
33
- continue
34
- entries, base = item
35
- specs.extend(_resolve_entries(entries, base))
36
- return specs
35
+ return [
36
+ {"path": _plan_entry_path(entry), "entrypoint": entry.entrypoint}
37
+ for entry in plan
38
+ ]
@@ -10,12 +10,12 @@ package name reaches the package machinery:
10
10
  2. **Bare name** -- a same-named local file under ``base`` *shadows* the
11
11
  package; otherwise the package dir is located via
12
12
  :func:`importlib.util.find_spec` and the current platform's loadable is
13
- globbed from inside it.
13
+ picked from inside it.
14
14
  """
15
15
 
16
16
  import os
17
17
 
18
- from .is_bare_name import is_bare_name
18
+ from ._dirsql import is_bare_name
19
19
  from .resolve_package import _resolve_package
20
20
 
21
21
 
@@ -0,0 +1,37 @@
1
+ """Location of an extension package's loadable file for this platform.
2
+
3
+ ``importlib`` finds the package's directories; the core picks the one loadable
4
+ file inside them (and raises on zero or several -- the caller must then
5
+ disambiguate with a literal path).
6
+ """
7
+
8
+ import glob as _glob
9
+ import importlib.util
10
+ import os
11
+
12
+ from ._dirsql import select_loadable
13
+
14
+
15
+ def _resolve_package(name):
16
+ """Locate ``name``'s package dir and pick its platform loadable file."""
17
+ try:
18
+ spec = importlib.util.find_spec(name)
19
+ except (ImportError, ValueError) as exc:
20
+ raise ValueError(
21
+ f"could not resolve extension package {name!r}: {exc}"
22
+ ) from exc
23
+ if spec is None:
24
+ raise ValueError(f"could not resolve extension package {name!r}: not installed")
25
+
26
+ dirs = list(spec.submodule_search_locations or [])
27
+ if not dirs and spec.origin and spec.origin not in ("built-in", "frozen"):
28
+ dirs.append(os.path.dirname(spec.origin))
29
+ if not dirs:
30
+ raise ValueError(
31
+ f"could not resolve extension package {name!r}: no package directory"
32
+ )
33
+
34
+ candidates = []
35
+ for d in dirs:
36
+ candidates.extend(_glob.glob(os.path.join(d, "**", "*"), recursive=True))
37
+ return select_loadable(name, dirs, candidates)
@@ -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.4.44"
7
+ version = "0.4.46"
8
8
  # abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
9
9
  # `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
10
10
  # (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
@@ -0,0 +1,3 @@
1
+ **Changed**
2
+
3
+ - **Extension resolution now plans in the Rust core.** `resolve_configs_extension_specs` reads each config off disk and hands `(path, contents)` pairs to `dirsql._dirsql.plan_config_extensions`; `_resolve_package` hands the located package directories and their files to `dirsql._dirsql.select_loadable`; `resolve_extension_path` classifies through `dirsql._dirsql.is_bare_name`. Python keeps only the two host-specific halves — reading the files, and `importlib.util.find_spec`. Seven internal modules go away (`is_bare_name`, `has_bare_name`, `load_extension_entries`, `resolve_entries`, `platform_patterns`, `resolve_config_extensions`, `toml_module`) against two added (`read_config`, `plan_entry_path`), and with them the `tomli` dependency on Python 3.10. No change to the published API or CLI behavior. (#1120)
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "just test-e2e",
3
+ "ran_at": 1789678578,
4
+ "exit_code": 0,
5
+ "commit": "98c2126ee5ab39e608a0ad9805b223f96f576a65",
6
+ "branch": "claude/1120-py-ext"
7
+ }
@@ -12,12 +12,13 @@
12
12
 
13
13
  #[cfg(feature = "extension-module")]
14
14
  mod python {
15
+ use ::dirsql::extension_resolution::{self, ConfigSource, PlanEntry};
15
16
  use ::dirsql::{DirSQL, Extension, Row, RowEvent, Table, Value};
16
- use pyo3::exceptions::{PyOverflowError, PyRuntimeError};
17
+ use pyo3::exceptions::{PyOverflowError, PyRuntimeError, PyValueError};
17
18
  use pyo3::prelude::*;
18
19
  use pyo3::types::{PyBool, PyByteArray, PyBytes, PyDict, PyInt, PyList};
19
20
  use std::collections::HashMap;
20
- use std::path::PathBuf;
21
+ use std::path::{Path, PathBuf};
21
22
  use std::time::Duration;
22
23
 
23
24
  /// A table definition. Mirrors `dirsql::Table` but holds a Python
@@ -469,6 +470,115 @@ mod python {
469
470
  dirsql::launcher::config_paths_from_argv(&argv)
470
471
  }
471
472
 
473
+ /// The loadable-file suffixes a CPython host recognizes.
474
+ const PY_SUFFIXES: &[&str] = &[".so", ".dylib", ".dll", ".pyd"];
475
+
476
+ /// The subset this platform actually builds, used when picking a
477
+ /// package's loadable out of everything installed beside it.
478
+ #[cfg(target_os = "macos")]
479
+ const PLATFORM_SUFFIXES: &[&str] = &[".dylib"];
480
+ #[cfg(target_os = "windows")]
481
+ const PLATFORM_SUFFIXES: &[&str] = &[".dll", ".pyd"];
482
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
483
+ const PLATFORM_SUFFIXES: &[&str] = &[".so"];
484
+
485
+ /// One planned `[[dirsql.extension]]` entry.
486
+ ///
487
+ /// Exactly one of `path` and `package` is set: a `path` is ready to load,
488
+ /// a `package` must be located with `importlib` first — unless `shadow`
489
+ /// names an existing file, which takes precedence over the package.
490
+ #[pyclass(name = "ExtensionPlanEntry", frozen)]
491
+ struct PyExtensionPlanEntry {
492
+ #[pyo3(get)]
493
+ path: Option<String>,
494
+ #[pyo3(get)]
495
+ package: Option<String>,
496
+ #[pyo3(get)]
497
+ shadow: Option<String>,
498
+ #[pyo3(get)]
499
+ entrypoint: Option<String>,
500
+ }
501
+
502
+ #[pymethods]
503
+ impl PyExtensionPlanEntry {
504
+ fn __repr__(&self) -> String {
505
+ format!(
506
+ "ExtensionPlanEntry(path={:?}, package={:?}, shadow={:?}, entrypoint={:?})",
507
+ self.path, self.package, self.shadow, self.entrypoint
508
+ )
509
+ }
510
+ }
511
+
512
+ fn plan_entry_to_py(entry: PlanEntry) -> PyExtensionPlanEntry {
513
+ match entry {
514
+ PlanEntry::Literal { path, entrypoint } => PyExtensionPlanEntry {
515
+ path: Some(display(&path)),
516
+ package: None,
517
+ shadow: None,
518
+ entrypoint,
519
+ },
520
+ PlanEntry::Package {
521
+ name,
522
+ shadow,
523
+ entrypoint,
524
+ } => PyExtensionPlanEntry {
525
+ path: None,
526
+ package: Some(name),
527
+ shadow: Some(display(&shadow)),
528
+ entrypoint,
529
+ },
530
+ }
531
+ }
532
+
533
+ fn display(path: &Path) -> String {
534
+ path.to_string_lossy().into_owned()
535
+ }
536
+
537
+ /// Plan several configs' `[[dirsql.extension]]` entries.
538
+ ///
539
+ /// `configs` pairs each config's absolute path with its contents, or
540
+ /// `None` when the launcher could not read it. `None` back means no entry
541
+ /// names a package, so the core's own config loading handles them all.
542
+ #[pyfunction]
543
+ fn plan_config_extensions(
544
+ configs: Vec<(String, Option<String>)>,
545
+ ) -> Option<Vec<PyExtensionPlanEntry>> {
546
+ let sources: Vec<ConfigSource<'_>> = configs
547
+ .iter()
548
+ .map(|(path, contents)| ConfigSource {
549
+ path: Path::new(path),
550
+ contents: contents.as_deref(),
551
+ })
552
+ .collect();
553
+ let plan = extension_resolution::plan_config_extensions(&sources, PY_SUFFIXES)?;
554
+ Some(plan.into_iter().map(plan_entry_to_py).collect())
555
+ }
556
+
557
+ /// Pick package `name`'s single loadable file out of `candidates`.
558
+ #[pyfunction]
559
+ fn select_loadable(name: &str, dirs: Vec<String>, candidates: Vec<String>) -> PyResult<String> {
560
+ selected_loadable(name, dirs, candidates).map_err(PyValueError::new_err)
561
+ }
562
+
563
+ /// The interpreter-free half of [`select_loadable`], so it stays unit-testable.
564
+ fn selected_loadable(
565
+ name: &str,
566
+ dirs: Vec<String>,
567
+ candidates: Vec<String>,
568
+ ) -> Result<String, String> {
569
+ let dirs: Vec<PathBuf> = dirs.into_iter().map(PathBuf::from).collect();
570
+ let candidates: Vec<PathBuf> = candidates.into_iter().map(PathBuf::from).collect();
571
+ extension_resolution::select_loadable(name, &dirs, &candidates, PLATFORM_SUFFIXES)
572
+ .map(|path| display(&path))
573
+ .map_err(|err| err.to_string())
574
+ }
575
+
576
+ /// Whether an extension entry names a package rather than a file.
577
+ #[pyfunction]
578
+ fn is_bare_name(path: &str) -> bool {
579
+ extension_resolution::is_bare_name(path, PY_SUFFIXES)
580
+ }
581
+
472
582
  #[pymodule]
473
583
  #[pyo3(name = "_dirsql")]
474
584
  fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
@@ -479,6 +589,10 @@ mod python {
479
589
  m.add_class::<PyScanFailure>()?;
480
590
  m.add_function(wrap_pyfunction!(run_cli, m)?)?;
481
591
  m.add_function(wrap_pyfunction!(config_paths_from_argv, m)?)?;
592
+ m.add_function(wrap_pyfunction!(plan_config_extensions, m)?)?;
593
+ m.add_function(wrap_pyfunction!(select_loadable, m)?)?;
594
+ m.add_function(wrap_pyfunction!(is_bare_name, m)?)?;
595
+ m.add_class::<PyExtensionPlanEntry>()?;
482
596
  Ok(())
483
597
  }
484
598
 
@@ -561,6 +675,101 @@ mod python {
561
675
  assert_eq!(OnFileError("bad".to_string()).to_string(), "bad");
562
676
  }
563
677
 
678
+ #[test]
679
+ fn planning_returns_none_when_no_entry_names_a_package() {
680
+ let plan = plan_config_extensions(vec![(
681
+ "/cfg/.dirsql.toml".into(),
682
+ Some("[[dirsql.extension]]\npath = \"vec0.so\"\n".into()),
683
+ )]);
684
+ assert!(plan.is_none());
685
+ }
686
+
687
+ #[test]
688
+ fn planning_marshals_a_literal_against_its_own_config_directory() {
689
+ let plan = plan_config_extensions(vec![(
690
+ "/cfg/.dirsql.toml".into(),
691
+ Some(
692
+ "[[dirsql.extension]]\npath = \"ext/vec0.so\"\nentrypoint = \"init\"\n\n[[dirsql.extension]]\npath = \"sqlite_vec\"\n"
693
+ .into(),
694
+ ),
695
+ )])
696
+ .expect("a bare name makes the SDK intervene");
697
+
698
+ assert_eq!(plan[0].path.as_deref(), Some("/cfg/ext/vec0.so"));
699
+ assert_eq!(plan[0].package, None);
700
+ assert_eq!(plan[0].shadow, None);
701
+ assert_eq!(plan[0].entrypoint.as_deref(), Some("init"));
702
+ }
703
+
704
+ #[test]
705
+ fn planning_marshals_a_package_with_its_shadow_probe() {
706
+ let plan = plan_config_extensions(vec![(
707
+ "/cfg/.dirsql.toml".into(),
708
+ Some("[[dirsql.extension]]\npath = \"sqlite_vec\"\n".into()),
709
+ )])
710
+ .expect("a bare name makes the SDK intervene");
711
+
712
+ assert_eq!(plan[0].path, None);
713
+ assert_eq!(plan[0].package.as_deref(), Some("sqlite_vec"));
714
+ assert_eq!(plan[0].shadow.as_deref(), Some("/cfg/sqlite_vec"));
715
+ assert_eq!(plan[0].entrypoint, None);
716
+ }
717
+
718
+ #[test]
719
+ fn an_unreadable_config_is_skipped_rather_than_reported() {
720
+ assert!(plan_config_extensions(vec![("/cfg/.dirsql.toml".into(), None)]).is_none());
721
+ }
722
+
723
+ #[test]
724
+ fn plan_entry_repr_names_every_field() {
725
+ let entry = plan_entry_to_py(PlanEntry::Package {
726
+ name: "vec".into(),
727
+ shadow: "/cfg/vec".into(),
728
+ entrypoint: Some("init".into()),
729
+ });
730
+ assert_eq!(
731
+ entry.__repr__(),
732
+ "ExtensionPlanEntry(path=None, package=Some(\"vec\"), shadow=Some(\"/cfg/vec\"), entrypoint=Some(\"init\"))"
733
+ );
734
+ }
735
+
736
+ #[cfg(target_os = "macos")]
737
+ const EXPECTED_PATTERNS: &str = "*.dylib";
738
+ #[cfg(target_os = "windows")]
739
+ const EXPECTED_PATTERNS: &str = "*.dll / *.pyd";
740
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
741
+ const EXPECTED_PATTERNS: &str = "*.so";
742
+
743
+ #[test]
744
+ fn select_loadable_returns_the_single_match() {
745
+ let found = selected_loadable(
746
+ "vec",
747
+ vec!["/pkg".into()],
748
+ vec!["/pkg/__init__.py".into(), "/pkg/vec0.so".into()],
749
+ );
750
+ assert_eq!(found, Ok("/pkg/vec0.so".to_string()));
751
+ }
752
+
753
+ #[test]
754
+ fn select_loadable_carries_the_cores_message_verbatim() {
755
+ let err = selected_loadable("vec", vec!["/pkg".into()], vec![])
756
+ .expect_err("no loadable file");
757
+ assert_eq!(
758
+ err,
759
+ format!(
760
+ "no loadable extension file ({EXPECTED_PATTERNS}) found in package 'vec' (searched /pkg)"
761
+ )
762
+ );
763
+ }
764
+
765
+ #[test]
766
+ fn is_bare_name_applies_the_cpython_suffix_list() {
767
+ assert!(is_bare_name("sqlite_vec"));
768
+ assert!(!is_bare_name("vec0.pyd"));
769
+ assert!(is_bare_name("vec0.node"));
770
+ assert!(!is_bare_name("ext/vec0"));
771
+ }
772
+
564
773
  #[test]
565
774
  fn config_paths_from_argv_forwards_to_the_core_scan() {
566
775
  assert_eq!(
@@ -6,7 +6,7 @@ description = "Ephemeral SQL index over a local directory"
6
6
  # version stays for the binding crates (publish = false). The literal
7
7
  # intentionally lags the published version: putitoutthere rewrites it to
8
8
  # the planned release version at build time and never commits it back.
9
- version = "0.4.44"
9
+ version = "0.4.46"
10
10
  edition.workspace = true
11
11
  # Literal `license` (not `license.workspace = true`) because
12
12
  # putitoutthere's preflight check reads `[package].license` directly
@@ -19,10 +19,6 @@ repository = "https://github.com/thekevinscott/dirsql"
19
19
  keywords = ["sql", "filesystem", "directory", "sqlite", "index"]
20
20
  readme = "README.md"
21
21
  dependencies = [
22
- # `tomllib` is stdlib only on 3.11+; `resolve_config_extensions` falls back
23
- # to the backport it was derived from. Drop this the day the floor reaches
24
- # 3.11 again.
25
- 'tomli>=2; python_version < "3.11"',
26
22
  # The console script runs the CLI in-process through this (#738); without
27
23
  # it `dirsql.cli.main` cannot import.
28
24
  "bin-shim>=0.1",
@@ -1,14 +0,0 @@
1
- """Whether any of a config's extension entries names a package."""
2
-
3
- from __future__ import annotations
4
-
5
- from .is_bare_name import is_bare_name
6
-
7
-
8
- def _has_bare_name(entries):
9
- return any(
10
- isinstance(e, dict)
11
- and isinstance(e.get("path"), str)
12
- and is_bare_name(e["path"])
13
- for e in entries
14
- )
@@ -1,14 +0,0 @@
1
- """Whether an extension ``path`` names a package rather than a file."""
2
-
3
- import os
4
-
5
- # Suffixes that mark a value as "already a file path", so package resolution
6
- # is never attempted.
7
- _LOADABLE_SUFFIXES = (".so", ".dylib", ".dll", ".pyd")
8
-
9
-
10
- def is_bare_name(path):
11
- """True when ``path`` is a bare package name rather than a file path."""
12
- if os.sep in path or (os.altsep and os.altsep in path):
13
- return False
14
- return not path.endswith(_LOADABLE_SUFFIXES)
@@ -1,28 +0,0 @@
1
- """Reading one TOML config's ``[[dirsql.extension]]`` array off disk."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
-
7
- from .toml_module import _toml
8
-
9
-
10
- def _load_extension_entries(config_path):
11
- """Return ``(entries, base_dir)`` for a config's ``[[dirsql.extension]]``.
12
-
13
- ``None`` when the config is missing, unreadable/malformed, or declares no
14
- extension array -- the caller should leave such configs to the core.
15
- """
16
- if not os.path.isfile(config_path):
17
- return None
18
- try:
19
- with open(config_path, "rb") as f:
20
- doc = _toml.load(f)
21
- except (OSError, _toml.TOMLDecodeError):
22
- # Leave a malformed / unreadable config for the core to report.
23
- return None
24
-
25
- entries = (doc.get("dirsql") or {}).get("extension")
26
- if not isinstance(entries, list):
27
- return None
28
- return entries, os.path.dirname(os.path.abspath(config_path))
@@ -1,12 +0,0 @@
1
- """The loadable-file glob pattern(s) the running platform uses."""
2
-
3
- import sys
4
-
5
-
6
- def _platform_patterns():
7
- """Loadable-file glob(s) for the current platform."""
8
- if sys.platform == "darwin":
9
- return ("*.dylib",)
10
- if sys.platform == "win32":
11
- return ("*.dll", "*.pyd")
12
- return ("*.so",)
@@ -1,39 +0,0 @@
1
- """SDK-side resolution of a TOML config's ``[[dirsql.extension]]`` entries.
2
-
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.
9
-
10
- Shared by the ``DirSQL`` constructor (``config=`` path) and the CLI launcher
11
- (which converts the resolved specs into ``--extension`` flags), both of which
12
- reach it through :mod:`dirsql.resolve_configs_extension_specs`.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- from .has_bare_name import _has_bare_name
18
- from .load_extension_entries import _load_extension_entries
19
- from .resolve_entries import _resolve_entries
20
-
21
-
22
- def resolve_config_extension_specs(config_path):
23
- """Resolve a TOML config's ``[[dirsql.extension]]`` entries to literal paths.
24
-
25
- Returns a list of ``{"path", "entrypoint"}`` dicts -- every entry resolved
26
- via :func:`resolve_extension_path` against the config file's parent
27
- directory -- when at least one entry's ``path`` is a bare package name.
28
- Returns ``None`` when the caller should not intervene: the config is
29
- missing, malformed, declares no extensions, or uses only literal paths --
30
- leaving the core's own loading (and error reporting) untouched. Raises if
31
- a package name cannot be resolved.
32
- """
33
- loaded = _load_extension_entries(config_path)
34
- if loaded is None:
35
- return None
36
- entries, base = loaded
37
- if not _has_bare_name(entries):
38
- return None
39
- return _resolve_entries(entries, base)
@@ -1,20 +0,0 @@
1
- """Resolution of one config's ``[[dirsql.extension]]`` entries to literal paths."""
2
-
3
- from __future__ import annotations
4
-
5
- from .resolve_extension import resolve_extension_path
6
-
7
-
8
- def _resolve_entries(entries, base):
9
- specs = []
10
- for e in entries:
11
- entrypoint = e.get("entrypoint")
12
- specs.append(
13
- {
14
- "path": resolve_extension_path(
15
- e["path"], base=base, resolve_relative=True
16
- ),
17
- "entrypoint": entrypoint if isinstance(entrypoint, str) else None,
18
- }
19
- )
20
- return specs