dirsql-plugin-embeddings 0.1.16__tar.gz → 0.1.18__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/.gitignore +3 -1
  2. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/PKG-INFO +1 -1
  3. dirsql_plugin_embeddings-0.1.18/changelog.d/2026-08-12-worker-semaphore-leak.md +1 -0
  4. dirsql_plugin_embeddings-0.1.18/changelog.d/2026-09-04-report-cache-hits.md +3 -0
  5. dirsql_plugin_embeddings-0.1.18/e2e-attestations/claude-815-loky-semaphore.json +7 -0
  6. dirsql_plugin_embeddings-0.1.18/e2e-attestations/claude-tackle-1034-plugin-flag.json +7 -0
  7. dirsql_plugin_embeddings-0.1.18/migrations.d/2026-09-04-report-cache-hits.md +39 -0
  8. dirsql_plugin_embeddings-0.1.18/src/dirsql_plugin_embeddings/embedding/progress.py +24 -0
  9. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/embedding/worker.py +17 -5
  10. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/testing-conventions.toml +3 -0
  11. dirsql_plugin_embeddings-0.1.16/src/dirsql_plugin_embeddings/embedding/progress.py +0 -13
  12. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/README.md +0 -0
  13. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/changelog.d/2026-08-10-delete-old-surface.md +0 -0
  14. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/changelog.d/2026-08-10-embed-worker.md +0 -0
  15. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/changelog.d/2026-08-11-one-liner-cli.md +0 -0
  16. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/changelog.d/2026-08-12-empty-search-reports-why.md +0 -0
  17. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/changelog.d/2026-08-12-no-per-call-progress-bar.md +0 -0
  18. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/changelog.d/2026-08-12-null-embedding-guard.md +0 -0
  19. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/e2e-attestations/claude-804-embed-worker.json +0 -0
  20. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/e2e-attestations/claude-805-one-liner.json +0 -0
  21. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/e2e-attestations/claude-814-suppress-per-call-progress.json +0 -0
  22. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/e2e-attestations/claude-816-empty-corpus-error.json +0 -0
  23. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/e2e-attestations/claude-817-null-embedding-guard.json +0 -0
  24. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/migrations.d/2026-08-10-delete-old-surface.md +0 -0
  25. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/pyproject.toml +0 -0
  26. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/__init__.py +0 -0
  27. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/cli/__init__.py +0 -0
  28. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/cli/main.py +0 -0
  29. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/cli/search.py +0 -0
  30. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/cli/worker.py +0 -0
  31. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/dirsql.toml +0 -0
  32. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/embedding/__init__.py +0 -0
  33. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/embedding/cache.py +0 -0
  34. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/embedding/model.py +0 -0
  35. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/embedding/values.py +0 -0
  36. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/search/__init__.py +0 -0
  37. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/search/output.py +0 -0
  38. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/search/run.py +0 -0
  39. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/src/dirsql_plugin_embeddings/search/sql.py +0 -0
  40. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/tests/conftest.py +0 -0
  41. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/tests/e2e/__init__.py +0 -0
  42. {dirsql_plugin_embeddings-0.1.16 → dirsql_plugin_embeddings-0.1.18}/tests/integration/__init__.py +0 -0
@@ -57,5 +57,7 @@ docs/blob-report/
57
57
  docs/.playwright/
58
58
  actionlint
59
59
 
60
- # cargo-mutants run output (never commit)
60
+ # cargo-mutants run output (never commit). The tool rotates the previous
61
+ # run's directory to `.old` on every re-run, so both forms need ignoring.
61
62
  mutants.out/
