shadowcache 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pratham Bhosale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: shadowcache
3
+ Version: 0.1.0
4
+ Summary: Transparent Redis caching for raw SQL connections - no ORM required
5
+ Author: Pratham Bhosale
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pratham2402/ShadowCache
8
+ Project-URL: Repository, https://github.com/pratham2402/ShadowCache
9
+ Keywords: redis,mysql,cache,sql,caching,database,db-api,transparent-cache,query-cache
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Database
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: redis>=4.0
24
+ Requires-Dist: mysql-connector-python>=8.0
25
+ Requires-Dist: sqlglot>=20.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ <p align="center">
31
+ <img src="https://img.shields.io/static/v1?label=%F0%9F%8C%9F&message=If%20Useful&style=flat&color=BC4E99">
32
+ <img src="https://badges.frapsoft.com/os/v1/open-source.svg?v=103">
33
+ <a href="https://github.com/pratham2402"><img src="https://img.shields.io/badge/View-My_Profile-green?logo=GitHub"></a>
34
+ <a href="https://github.com/pratham2402?tab=repositories"><img src="https://img.shields.io/badge/View-My_Repositories-blue?logo=GitHub"></a>
35
+ </p>
36
+
37
+ <p align="center">
38
+ <img src="https://img.shields.io/badge/python-3.8+-blue?logo=python">
39
+ <img src="https://img.shields.io/badge/license-MIT-green">
40
+ <img src="https://img.shields.io/badge/platform-mysql%20%7C%20redis-red">
41
+ </p>
42
+
43
+ # ShadowCache
44
+
45
+ <p align="center">
46
+ <img src="./README%20Banner%20Art.png" alt="ShadowCache Banner">
47
+ </p>
48
+
49
+ > **Write SQL. Get caching. Nothing else.**
50
+
51
+ ShadowCache wraps your MySQL connection and transparently caches SELECT results
52
+ in Redis. INSERT, UPDATE, or DELETE statements automatically evict affected cache
53
+ entries so your reads never serve stale data. No ORM. No boilerplate. No config.
54
+
55
+ <br>
56
+
57
+ <details open>
58
+ <summary><b>Table of Contents</b></summary>
59
+
60
+ - [The Problem](#the-problem)
61
+ - [Features](#features)
62
+ - [Installation](#installation)
63
+ - [Quick Start](#quick-start)
64
+ - [API Reference](#api-reference)
65
+ - [Configuration](#configuration)
66
+ - [Running Tests](#running-tests)
67
+ - [License](#license)
68
+
69
+ </details>
70
+
71
+ ## The Problem
72
+
73
+ Every developer who writes raw SQL eventually writes this:
74
+
75
+ ```python
76
+ # 8 lines of boilerplate for every cached query
77
+ cache_key = f"user:{user_id}"
78
+ cached = redis.get(cache_key)
79
+ if cached:
80
+ return json.loads(cached)
81
+
82
+ cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
83
+ row = cursor.fetchone()
84
+ redis.set(cache_key, json.dumps(row), ex=300)
85
+ return row
86
+ ```
87
+
88
+ And on every INSERT, UPDATE, or DELETE you need to remember:
89
+
90
+ ```python
91
+ cursor.execute("UPDATE users SET name = %s WHERE id = %s", (name, user_id))
92
+ redis.delete(f"user:{user_id}") # easy to forget, easy to get wrong
93
+ ```
94
+
95
+ ```diff
96
+ - Boilerplate for every query
97
+ - Manual invalidation you will forget
98
+ + One line. Caching and invalidation are automatic.
99
+ ```
100
+
101
+ **With ShadowCache:**
102
+
103
+ ```python
104
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
105
+ cursor, _ = cache.execute("UPDATE users SET name = %s WHERE id = %s", ("Alice", 42))
106
+ ```
107
+
108
+ ## Features
109
+
110
+ | | |
111
+ |---|---|
112
+ | **Zero-schema caching** | Works with any MySQL table, any query. No model definitions needed. |
113
+ | **Write-triggered eviction** | INSERT, UPDATE, and DELETE automatically evict cached SELECTs for the same table. |
114
+ | **TTL safety net** | Cached entries expire after a configurable time-to-live. Eventual consistency guaranteed. |
115
+ | **Graceful fallback** | If Redis is unreachable, queries still execute against MySQL. |
116
+
117
+ ## Installation
118
+
119
+ ```bash
120
+ pip install shadowcache
121
+ ```
122
+
123
+ > Or install from source:
124
+
125
+ ```bash
126
+ git clone https://github.com/pratham2402/ShadowCache.git
127
+ cd ShadowCache
128
+ pip install -r requirements.txt
129
+ ```
130
+
131
+ **Requires:** Python 3.8+, Redis, MySQL.
132
+
133
+ ## Quick Start
134
+
135
+ ```python
136
+ import mysql.connector
137
+ from shadowcache import ShadowCache
138
+
139
+ conn = mysql.connector.connect(
140
+ host="localhost", database="my_app",
141
+ user="app_user", password="secret",
142
+ )
143
+
144
+ cache = ShadowCache(conn)
145
+
146
+ # Cold miss -- hits MySQL, stores in Redis
147
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
148
+
149
+ # Warm hit -- returns from Redis instantly
150
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
151
+
152
+ # Write evicts the cache
153
+ cache.execute("UPDATE users SET name = %s WHERE id = %s", ("Alice", 42))
154
+
155
+ # Cache was evicted -- fresh data from MySQL
156
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
157
+ ```
158
+
159
+ ## API Reference
160
+
161
+ ### `ShadowCache(db_connection, *, ...)`
162
+
163
+ ```python
164
+ ShadowCache(
165
+ db_connection,
166
+ *,
167
+ redis_client=None,
168
+ redis_host="localhost",
169
+ redis_port=6379,
170
+ ttl=300,
171
+ auto_invalidate=True,
172
+ key_prefix="shadowcache",
173
+ )
174
+ ```
175
+
176
+ | Parameter | Default | Description |
177
+ |---|---|---|
178
+ | `db_connection` | *(required)* | An open DB-API2 MySQL connection |
179
+ | `redis_client` | `None` | Pre-configured `redis.Redis` instance; created automatically if omitted |
180
+ | `redis_host` | `"localhost"` | Redis hostname |
181
+ | `redis_port` | `6379` | Redis port |
182
+ | `ttl` | `300` | Cache TTL in seconds |
183
+ | `auto_invalidate` | `True` | Whether writes automatically evict related cache entries |
184
+ | `key_prefix` | `"shadowcache"` | Namespace prefix for all Redis keys |
185
+
186
+ ### `ShadowCache.execute(sql, params=None)`
187
+
188
+ Returns `(cursor, rows)`.
189
+
190
+ | SQL | Behaviour |
191
+ |---|---|
192
+ | `SELECT` | Checks Redis first. Hit returns `(None, cached_rows)`. Miss executes on MySQL, caches, returns `(cursor, rows)`. |
193
+ | `INSERT` | Executes on MySQL. Returns `(cursor, None)`. See `cursor.lastrowid`. |
194
+ | `UPDATE` / `DELETE` | Executes on MySQL, evicts cache for affected tables. Returns `(cursor, None)`. See `cursor.rowcount`. |
195
+ | DDL / other | Executes on MySQL. No caching, no eviction. |
196
+
197
+ ### Other Methods
198
+
199
+ | Method | Description |
200
+ |---|---|
201
+ | `invalidate_table(name)` | Evict all cached entries for a table. Returns count of keys removed. |
202
+ | `flush_cache()` | Remove all ShadowCache keys from Redis. Returns count of keys removed. |
203
+ | `stats` | Property. Returns a dict with keys `hits`, `misses`, `total_requests`, `hit_ratio`. |
204
+ | `close()` | Close the wrapped database connection. |
205
+
206
+ ## Configuration
207
+
208
+ Copy `.env.example` to `.env` and set your credentials:
209
+
210
+ ```
211
+ REDIS_HOST=localhost
212
+ REDIS_PORT=6379
213
+ MYSQL_HOST=localhost
214
+ MYSQL_PORT=3306
215
+ MYSQL_USER=your_db_user
216
+ MYSQL_PASSWORD=your_db_password
217
+ MYSQL_DATABASE=your_database
218
+ LOG_LEVEL=INFO
219
+ ```
220
+
221
+ ## Running Tests
222
+
223
+ ```bash
224
+ # Unit tests -- no Redis or MySQL needed, all mocks
225
+ python -m pytest tests/ -v
226
+ ```
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,201 @@
1
+ <p align="center">
2
+ <img src="https://img.shields.io/static/v1?label=%F0%9F%8C%9F&message=If%20Useful&style=flat&color=BC4E99">
3
+ <img src="https://badges.frapsoft.com/os/v1/open-source.svg?v=103">
4
+ <a href="https://github.com/pratham2402"><img src="https://img.shields.io/badge/View-My_Profile-green?logo=GitHub"></a>
5
+ <a href="https://github.com/pratham2402?tab=repositories"><img src="https://img.shields.io/badge/View-My_Repositories-blue?logo=GitHub"></a>
6
+ </p>
7
+
8
+ <p align="center">
9
+ <img src="https://img.shields.io/badge/python-3.8+-blue?logo=python">
10
+ <img src="https://img.shields.io/badge/license-MIT-green">
11
+ <img src="https://img.shields.io/badge/platform-mysql%20%7C%20redis-red">
12
+ </p>
13
+
14
+ # ShadowCache
15
+
16
+ <p align="center">
17
+ <img src="./README%20Banner%20Art.png" alt="ShadowCache Banner">
18
+ </p>
19
+
20
+ > **Write SQL. Get caching. Nothing else.**
21
+
22
+ ShadowCache wraps your MySQL connection and transparently caches SELECT results
23
+ in Redis. INSERT, UPDATE, or DELETE statements automatically evict affected cache
24
+ entries so your reads never serve stale data. No ORM. No boilerplate. No config.
25
+
26
+ <br>
27
+
28
+ <details open>
29
+ <summary><b>Table of Contents</b></summary>
30
+
31
+ - [The Problem](#the-problem)
32
+ - [Features](#features)
33
+ - [Installation](#installation)
34
+ - [Quick Start](#quick-start)
35
+ - [API Reference](#api-reference)
36
+ - [Configuration](#configuration)
37
+ - [Running Tests](#running-tests)
38
+ - [License](#license)
39
+
40
+ </details>
41
+
42
+ ## The Problem
43
+
44
+ Every developer who writes raw SQL eventually writes this:
45
+
46
+ ```python
47
+ # 8 lines of boilerplate for every cached query
48
+ cache_key = f"user:{user_id}"
49
+ cached = redis.get(cache_key)
50
+ if cached:
51
+ return json.loads(cached)
52
+
53
+ cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
54
+ row = cursor.fetchone()
55
+ redis.set(cache_key, json.dumps(row), ex=300)
56
+ return row
57
+ ```
58
+
59
+ And on every INSERT, UPDATE, or DELETE you need to remember:
60
+
61
+ ```python
62
+ cursor.execute("UPDATE users SET name = %s WHERE id = %s", (name, user_id))
63
+ redis.delete(f"user:{user_id}") # easy to forget, easy to get wrong
64
+ ```
65
+
66
+ ```diff
67
+ - Boilerplate for every query
68
+ - Manual invalidation you will forget
69
+ + One line. Caching and invalidation are automatic.
70
+ ```
71
+
72
+ **With ShadowCache:**
73
+
74
+ ```python
75
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
76
+ cursor, _ = cache.execute("UPDATE users SET name = %s WHERE id = %s", ("Alice", 42))
77
+ ```
78
+
79
+ ## Features
80
+
81
+ | | |
82
+ |---|---|
83
+ | **Zero-schema caching** | Works with any MySQL table, any query. No model definitions needed. |
84
+ | **Write-triggered eviction** | INSERT, UPDATE, and DELETE automatically evict cached SELECTs for the same table. |
85
+ | **TTL safety net** | Cached entries expire after a configurable time-to-live. Eventual consistency guaranteed. |
86
+ | **Graceful fallback** | If Redis is unreachable, queries still execute against MySQL. |
87
+
88
+ ## Installation
89
+
90
+ ```bash
91
+ pip install shadowcache
92
+ ```
93
+
94
+ > Or install from source:
95
+
96
+ ```bash
97
+ git clone https://github.com/pratham2402/ShadowCache.git
98
+ cd ShadowCache
99
+ pip install -r requirements.txt
100
+ ```
101
+
102
+ **Requires:** Python 3.8+, Redis, MySQL.
103
+
104
+ ## Quick Start
105
+
106
+ ```python
107
+ import mysql.connector
108
+ from shadowcache import ShadowCache
109
+
110
+ conn = mysql.connector.connect(
111
+ host="localhost", database="my_app",
112
+ user="app_user", password="secret",
113
+ )
114
+
115
+ cache = ShadowCache(conn)
116
+
117
+ # Cold miss -- hits MySQL, stores in Redis
118
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
119
+
120
+ # Warm hit -- returns from Redis instantly
121
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
122
+
123
+ # Write evicts the cache
124
+ cache.execute("UPDATE users SET name = %s WHERE id = %s", ("Alice", 42))
125
+
126
+ # Cache was evicted -- fresh data from MySQL
127
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
128
+ ```
129
+
130
+ ## API Reference
131
+
132
+ ### `ShadowCache(db_connection, *, ...)`
133
+
134
+ ```python
135
+ ShadowCache(
136
+ db_connection,
137
+ *,
138
+ redis_client=None,
139
+ redis_host="localhost",
140
+ redis_port=6379,
141
+ ttl=300,
142
+ auto_invalidate=True,
143
+ key_prefix="shadowcache",
144
+ )
145
+ ```
146
+
147
+ | Parameter | Default | Description |
148
+ |---|---|---|
149
+ | `db_connection` | *(required)* | An open DB-API2 MySQL connection |
150
+ | `redis_client` | `None` | Pre-configured `redis.Redis` instance; created automatically if omitted |
151
+ | `redis_host` | `"localhost"` | Redis hostname |
152
+ | `redis_port` | `6379` | Redis port |
153
+ | `ttl` | `300` | Cache TTL in seconds |
154
+ | `auto_invalidate` | `True` | Whether writes automatically evict related cache entries |
155
+ | `key_prefix` | `"shadowcache"` | Namespace prefix for all Redis keys |
156
+
157
+ ### `ShadowCache.execute(sql, params=None)`
158
+
159
+ Returns `(cursor, rows)`.
160
+
161
+ | SQL | Behaviour |
162
+ |---|---|
163
+ | `SELECT` | Checks Redis first. Hit returns `(None, cached_rows)`. Miss executes on MySQL, caches, returns `(cursor, rows)`. |
164
+ | `INSERT` | Executes on MySQL. Returns `(cursor, None)`. See `cursor.lastrowid`. |
165
+ | `UPDATE` / `DELETE` | Executes on MySQL, evicts cache for affected tables. Returns `(cursor, None)`. See `cursor.rowcount`. |
166
+ | DDL / other | Executes on MySQL. No caching, no eviction. |
167
+
168
+ ### Other Methods
169
+
170
+ | Method | Description |
171
+ |---|---|
172
+ | `invalidate_table(name)` | Evict all cached entries for a table. Returns count of keys removed. |
173
+ | `flush_cache()` | Remove all ShadowCache keys from Redis. Returns count of keys removed. |
174
+ | `stats` | Property. Returns a dict with keys `hits`, `misses`, `total_requests`, `hit_ratio`. |
175
+ | `close()` | Close the wrapped database connection. |
176
+
177
+ ## Configuration
178
+
179
+ Copy `.env.example` to `.env` and set your credentials:
180
+
181
+ ```
182
+ REDIS_HOST=localhost
183
+ REDIS_PORT=6379
184
+ MYSQL_HOST=localhost
185
+ MYSQL_PORT=3306
186
+ MYSQL_USER=your_db_user
187
+ MYSQL_PASSWORD=your_db_password
188
+ MYSQL_DATABASE=your_database
189
+ LOG_LEVEL=INFO
190
+ ```
191
+
192
+ ## Running Tests
193
+
194
+ ```bash
195
+ # Unit tests -- no Redis or MySQL needed, all mocks
196
+ python -m pytest tests/ -v
197
+ ```
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shadowcache"
7
+ version = "0.1.0"
8
+ description = "Transparent Redis caching for raw SQL connections - no ORM required"
9
+ readme = {file = "README.md", content-type = "text/markdown"}
10
+ license = "MIT"
11
+ authors = [{name = "Pratham Bhosale"}]
12
+ keywords = ["redis", "mysql", "cache", "sql", "caching", "database", "db-api", "transparent-cache", "query-cache"]
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.8",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Database",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+ dependencies = [
27
+ "redis>=4.0",
28
+ "mysql-connector-python>=8.0",
29
+ "sqlglot>=20.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/pratham2402/ShadowCache"
34
+ Repository = "https://github.com/pratham2402/ShadowCache"
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=7.0",
39
+ ]
40
+
41
+ [tool.setuptools.packages.find]
42
+ include = ["shadowcache*"]
43
+
44
+ [tool.setuptools.package-data]
45
+ shadowcache = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ """ShadowCache -- Transparent Redis caching for raw SQL connections.
2
+
3
+ Usage:
4
+ import mysql.connector
5
+ from shadowcache import ShadowCache
6
+
7
+ conn = mysql.connector.connect(host="localhost", database="mydb")
8
+ cache = ShadowCache(conn)
9
+
10
+ # SELECTs are transparently cached
11
+ cursor, rows = cache.execute("SELECT * FROM users WHERE id = %s", (42,))
12
+
13
+ # INSERT/UPDATE/DELETE automatically evict related cache entries
14
+ cache.execute("UPDATE users SET name = %s WHERE id = %s", ("Alice", 42))
15
+ """
16
+
17
+ from shadowcache.exceptions import ShadowCacheError
18
+
19
+ __all__ = ["ShadowCache", "ShadowCacheError"]
20
+ __version__ = "0.1.0"
21
+
22
+
23
+ def __getattr__(name):
24
+ """Lazily import ShadowCache so the package is usable even when
25
+ only a subset of modules have been installed."""
26
+ if name == "ShadowCache":
27
+ from shadowcache.core import ShadowCache as _sc
28
+
29
+ return _sc
30
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
31
+