Skip to content

Commit 62771b2

Browse files
committed
fixes
1 parent d46f253 commit 62771b2

9 files changed

Lines changed: 341 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Build and Deploy sqlite-cache-storage
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
paths:
7+
- 'sqlite-cache-storage/**'
8+
9+
jobs:
10+
build-and-deploy:
11+
permissions:
12+
contents: write
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v3
18+
with:
19+
persist-credentials: false
20+
fetch-depth: 0
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v4
24+
with:
25+
python-version: '3.x'
26+
27+
- name: Install build tools
28+
run: |
29+
python -m pip install --upgrade pip
30+
python -m pip install twine setuptools
31+
32+
- name: Build and Push
33+
run: |
34+
npm run upload -- -u __token__ -p ${{ secrets.PYPI_TOKEN }}
35+
working-directory: sqlite-cache-storage
36+
37+
- name: Pull Changes
38+
run: |
39+
git pull
40+
41+
- name: Commit & Push changes
42+
uses: actions-js/push@master
43+
with:
44+
github_token: ${{ secrets.GITHUB_TOKEN }}
45+
branch: master
46+
47+

pg-cache-storage/pg_cache_storage/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
from hashlib import sha256
33

4+
__all__ = ['PostgresCacheStorage']
5+
46

57
class PostgresCacheStorage:
68
"""PostgreSQL cache storage using psycopg3."""

sqlite-cache-storage/LICENSE

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Omkar Cloud
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.
22+
23+

sqlite-cache-storage/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# sqlite-cache-storage
2+
3+
SQLite cache storage backend for botasaurus.
4+
5+
## Installation
6+
7+
```bash
8+
pip install sqlite-cache-storage
9+
```
10+
11+
## Usage
12+
13+
### With decorators like `@task`, `@request`, `@browser`
14+
15+
```python
16+
from sqlite_cache_storage import SqliteCacheStorage
17+
from botasaurus.task import task
18+
from datetime import timedelta
19+
20+
# Create storage instance
21+
sqlite_storage = SqliteCacheStorage(
22+
db_path="cache.db"
23+
)
24+
25+
@task(cache=True, expires_in=timedelta(days=1), cache_storage=sqlite_storage)
26+
def my_scraper(data):
27+
# scraping logic
28+
return result
29+
```
30+
31+
### With `@cache` decorator
32+
33+
```python
34+
from sqlite_cache_storage import SqliteCacheStorage
35+
from botasaurus.decorator_helpers import cache
36+
from datetime import timedelta
37+
38+
# Create storage instance
39+
sqlite_storage = SqliteCacheStorage(
40+
db_path="cache.db"
41+
)
42+
43+
# Use the decorator
44+
@cache(expires_in=timedelta(days=1), cache_storage=sqlite_storage)
45+
def my_function(data):
46+
# time-consuming operation
47+
return result
48+
```
49+
50+
51+
## API
52+
53+
### SqliteCacheStorage
54+
55+
```python
56+
SqliteCacheStorage(
57+
db_path: str = 'cache.db',
58+
table_name: str = 'botasaurus_cache'
59+
)
60+
```
61+
62+
#### Methods
63+
64+
- `get(func_name, key_data, expires_in=None)` - Get cached value. Returns `{"data": value}` or `None`
65+
- `put(func_name, key_data, data)` - Store value in cache
66+
- `delete(func_name, key_data)` - Delete cached value
67+
68+
## License
69+
70+
MIT
71+
72+
73+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import re
2+
3+
def main():
4+
with open("setup.py", "r") as file:
5+
setup_file = file.read()
6+
7+
# Match the version string using a robust pattern
8+
version_match = re.search(r"version=['\"](.*?)['\"]", setup_file)
9+
10+
if version_match:
11+
current_version = version_match.group(1).split(".")
12+
13+
# Increment the last part of the version (patch level)
14+
try:
15+
new_version = (
16+
f"{current_version[0]}.{current_version[1]}.{int(current_version[2]) + 1}"
17+
)
18+
except ValueError:
19+
print(
20+
"Invalid version format in setup.py. Please use a valid semantic versioning format (e.g., 1.2.3)."
21+
)
22+
return
23+
24+
setup_file = re.sub(
25+
r"version=['\"](.*?)['\"]", f"version='{new_version}'", setup_file
26+
)
27+
28+
with open("setup.py", "w") as file:
29+
file.write(setup_file)
30+
print(f"Version incremented to: {new_version}")
31+
else:
32+
print("Version string not found in setup.py")
33+
34+
main()
35+
36+

sqlite-cache-storage/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"scripts": {
3+
"upload": "python3 increment_version.py && rm -rf dist/ sqlite_cache_storage.egg-info MANIFEST && python3 setup.py sdist && python3 -m pip install -e . && python3 -m twine upload dist/* --verbose",
4+
"install": "rm -rf dist/ sqlite_cache_storage.egg-info MANIFEST && python3 setup.py sdist && python3 -m pip install -e .",
5+
"local-install": "python3 -m pip install ."
6+
}
7+
}
8+
9+

sqlite-cache-storage/setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[metadata]
2+
description_file = README.md
3+
4+

