dirsql-plugin-embeddings 0.1.5__tar.gz → 0.1.6__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 (22) hide show
  1. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/PKG-INFO +2 -1
  2. dirsql_plugin_embeddings-0.1.5/e2e-attestations/claude-699-read-pdf.json → dirsql_plugin_embeddings-0.1.6/e2e-attestations/claude-701-cache-pdf.json +3 -3
  3. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/pyproject.toml +5 -1
  4. dirsql_plugin_embeddings-0.1.6/src/dirsql_plugin_embeddings/cache.py +20 -0
  5. dirsql_plugin_embeddings-0.1.6/src/dirsql_plugin_embeddings/config.py +31 -0
  6. dirsql_plugin_embeddings-0.1.6/src/dirsql_plugin_embeddings/on_file/read_pdf.py +31 -0
  7. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/tests/conftest.py +8 -0
  8. dirsql_plugin_embeddings-0.1.5/src/dirsql_plugin_embeddings/on_file/read_pdf.py +0 -16
  9. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/.gitignore +0 -0
  10. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/README.md +0 -0
  11. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/__init__.py +0 -0
  12. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/dirsql.toml +0 -0
  13. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/embedder.py +0 -0
  14. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/on_file/__init__.py +0 -0
  15. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/on_file/build_rows.py +0 -0
  16. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/on_file/on_file.py +0 -0
  17. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/on_file/read_content.py +0 -0
  18. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/on_file/read_text.py +0 -0
  19. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/src/dirsql_plugin_embeddings/pre_query.py +0 -0
  20. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/testing-conventions.toml +0 -0
  21. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/tests/e2e/__init__.py +0 -0
  22. {dirsql_plugin_embeddings-0.1.5 → dirsql_plugin_embeddings-0.1.6}/tests/integration/__init__.py +0 -0
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql-plugin-embeddings
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: First-party dirsql plugin: semantic search via an OpenAI-compatible embeddings endpoint.
5
5
  Requires-Python: >=3.10
6
+ Requires-Dist: cachetta>=0.6.15
6
7
  Requires-Dist: pypdf>=6
7
8
  Description-Content-Type: text/markdown
8
9
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "command": "uv run --with sqlite-vec --with-editable . --with-editable ../../packages/python python -m pytest tests/e2e -x -q",
3
- "ran_at": 1785327111,
3
+ "ran_at": 1785337806,
4
4
  "exit_code": 0,
5
- "commit": "b59ad4bf5dce929e2172cde15888918de9d277fd",
6
- "branch": "claude/699-read-pdf"
5
+ "commit": "43a5ab7f9e72cde7f55e943649f4e24b85d7fe69",
6
+ "branch": "claude/701-cache-pdf"
7
7
  }
@@ -14,7 +14,11 @@ readme = "README.md"
14
14
  requires-python = ">=3.10"
15
15
  # pypdf, not pymupdf: pymupdf is AGPL-3.0, and a runtime dependency of this
16
16
  # MIT-licensed package propagates to everyone who installs it.
17
- dependencies = ["pypdf>=6"]
17
+ #
18
+ # cachetta >=0.6.15 is load-bearing, not a nicety: every earlier release
19
+ # declares `requires-python >=3.12`, which would drag this package's `>=3.11`
20
+ # floor up with it. 0.6.15 lowered it to `>=3.10`.
21
+ dependencies = ["pypdf>=6", "cachetta>=0.6.15"]
18
22
 
19
23
  [tool.hatch.version]
20
24
  source = "vcs"
