dirsql 0.3.50__tar.gz → 0.3.52__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 (123) hide show
  1. {dirsql-0.3.50 → dirsql-0.3.52}/Cargo.lock +1 -1
  2. {dirsql-0.3.50 → dirsql-0.3.52}/PKG-INFO +1 -1
  3. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/_async.py +38 -12
  4. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/_dirsql.pyi +1 -0
  5. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/cli/resolve_config_extensions.py +17 -41
  6. dirsql-0.3.52/dirsql/resolve_config_extensions.py +71 -0
  7. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/resolve_extension.py +13 -9
  8. {dirsql-0.3.50 → dirsql-0.3.52}/docs/api/index.md +1 -1
  9. {dirsql-0.3.50 → dirsql-0.3.52}/docs/cli/config.md +3 -1
  10. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/Cargo.toml +1 -1
  11. {dirsql-0.3.50/packages/rust → dirsql-0.3.52/packages/python}/docs/api/index.md +1 -1
  12. {dirsql-0.3.50/packages/rust → dirsql-0.3.52/packages/python}/docs/cli/config.md +3 -1
  13. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/e2e-attestation.json +2 -2
  14. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/src/lib.rs +10 -2
  15. dirsql-0.3.52/packages/python/tests/smoke/__init__.py +0 -0
  16. {dirsql-0.3.50/packages/python → dirsql-0.3.52/packages/rust}/docs/api/index.md +1 -1
  17. {dirsql-0.3.50/packages/python → dirsql-0.3.52/packages/rust}/docs/cli/config.md +3 -1
  18. {dirsql-0.3.50 → dirsql-0.3.52}/Cargo.toml +0 -0
  19. {dirsql-0.3.50 → dirsql-0.3.52}/README.md +0 -0
  20. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/__init__.py +0 -0
  21. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/cli/__init__.py +0 -0
  22. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/cli/binary_path.py +0 -0
  23. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/cli/interpret/__init__.py +0 -0
  24. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/cli/is_windows.py +0 -0
  25. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/cli/main.py +0 -0
  26. {dirsql-0.3.50 → dirsql-0.3.52}/dirsql/py.typed +0 -0
  27. {dirsql-0.3.50 → dirsql-0.3.52}/docs/.claude/CLAUDE.md +0 -0
  28. {dirsql-0.3.50 → dirsql-0.3.52}/docs/.vitepress/config.ts +0 -0
  29. {dirsql-0.3.50 → dirsql-0.3.52}/docs/.vitepress/theme/index.ts +0 -0
  30. {dirsql-0.3.50 → dirsql-0.3.52}/docs/.vitepress/theme/lang.ts +0 -0
  31. {dirsql-0.3.50 → dirsql-0.3.52}/docs/AGENTS.md +0 -0
  32. {dirsql-0.3.50 → dirsql-0.3.52}/docs/cli/http-api.md +0 -0
  33. {dirsql-0.3.50 → dirsql-0.3.52}/docs/cli/index.md +0 -0
  34. {dirsql-0.3.50 → dirsql-0.3.52}/docs/cli/init.md +0 -0
  35. {dirsql-0.3.50 → dirsql-0.3.52}/docs/cli/server.md +0 -0
  36. {dirsql-0.3.50 → dirsql-0.3.52}/docs/getting-started.md +0 -0
  37. {dirsql-0.3.50 → dirsql-0.3.52}/docs/guide/async.md +0 -0
  38. {dirsql-0.3.50 → dirsql-0.3.52}/docs/guide/crdt.md +0 -0
  39. {dirsql-0.3.50 → dirsql-0.3.52}/docs/guide/persistence.md +0 -0
  40. {dirsql-0.3.50 → dirsql-0.3.52}/docs/guide/querying.md +0 -0
  41. {dirsql-0.3.50 → dirsql-0.3.52}/docs/guide/tables.md +0 -0
  42. {dirsql-0.3.50 → dirsql-0.3.52}/docs/guide/watching.md +0 -0
  43. {dirsql-0.3.50 → dirsql-0.3.52}/docs/index.md +0 -0
  44. {dirsql-0.3.50 → dirsql-0.3.52}/docs/migrations.md +0 -0
  45. {dirsql-0.3.50 → dirsql-0.3.52}/docs/package.json +0 -0
  46. {dirsql-0.3.50 → dirsql-0.3.52}/docs/playwright.config.ts +0 -0
  47. {dirsql-0.3.50 → dirsql-0.3.52}/docs/pnpm-lock.yaml +0 -0
  48. {dirsql-0.3.50 → dirsql-0.3.52}/docs/pnpm-workspace.yaml +0 -0
  49. {dirsql-0.3.50 → dirsql-0.3.52}/docs/tests/integration/home.spec.ts +0 -0
  50. {dirsql-0.3.50 → dirsql-0.3.52}/docs/tests/integration/language-flag.spec.ts +0 -0
  51. {dirsql-0.3.50 → dirsql-0.3.52}/docs/tests/integration/sidebar.spec.ts +0 -0
  52. {dirsql-0.3.50 → dirsql-0.3.52}/docs/tests/unit/config.test.ts +0 -0
  53. {dirsql-0.3.50 → dirsql-0.3.52}/docs/tests/unit/lang.test.ts +0 -0
  54. {dirsql-0.3.50 → dirsql-0.3.52}/docs/vitest.config.ts +0 -0
  55. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/README.md +0 -0
  56. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/conftest.py +0 -0
  57. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/.claude/CLAUDE.md +0 -0
  58. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/.vitepress/config.ts +0 -0
  59. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  60. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  61. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/AGENTS.md +0 -0
  62. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/cli/http-api.md +0 -0
  63. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/cli/index.md +0 -0
  64. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/cli/init.md +0 -0
  65. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/cli/server.md +0 -0
  66. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/getting-started.md +0 -0
  67. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/guide/async.md +0 -0
  68. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/guide/crdt.md +0 -0
  69. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/guide/persistence.md +0 -0
  70. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/guide/querying.md +0 -0
  71. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/guide/tables.md +0 -0
  72. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/guide/watching.md +0 -0
  73. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/index.md +0 -0
  74. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/migrations.md +0 -0
  75. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/package.json +0 -0
  76. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/playwright.config.ts +0 -0
  77. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/pnpm-lock.yaml +0 -0
  78. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/pnpm-workspace.yaml +0 -0
  79. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  80. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  81. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
  82. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/tests/unit/config.test.ts +0 -0
  83. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  84. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/docs/vitest.config.ts +0 -0
  85. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/tests/__init__.py +0 -0
  86. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/tests/conftest.py +0 -0
  87. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/tests/e2e/__init__.py +0 -0
  88. {dirsql-0.3.50 → dirsql-0.3.52}/packages/python/tests/integration/__init__.py +0 -0
  89. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/Cargo.toml +0 -0
  90. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/README.md +0 -0
  91. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/benches/db_bench.rs +0 -0
  92. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/benches/differ_bench.rs +0 -0
  93. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/benches/matcher_bench.rs +0 -0
  94. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/benches/scanner_bench.rs +0 -0
  95. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/cli/http-api.md +0 -0
  96. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/cli/index.md +0 -0
  97. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/cli/init.md +0 -0
  98. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/cli/server.md +0 -0
  99. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/getting-started.md +0 -0
  100. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/guide/async.md +0 -0
  101. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/guide/crdt.md +0 -0
  102. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/guide/persistence.md +0 -0
  103. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/guide/querying.md +0 -0
  104. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/guide/tables.md +0 -0
  105. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/guide/watching.md +0 -0
  106. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/index.md +0 -0
  107. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/docs/migrations.md +0 -0
  108. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/bin/dirsql.rs +0 -0
  109. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/cli/init.rs +0 -0
  110. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/cli/mod.rs +0 -0
  111. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/cli/router.rs +0 -0
  112. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/cli/serialize.rs +0 -0
  113. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/cli/server.rs +0 -0
  114. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/command.rs +0 -0
  115. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/config.rs +0 -0
  116. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/db.rs +0 -0
  117. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/differ.rs +0 -0
  118. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/lib.rs +0 -0
  119. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/matcher.rs +0 -0
  120. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/persist.rs +0 -0
  121. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/scanner.rs +0 -0
  122. {dirsql-0.3.50 → dirsql-0.3.52}/packages/rust/src/watcher.rs +0 -0
  123. {dirsql-0.3.50 → dirsql-0.3.52}/pyproject.toml +0 -0