sqlite-cache-storage/setup.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from setuptools import setup
2+
3+
__author__ = "Chetan Jain <53407137+Chetan11-dev@users.noreply.github.com>"
4+
5+
6+
install_requires = []
7+
extras_require = {}
8+
9+
10+
def get_description():
11+
try:
12+
with open("README.md", encoding="utf-8") as readme_file:
13+
long_description = readme_file.read()
14+
return long_description
15+
except:
16+
return None
17+
18+
19+
setup(
20+
name="sqlite-cache-storage",
21+
version='1.0.0',
22+
author="Chetan Jain",
23+
author_email="53407137+Chetan11-dev@users.noreply.github.com",
24+
description="SQLite cache storage for botasaurus.",
25+
license="MIT",
26+
keywords=["sqlite", "sqlite-cache-storage", "cache", "botasaurus", "sqlite3"],
27+
url="https://github.com/omkarcloud/botasaurus",
28+
packages=["sqlite_cache_storage"],
29+
long_description_content_type="text/markdown",
30+
long_description=get_description(),
31+
python_requires=">=3.7",
32+
classifiers=[
33+
"Development Status :: 5 - Production/Stable",
34+
"Topic :: Software Development :: Libraries :: Python Modules",
35+
"License :: OSI Approved :: MIT License",
36+
"Programming Language :: Python :: 3",
37+
"Programming Language :: Python :: 3.7",
38+
"Programming Language :: Python :: 3.8",
39+
"Programming Language :: Python :: 3.9",
40+
"Programming Language :: Python :: 3.10",
41+
"Programming Language :: Python :: 3.11",
42+
"Programming Language :: Python :: 3.12",
43+
],
44+
install_requires=install_requires,
45+
extras_require=extras_require,
46+
)
47+
48+
49+
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import json
2+
from hashlib import sha256
3+
4+
__all__ = ['SqliteCacheStorage']
5+
6+
7+
class SqliteCacheStorage:
8+
"""SQLite cache storage using sqlite3."""
9+
10+
def __init__(self, db_path: str = 'cache.db', table_name: str = "botasaurus_cache"):
11+
self.db_path = db_path
12+
self.table_name = table_name
13+
self._ensure_table()
14+
15+
def _get_connection(self):
16+
import sqlite3
17+
conn = sqlite3.connect(self.db_path)
18+
conn.row_factory = sqlite3.Row
19+
return conn
20+
21+
def _hash(self, data) -> str:
22+
"""Generate sha256 hash from data."""
23+
serialized = json.dumps(data).encode('utf-8')
24+
return sha256(serialized).hexdigest()
25+
26+
def _make_key(self, func_name: str, key_data) -> str:
27+
"""Create cache key from func_name and key_data."""
28+
return self._hash([func_name, key_data])
29+
30+
def _ensure_table(self):
31+
"""Create cache table if not exists."""
32+
with self._get_connection() as conn:
33+
conn.execute("""
34+
CREATE TABLE IF NOT EXISTS %s (
35+
key CHAR(64) PRIMARY KEY,
36+
data TEXT,
37+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
38+
)
39+
""" % self.table_name)
40+
conn.commit()
41+
42+
def get(self, func_name: str, key_data, expires_in=None):
43+
"""
44+
Returns:
45+
{"data": value} if cache hit (value can be None)
46+
None if cache miss or expired
47+
"""
48+
key = self._make_key(func_name, key_data)
49+
with self._get_connection() as conn:
50+
cursor = conn.cursor()
51+
if expires_in is not None:
52+
cursor.execute(
53+
f"""SELECT data FROM {self.table_name}
54+
WHERE key = ? AND created_at > datetime('now', ?)""",
55+
(key, f'-{int(expires_in.total_seconds())} seconds')
56+
)
57+
row = cursor.fetchone()
58+
if row is None:
59+
cursor.execute(
60+
"DELETE FROM %s WHERE key = ?" % self.table_name,
61+
(key,)
62+
)
63+
conn.commit()
64+
return None
65+
else:
66+
cursor.execute(
67+
"SELECT data FROM %s WHERE key = ?" % self.table_name,
68+
(key,)
69+
)
70+
row = cursor.fetchone()
71+
72+
if row:
73+
return {"data": json.loads(row["data"])}
74+
return None
75+
76+
def put(self, func_name: str, key_data, data) -> None:
77+
key = self._make_key(func_name, key_data)
78+
data_json = json.dumps(data)
79+
with self._get_connection() as conn:
80+
conn.execute("""
81+
INSERT INTO %s (key, data, created_at)
82+
VALUES (?, ?, CURRENT_TIMESTAMP)
83+
ON CONFLICT (key) DO UPDATE SET
84+
data = excluded.data,
85+
created_at = CURRENT_TIMESTAMP
86+
""" % self.table_name, (key, data_json))
87+
conn.commit()
88+
89+
def delete(self, func_name: str, key_data) -> None:
90+
key = self._make_key(func_name, key_data)
91+
with self._get_connection() as conn:
92+
conn.execute(
93+
"DELETE FROM %s WHERE key = ?" % self.table_name,
94+
(key,)
95+
)
96+
conn.commit()
97+
98+

0 commit comments

Comments
 (0)