dirsql 0.3.14__tar.gz → 0.3.16__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 (141) hide show
  1. {dirsql-0.3.14 → dirsql-0.3.16}/Cargo.lock +1 -1
  2. {dirsql-0.3.14 → dirsql-0.3.16}/PKG-INFO +2 -1
  3. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/_async.py +2 -2
  4. dirsql-0.3.16/dirsql/_dirsql.pyi +63 -0
  5. dirsql-0.3.16/dirsql/cli/binary_path_test.py +51 -0
  6. dirsql-0.3.16/dirsql/cli/is_windows.py +9 -0
  7. dirsql-0.3.16/dirsql/cli/is_windows_test.py +20 -0
  8. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/cli/main.py +1 -1
  9. dirsql-0.3.16/dirsql/cli/main_test.py +87 -0
  10. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/resolve_config.py +1 -1
  11. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/Cargo.toml +1 -1
  12. dirsql-0.3.16/packages/python/tests/e2e/__init__.py +0 -0
  13. {dirsql-0.3.14 → dirsql-0.3.16}/pyproject.toml +30 -10
  14. dirsql-0.3.14/dirsql/cli/is_windows.py +0 -10
  15. {dirsql-0.3.14 → dirsql-0.3.16}/Cargo.toml +0 -0
  16. {dirsql-0.3.14 → dirsql-0.3.16}/README.md +0 -0
  17. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/__init__.py +0 -0
  18. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/cli/__init__.py +0 -0
  19. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/cli/binary_path.py +0 -0
  20. /dirsql-0.3.14/packages/python/tests/e2e/__init__.py → /dirsql-0.3.16/dirsql/py.typed +0 -0
  21. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/resolve_config_test.py +0 -0
  22. {dirsql-0.3.14 → dirsql-0.3.16}/dirsql/test_async.py +0 -0
  23. {dirsql-0.3.14 → dirsql-0.3.16}/docs/.claude/CLAUDE.md +0 -0
  24. {dirsql-0.3.14 → dirsql-0.3.16}/docs/.vitepress/config.ts +0 -0
  25. {dirsql-0.3.14 → dirsql-0.3.16}/docs/.vitepress/theme/index.ts +0 -0
  26. {dirsql-0.3.14 → dirsql-0.3.16}/docs/.vitepress/theme/lang.ts +0 -0
  27. {dirsql-0.3.14 → dirsql-0.3.16}/docs/AGENTS.md +0 -0
  28. {dirsql-0.3.14 → dirsql-0.3.16}/docs/api/index.md +0 -0
  29. {dirsql-0.3.14 → dirsql-0.3.16}/docs/cli/config.md +0 -0
  30. {dirsql-0.3.14 → dirsql-0.3.16}/docs/cli/http-api.md +0 -0
  31. {dirsql-0.3.14 → dirsql-0.3.16}/docs/cli/index.md +0 -0
  32. {dirsql-0.3.14 → dirsql-0.3.16}/docs/cli/init.md +0 -0
  33. {dirsql-0.3.14 → dirsql-0.3.16}/docs/cli/server.md +0 -0
  34. {dirsql-0.3.14 → dirsql-0.3.16}/docs/getting-started.md +0 -0
  35. {dirsql-0.3.14 → dirsql-0.3.16}/docs/guide/async.md +0 -0
  36. {dirsql-0.3.14 → dirsql-0.3.16}/docs/guide/crdt.md +0 -0
  37. {dirsql-0.3.14 → dirsql-0.3.16}/docs/guide/persistence.md +0 -0
  38. {dirsql-0.3.14 → dirsql-0.3.16}/docs/guide/querying.md +0 -0
  39. {dirsql-0.3.14 → dirsql-0.3.16}/docs/guide/tables.md +0 -0
  40. {dirsql-0.3.14 → dirsql-0.3.16}/docs/guide/watching.md +0 -0
  41. {dirsql-0.3.14 → dirsql-0.3.16}/docs/index.md +0 -0
  42. {dirsql-0.3.14 → dirsql-0.3.16}/docs/migrations.md +0 -0
  43. {dirsql-0.3.14 → dirsql-0.3.16}/docs/package.json +0 -0
  44. {dirsql-0.3.14 → dirsql-0.3.16}/docs/playwright.config.ts +0 -0
  45. {dirsql-0.3.14 → dirsql-0.3.16}/docs/pnpm-lock.yaml +0 -0
  46. {dirsql-0.3.14 → dirsql-0.3.16}/docs/pnpm-workspace.yaml +0 -0
  47. {dirsql-0.3.14 → dirsql-0.3.16}/docs/tests/integration/home.spec.ts +0 -0
  48. {dirsql-0.3.14 → dirsql-0.3.16}/docs/tests/integration/language-flag.spec.ts +0 -0
  49. {dirsql-0.3.14 → dirsql-0.3.16}/docs/tests/unit/config.test.ts +0 -0
  50. {dirsql-0.3.14 → dirsql-0.3.16}/docs/tests/unit/lang.test.ts +0 -0
  51. {dirsql-0.3.14 → dirsql-0.3.16}/docs/vitest.config.ts +0 -0
  52. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/README.md +0 -0
  53. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/conftest.py +0 -0
  54. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/.claude/CLAUDE.md +0 -0
  55. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/.vitepress/config.ts +0 -0
  56. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  57. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  58. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/AGENTS.md +0 -0
  59. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/api/index.md +0 -0
  60. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/cli/config.md +0 -0
  61. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/cli/http-api.md +0 -0
  62. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/cli/index.md +0 -0
  63. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/cli/init.md +0 -0
  64. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/cli/server.md +0 -0
  65. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/getting-started.md +0 -0
  66. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/guide/async.md +0 -0
  67. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/guide/crdt.md +0 -0
  68. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/guide/persistence.md +0 -0
  69. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/guide/querying.md +0 -0
  70. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/guide/tables.md +0 -0
  71. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/guide/watching.md +0 -0
  72. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/index.md +0 -0
  73. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/migrations.md +0 -0
  74. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/package.json +0 -0
  75. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/playwright.config.ts +0 -0
  76. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/pnpm-lock.yaml +0 -0
  77. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/pnpm-workspace.yaml +0 -0
  78. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  79. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  80. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/tests/unit/config.test.ts +0 -0
  81. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  82. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/docs/vitest.config.ts +0 -0
  83. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/src/lib.rs +0 -0
  84. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/__init__.py +0 -0
  85. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/conftest.py +0 -0
  86. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/__init__.py +0 -0
  87. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_async_dirsql.py +0 -0
  88. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_binding.py +0 -0
  89. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_dirsql.py +0 -0
  90. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_docs_examples.py +0 -0
  91. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_docs_gaps.py +0 -0
  92. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_from_config.py +0 -0
  93. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_persist.py +0 -0
  94. {dirsql-0.3.14 → dirsql-0.3.16}/packages/python/tests/integration/test_serialization.py +0 -0
  95. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/Cargo.toml +0 -0
  96. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/README.md +0 -0
  97. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/benches/db_bench.rs +0 -0
  98. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/benches/differ_bench.rs +0 -0
  99. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/benches/matcher_bench.rs +0 -0
  100. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/benches/scanner_bench.rs +0 -0
  101. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/api/index.md +0 -0
  102. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/cli/config.md +0 -0
  103. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/cli/http-api.md +0 -0
  104. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/cli/index.md +0 -0
  105. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/cli/init.md +0 -0
  106. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/cli/server.md +0 -0
  107. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/getting-started.md +0 -0
  108. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/guide/async.md +0 -0
  109. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/guide/crdt.md +0 -0
  110. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/guide/persistence.md +0 -0
  111. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/guide/querying.md +0 -0
  112. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/guide/tables.md +0 -0
  113. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/guide/watching.md +0 -0
  114. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/index.md +0 -0
  115. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/docs/migrations.md +0 -0
  116. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/bin/dirsql.rs +0 -0
  117. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/cli/init.rs +0 -0
  118. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/cli/mod.rs +0 -0
  119. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/cli/router.rs +0 -0
  120. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/cli/serialize.rs +0 -0
  121. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/cli/server.rs +0 -0
  122. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/config.rs +0 -0
  123. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/db.rs +0 -0
  124. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/differ.rs +0 -0
  125. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/lib.rs +0 -0
  126. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/matcher.rs +0 -0
  127. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/persist.rs +0 -0
  128. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/scanner.rs +0 -0
  129. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/src/watcher.rs +0 -0
  130. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/async_sdk.rs +0 -0
  131. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/cli_e2e.rs +0 -0
  132. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/cli_integration.rs +0 -0
  133. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/docs_examples.rs +0 -0
  134. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/docs_gaps.rs +0 -0
  135. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/from_config.rs +0 -0
  136. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/init_e2e.rs +0 -0
  137. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/init_integration.rs +0 -0
  138. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/persist.rs +0 -0
  139. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/readonly_query.rs +0 -0
  140. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/sdk.rs +0 -0
  141. {dirsql-0.3.14 → dirsql-0.3.16}/packages/rust/tests/serialization.rs +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.3.14"