@@ -0,0 +1,20 @@
1
+ """The plugin's one configured cache.
2
+
3
+ Every cached function derives its own sub-cache from this singleton with
4
+ cachetta's ``cache / 'name'``, which keeps the defaults and appends a
5
+ subdirectory. Naming that subdirectory after the function is what keeps caches
6
+ apart: cachetta hashes the *arguments* of a call, not the identity of the
7
+ function, so two cached functions sharing a directory would collide as soon as
8
+ their argument shapes matched.
9
+ """
10
+
11
+ from cachetta import Cachetta
12
+
13
+ from .config import CACHE_DIR, CACHE_DURATION, CACHE_READ
14
+
15
+ cache = Cachetta(
16
+ path=CACHE_DIR,
17
+ hashed=True,
18
+ duration=CACHE_DURATION,
19
+ read=CACHE_READ,
20
+ )
@@ -0,0 +1,31 @@
1
+ """Cache configuration: what the plugin caches, where, and for how long.
2
+
3
+ Annotations are evaluated at runtime (no ``from __future__ import
4
+ annotations``) so a mutated ``X | None`` union in a signature fails at import
5
+ rather than surviving as an inert string.
6
+ """
7
+
8
+ import os
9
+ from datetime import timedelta
10
+ from pathlib import Path
11
+
12
+ PLUGIN_NAME = "dirsql-plugin-embeddings"
13
+
14
+ CACHE_DIR = Path.home() / ".cache" / PLUGIN_NAME
15
+
16
+ # Every cache here keys on the source file's mtime, so a hit can never be
17
+ # stale and expiry would only re-do work for a file nothing has touched.
18
+ # cachetta's own default is 7 days.
19
+ CACHE_DURATION = timedelta(days=365)
20
+
21
+ # Set to "0" to make every call recompute. Writes still happen -- it is reads
22
+ # that would otherwise let one test run's leftovers decide the next one's
23
+ # result, and the hooks are subprocesses, so an inherited environment variable
24
+ # is what reaches them.
25
+ ENV_CACHE_READ = "DIRSQL_EMBEDDINGS_CACHE_READ"
26
+ CACHE_READ_OFF = frozenset({"0"})
27
+
28
+ # Membership rather than `!= "0"`: CPython interns single-character strings, so
29
+ # for any value the environment can hold, `x == "0"` and `x is "0"` agree --
30
+ # which makes the equality form's identity variant untestable in one direction.
31
+ CACHE_READ = os.environ.get(ENV_CACHE_READ, "1") not in CACHE_READ_OFF
@@ -0,0 +1,31 @@
1
+ """Extract a PDF's text, once per version of the file.
2
+
3
+ pypdf rather than pymupdf: pymupdf is AGPL-3.0, and a runtime dependency of
4
+ this MIT-licensed package propagates to everyone who installs it.
5
+
6
+ Extraction is the expensive step of a scan and a scan re-reads every matched
7
+ file, so the result is persisted to disk. The cache key is the pair
8
+ ``(path, mtime)``: an edited PDF gets a different key rather than a stale hit.
9
+
10
+ Annotations are evaluated at runtime (no ``from __future__ import
11
+ annotations``) so a mutated ``X | None`` union in a signature fails at import
12
+ rather than surviving as an inert string.
13
+ """
14
+
15
+ import os
16
+
17
+ from pypdf import PdfReader
18
+
19
+ from ..cache import cache
20
+
21
+
22
+ @cache / "extract"
23
+ def extract(path: str, mtime: float) -> str:
24
+ # `mtime` is unread on purpose: it is what makes an edited PDF a cache
25
+ # miss, since cachetta derives the key from the arguments it is called with.
26
+ reader = PdfReader(path)
27
+ return "\n".join(page.extract_text() for page in reader.pages)
28
+
29
+
30
+ def read_pdf(path: str) -> str:
31
+ return extract(path, os.path.getmtime(path))
@@ -4,12 +4,20 @@
4
4
  `/v1/embeddings` endpoint. Its embedding is a deterministic keyword-count
5
5
  vector, so nearest-neighbor search over the fixtures is reproducible without a
6
6
  real model or network.
7
+
8
+ Cache reads are switched off for both tiers. These tiers run the hooks as
9
+ subprocesses, which no in-process mock can reach, so a cached extraction from an
10
+ earlier run would otherwise decide this one's result. Writes are left alone --
11
+ they are harmless, and it is only the read that leaks state between runs.
7
12
  """
8
13
 
9
14
  import json
15
+ import os
10
16
  import threading
11
17
  from http.server import BaseHTTPRequestHandler, HTTPServer
12
18
 
19
+ os.environ["DIRSQL_EMBEDDINGS_CACHE_READ"] = "0"
20
+
13
21
  import pytest
14
22
 
15
23
  # One dimension per keyword; the embedding of a text is the per-keyword count.
@@ -1,16 +0,0 @@
1
- """Extract a PDF's text.
2
-
3
- pypdf rather than pymupdf: pymupdf is AGPL-3.0, and a runtime dependency of
4
- this MIT-licensed package propagates to everyone who installs it.
5
-
6
- Annotations are evaluated at runtime (no ``from __future__ import
7
- annotations``) so a mutated ``X | None`` union in a signature fails at import
8
- rather than surviving as an inert string.
9
- """
10
-
11
- from pypdf import PdfReader
12
-
13
-
14
- def read_pdf(path: str) -> str:
15
- reader = PdfReader(path)
16
- return "\n".join(page.extract_text() for page in reader.pages)