mirror of
https://github.com/Redume/Shirino.git
synced 2025-05-19 02:05:26 +01:00
feat: add sqlite database
This commit is contained in:
parent
e170b5bff2
commit
4aa4742cd2
4 changed files with 205 additions and 1 deletions
5
database/schemas/data.sql
Normal file
5
database/schemas/data.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
user_id INTEGER UNIQUE NOT NULL,
|
||||||
|
chart BOOLEAN DEFAULT 1,
|
||||||
|
lang TEXT DEFAULT 'en'
|
||||||
|
);
|
189
database/server.py
Normal file
189
database/server.py
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
import json
|
||||||
|
from datetime import date, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
|
||||||
|
|
||||||
|
def custom_encoder(obj):
|
||||||
|
"""
|
||||||
|
Custom JSON encoder for objects not serializable by default.
|
||||||
|
|
||||||
|
Converts date and datetime objects to ISO 8601 string format.
|
||||||
|
Raises TypeError for unsupported types.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (date, datetime)):
|
||||||
|
return obj.isoformat()
|
||||||
|
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
"""
|
||||||
|
Asynchronous SQLite database handler using aiosqlite.
|
||||||
|
|
||||||
|
This class manages a single asynchronous connection to an SQLite database file
|
||||||
|
and provides common methods for executing queries, including:
|
||||||
|
|
||||||
|
- Connecting and disconnecting to the database.
|
||||||
|
- Fetching a single row or multiple rows.
|
||||||
|
- Inserting data and returning the last inserted row ID.
|
||||||
|
- Updating or deleting data and returning the count of affected rows.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
db_path (str): Path to the SQLite database file.
|
||||||
|
conn (Optional[aiosqlite.Connection]): The active database connection, or None if disconnected.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
connect(): Asynchronously open a connection to the database.
|
||||||
|
disconnect(): Asynchronously close the database connection.
|
||||||
|
fetch(query, *args): Execute a query and return a single row as a dictionary.
|
||||||
|
fetchmany(query, *args): Execute a query and return multiple rows as a list of dictionaries.
|
||||||
|
insert(query, *args): Execute an INSERT query and return the last inserted row ID.
|
||||||
|
update(query, *args): Execute an UPDATE or DELETE query and return the number of affected rows.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If any method is called before the database connection is established.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
db = Database("example.db")
|
||||||
|
await db.connect()
|
||||||
|
user = await db.fetch("SELECT * FROM users WHERE id = ?", user_id)
|
||||||
|
await db.disconnect()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_path: str):
|
||||||
|
"""
|
||||||
|
Initialize Database instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_path (str): Path to SQLite database file.
|
||||||
|
"""
|
||||||
|
self.db_path = db_path
|
||||||
|
self.conn: Optional[aiosqlite.Connection] = None
|
||||||
|
|
||||||
|
|
||||||
|
async def _create_table(self) -> None:
|
||||||
|
"""
|
||||||
|
Create table from SQL file using aiosqlite.
|
||||||
|
|
||||||
|
Reads SQL commands from 'schemas/data.sql'
|
||||||
|
and executes them as a script.
|
||||||
|
"""
|
||||||
|
sql_file = Path(__file__).parent / "schemas" / "data.sql"
|
||||||
|
sql = sql_file.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
async with self.conn.execute("BEGIN"):
|
||||||
|
await self.conn.executescript(sql)
|
||||||
|
await self.conn.commit()
|
||||||
|
|
||||||
|
async def connect(self) -> None:
|
||||||
|
"""
|
||||||
|
Open SQLite database connection asynchronously.
|
||||||
|
"""
|
||||||
|
self.conn = await aiosqlite.connect(self.db_path)
|
||||||
|
self.conn.row_factory = aiosqlite.Row # return dict-like rows
|
||||||
|
|
||||||
|
async def disconnect(self) -> None:
|
||||||
|
"""
|
||||||
|
Close SQLite database connection asynchronously.
|
||||||
|
"""
|
||||||
|
if self.conn:
|
||||||
|
await self.conn.close()
|
||||||
|
self.conn = None
|
||||||
|
|
||||||
|
async def fetch(self, query: str, *args) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute a query and fetch a single row as a dictionary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): SQL query string.
|
||||||
|
*args: Parameters for the SQL query.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: The first row returned by the query as a dict,
|
||||||
|
or an empty dict if no row is found.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If the database connection is not initialized.
|
||||||
|
"""
|
||||||
|
if not self.conn:
|
||||||
|
raise RuntimeError("Database connection is not initialized.")
|
||||||
|
|
||||||
|
async with self.conn.execute(query, args) as cursor:
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return json.loads(json.dumps(dict(row), default=custom_encoder))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def fetchmany(self, query: str, *args) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Execute a query and fetch multiple rows as a list of dictionaries.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): SQL query string.
|
||||||
|
*args: Parameters for the SQL query.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: List of rows as dictionaries,
|
||||||
|
or empty list if no rows found.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If the database connection is not initialized.
|
||||||
|
"""
|
||||||
|
if not self.conn:
|
||||||
|
raise RuntimeError("Database connection is not initialized.")
|
||||||
|
|
||||||
|
async with self.conn.execute(query, args) as cursor:
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
return json.loads(
|
||||||
|
json.dumps(
|
||||||
|
[dict(row) for row in rows],
|
||||||
|
default=custom_encoder
|
||||||
|
)
|
||||||
|
) if rows else []
|
||||||
|
|
||||||
|
async def insert(self, query: str, *args) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute an INSERT query and return the last inserted row ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): SQL INSERT query string.
|
||||||
|
*args: Parameters for the SQL query.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: Dictionary containing the last inserted row ID.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If the database connection is not initialized.
|
||||||
|
"""
|
||||||
|
if not self.conn:
|
||||||
|
raise RuntimeError("Database connection is not initialized.")
|
||||||
|
|
||||||
|
async with self.conn.execute(query, args) as cursor:
|
||||||
|
await self.conn.commit()
|
||||||
|
return {"last_row_id": cursor.lastrowid}
|
||||||
|
|
||||||
|
async def update(self, query: str, *args) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute an UPDATE or DELETE query and return affected rows count.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): SQL UPDATE or DELETE query string.
|
||||||
|
*args: Parameters for the SQL query.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: Dictionary containing the number of affected rows.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If the database connection is not initialized.
|
||||||
|
"""
|
||||||
|
if not self.conn:
|
||||||
|
raise RuntimeError("Database connection is not initialized.")
|
||||||
|
|
||||||
|
async with self.conn.execute(query, args) as cursor:
|
||||||
|
await self.conn.commit()
|
||||||
|
return {"rows_affected": cursor.rowcount}
|
9
main.py
9
main.py
|
@ -13,12 +13,14 @@ from functions.convert import Converter
|
||||||
from functions.create_chart import create_chart
|
from functions.create_chart import create_chart
|
||||||
from utils.format_number import format_number
|
from utils.format_number import format_number
|
||||||
from utils.inline_query import reply
|
from utils.inline_query import reply
|
||||||
|
from database.server import Database
|
||||||
|
|
||||||
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
|
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
|
||||||
bot = Bot(
|
bot = Bot(
|
||||||
token=config['telegram_token'],
|
token=config['telegram_token'],
|
||||||
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
|
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
|
||||||
)
|
)
|
||||||
|
db = Database('shirino.db')
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
@ -127,6 +129,8 @@ async def currency(query: types.InlineQuery) -> None:
|
||||||
|
|
||||||
|
|
||||||
async def on_startup(bot: Bot) -> None:
|
async def on_startup(bot: Bot) -> None:
|
||||||
|
await db.connect()
|
||||||
|
await db._create_table()
|
||||||
await bot.set_webhook(
|
await bot.set_webhook(
|
||||||
f"{config['webhook']['base_url']}{config['webhook']['path']}",
|
f"{config['webhook']['base_url']}{config['webhook']['path']}",
|
||||||
secret_token=config['webhook']['secret_token'],
|
secret_token=config['webhook']['secret_token'],
|
||||||
|
@ -134,11 +138,16 @@ async def on_startup(bot: Bot) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_shutdown():
|
||||||
|
await db.disconnect()
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
||||||
dp.include_router(router)
|
dp.include_router(router)
|
||||||
dp.startup.register(on_startup)
|
dp.startup.register(on_startup)
|
||||||
|
dp.shutdown.register(on_shutdown)
|
||||||
|
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
webhook_requests_handler = SimpleRequestHandler(
|
webhook_requests_handler = SimpleRequestHandler(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
aiohttp~=3.9.5
|
aiohttp~=3.9.5
|
||||||
PyYAML~=6.0.1
|
PyYAML~=6.0.1
|
||||||
aiogram~=3.15.0
|
aiogram~=3.15.0
|
||||||
|
aiosqlite~=0.21.0
|
Loading…
Add table
Reference in a new issue