502
+ version = "0.3.16"
503
503
  dependencies = [
504
504
  "dirsql",
505
505
  "pyo3",
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.14
3
+ Version: 0.3.16
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'
7
7
  Requires-Dist: pytest-cov>=5 ; extra == 'dev'
8
8
  Requires-Dist: ruff>=0.4 ; extra == 'dev'
9
9
  Requires-Dist: maturin>=1.0 ; extra == 'dev'
10
+ Requires-Dist: ty==0.0.42 ; extra == 'dev'
10
11
  Provides-Extra: dev
11
12
  Summary: Ephemeral SQL index over a local directory
12
13
  Keywords: sql,filesystem,directory,sqlite,index
@@ -104,14 +104,14 @@ class DirSQL:
104
104
 
105
105
  async def query(self, sql):
106
106
  """Execute a SQL query asynchronously."""
107
- return await asyncio.to_thread(self._db.query, sql)
107
+ return await asyncio.to_thread(self._db.query, sql) # ty:ignore[unresolved-attribute]
108
108
 
109
109
  def watch(self):
110
110
  """Start watching for file changes. Returns an async iterable of RowEvent."""
111
111
  return _WatchStream(self._db)
112
112
 
113
113
  @property
114
- def __dict__(self):
114
+ def __dict__(self): # ty:ignore[missing-override-decorator]
115
115
  """Resolved construction state as a JSON-serializable dict.
116
116
 
117
117
  Recomputed on each access; reads the ``.dirsql.toml`` if ``config=``
@@ -0,0 +1,63 @@
1
+ """Type stubs for the native PyO3 extension module.
2
+
3
+ Mirrors the surface defined in ``packages/python/src/lib.rs``. Hand-written
4
+ because pyo3-stub-gen would otherwise demand a build-time hook that the
5
+ maturin / putitoutthere release pipeline does not yet run.
6
+
7
+ Whenever ``src/lib.rs`` adds, renames, or removes a ``#[pyclass]``,
8
+ ``#[pymethods]``, or module-level binding, this file MUST be updated in the
9
+ same PR -- and ``PARITY.md`` is the canonical reminder.
10
+ """
11
+
12
+ from collections.abc import Callable
13
+ from os import PathLike
14
+ from typing import Any
15
+
16
+ __version__: str
17
+
18
+ Row = dict[str, Any]
19
+
20
+ class Table:
21
+ """A table definition. Construct via keyword arguments only."""
22
+
23
+ ddl: str
24
+ glob: str
25
+ strict: bool
26
+
27
+ def __init__(
28
+ self,
29
+ *,
30
+ ddl: str,
31
+ glob: str,
32
+ extract: Callable[[str], list[Row]],
33
+ strict: bool = False,
34
+ ) -> None: ...
35
+
36
+ class RowEvent:
37
+ """A row event produced by the watch loop."""
38
+
39
+ table: str | None
40
+ action: str
41
+ row: Row | None
42
+ old_row: Row | None
43
+ error: str | None
44
+ file_path: str | None
45
+
46
+ def __repr__(self) -> str: ... # ty:ignore[missing-override-decorator]
47
+
48
+ class DirSQL:
49
+ """Synchronous binding class. ``dirsql._async.DirSQL`` wraps it."""
50
+
51
+ def __init__(
52
+ self,
53
+ root: str | None = None,
54
+ *,
55
+ tables: list[Table] | None = None,
56
+ ignore: list[str] | None = None,
57
+ config: str | None = None,
58
+ persist: bool = False,
59
+ persist_path: str | PathLike[str] | None = None,
60
+ ) -> None: ...
61
+ def query(self, sql: str) -> list[Row]: ...
62
+ def _start_watcher(self) -> None: ...
63
+ def _poll_events(self, timeout_ms: int) -> list[RowEvent]: ...
@@ -0,0 +1,51 @@
1
+ """Unit tests for `binary_path`."""
2
+
3
+ from pathlib import Path
4
+ from unittest.mock import patch
5
+
6
+ import pytest
7
+
8
+ from dirsql.cli import binary_path as bp_module
9
+ from dirsql.cli.binary_path import binary_path
10
+
11
+
12
+ def _stage_binary(root: Path, name: str) -> Path:
13
+ binary_dir = root / "_binary"
14
+ binary_dir.mkdir()
15
+ target = binary_dir / name
16
+ target.write_text("")
17
+ return target
18
+
19
+
20
+ def describe_binary_path():
21
+ def it_returns_the_dirsql_path_on_posix(tmp_path: Path):
22
+ target = _stage_binary(tmp_path, "dirsql")
23
+ with (
24
+ patch.object(bp_module, "is_windows", return_value=False),
25
+ patch.object(bp_module, "files", return_value=tmp_path),
26
+ ):
27
+ assert binary_path() == str(target)
28
+
29
+ def it_returns_the_dirsql_exe_path_on_windows(tmp_path: Path):
30
+ target = _stage_binary(tmp_path, "dirsql.exe")
31
+ with (
32
+ patch.object(bp_module, "is_windows", return_value=True),
33
+ patch.object(bp_module, "files", return_value=tmp_path),
34
+ ):
35
+ assert binary_path() == str(target)
36
+
37
+ def it_raises_file_not_found_when_the_posix_binary_is_missing(tmp_path: Path):
38
+ with (
39
+ patch.object(bp_module, "is_windows", return_value=False),
40
+ patch.object(bp_module, "files", return_value=tmp_path),
41
+ pytest.raises(FileNotFoundError, match="bundled `dirsql` not found"),
42
+ ):
43
+ binary_path()
44
+
45
+ def it_raises_file_not_found_when_the_windows_binary_is_missing(tmp_path: Path):
46
+ with (
47
+ patch.object(bp_module, "is_windows", return_value=True),
48
+ patch.object(bp_module, "files", return_value=tmp_path),
49
+ pytest.raises(FileNotFoundError, match="bundled `dirsql.exe` not found"),
50
+ ):
51
+ binary_path()
@@ -0,0 +1,9 @@
1
+ """Platform check used by the launcher."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+
8
+ def is_windows() -> bool:
9
+ return os.name == "nt"
@@ -0,0 +1,20 @@
1
+ """Unit tests for `is_windows`."""
2
+
3
+ import os
4
+ from unittest.mock import patch
5
+
6
+ from dirsql.cli.is_windows import is_windows
7
+
8
+
9
+ def describe_is_windows():
10
+ def it_returns_true_when_os_name_is_nt():
11
+ with patch.object(os, "name", "nt"):
12
+ assert is_windows() is True
13
+
14
+ def it_returns_false_when_os_name_is_posix():
15
+ with patch.object(os, "name", "posix"):
16
+ assert is_windows() is False
17
+
18
+ def it_returns_false_when_os_name_is_anything_else():
19
+ with patch.object(os, "name", "java"):
20
+ assert is_windows() is False
@@ -24,4 +24,4 @@ def main(argv: list[str] | None = None) -> int:
24
24
  completed = subprocess.run([binary, *argv])
25
25
  return completed.returncode
26
26
  os.execv(binary, [binary, *argv])
27
- return 0 # unreachable
27
+ return 0
@@ -0,0 +1,87 @@
1
+ """Unit tests for the launcher's `main`."""
2
+
3
+ import io
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from unittest.mock import patch
8
+
9
+ from dirsql.cli import main as main_module
10
+ from dirsql.cli.main import main
11
+
12
+
13
+ class _Completed:
14
+ def __init__(self, returncode: int):
15
+ self.returncode = returncode
16
+
17
+
18
+ def describe_main():
19
+ def describe_when_binary_path_raises_file_not_found():
20
+ def it_returns_1_and_writes_a_dirsql_prefixed_message_to_stderr():
21
+ fake_stderr = io.StringIO()
22
+ with (
23
+ patch.object(
24
+ main_module,
25
+ "binary_path",
26
+ side_effect=FileNotFoundError("bundled `dirsql` missing"),
27
+ ),
28
+ patch.object(sys, "stderr", fake_stderr),
29
+ ):
30
+ assert main([]) == 1
31
+ assert fake_stderr.getvalue() == "dirsql: bundled `dirsql` missing\n"
32
+
33
+ def describe_on_windows():
34
+ def it_runs_the_binary_via_subprocess_and_returns_its_returncode():
35
+ with (
36
+ patch.object(main_module, "binary_path", return_value="C:/dirsql.exe"),
37
+ patch.object(main_module, "is_windows", return_value=True),
38
+ patch.object(subprocess, "run", return_value=_Completed(7)) as run,
39
+ ):
40
+ assert main(["--version"]) == 7
41
+ run.assert_called_once_with(["C:/dirsql.exe", "--version"])
42
+
43
+ def it_does_not_invoke_execv():
44
+ with (
45
+ patch.object(main_module, "binary_path", return_value="C:/dirsql.exe"),
46
+ patch.object(main_module, "is_windows", return_value=True),
47
+ patch.object(subprocess, "run", return_value=_Completed(0)),
48
+ patch.object(os, "execv") as execv,
49
+ ):
50
+ main([])
51
+ execv.assert_not_called()
52
+
53
+ def describe_on_posix():
54
+ def it_hands_off_to_execv_with_the_binary_argv_pair():
55
+ with (
56
+ patch.object(
57
+ main_module, "binary_path", return_value="/usr/local/bin/dirsql"
58
+ ),
59
+ patch.object(main_module, "is_windows", return_value=False),
60
+ patch.object(os, "execv") as execv,
61
+ ):
62
+ main(["query", "select 1"])
63
+ execv.assert_called_once_with(
64
+ "/usr/local/bin/dirsql",
65
+ ["/usr/local/bin/dirsql", "query", "select 1"],
66
+ )
67
+
68
+ def it_returns_0_when_execv_returns():
69
+ # In production `os.execv` never returns. The mock returns,
70
+ # so `main` falls through to `return 0`.
71
+ with (
72
+ patch.object(main_module, "binary_path", return_value="/bin/dirsql"),
73
+ patch.object(main_module, "is_windows", return_value=False),
74
+ patch.object(os, "execv"),
75
+ ):
76
+ assert main([]) == 0
77
+
78
+ def describe_when_argv_is_none():
79
+ def it_pulls_from_sys_argv_skipping_the_program_name():
80
+ with (
81
+ patch.object(sys, "argv", ["dirsql", "--help"]),
82
+ patch.object(main_module, "binary_path", return_value="C:/dirsql.exe"),
83
+ patch.object(main_module, "is_windows", return_value=True),
84
+ patch.object(subprocess, "run", return_value=_Completed(0)) as run,
85
+ ):
86
+ main(argv=None)
87
+ run.assert_called_once_with(["C:/dirsql.exe", "--help"])
@@ -20,7 +20,7 @@ def resolve_config(root, tables, ignore, config, persist, persist_path):
20
20
  cfg_dir = os.path.dirname(os.path.abspath(config))
21
21
 
22
22
  def _abs(p):
23
- return p if os.path.isabs(p) else os.path.join(cfg_dir, p)
23
+ return p if os.path.isabs(p) else os.path.join(cfg_dir, p) # ty:ignore[no-matching-overload]
24
24
 
25
25
  return {
26
26
  "root": root or (_abs(cfg["root"]) if "root" in cfg else cfg_dir),
@@ -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.14"
7
+ version = "0.3.16"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
File without changes
@@ -27,6 +27,8 @@ dev = [
27
27
  "pytest-cov>=5",
28
28
  "ruff>=0.4",
29
29
  "maturin>=1.0",
30
+ # ty is pre-1.0 -- pin tightly. Re-pin in lockstep with CI.
31
+ "ty==0.0.42",
30
32
  ]
31
33
 
32
34
  [project.scripts]
@@ -57,22 +59,40 @@ include = [
57
59
  ]
58
60
  manifest-path = "packages/python/Cargo.toml"
59
61
 
60
- # CLI launcher shims (`cli/main.py`, `cli/binary_path.py`,
61
- # `cli/is_windows.py`) that locate and exec the bundled Rust binary --
62
- # the Python analogue of the excluded `src/bin/` Rust launcher. The
63
- # AGENTS.md test-boundary rules forbid monkeypatch unit tests on
64
- # production module attributes (`os.execv`, `binary_path`, etc.) -- the
65
- # load-bearing line is the `os.execv` hand-off, whose only meaningful
66
- # coverage is end-to-end (real wheel install + `dirsql --version`),
67
- # verified by putitoutthere's wheel-content guard post-build. Omitting
68
- # these files keeps the 100% coverage floor honest about library code.
62
+ # Type checking. Migration model: existing untyped code is frozen behind
63
+ # `# ty: ignore[...]` baseline comments inserted by `ty check --add-ignore`;
64
+ # any new error in new or touched code fails CI. Every rule is promoted to
65
+ # `"error"`, matching TypeScript-strict discipline. Test files are excluded
66
+ # because they monkeypatch fakes onto production module attributes
67
+ # (`monkeypatch.setattr(async_mod, "_RustDirSQL", ...)`) -- AGENTS.md flags
68
+ # that as a smell, ty surfaces it accurately, and the cleanup (constructor
69
+ # injection) is tracked separately.
70
+ [tool.ty.src]
71
+ exclude = [
72
+ "**/test_*.py",
73
+ "**/*_test.py",
74
+ "tests/",
75
+ "build/",
76
+ "dirsql/_binary/",
77
+ ]
78
+
79
+ [tool.ty.environment]
80
+ python-version = "3.11"
81
+
82
+ # Ruff enforces "every type-ignore must cite a rule code" (PGH003) so a
83
+ # `# type: ignore` or `# ty: ignore` without `[...]` fails lint. Stops the
84
+ # escape hatch from quietly widening.
85
+ [tool.ruff.lint]
86
+ extend-select = ["PGH003"]
87
+
69
88
  [tool.coverage.run]
70
89
  omit = [
71
- "dirsql/cli/*",
72
90
  # Colocated unit tests live alongside the package source; instrumenting
73
91
  # them lets a test double's own branches pad the metric (e.g. a fake's
74
92
  # empty-list return). Exclude test files so the floor reflects only
75
93
  # library code.
76
94
  "dirsql/test_*.py",
77
95
  "dirsql/*_test.py",
96
+ "dirsql/cli/test_*.py",
97
+ "dirsql/cli/*_test.py",
78
98
  ]
@@ -1,10 +0,0 @@
1
- """Platform check used by the launcher. Isolated so tests can override it
2
- without mutating the ``os.name`` global (which upsets pathlib)."""
3
-
4
- from __future__ import annotations
5
-
6
- import os
7
-
8
-
9
- def is_windows() -> bool:
10
- return os.name == "nt"
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