functualize-state-sqlite 0.1.0__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 (23) hide show
  1. functualize_state_sqlite-0.1.0/.gitignore +101 -0
  2. functualize_state_sqlite-0.1.0/PKG-INFO +88 -0
  3. functualize_state_sqlite-0.1.0/README.md +67 -0
  4. functualize_state_sqlite-0.1.0/examples/README.md +11 -0
  5. functualize_state_sqlite-0.1.0/examples/persistent_counter/persistent_counter.py +25 -0
  6. functualize_state_sqlite-0.1.0/examples/persistent_counter/test_persistent_counter.py +23 -0
  7. functualize_state_sqlite-0.1.0/pyproject.toml +43 -0
  8. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/__init__.py +13 -0
  9. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/_backend.py +160 -0
  10. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/_execution_store.py +379 -0
  11. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/_migrations.py +157 -0
  12. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/_plugin.py +205 -0
  13. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/plugin.py +345 -0
  14. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/py.typed +0 -0
  15. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/sqlite_backend.py +721 -0
  16. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/state_store.py +178 -0
  17. functualize_state_sqlite-0.1.0/src/functualize_state_sqlite/tracker.py +318 -0
  18. functualize_state_sqlite-0.1.0/tests/__init__.py +0 -0
  19. functualize_state_sqlite-0.1.0/tests/conftest.py +19 -0
  20. functualize_state_sqlite-0.1.0/tests/test_execution_state_integration.py +638 -0
  21. functualize_state_sqlite-0.1.0/tests/test_plugin.py +86 -0
  22. functualize_state_sqlite-0.1.0/tests/test_sqlite_backend.py +437 -0
  23. functualize_state_sqlite-0.1.0/tests/test_sqlite_state_store_properties.py +373 -0
