mirror of
https://github.com/kennethreitz/replit-py.git
synced 2026-06-05 23:10:18 +00:00
d047feba9a
Make async delete raise key error if it runs into a non-existent key
235 lines
6.8 KiB
Python
235 lines
6.8 KiB
Python
"""Async and dict-like interfaces for interacting with Repl.it Database."""
|
|
from collections import abc
|
|
import json
|
|
from typing import Any, Dict, Iterator, Tuple
|
|
import urllib
|
|
|
|
import aiohttp
|
|
import requests
|
|
|
|
|
|
class AsyncDatabase:
|
|
"""Async interface for Repl.it Database."""
|
|
|
|
__slots__ = ("db_url", "sess")
|
|
|
|
def __init__(self, db_url: str) -> None:
|
|
"""Initialize database. You shouldn't have to do this manually.
|
|
|
|
Args:
|
|
db_url (str): Database url to use.
|
|
"""
|
|
self.db_url = db_url
|
|
|
|
async def get(self, key: str) -> str:
|
|
"""Get the value of an item from the database.
|
|
|
|
Args:
|
|
key (str): The key to retreive
|
|
|
|
Raises:
|
|
KeyError: Key is not set
|
|
|
|
Returns:
|
|
str: The value of the key
|
|
"""
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(
|
|
self.db_url + "/" + urllib.parse.quote(key)
|
|
) as response:
|
|
if response.status == 404:
|
|
raise KeyError(key)
|
|
response.raise_for_status()
|
|
return await response.text()
|
|
|
|
async def set(self, key: str, value: str) -> None:
|
|
"""Set a key in the database to value.
|
|
|
|
Args:
|
|
key (str): The key to set
|
|
value (str): The value to set it to
|
|
"""
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(self.db_url, data={key: value}) as response:
|
|
response.raise_for_status()
|
|
|
|
async def delete(self, key: str) -> None:
|
|
"""Delete a key from the database.
|
|
|
|
Args:
|
|
key (str): The key to delete
|
|
"""
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.delete(
|
|
self.db_url + "/" + urllib.parse.quote(key)
|
|
) as response:
|
|
if(response.status_code==404):
|
|
raise KeyError(key)
|
|
response.raise_for_status()
|
|
|
|
async def list(self, prefix: str) -> Tuple[str, ...]:
|
|
"""List keys in the database which start with prefix.
|
|
|
|
Args:
|
|
prefix (str): The prefix keys must start with, blank not not check.
|
|
|
|
Returns:
|
|
Tuple[str]: The keys found.
|
|
"""
|
|
params = {"prefix": prefix, "encode": "true"}
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(self.db_url, params=params) as response:
|
|
response.raise_for_status()
|
|
text = await response.text()
|
|
if not text:
|
|
return tuple()
|
|
else:
|
|
return tuple(urllib.parse.unquote(k) for k in text.split("\n"))
|
|
|
|
async def to_dict(self, prefix: str = "") -> Dict[str, str]:
|
|
"""Dump all data in the database into a dictionary.
|
|
|
|
Args:
|
|
prefix (str): The prefix the keys must start with,
|
|
blank means anything. Defaults to "".
|
|
|
|
Returns:
|
|
Dict[str, str]: All keys in the database.
|
|
"""
|
|
ret = {}
|
|
keys = await self.list(prefix=prefix)
|
|
for i in keys:
|
|
ret[i] = await self.get(i)
|
|
return ret
|
|
|
|
async def keys(self) -> Tuple[str, ...]:
|
|
"""Get all keys in the database.
|
|
|
|
Returns:
|
|
Tuple[str]: The keys in the database.
|
|
"""
|
|
return await self.list("")
|
|
|
|
async def values(self) -> Tuple[str, ...]:
|
|
"""Get every value in the database.
|
|
|
|
Returns:
|
|
Tuple[str]: The values in the database.
|
|
"""
|
|
data = await self.to_dict()
|
|
return tuple(data.values())
|
|
|
|
async def items(self) -> Tuple[Tuple[str, str], ...]:
|
|
"""Convert the database to a dict and return the dict's items method.
|
|
|
|
Returns:
|
|
Tuple[Tuple[str]]: The items
|
|
"""
|
|
return tuple((await self.to_dict()).items())
|
|
|
|
def __repr__(self) -> str:
|
|
"""A representation of the database.
|
|
|
|
Returns:
|
|
A string representation of the database object.
|
|
"""
|
|
return f"<{self.__class__.__name__}(db_url={self.db_url!r})>"
|
|
|
|
|
|
class Database(abc.MutableMapping):
|
|
"""Dictionary-like interface for Repl.it Database.
|
|
|
|
This interface will coerce all values everything to and from JSON. If you
|
|
don't want this, use AsyncDatabase instead.
|
|
"""
|
|
|
|
__slots__ = ("db_url", "sess")
|
|
|
|
def __init__(self, db_url: str) -> None:
|
|
"""Initialize database. You shouldn't have to do this manually.
|
|
|
|
Args:
|
|
db_url (str): Database url to use.
|
|
"""
|
|
self.db_url = db_url
|
|
self.sess = requests.Session()
|
|
|
|
def __getitem__(self, key: str) -> Any:
|
|
"""Get the value of an item from the database.
|
|
|
|
Args:
|
|
key (str): The key to retreive
|
|
|
|
Raises:
|
|
KeyError: Key is not set
|
|
|
|
Returns:
|
|
Any: The value of the key
|
|
"""
|
|
r = self.sess.get(f"{self.db_url}/{key}")
|
|
if r.status_code == 404:
|
|
raise KeyError(key)
|
|
|
|
r.raise_for_status()
|
|
return json.loads(r.text)
|
|
|
|
def __setitem__(self, key: str, value: Any) -> None:
|
|
"""Set a key in the database to value.
|
|
|
|
Args:
|
|
key (str): The key to set
|
|
value (Any): The value to set it to. Must be JSON-serializable.
|
|
"""
|
|
j = json.dumps(value, separators=(",", ":"))
|
|
r = self.sess.post(self.db_url, data={key: j})
|
|
r.raise_for_status()
|
|
|
|
def __delitem__(self, key: str) -> None:
|
|
"""Delete a key from the database.
|
|
|
|
Args:
|
|
key (str): The key to delete
|
|
|
|
Raises:
|
|
KeyError: Key is not set
|
|
"""
|
|
r = self.sess.delete(f"{self.db_url}/{key}")
|
|
if r.status_code == 404:
|
|
raise KeyError(key)
|
|
|
|
r.raise_for_status()
|
|
|
|
def __iter__(self) -> Iterator[str]:
|
|
"""Return an iterator for the database."""
|
|
return iter(self.prefix(""))
|
|
|
|
def __len__(self) -> int:
|
|
"""The number of keys in the database."""
|
|
return len(self.prefix(""))
|
|
|
|
def prefix(self, prefix: str) -> Tuple[str, ...]:
|
|
"""Return all of the keys in the database that begin with the prefix.
|
|
|
|
Args:
|
|
prefix (str): The prefix the keys must start with,
|
|
blank means anything.
|
|
|
|
Returns:
|
|
Tuple[str]: The keys found.
|
|
"""
|
|
r = requests.get(f"{self.db_url}", params={"prefix": prefix, "encode": "true"})
|
|
r.raise_for_status()
|
|
|
|
if not r.text:
|
|
return tuple()
|
|
else:
|
|
return tuple(urllib.parse.unquote(k) for k in r.text.split("\n"))
|
|
|
|
def __repr__(self) -> str:
|
|
"""A representation of the database.
|
|
|
|
Returns:
|
|
A string representation of the database object.
|
|
"""
|
|
return f"<{self.__class__.__name__}(db_url={self.db_url!r})>"
|