import csv
import importlib.resources
import json
from io import StringIO
from typing import Any
import banterbot.resources
from banterbot import config
[docs]
class ResourceManager:
"""
An interface to simplify loading resources from the `/banterbot/resources/` data directory. In addition to
syntactically simplifying the process, this class gives the option to cache the loaded files to reduce overhead on
future calls.
"""
_raw_data: dict[str] = {}
_csv_data: dict[list[list[str]]] = {}
_json_data: dict[dict[Any]] = {}
[docs]
@classmethod
def reset_cache(cls) -> None:
"""
Reset the entire cache by deleting the contents of the `ResourceLoader._raw_data`, `ResourceLoader._csv_data`,
and `ResourceLoader._json_data` dicts.
"""
cls.reset_raw_cache()
cls.reset_csv_cache()
cls.reset_json_cache()
[docs]
@classmethod
def reset_raw_cache(cls) -> None:
"""
Reset the raw data cache by deleting the contents of the `ResourceLoader._raw_data` dict.
"""
cls._raw_data.clear()
[docs]
@classmethod
def reset_csv_cache(cls) -> None:
"""
Reset the CSV data cache by deleting the contents of the `ResourceLoader._csv_data` dict.
"""
cls._csv_data.clear()
[docs]
@classmethod
def reset_json_cache(cls) -> None:
"""
Reset the JSON data cache by deleting the contents of the `ResourceLoader._json_data` dict.
"""
cls._json_data.clear()
[docs]
@classmethod
def load_raw(cls, filename: str, cache: bool = True, reset: bool = False, encoding: str = "utf-8") -> str:
"""
Load a specified file by filename and return its contents as a string.
Args:
filename (str): The name of the resource file — should including its suffix.
cache (bool): If True, cache the loaded data to reduce overhead the next time its loaded.
reset (bool): If set to True, reloads the contents from file, disregarding the current state of the cache.
encoding (str): The type of encoding to use when loading the resource.
Returns:
str: The resource file's contents as a string.
"""
if reset or filename not in cls._raw_data:
path = importlib.resources.files(banterbot.resources).joinpath(filename)
try:
with open(file=path, mode="r", encoding=config.ENCODING) as fs:
data = fs.read()
except FileNotFoundError:
message = f"Class `ResourceLoader` found no such resource: `{filename}`"
raise FileNotFoundError(message)
if cache:
cls._raw_data[filename] = data
else:
data = cls._raw_data[filename]
return data
[docs]
@classmethod
def load_json(cls, filename: str, cache: bool = True, reset: bool = False, encoding: str = "utf-8") -> dict[Any]:
"""
Load a specified JSON file by filename and return its contents as a dictionary.
Args:
filename (str): The name of the resource file — should be a JSON file.
cache (bool): If True, cache the loaded data to reduce overhead the next time its loaded.
reset (bool): If set to True, reloads the contents from file, disregarding the current state of the cache.
encoding (str): The type of encoding to use when loading the resource.
Returns:
dict[Any]: The JSON data formatted as a dictionary.
"""
if reset or filename not in cls._json_data:
raw_data = cls.load_raw(filename=filename, cache=False, encoding=encoding)
try:
data = json.loads(raw_data)
except json.decoder.JSONDecodeError as e:
message = f"Class `ResourceLoader` unable to interpret resource as JSON: `{filename}`. {e.args[0]}"
raise json.decoder.JSONDecodeError(message)
if cache:
cls._json_data[filename] = data
else:
data = cls._json_data[filename]
return data
[docs]
@classmethod
def load_csv(
cls,
filename: str,
cache: bool = True,
reset: bool = False,
encoding: str = "utf-8",
delimiter: str = ",",
quotechar: str = '"',
dialect: str = "excel",
strict: bool = True,
) -> list[list[str]]:
"""
Load a specified CSV file by filename and return its contents as a nested list of strings.
Args:
filename (str): The name of the resource file — should be a CSV file.
cache (bool): If True, cache the loaded data to reduce overhead the next time its loaded.
reset (bool): If set to True, reloads the contents from file, disregarding the current state of the cache.
encoding (str): The type of encoding to use when loading the resource.
delimiter (str): The CSV delimiter character.
quotechar (str): The CSV quote character.
dialect (str): The CSV dialect.
strict (bool): If True, raises an exception when the file is not correctly formatted.
Returns:
list[list[str]]: The CSV data formatted as a nested list of strings.
"""
if reset or filename not in cls._csv_data:
raw_data = cls.load_raw(filename=filename, cache=False, encoding=encoding)
try:
data = []
with StringIO(raw_data, newline="") as fs:
reader = csv.reader(fs, delimiter=delimiter, quotechar=quotechar, dialect=dialect, strict=strict)
for row in reader:
data.append(row)
except csv.Error as e:
message = f"Class `ResourceLoader` unable to interpret resource as CSV: `{filename}`, {e.args[0]}"
raise csv.Error(message)
if cache:
cls._csv_data[filename] = data
else:
data = cls._csv_data[filename]
return data