@@ -501,7 +501,7 @@ dependencies = [
501
501
 
502
502
  [[package]]
503
503
  name = "dirsql-py-ext"
504
- version = "0.3.50"
504
+ version = "0.3.52"
505
505
  dependencies = [
506
506
  "dirsql",
507
507
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.50
3
+ Version: 0.3.52
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -4,6 +4,7 @@ import asyncio
4
4
  import os
5
5
 
6
6
  from dirsql._dirsql import DirSQL as _RustDirSQL
7
+ from dirsql.resolve_config_extensions import resolve_config_extension_specs
7
8
  from dirsql.resolve_extension import resolve_extension_path
8
9
 
9
10
 
@@ -56,7 +57,9 @@ class DirSQL:
56
57
  Pass ``extensions`` -- a list of ``{"path": ..., "entrypoint": ...}`` dicts
57
58
  (``entrypoint`` optional) -- to load SQLite extensions onto the connection
58
59
  at startup. Any ``[[dirsql.extension]]`` entries in a ``config`` file are
59
- appended after the programmatic ones.
60
+ appended after the programmatic ones. A ``path`` (programmatic or
61
+ config-file) may be a bare **package name**, resolved from the installed
62
+ package in the runtime env (#298 / #313).
60
63
  """
61
64
 
62
65
  def __init__(
@@ -85,28 +88,51 @@ class DirSQL:
85
88
  async def _init_bg(self):
86
89
  """Run the scan in the background."""
87
90
  try:
88
- self._db = await asyncio.to_thread(
89
- _RustDirSQL,
90
- self._root,
91
- tables=self._tables,
92
- ignore=self._ignore,
93
- config=self._config,
94
- persist=self._persist,
95
- persist_path=self._persist_path,
96
- extensions=self._resolved_extensions(),
97
- )
91
+ self._db = await asyncio.to_thread(self._build_db)
98
92
  except Exception as exc:
99
93
  self._init_error = exc
100
94
  finally:
101
95
  self._ready_event.set()
102
96
 
97
+ def _build_db(self):
98
+ """Resolve extensions and construct the Rust-backed instance.
99
+
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.
103
+
104
+ When the ``config`` file names an extension by bare package name, the
105
+ 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).
110
+ """
111
+ extensions = self._resolved_extensions()
112
+ suppress = False
113
+ if self._config is not None:
114
+ config_extensions = resolve_config_extension_specs(self._config)
115
+ if config_extensions is not None:
116
+ extensions = [*(extensions or []), *config_extensions]
117
+ suppress = True
118
+ return _RustDirSQL(
119
+ self._root,
120
+ tables=self._tables,
121
+ ignore=self._ignore,
122
+ config=self._config,
123
+ persist=self._persist,
124
+ persist_path=self._persist_path,
125
+ extensions=extensions,
126
+ suppress_config_extensions=suppress,
127
+ )
128
+
103
129
  def _resolved_extensions(self):
104
130
  """Resolve each programmatic extension's ``path`` to a loadable file.
105
131
 
106
132
  A bare package name is resolved to the loadable installed in the runtime
107
133
  env (#298); path-looking values are passed through verbatim (mirroring
108
134
  the Rust builder, which takes programmatic paths as-is). Config-file
109
- ``[[dirsql.extension]]`` entries are resolved by the Rust core, not here.
135
+ ``[[dirsql.extension]]`` entries are handled by ``_build_db``.
110
136
  """
111
137
  if not self._extensions:
112
138
  return self._extensions
@@ -69,6 +69,7 @@ class DirSQL:
69
69
  persist: bool = False,
70
70
  persist_path: str | PathLike[str] | None = None,
71
71
  extensions: list[ExtensionSpec] | None = None,
72
+ suppress_config_extensions: bool = False,
72
73
  ) -> None: ...
73
74
  def query(self, sql: str) -> list[Row]: ...
74
75
  def _start_watcher(self) -> None: ...
@@ -3,11 +3,12 @@
3
3
  Mirrors the TypeScript launcher. The compiled ``dirsql`` binary reads a
4
4
  ``.dirsql.toml`` itself and loads its extensions literally -- it has no
5
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, we resolve
7
- every one of its extensions here and pass the resolved literal paths to the
8
- binary via repeatable ``--extension`` flags; the binary then loads those and
9
- ignores the config's own extension entries (the Rust ``--extension`` flag /
10
- ``suppress_config_extensions``).
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``).
11
12
 
12
13
  Native-language configs (``.py`` / ``.js`` / ``.mjs`` / ``.cjs``) are untouched:
13
14
  the binary dispatches those to ``dirsql interpret``, whose handshake already
@@ -16,10 +17,7 @@ carries resolved paths.
16
17
 
17
18
  from __future__ import annotations
18
19
 
19
- import os
20
- import tomllib
21
-
22
- from ..resolve_extension import is_bare_name, resolve_extension_path
20
+ from ..resolve_config_extensions import resolve_config_extension_specs
23
21
 
24
22
  # Config extensions the binary dispatches to `dirsql interpret`; never
25
23
  # pre-resolved here (that path resolves via the handshake).
@@ -28,14 +26,12 @@ _NATIVE_SUFFIXES = (".py", ".js", ".mjs", ".cjs")
28
26
 
29
27
  def _config_path_from_argv(argv: list[str]) -> str:
30
28
  """The ``--config`` value (``--config X`` or ``--config=X``), or the default."""
31
- i = 0
32
- while i < len(argv):
33
- a = argv[i]
29
+ for i, a in enumerate(argv):
34
30
  if a == "--config":
35
- return argv[i + 1] if i + 1 < len(argv) else ""
31
+ # A bare trailing `--config` (no following value) yields "".
32
+ return next(iter(argv[i + 1 :]), "")
36
33
  if a.startswith("--config="):
37
34
  return a[len("--config=") :]
38
- i += 1
39
35
  return "./.dirsql.toml"
40
36
 
41
37
 
@@ -48,34 +44,14 @@ def with_resolved_extensions(argv: list[str]) -> list[str]:
48
44
  config_path = _config_path_from_argv(argv)
49
45
  if config_path.endswith(_NATIVE_SUFFIXES):
50
46
  return argv
51
- if not os.path.isfile(config_path):
52
- return argv
53
- try:
54
- with open(config_path, "rb") as f:
55
- doc = tomllib.load(f)
56
- except (OSError, tomllib.TOMLDecodeError):
57
- # Leave a malformed / unreadable config for the binary to report.
58
- return argv
59
-
60
- cfg = doc.get("dirsql") or {}
61
- entries = cfg.get("extension") or []
62
- if not isinstance(entries, list) or not entries:
63
- return argv
64
- # Only intervene when at least one path is a bare package name; a config
65
- # with only literal paths keeps the binary's existing behavior untouched.
66
- if not any(
67
- isinstance(e, dict)
68
- and isinstance(e.get("path"), str)
69
- and is_bare_name(e["path"])
70
- for e in entries
71
- ):
47
+ specs = resolve_config_extension_specs(config_path)
48
+ if specs is None:
72
49
  return argv
73
-
74
- base = os.path.dirname(os.path.abspath(config_path))
75
50
  flags: list[str] = []
76
- for e in entries:
77
- path = resolve_extension_path(e["path"], base=base, resolve_relative=True)
78
- entrypoint = e.get("entrypoint")
51
+ for spec in specs:
52
+ entrypoint = spec["entrypoint"]
79
53
  flags.append("--extension")
80
- flags.append(f"{path}::{entrypoint}" if isinstance(entrypoint, str) else path)
54
+ flags.append(
55
+ f"{spec['path']}::{entrypoint}" if entrypoint is not None else spec["path"]
56
+ )
81
57
  return [*argv, *flags]
@@ -0,0 +1,71 @@
1
+ """SDK-side resolution of a TOML config's ``[[dirsql.extension]]`` entries.
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.
10
+
11
+ 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).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import tomllib
20
+
21
+ from .resolve_extension import is_bare_name, resolve_extension_path
22
+
23
+
24
+ def resolve_config_extension_specs(config_path):
25
+ """Resolve a TOML config's ``[[dirsql.extension]]`` entries to literal paths.
26
+
27
+ Returns a list of ``{"path", "entrypoint"}`` dicts -- every entry resolved
28
+ via :func:`resolve_extension_path` against the config file's parent
29
+ directory -- when at least one entry's ``path`` is a bare package name.
30
+ Returns ``None`` when the caller should not intervene: the config is
31
+ missing, malformed, declares no extensions, or uses only literal paths --
32
+ leaving the core's own loading (and error reporting) untouched. Raises if
33
+ a package name cannot be resolved.
34
+ """
35
+ if not os.path.isfile(config_path):
36
+ return None
37
+ try:
38
+ with open(config_path, "rb") as f:
39
+ doc = tomllib.load(f)
40
+ except (OSError, tomllib.TOMLDecodeError):
41
+ # Leave a malformed / unreadable config for the core to report.
42
+ return None
43
+
44
+ cfg = doc.get("dirsql") or {}
45
+ entries = cfg.get("extension")
46
+ if not isinstance(entries, list):
47
+ 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.
51
+ if not any(
52
+ isinstance(e, dict)
53
+ and isinstance(e.get("path"), str)
54
+ and is_bare_name(e["path"])
55
+ for e in entries
56
+ ):
57
+ return None
58
+
59
+ base = os.path.dirname(os.path.abspath(config_path))
60
+ specs = []
61
+ for e in entries:
62
+ entrypoint = e.get("entrypoint")
63
+ specs.append(
64
+ {
65
+ "path": resolve_extension_path(
66
+ e["path"], base=base, resolve_relative=True
67
+ ),
68
+ "entrypoint": entrypoint if isinstance(entrypoint, str) else None,
69
+ }
70
+ )
71
+ return specs
@@ -73,18 +73,22 @@ def _resolve_package(name):
73
73
  matches.update(_glob.glob(os.path.join(d, "**", pat), recursive=True))
74
74
  found = sorted(matches)
75
75
 
76
- pat_desc = " / ".join(patterns)
77
- if not found:
78
- raise ValueError(
79
- f"no loadable extension file ({pat_desc}) found in package "
80
- f"{name!r} (searched {', '.join(dirs)})"
81
- )
82
- if len(found) > 1:
76
+ # Exactly one loadable resolves; unpacking (rather than a guarded
77
+ # subscript) makes the zero- and multiple-match failures each observable.
78
+ try:
79
+ (single,) = found
80
+ except ValueError:
81
+ pat_desc = " / ".join(patterns)
82
+ if not found:
83
+ raise ValueError(
84
+ f"no loadable extension file ({pat_desc}) found in package "
85
+ f"{name!r} (searched {', '.join(dirs)})"
86
+ ) from None
83
87
  raise ValueError(
84
88
  f"multiple loadable extension files found in package {name!r}: "
85
89
  f"{', '.join(found)}; disambiguate with a literal path"
86
- )
87
- return found[0]
90
+ ) from None
91
+ return single
88
92
 