63
+ mutants.out.old/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dirsql-plugin-embeddings
3
- Version: 0.1.16
3
+ Version: 0.1.18
4
4
  Summary: First-party dirsql plugin: semantic search over files.
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: cachetta>=0.7
@@ -0,0 +1 @@
1
+ **Fixed** the `resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown: {'/loky-…'}` warning that every embedding run printed on stderr. model2vec's encode loop constructs a (disabled) tqdm bar whose default write lock allocates a multiprocessing semaphore, and the dirsql core tears the worker down with a kill, so the semaphore was never released. The worker now installs a process-local tqdm write lock at startup, so no semaphore exists to leak.
@@ -0,0 +1,3 @@
1
+ **Added**
2
+
3
+ - **The worker now tells dirsql which embeddings cost nothing.** Every `ok` response carries `"meta": {"cached": <bool>}`, so the progress line can split its worker-call count — `dirsql: ran 41231 worker calls in 2m41s (38104 cached)` — instead of reporting one undifferentiated total for a run that was mostly disk reads. `cachetta`'s wrapper returns a hit and a miss identically, so the flag is recovered from whether the wrapped compute actually ran. (#1034)
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run --with-editable ../../packages/python --with-editable . python -m pytest tests/e2e -q",
3
+ "ran_at": 1786559931,
4
+ "exit_code": 0,
5
+ "commit": "b63619a59bf6138234daa5c08d2e29981729642d",
6
+ "branch": "claude/815-loky-semaphore"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run --with-editable ../../packages/python python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1788558384,
4
+ "exit_code": 0,
5
+ "commit": "fc3ee82adb022a9b6da85687695ca752dd178f03",
6
+ "branch": "claude/tackle-1034-plugin-flag"
7
+ }
@@ -0,0 +1,39 @@
1
+ ### dirsql-plugin-embeddings: `ok` responses now carry cache metadata
2
+
3
+ #### Summary
4
+
5
+ The worker's success responses gain an optional `"meta"` object:
6
+ `{"ok": [floats...], "meta": {"cached": true|false}}`. Nothing is removed and
7
+ no signature a user calls changes — the plugin exports no Python API, and its
8
+ CLI arguments, exit codes and vectors are all untouched. The one observable
9
+ difference is the extra key on the wire, and the progress line dirsql draws
10
+ from it.
11
+
12
+ #### Required changes
13
+
14
+ _None._
15
+
16
+ #### Deprecations removed
17
+
18
+ _None._
19
+
20
+ #### Behavior changes without code changes
21
+
22
+ - A worker `ok` line previously read `{"ok": [1.0, 0.0]}` and now reads
23
+ `{"ok": [1.0, 0.0], "meta": {"cached": false}}`. dirsql ignores unknown keys,
24
+ so this is additive for it; a third party parsing worker output with an
25
+ exact-equality check on the whole object will see a mismatch and should
26
+ compare `response["ok"]` instead.
27
+ - `{"ok": null}` for a NULL value is unchanged — no compute is attempted, so
28
+ there is no cache state to report.
29
+ - With the plugin installed, `dirsql` runs that hit the on-disk cache now show
30
+ the split in the progress line (`ran 41231 worker calls in 2m41s
31
+ (38104 cached)`). A run with no hits looks exactly as it did before.
32
+
33
+ #### Verification
34
+
35
+ ```bash
36
+ printf '{"call": ["hello"]}\n{"call": ["hello"]}\n' \
37
+ | uvx --from dirsql-plugin-embeddings dirsql-plugin-embeddings worker
38
+ # expected: first line "cached":false, second line "cached":true
39
+ ```
@@ -0,0 +1,24 @@
1
+ import os
2
+ import sys
3
+ import threading
4
+
5
+ from tqdm import tqdm
6
+
7
+
8
+ def stderr_is_tty():
9
+ return sys.stderr.isatty()
10
+
11
+
12
+ def configure():
13
+ # Hugging Face hub download bars honor this variable; setdefault keeps an
14
+ # explicit user choice in force.
15
+ if not stderr_is_tty():
16
+ os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
17
+ # tqdm's first bar (model2vec's encode loop constructs one even when
18
+ # disabled) lazily allocates a multiprocessing RLock -- a named semaphore
19
+ # registered with the resource tracker. The dirsql core tears this worker
20
+ # down with a kill, not a graceful exit, so that semaphore is never
21
+ # unregistered and the tracker prints a "leaked semaphore" warning after
22
+ # every run. A single worker process needs no cross-process bar
23
+ # coordination: a process-local lock means no semaphore exists at all.
24
+ tqdm.set_lock(threading.RLock())
@@ -18,6 +18,7 @@ class Worker:
18
18
  self._cache = make_cache()
19
19
  self._compute_cached = self._cache.wrap(self._compute)
20
20
  self._pending = None
21
+ self._computed = False
21
22
 
22
23
  def _model(self, model_id):
23
24
  if model_id not in self._models:
@@ -25,6 +26,10 @@ class Worker:
25
26
  return self._models[model_id]
26
27
 
27
28
  def _compute(self, digest, identifier):
29
+ # cachetta's wrapper returns a hit and a miss identically, so the only
30
+ # place a miss is observable is here: _compute runs on a miss and never
31
+ # on a hit. embed() clears the flag and reads it back afterwards.
32
+ self._computed = True
28
33
  text, loaded = self._pending
29
34
  # No progress bar: the protocol embeds one value per round trip, so a
30
35
  # per-call bar can only ever say 1/1. The model *download* bar, which
@@ -33,11 +38,21 @@ class Worker:
33
38
  return [float(component) for component in vector]
34
39
 
35
40
  def embed(self, text, model_id):
41
+ """Return the vector for ``text``, and whether the cache served it."""
36
42
  loaded = self._model(model_id)
37
43
  identifier = model.model_identifier(model_id, loaded)
38
44
  digest = sha256(text.encode("utf-8")).hexdigest()
39
45
  self._pending = (text, loaded)
40
- return self._compute_cached(digest, identifier)
46
+ self._computed = False
47
+ vector = self._compute_cached(digest, identifier)
48
+ return vector, not self._computed
49
+
50
+ def _embed_response(self, text, model_id):
51
+ try:
52
+ vector, cached = self.embed(text, model_id)
53
+ except Exception as error:
54
+ return {"err": f"embed({model_id!r}) failed: {error}"}
55
+ return {"ok": vector, "meta": {"cached": cached}}
41
56
 
42
57
  def handle(self, line):
43
58
  try:
@@ -59,10 +74,7 @@ class Worker:
59
74
  (model_id,) = rest or [model.DEFAULT_MODEL_ID]
60
75
  if not isinstance(model_id, str):
61
76
  return {"err": MALFORMED_MODEL_ID}
62
- try:
63
- return {"ok": self.embed(text, model_id)}
64
- except Exception as error:
65
- return {"err": f"embed({model_id!r}) failed: {error}"}
77
+ return self._embed_response(text, model_id)
66
78
 
67
79
  def serve(self, stdin, stdout):
68
80
  for line in stdin:
@@ -2,6 +2,9 @@
2
2
  # package. Mirrors internals/checks: python coverage floor 100 + branch, zero
3
3
  # exemptions, and deliberately no `[e2e] extra_scope` -- this package compiles
4
4
  # none of packages/rust/src, so a core change never stales its attestation.
5
+ [python]
6
+ one_function_per_file = { max_lines = 24 }
7
+
5
8
  [python.coverage]
6
9
  fail_under = 100
7
10
  branch = true
@@ -1,13 +0,0 @@
1
- import os
2
- import sys
3
-
4
-
5
- def stderr_is_tty():
6
- return sys.stderr.isatty()
7
-
8
-
9
- def configure():
10
- # Hugging Face hub download bars honor this variable; setdefault keeps an
11
- # explicit user choice in force.
12
- if not stderr_is_tty():
13
- os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")