@@ -0,0 +1,101 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ *.egg
8
+ dist/
9
+ build/
10
+ *.whl
11
+
12
+ # Agents
13
+ .spec/archive/
14
+ .spec/features/
15
+ .spec/scrutiny-reports/
16
+ .spec/.agentic-coding
17
+ .spec/STATE.md
18
+ .spec/PROJECT.md
19
+ .spec/REQUIREMENTS.md
20
+ .spec/ROADMAP.md
21
+ .opencode/
22
+
23
+
24
+ # Virtual environments
25
+ .venv/
26
+ venv/
27
+ ENV/
28
+
29
+ # Testing
30
+ .coverage
31
+ .pytest_cache/
32
+ htmlcov/
33
+ .hypothesis/
34
+ snapshot_report.html
35
+ _*_result*.txt
36
+ _debug.txt
37
+ _tui_debug.txt
38
+ _tui_eval_debug.txt
39
+
40
+ # IDE
41
+ .idea/
42
+ *.swp
43
+ *.swo
44
+ *~
45
+ *.code-workspace
46
+
47
+ # Coding-agent tooling state (guards — these dirs are not part of the repo)
48
+ .kiro/
49
+ .moai/
50
+
51
+ # OS
52
+ .DS_Store
53
+ Thumbs.db
54
+
55
+ # Environment / secrets
56
+ .env
57
+ .env.*
58
+ !.env.example
59
+
60
+ # Agent scratch space (test output, temp scripts)
61
+ tmp/
62
+
63
+ # Local-only files (not for the repo)
64
+ *.local.md
65
+ *.local.*
66
+
67
+ # Personal notes
68
+ HUMAN_NOTE.md
69
+
70
+ # Distribution
71
+ dist/
72
+
73
+ # Documentation site build output
74
+ site/
75
+
76
+ # uv
77
+ .python-version
78
+ .functualize/cache.json
79
+ .functualize_cache.json
80
+ .todos/
81
+ .sidecar/
82
+ .sidecar-agent
83
+ .sidecar-task
84
+ .sidecar-pr
85
+ .sidecar-start.sh
86
+ .sidecar-base
87
+ .td-root
88
+ .functualize/
89
+ .import_linter_cache/
90
+ .mypy_cache/
91
+ .pytest_cache/
92
+ .ruff_cache/
93
+
94
+ # OmO / OpenCode agent run-continuation scratch state
95
+ .omo/
96
+ .mcp.json
97
+ .agentsroom/handoff-transcript-*.txt
98
+ .agentsroom/handoff-summary-*.md
99
+
100
+ # Internal pre-release audit reports (contain session IDs / local infra notes)
101
+ .release/
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: functualize-state-sqlite
3
+ Version: 0.1.0
4
+ Summary: SQLite-backed state persistence and execution tracking plugin for functualize
5
+ Author-email: Mohammad Hakim Adiprasetya <viltohmyst@gmail.com>
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: functualize-state<1.0.0,>=0.1.0
15
+ Requires-Dist: functualize<1.0.0,>=0.1.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: hypothesis>=6.82.0; extra == 'dev'
18
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
19
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # functualize-state-sqlite
23
+
24
+ > **Status: Published** — Independently installable from PyPI.
25
+
26
+ SQLite-backed state persistence and execution tracking plugin for functualize. Implements the `StateBackend` and `ExecutionStore` protocols from `functualize-state`, providing durable key-value storage and full execution history using a local SQLite database in WAL mode. Zero external dependencies beyond the Python standard library's `sqlite3` module.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install functualize-state-sqlite
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from functualize_state_sqlite import SQLiteStateBackend
38
+
39
+ # Use as a context manager for automatic cleanup
40
+ with SQLiteStateBackend(db_path="my_app.db") as backend:
41
+ backend.set("deploy_count", 42)
42
+ backend.set("last_env", "staging")
43
+
44
+ count = backend.get("deploy_count")
45
+ print(f"Deployments: {count}")
46
+
47
+ keys = backend.keys(prefix="deploy")
48
+ print(f"Keys: {keys}")
49
+ ```
50
+
51
+ ## Features
52
+
53
+ - **Persistent key-value state** — JSON-encoded values stored in SQLite with automatic schema initialization
54
+ - **WAL mode for concurrency** — concurrent read access without blocking writes, suitable for multi-process environments
55
+ - **Execution tracking** — full session and execution history with nested invocation support and phase recording
56
+ - **Namespace-scoped state** — `SQLiteStateStore` provides per-job namespace isolation within workflow scopes
57
+ - **Context manager support** — all backends support `with` statements for automatic connection cleanup
58
+ - **Zero external dependencies** — uses only Python's built-in `sqlite3` module
59
+ - **Automatic schema migrations** — database schema is created and migrated transparently on first access
60
+
61
+ ## API Reference
62
+
63
+ Public classes exported by this plugin:
64
+
65
+ - `SQLiteStateBackend` — Implements the `StateBackend` protocol with `get()`, `set()`, `delete()`, and `keys()` methods for persistent key-value storage using a dedicated `kv_state` table.
66
+ - `SQLiteExecutionStore` — Implements the `ExecutionStore` protocol for recording and querying execution records and phase tracking. Methods include `insert_execution()`, `update_execution()`, `get_session_executions()`, `insert_phase()`, and `get_execution_phases()`.
67
+ - `SQLiteStatePlugin` — Plugin that registers `SQLiteStateBackend` and `SQLiteExecutionStore` with the DI registry at boot time. Hooks into `APP_READY` and `ON_SCOPE_CREATED` lifecycle events.
68
+ - `ExecutionStatePlugin` — Full lifecycle plugin that tracks job executions automatically. Hooks into `BEFORE_JOB`, `AFTER_SUCCESS`, `AFTER_FAILURE`, `INVOKE_START`, `INVOKE_END`, and `ON_SCOPE_CREATED` for comprehensive execution history.
69
+
70
+ Internal classes (available via direct import but not part of the public protocol surface):
71
+
72
+ - `SQLiteBackend` — Low-level connection manager with WAL mode, schema initialization, and query helpers for sessions, executions, steps, and namespaced state.
73
+ - `SQLiteStateStore` — `StateStoreProtocol` implementation scoped to a `(scope_id, job_namespace)` pair, with `get()`, `set()`, `delete()`, `keys()`, `to_dict()`, `clear()`, and cross-job access via `get_job_state()`.
74
+ - `ExecutionTracker` — High-level session management and execution recording with automatic session resume based on TTL, and AI context summary generation via `to_ai_context()`.
75
+
76
+ ## Development
77
+
78
+ Run plugin tests:
79
+
80
+ ```bash
81
+ uv run pytest plugins/functualize-state-sqlite/tests/ -v
82
+ ```
83
+
84
+ Build the package:
85
+
86
+ ```bash
87
+ uv build --package functualize-state-sqlite
88
+ ```
@@ -0,0 +1,67 @@
1
+ # functualize-state-sqlite
2
+
3
+ > **Status: Published** — Independently installable from PyPI.
4
+
5
+ SQLite-backed state persistence and execution tracking plugin for functualize. Implements the `StateBackend` and `ExecutionStore` protocols from `functualize-state`, providing durable key-value storage and full execution history using a local SQLite database in WAL mode. Zero external dependencies beyond the Python standard library's `sqlite3` module.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install functualize-state-sqlite
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from functualize_state_sqlite import SQLiteStateBackend
17
+
18
+ # Use as a context manager for automatic cleanup
19
+ with SQLiteStateBackend(db_path="my_app.db") as backend:
20
+ backend.set("deploy_count", 42)
21
+ backend.set("last_env", "staging")
22
+
23
+ count = backend.get("deploy_count")
24
+ print(f"Deployments: {count}")
25
+
26
+ keys = backend.keys(prefix="deploy")
27
+ print(f"Keys: {keys}")
28
+ ```
29
+
30
+ ## Features
31
+
32
+ - **Persistent key-value state** — JSON-encoded values stored in SQLite with automatic schema initialization
33
+ - **WAL mode for concurrency** — concurrent read access without blocking writes, suitable for multi-process environments
34
+ - **Execution tracking** — full session and execution history with nested invocation support and phase recording
35
+ - **Namespace-scoped state** — `SQLiteStateStore` provides per-job namespace isolation within workflow scopes
36
+ - **Context manager support** — all backends support `with` statements for automatic connection cleanup
37
+ - **Zero external dependencies** — uses only Python's built-in `sqlite3` module
38
+ - **Automatic schema migrations** — database schema is created and migrated transparently on first access
39
+
40
+ ## API Reference
41
+
42
+ Public classes exported by this plugin:
43
+
44
+ - `SQLiteStateBackend` — Implements the `StateBackend` protocol with `get()`, `set()`, `delete()`, and `keys()` methods for persistent key-value storage using a dedicated `kv_state` table.
45
+ - `SQLiteExecutionStore` — Implements the `ExecutionStore` protocol for recording and querying execution records and phase tracking. Methods include `insert_execution()`, `update_execution()`, `get_session_executions()`, `insert_phase()`, and `get_execution_phases()`.
46
+ - `SQLiteStatePlugin` — Plugin that registers `SQLiteStateBackend` and `SQLiteExecutionStore` with the DI registry at boot time. Hooks into `APP_READY` and `ON_SCOPE_CREATED` lifecycle events.
47
+ - `ExecutionStatePlugin` — Full lifecycle plugin that tracks job executions automatically. Hooks into `BEFORE_JOB`, `AFTER_SUCCESS`, `AFTER_FAILURE`, `INVOKE_START`, `INVOKE_END`, and `ON_SCOPE_CREATED` for comprehensive execution history.
48
+
49
+ Internal classes (available via direct import but not part of the public protocol surface):
50
+
51
+ - `SQLiteBackend` — Low-level connection manager with WAL mode, schema initialization, and query helpers for sessions, executions, steps, and namespaced state.
52
+ - `SQLiteStateStore` — `StateStoreProtocol` implementation scoped to a `(scope_id, job_namespace)` pair, with `get()`, `set()`, `delete()`, `keys()`, `to_dict()`, `clear()`, and cross-job access via `get_job_state()`.
53
+ - `ExecutionTracker` — High-level session management and execution recording with automatic session resume based on TTL, and AI context summary generation via `to_ai_context()`.
54
+
55
+ ## Development
56
+
57
+ Run plugin tests:
58
+
59
+ ```bash
60
+ uv run pytest plugins/functualize-state-sqlite/tests/ -v
61
+ ```
62
+
63
+ Build the package:
64
+
65
+ ```bash
66
+ uv build --package functualize-state-sqlite
67
+ ```
@@ -0,0 +1,11 @@
1
+ # functualize-state-sqlite Examples
2
+
3
+ Durable SQLite-backed state (WAL mode, stdlib-only).
4
+
5
+ | Directory | Demonstrates |
6
+ |-----------|--------------|
7
+ | [`persistent_counter/`](persistent_counter/) | `SQLiteStateBackend` as a `StateBackend`: values survive across processes; keys listing by prefix |
8
+
9
+ ```bash
10
+ uv run pytest plugins/functualize-state-sqlite/examples/ -v
11
+ ```
@@ -0,0 +1,25 @@
1
+ """Durable state: a run counter that survives process restarts.
2
+
3
+ Run twice and watch the count keep climbing:
4
+ func persistent_counter.py bump
5
+ func persistent_counter.py bump
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from functualize_state_sqlite import SQLiteStateBackend
11
+
12
+ from functualize.job import RunContext
13
+
14
+ DB_PATH = Path(__file__).parent / "counter.db"
15
+
16
+
17
+ def bump(rc: RunContext) -> int:
18
+ """Increment a counter stored durably in SQLite."""
19
+ with SQLiteStateBackend(db_path=str(DB_PATH)) as backend:
20
+ count = backend.get("runs", 0) + 1
21
+ backend.set("runs", count)
22
+ backend.set("last_run_note", f"run #{count}")
23
+ rc.log(f"Persistent run count: {count}")
24
+ rc.log(f"Keys stored: {backend.keys()}")
25
+ return count
@@ -0,0 +1,23 @@
1
+ """Tests for the persistent counter example."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from unittest.mock import MagicMock
6
+
7
+ sys.path.insert(0, str(Path(__file__).parent))
8
+
9
+ import persistent_counter
10
+ from functualize_state_sqlite import SQLiteStateBackend
11
+
12
+
13
+ def test_counter_persists_across_backend_instances(tmp_path, monkeypatch):
14
+ db = tmp_path / "counter.db"
15
+ monkeypatch.setattr(persistent_counter, "DB_PATH", db)
16
+
17
+ rc = MagicMock()
18
+ assert persistent_counter.bump(rc) == 1
19
+ # New backend instance (fresh "process") still sees the stored value
20
+ assert persistent_counter.bump(rc) == 2
21
+
22
+ with SQLiteStateBackend(db_path=str(db)) as backend:
23
+ assert backend.get("runs") == 2
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "functualize-state-sqlite"
3
+ version = "0.1.0"
4
+ description = "SQLite-backed state persistence and execution tracking plugin for functualize"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Mohammad Hakim Adiprasetya", email = "viltohmyst@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.11"
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Typing :: Typed",
18
+ ]
19
+ dependencies = [
20
+ "functualize>=0.1.0,<1.0.0",
21
+ "functualize-state>=0.1.0,<1.0.0",
22
+ ]
23
+
24
+ [project.entry-points."functualize.state_providers"]
25
+ sqlite = "functualize_state_sqlite:SQLiteStatePlugin"
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ "pytest>=7.4.0",
30
+ "pytest-cov>=4.1.0",
31
+ "hypothesis>=6.82.0",
32
+ ]
33
+
34
+ [build-system]
35
+ requires = ["hatchling"]
36
+ build-backend = "hatchling.build"
37
+
38
+ [tool.uv.sources]
39
+ functualize = { workspace = true }
40
+ functualize-state = { workspace = true }
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/functualize_state_sqlite"]
@@ -0,0 +1,13 @@
1
+ """Functualize State SQLite Plugin - SQLite-backed state persistence and execution tracking."""
2
+
3
+ from functualize_state_sqlite._backend import SQLiteStateBackend
4
+ from functualize_state_sqlite._execution_store import SQLiteExecutionStore
5
+ from functualize_state_sqlite._plugin import SQLiteStatePlugin
6
+ from functualize_state_sqlite.plugin import ExecutionStatePlugin
7
+
8
+ __all__ = [
9
+ "ExecutionStatePlugin",
10
+ "SQLiteExecutionStore",
11
+ "SQLiteStateBackend",
12
+ "SQLiteStatePlugin",
13
+ ]
@@ -0,0 +1,160 @@
1
+ """SQLite StateBackend implementation.
2
+
3
+ Implements the StateBackend protocol from functualize-state using SQLite
4
+ in WAL mode for concurrent access. Values are JSON-encoded for storage.
5
+ Uses only stdlib sqlite3 (zero external dependencies).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import sqlite3
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ __all__ = ["SQLiteStateBackend"]
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ _STATE_SCHEMA_SQL = """\
22
+ CREATE TABLE IF NOT EXISTS kv_state (
23
+ key TEXT PRIMARY KEY,
24
+ value TEXT NOT NULL,
25
+ updated_at REAL NOT NULL
26
+ );
27
+ """
28
+
29
+
30
+ class SQLiteStateBackend:
31
+ """StateBackend protocol implementation backed by SQLite.
32
+
33
+ Provides persistent key-value state storage with JSON-encoded values.
34
+ Uses WAL mode for concurrent read access without blocking writes.
35
+
36
+ Args:
37
+ db_path: Path to the SQLite database file. If None, defaults to
38
+ `.functualize/state.db` relative to the current working directory.
39
+ """
40
+
41
+ def __init__(self, db_path: str | Path | None = None) -> None:
42
+ if db_path is None:
43
+ self._db_path = Path.cwd() / ".functualize" / "state.db"
44
+ else:
45
+ self._db_path = Path(db_path)
46
+
47
+ self._conn: sqlite3.Connection | None = None
48
+ self._initialized = False
49
+
50
+ @property
51
+ def db_path(self) -> Path:
52
+ """The resolved path to the database file."""
53
+ return self._db_path
54
+
55
+ def _ensure_initialized(self) -> sqlite3.Connection:
56
+ """Ensure the database is initialized and return the connection."""
57
+ if self._conn is not None and self._initialized:
58
+ return self._conn
59
+
60
+ # Ensure the directory exists
61
+ self._db_path.parent.mkdir(parents=True, exist_ok=True)
62
+
63
+ # Open connection
64
+ self._conn = sqlite3.connect(
65
+ str(self._db_path),
66
+ timeout=10.0,
67
+ check_same_thread=False,
68
+ )
69
+
70
+ # Enable WAL mode for concurrent access
71
+ self._conn.execute("PRAGMA journal_mode = WAL")
72
+ self._conn.execute("PRAGMA synchronous = NORMAL")
73
+ self._conn.commit()
74
+
75
+ # Create schema
76
+ self._conn.executescript(_STATE_SCHEMA_SQL)
77
+ self._conn.commit()
78
+
79
+ self._initialized = True
80
+ logger.debug("SQLiteStateBackend initialized at %s", self._db_path)
81
+ return self._conn
82
+
83
+ def get(self, key: str, default: Any = None) -> Any:
84
+ """Get a value by key, returning default if not found.
85
+
86
+ The stored JSON value is deserialized back to a Python object.
87
+ """
88
+ conn = self._ensure_initialized()
89
+ cursor = conn.execute(
90
+ "SELECT value FROM kv_state WHERE key = ?",
91
+ (key,),
92
+ )
93
+ row = cursor.fetchone()
94
+ if row is None:
95
+ return default
96
+ return json.loads(row[0])
97
+
98
+ def set(self, key: str, value: Any) -> None:
99
+ """Set a value for a key.
100
+
101
+ The value is JSON-encoded before storage.
102
+ """
103
+ conn = self._ensure_initialized()
104
+ value_json = json.dumps(value)
105
+ now = time.time()
106
+ conn.execute(
107
+ "INSERT OR REPLACE INTO kv_state (key, value, updated_at) VALUES (?, ?, ?)",
108
+ (key, value_json, now),
109
+ )
110
+ conn.commit()
111
+
112
+ def delete(self, key: str) -> None:
113
+ """Delete a key from the state backend.
114
+
115
+ No-op if the key does not exist.
116
+ """
117
+ conn = self._ensure_initialized()
118
+ conn.execute("DELETE FROM kv_state WHERE key = ?", (key,))
119
+ conn.commit()
120
+
121
+ def keys(self, prefix: str = "") -> list[str]:
122
+ """Return all keys, optionally filtered by prefix."""
123
+ conn = self._ensure_initialized()
124
+ if prefix:
125
+ cursor = conn.execute(
126
+ "SELECT key FROM kv_state WHERE key LIKE ? ESCAPE '\\'",
127
+ (self._escape_like(prefix) + "%",),
128
+ )
129
+ else:
130
+ cursor = conn.execute("SELECT key FROM kv_state")
131
+ return [row[0] for row in cursor.fetchall()]
132
+
133
+ def close(self) -> None:
134
+ """Close the database connection."""
135
+ if self._conn is not None:
136
+ try:
137
+ self._conn.close()
138
+ except sqlite3.Error as e:
139
+ logger.warning("Error closing SQLiteStateBackend connection: %s", e)
140
+ finally:
141
+ self._conn = None
142
+ self._initialized = False
143
+
144
+ @staticmethod
145
+ def _escape_like(value: str) -> str:
146
+ """Escape special characters in a LIKE pattern."""
147
+ return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
148
+
149
+ def __enter__(self) -> SQLiteStateBackend:
150
+ """Context manager entry — initialize if needed."""
151
+ self._ensure_initialized()
152
+ return self
153
+
154
+ def __exit__(self, *exc: Any) -> None:
155
+ """Context manager exit — close connection."""
156
+ self.close()
157
+
158
+ def __del__(self) -> None:
159
+ """Ensure connection is closed on garbage collection."""
160
+ self.close()