89
93
 
90
94
  def resolve_extension_path(path, base, resolve_relative):
@@ -77,7 +77,7 @@ In Python, the constructor starts scanning in a background thread and returns im
77
77
  - `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
78
78
  - `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
79
79
  - `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
80
- - `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor and CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
80
+ - `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor — for both the programmatic `extensions` list and a `config` file's `[[dirsql.extension]]` entries — and on the CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
81
81
 
82
82
  ### Methods
83
83
 
@@ -161,7 +161,9 @@ entrypoint = "sqlite3_myext_init"
161
161
  - **`path`** — the extension's shared library: either a file path (`.so` /
162
162
  `.dylib` / `.dll`, relative to the config file's directory) or a bare
163
163
  **package name**. A package name is resolved from the installed package when
164
- run through the pip/npm `dirsql` CLI; the standalone Rust binary is
164
+ run through the pip/npm `dirsql` CLI, or when a Python/TypeScript `DirSQL`
165
+ loads the config in-process (`DirSQL(config=...)` /
166
+ `new DirSQL(configPath)`); the standalone Rust binary and the Rust SDK are
165
167
  file-path-only.
166
168
  - **`entrypoint`** *(optional)* — the extension's init symbol. When omitted,
167
169
  SQLite derives a default from the filename; set it when that default does not
@@ -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.50"
7
+ version = "0.3.52"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -77,7 +77,7 @@ In Python, the constructor starts scanning in a background thread and returns im
77
77
  - `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
78
78
  - `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
79
79
  - `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
80
- - `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor and CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
80
+ - `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor — for both the programmatic `extensions` list and a `config` file's `[[dirsql.extension]]` entries — and on the CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
81
81
 
82
82
  ### Methods
83
83
 
@@ -161,7 +161,9 @@ entrypoint = "sqlite3_myext_init"
161
161
  - **`path`** — the extension's shared library: either a file path (`.so` /
162
162
  `.dylib` / `.dll`, relative to the config file's directory) or a bare
163
163
  **package name**. A package name is resolved from the installed package when
164
- run through the pip/npm `dirsql` CLI; the standalone Rust binary is
164
+ run through the pip/npm `dirsql` CLI, or when a Python/TypeScript `DirSQL`
165
+ loads the config in-process (`DirSQL(config=...)` /
166
+ `new DirSQL(configPath)`); the standalone Rust binary and the Rust SDK are
165
167
  file-path-only.
166
168
  - **`entrypoint`** *(optional)* — the extension's init symbol. When omitted,
167
169
  SQLite derives a default from the filename; set it when that default does not
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "command": "uv run python -m pytest tests/e2e/ -x -q",
3
- "ran_at": 1783031403,
3
+ "ran_at": 1783076458,
4
4
  "exit_code": 0,
5
- "commit": "3d7c9268191f94a3180442716aa1c49f48699776"
5
+ "commit": "b1df6dc7f05c1dec2c7189cd1489283b6b547478"
6
6
  }
@@ -114,8 +114,13 @@ mod python {
114
114
 
115
115
  #[pymethods]
116
116
  impl PyDirSQL {
117
+ /// `suppress_config_extensions` skips the core's own loading of the
118
+ /// config's `[[dirsql.extension]]` entries; the SDK sets it after
119
+ /// 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.
117
122
  #[new]
118
- #[pyo3(signature = (root=None, *, tables=None, ignore=None, config=None, persist=false, persist_path=None, extensions=None))]
123
+ #[pyo3(signature = (root=None, *, tables=None, ignore=None, config=None, persist=false, persist_path=None, extensions=None, suppress_config_extensions=false))]
119
124
  fn new(
120
125
  py: Python<'_>,
121
126
  root: Option<String>,
@@ -125,6 +130,7 @@ mod python {
125
130
  persist: bool,
126
131
  persist_path: Option<PathBuf>,
127
132
  extensions: Option<Vec<PyExtensionSpec>>,
133
+ suppress_config_extensions: bool,
128
134
  ) -> PyResult<Self> {
129
135
  let rust_tables: Vec<Table> = tables
130
136
  .as_deref()
@@ -164,7 +170,9 @@ mod python {
164
170
  if !rust_extensions.is_empty() {
165
171
  builder = builder.extensions(rust_extensions);
166
172
  }
167
- builder.build()
173
+ builder
174
+ .suppress_config_extensions(suppress_config_extensions)
175
+ .build()
168
176
  })
169
177
  .map_err(to_py_err)?;
170
178
 
File without changes
@@ -77,7 +77,7 @@ In Python, the constructor starts scanning in a background thread and returns im
77
77
  - `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
78
78
  - `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
79
79
  - `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
80
- - `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor and CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
80
+ - `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor — for both the programmatic `extensions` list and a `config` file's `[[dirsql.extension]]` entries — and on the CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
81
81
 
82
82
  ### Methods
83
83
 
@@ -161,7 +161,9 @@ entrypoint = "sqlite3_myext_init"
161
161
  - **`path`** — the extension's shared library: either a file path (`.so` /
162
162
  `.dylib` / `.dll`, relative to the config file's directory) or a bare
163
163
  **package name**. A package name is resolved from the installed package when
164
- run through the pip/npm `dirsql` CLI; the standalone Rust binary is
164
+ run through the pip/npm `dirsql` CLI, or when a Python/TypeScript `DirSQL`
165
+ loads the config in-process (`DirSQL(config=...)` /
166
+ `new DirSQL(configPath)`); the standalone Rust binary and the Rust SDK are
165
167
  file-path-only.
166
168
  - **`entrypoint`** *(optional)* — the extension's init symbol. When omitted,
167
169
  SQLite derives a default from the filename; set it when that default does not
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes