mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Added full offline support and configurable logging level and API requests debug logs
This commit is contained in:
@@ -108,5 +108,8 @@ DISABLE_RUFFLE_RS = str_to_bool(os.environ.get("DISABLE_RUFFLE_RS", "false"))
|
||||
# FRONTEND
|
||||
UPLOAD_TIMEOUT = int(os.environ.get("UPLOAD_TIMEOUT", 600))
|
||||
|
||||
# LOGGING
|
||||
LOGLEVEL: Final = os.environ.get("LOGLEVEL", "INFO")
|
||||
|
||||
# TESTING
|
||||
IS_PYTEST_RUN: Final = bool(os.environ.get("PYTEST_VERSION", False))
|
||||
|
||||
@@ -22,11 +22,11 @@ from handler.filesystem import (
|
||||
fs_rom_handler,
|
||||
)
|
||||
from handler.filesystem.roms_handler import FSRom
|
||||
from handler.metadata.igdb_handler import IGDB_API_ENABLED
|
||||
from handler.metadata.moby_handler import MOBY_API_ENABLED
|
||||
from handler.redis_handler import high_prio_queue, redis_client
|
||||
from handler.scan_handler import ScanType, scan_firmware, scan_platform, scan_rom
|
||||
from handler.socket_handler import socket_handler
|
||||
from logger.formatter import LIGHTYELLOW
|
||||
from logger.formatter import highlight as hl
|
||||
from logger.logger import log
|
||||
from models.platform import Platform
|
||||
from models.rom import Rom
|
||||
@@ -124,11 +124,6 @@ async def scan_platforms(
|
||||
|
||||
sm = _get_socket_manager()
|
||||
|
||||
if not IGDB_API_ENABLED and not MOBY_API_ENABLED:
|
||||
log.error("Search error: No metadata providers enabled")
|
||||
await sm.emit("scan:done_ko", "No metadata providers enabled")
|
||||
return
|
||||
|
||||
try:
|
||||
fs_platforms: list[str] = fs_platform_handler.get_platforms()
|
||||
except FolderStructureNotMatchException as e:
|
||||
@@ -149,11 +144,14 @@ async def scan_platforms(
|
||||
] or fs_platforms
|
||||
|
||||
if len(platform_list) == 0:
|
||||
log.warn(
|
||||
"⚠️ No platforms found, verify that the folder structure is right and the volume is mounted correctly "
|
||||
log.warning(
|
||||
emoji.emojize(
|
||||
f"{hl(':warning:', color=LIGHTYELLOW)} No platforms found, verify that the folder structure is right and the volume is mounted correctly. \
|
||||
Check https://github.com/rommapp/romm?tab=readme-ov-file#folder-structure for more details."
|
||||
)
|
||||
)
|
||||
else:
|
||||
log.info(f"Found {len(platform_list)} platforms in file system ")
|
||||
log.info(f"Found {len(platform_list)} platforms in the file system")
|
||||
|
||||
for platform_slug in platform_list:
|
||||
scan_stats += await _identify_platform(
|
||||
@@ -165,11 +163,15 @@ async def scan_platforms(
|
||||
socket_manager=sm,
|
||||
)
|
||||
|
||||
# Same protection for platforms
|
||||
# Only purge platforms if there are some platforms remaining in the library
|
||||
# This protects against accidental deletion of entries when
|
||||
# the folder structure is not correct or the drive is not mounted
|
||||
if len(fs_platforms) > 0:
|
||||
log.info("Purging platforms not found in the filesystem:")
|
||||
log.info("\n".join([f" - {platform}" for platform in fs_platforms]))
|
||||
db_platform_handler.purge_platforms(fs_platforms)
|
||||
purged_platforms = db_platform_handler.purge_platforms(fs_platforms)
|
||||
if len(purged_platforms) > 0:
|
||||
log.info("Purging platforms not found in the filesystem:")
|
||||
for p in purged_platforms:
|
||||
log.info(f" - {p.slug}")
|
||||
|
||||
log.info(emoji.emojize(":check_mark: Scan completed "))
|
||||
await sm.emit("scan:done", scan_stats.__dict__)
|
||||
@@ -233,7 +235,11 @@ async def _identify_platform(
|
||||
fs_firmware = []
|
||||
|
||||
if len(fs_firmware) == 0:
|
||||
log.warning(" ⚠️ No firmware found, skipping firmware scan for this platform")
|
||||
log.warning(
|
||||
emoji.emojize(
|
||||
f" {hl(':warning:', color=LIGHTYELLOW)} No firmware found, skipping firmware scan for this platform"
|
||||
)
|
||||
)
|
||||
else:
|
||||
log.info(f" {len(fs_firmware)} firmware files found")
|
||||
|
||||
@@ -251,9 +257,13 @@ async def _identify_platform(
|
||||
return scan_stats
|
||||
|
||||
if len(fs_roms) == 0:
|
||||
log.warning(" ⚠️ No roms found, verify that the folder structure is correct")
|
||||
log.warning(
|
||||
emoji.emojize(
|
||||
f" {hl(':warning:', color=LIGHTYELLOW)} No roms found, verify that the folder structure is correct"
|
||||
)
|
||||
)
|
||||
else:
|
||||
log.info(f" {len(fs_roms)} roms found")
|
||||
log.info(f" {len(fs_roms)} roms found in the file system")
|
||||
|
||||
for fs_rom in fs_roms:
|
||||
scan_stats += await _identify_rom(
|
||||
@@ -268,17 +278,24 @@ async def _identify_platform(
|
||||
# Only purge entries if there are some file remaining in the library
|
||||
# This protects against accidental deletion of entries when
|
||||
# the folder structure is not correct or the drive is not mounted
|
||||
|
||||
if len(fs_roms) > 0:
|
||||
log.info("Purging roms not found in the filesystem:")
|
||||
log.info("\n".join([f" - {rom['file_name']}" for rom in fs_roms]))
|
||||
db_rom_handler.purge_roms(platform.id, [rom["file_name"] for rom in fs_roms])
|
||||
purged_roms = db_rom_handler.purge_roms(
|
||||
platform.id, [rom["file_name"] for rom in fs_roms]
|
||||
)
|
||||
if len(purged_roms) > 0:
|
||||
log.info("Purging roms not found in the filesystem:")
|
||||
for r in purged_roms:
|
||||
log.info(f" - {r.file_name}")
|
||||
|
||||
# Same protection for firmware
|
||||
if len(fs_firmware) > 0:
|
||||
log.info("Purging firmware not found in the filesystem:")
|
||||
log.info("\n".join([f" - {fw}" for fw in fs_firmware]))
|
||||
db_firmware_handler.purge_firmware(platform.id, [fw for fw in fs_firmware])
|
||||
purged_firmware = db_firmware_handler.purge_firmware(
|
||||
platform.id, [fw for fw in fs_firmware]
|
||||
)
|
||||
if len(purged_firmware) > 0:
|
||||
log.info("Purging firmware not found in the filesystem:")
|
||||
for f in purged_firmware:
|
||||
log.info(f" - {f}")
|
||||
|
||||
return scan_stats
|
||||
|
||||
|
||||
@@ -62,15 +62,30 @@ class DBFirmwareHandler(DBBaseHandler):
|
||||
|
||||
@begin_session
|
||||
def purge_firmware(
|
||||
self, platform_id: int, firmware: list[str], session: Session = None
|
||||
self, platform_id: int, fs_firmwares: list[str], session: Session = None
|
||||
) -> None:
|
||||
return session.execute(
|
||||
purged_firmware = (
|
||||
session.scalars(
|
||||
select(Firmware)
|
||||
.order_by(Firmware.file_name.asc())
|
||||
.where(
|
||||
and_(
|
||||
Firmware.platform_id == platform_id,
|
||||
Firmware.file_name.not_in(fs_firmwares),
|
||||
)
|
||||
)
|
||||
) # type: ignore[attr-defined]
|
||||
.unique()
|
||||
.all()
|
||||
)
|
||||
session.execute(
|
||||
delete(Firmware)
|
||||
.where(
|
||||
and_(
|
||||
Firmware.platform_id == platform_id,
|
||||
Firmware.file_name.not_in(firmware),
|
||||
Firmware.file_name.not_in(fs_firmwares),
|
||||
)
|
||||
)
|
||||
.execution_options(synchronize_session="evaluate")
|
||||
)
|
||||
return purged_firmware
|
||||
|
||||
@@ -59,9 +59,23 @@ class DBPlatformsHandler(DBBaseHandler):
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def purge_platforms(self, fs_platforms: list[str], session: Session) -> int:
|
||||
return session.execute(
|
||||
def purge_platforms(
|
||||
self, fs_platforms: list[str], session: Session
|
||||
) -> Select[tuple[Platform]]:
|
||||
purged_platforms = (
|
||||
session.scalars(
|
||||
select(Platform)
|
||||
.order_by(Platform.name.asc())
|
||||
.where(
|
||||
or_(Platform.fs_slug.not_in(fs_platforms), Platform.slug.is_(None))
|
||||
)
|
||||
) # type: ignore[attr-defined]
|
||||
.unique()
|
||||
.all()
|
||||
)
|
||||
session.execute(
|
||||
delete(Platform)
|
||||
.where(or_(Platform.fs_slug.not_in(fs_platforms), Platform.slug.is_(None))) # type: ignore[attr-defined]
|
||||
.execution_options(synchronize_session="fetch")
|
||||
)
|
||||
return purged_platforms
|
||||
|
||||
@@ -191,13 +191,25 @@ class DBRomsHandler(DBBaseHandler):
|
||||
|
||||
@begin_session
|
||||
def purge_roms(
|
||||
self, platform_id: int, roms: list[str], session: Session = None
|
||||
) -> int:
|
||||
return session.execute(
|
||||
self, platform_id: int, fs_roms: list[str], session: Session = None
|
||||
) -> list[Rom]:
|
||||
purged_roms = (
|
||||
session.scalars(
|
||||
select(Rom)
|
||||
.order_by(Rom.file_name.asc())
|
||||
.where(
|
||||
and_(Rom.platform_id == platform_id, Rom.file_name.not_in(fs_roms))
|
||||
)
|
||||
) # type: ignore[attr-defined]
|
||||
.unique()
|
||||
.all()
|
||||
)
|
||||
session.execute(
|
||||
delete(Rom)
|
||||
.where(and_(Rom.platform_id == platform_id, Rom.file_name.not_in(roms))) # type: ignore[attr-defined]
|
||||
.where(and_(Rom.platform_id == platform_id, Rom.file_name.not_in(fs_roms))) # type: ignore[attr-defined]
|
||||
.execution_options(synchronize_session="evaluate")
|
||||
)
|
||||
return purged_roms
|
||||
|
||||
@begin_session
|
||||
def add_rom_user(
|
||||
|
||||
@@ -191,3 +191,24 @@ class MetadataHandler:
|
||||
)
|
||||
|
||||
return search_term
|
||||
|
||||
def _mask_sensitive_values(self, values: dict[str, str]) -> dict[str, str]:
|
||||
"""
|
||||
Mask sensitive values (headers or params), leaving only the first 3 and last 3 characters of the token.
|
||||
"""
|
||||
return {
|
||||
key: (
|
||||
# Mask 'Authorization' Bearer tokens
|
||||
f"Bearer {values[key].split(' ')[1][:3]}***{values[key].split(' ')[1][-3:]}"
|
||||
if key == "Authorization" and values[key].startswith("Bearer ")
|
||||
# Mask 'Client-ID' and 'Client-Secret'
|
||||
else (
|
||||
f"{values[key][:3]}***{values[key][-3:]}"
|
||||
if key
|
||||
in {"Client-ID", "Client-Secret", "client_id", "client_secret"}
|
||||
# Leave other keys unchanged
|
||||
else values[key]
|
||||
)
|
||||
)
|
||||
for key in values
|
||||
}
|
||||
|
||||
@@ -220,6 +220,14 @@ class IGDBBaseHandler(MetadataHandler):
|
||||
async def _request(self, url: str, data: str, timeout: int = 120) -> list:
|
||||
httpx_client = ctx_httpx_client.get()
|
||||
try:
|
||||
masked_headers = self._mask_sensitive_values(self.headers)
|
||||
log.debug(
|
||||
"API request: URL=%s, Headers=%s, Content=%s, Timeout=%s",
|
||||
url,
|
||||
masked_headers,
|
||||
f"{data} limit {self.pagination_limit};",
|
||||
timeout,
|
||||
)
|
||||
res = await httpx_client.post(
|
||||
url,
|
||||
content=f"{data} limit {self.pagination_limit};",
|
||||
@@ -250,6 +258,13 @@ class IGDBBaseHandler(MetadataHandler):
|
||||
pass
|
||||
|
||||
try:
|
||||
log.debug(
|
||||
"Making a second attempt API request: URL=%s, Headers=%s, Content=%s, Timeout=%s",
|
||||
url,
|
||||
masked_headers,
|
||||
f"{data} limit {self.pagination_limit};",
|
||||
timeout,
|
||||
)
|
||||
res = await httpx_client.post(
|
||||
url,
|
||||
content=f"{data} limit {self.pagination_limit};",
|
||||
@@ -606,7 +621,17 @@ class IGDBBaseHandler(MetadataHandler):
|
||||
]
|
||||
|
||||
|
||||
class TwitchAuth:
|
||||
class TwitchAuth(MetadataHandler):
|
||||
def __init__(self):
|
||||
self.BASE_URL = "https://id.twitch.tv/oauth2/token"
|
||||
self.params = {
|
||||
"client_id": IGDB_CLIENT_ID,
|
||||
"client_secret": IGDB_CLIENT_SECRET,
|
||||
"grant_type": "client_credentials",
|
||||
}
|
||||
self.masked_params = self._mask_sensitive_values(self.params)
|
||||
self.timeout = 10
|
||||
|
||||
async def _update_twitch_token(self) -> str:
|
||||
token = None
|
||||
expires_in = 0
|
||||
@@ -616,14 +641,16 @@ class TwitchAuth:
|
||||
|
||||
httpx_client = ctx_httpx_client.get()
|
||||
try:
|
||||
log.debug(
|
||||
"API request: URL=%s, Params=%s, Timeout=%s",
|
||||
self.BASE_URL,
|
||||
self.masked_params,
|
||||
self.timeout,
|
||||
)
|
||||
res = await httpx_client.post(
|
||||
url="https://id.twitch.tv/oauth2/token",
|
||||
params={
|
||||
"client_id": IGDB_CLIENT_ID,
|
||||
"client_secret": IGDB_CLIENT_SECRET,
|
||||
"grant_type": "client_credentials",
|
||||
},
|
||||
timeout=10,
|
||||
url=self.BASE_URL,
|
||||
params=self.params,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
|
||||
if res.status_code == 400:
|
||||
|
||||
@@ -78,12 +78,20 @@ def extract_metadata_from_moby_rom(rom: dict) -> MobyMetadata:
|
||||
|
||||
class MobyGamesHandler(MetadataHandler):
|
||||
def __init__(self) -> None:
|
||||
self.platform_url = "https://api.mobygames.com/v1/platforms"
|
||||
self.games_url = "https://api.mobygames.com/v1/games"
|
||||
self.BASE_URL = "https://api.mobygames.com/v1"
|
||||
self.platform_url = f"{self.BASE_URL}/platforms"
|
||||
self.games_url = f"{self.BASE_URL}/games"
|
||||
|
||||
async def _request(self, url: str, timeout: int = 120) -> dict:
|
||||
httpx_client = ctx_httpx_client.get()
|
||||
authorized_url = yarl.URL(url).update_query(api_key=MOBYGAMES_API_KEY)
|
||||
|
||||
log.debug(
|
||||
"API request: URL=%s, Timeout=%s",
|
||||
authorized_url,
|
||||
timeout,
|
||||
)
|
||||
|
||||
try:
|
||||
res = await httpx_client.get(str(authorized_url), timeout=timeout)
|
||||
res.raise_for_status()
|
||||
@@ -107,10 +115,16 @@ class MobyGamesHandler(MetadataHandler):
|
||||
log.error(err)
|
||||
return {}
|
||||
except httpx.TimeoutException:
|
||||
log.debug(
|
||||
"Request to URL=%s timed out. Retrying with URL=%s", authorized_url, url
|
||||
)
|
||||
# Retry the request once if it times out
|
||||
pass
|
||||
|
||||
try:
|
||||
log.debug(
|
||||
"API request: URL=%s, Timeout=%s",
|
||||
url,
|
||||
timeout,
|
||||
)
|
||||
res = await httpx_client.get(url, timeout=timeout)
|
||||
res.raise_for_status()
|
||||
except (httpx.HTTPStatusError, httpx.TimeoutException) as err:
|
||||
@@ -120,7 +134,6 @@ class MobyGamesHandler(MetadataHandler):
|
||||
):
|
||||
# Sometimes Mobygames returns 401 even with a valid API key
|
||||
return {}
|
||||
|
||||
# Log the error and return an empty dict if the request fails with a different code
|
||||
log.error(err)
|
||||
return {}
|
||||
|
||||
@@ -6,6 +6,8 @@ from config import STEAMGRIDDB_API_KEY
|
||||
from logger.logger import log
|
||||
from utils.context import ctx_httpx_client
|
||||
|
||||
from .base_hander import MetadataHandler
|
||||
|
||||
# Used to display the Mobygames API status in the frontend
|
||||
STEAMGRIDDB_API_ENABLED: Final = bool(STEAMGRIDDB_API_KEY)
|
||||
|
||||
@@ -23,7 +25,7 @@ ANIMATED: Final = "animated"
|
||||
SGDB_API_COVER_LIMIT: Final = 50
|
||||
|
||||
|
||||
class SGDBBaseHandler:
|
||||
class SGDBBaseHandler(MetadataHandler):
|
||||
def __init__(self) -> None:
|
||||
self.BASE_URL = "https://www.steamgriddb.com/api/v2"
|
||||
self.search_endpoint = f"{self.BASE_URL}/search/autocomplete"
|
||||
@@ -32,14 +34,25 @@ class SGDBBaseHandler:
|
||||
"Authorization": f"Bearer {STEAMGRIDDB_API_KEY}",
|
||||
"Accept": "*/*",
|
||||
}
|
||||
self.masked_headers = self._mask_sensitive_values(self.headers)
|
||||
self.timeout = 120
|
||||
|
||||
async def get_details(self, search_term: str) -> list[dict[str, Any]]:
|
||||
httpx_client = ctx_httpx_client.get()
|
||||
url = f"{self.search_endpoint}/{search_term}"
|
||||
|
||||
log.debug(
|
||||
"API request: Method=GET, URL=%s, Headers=%s, Timeout=%s",
|
||||
url,
|
||||
self.masked_headers,
|
||||
self.timeout,
|
||||
)
|
||||
|
||||
search_response = (
|
||||
await httpx_client.get(
|
||||
f"{self.search_endpoint}/{search_term}",
|
||||
url,
|
||||
headers=self.headers,
|
||||
timeout=120,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
).json()
|
||||
|
||||
@@ -60,23 +73,36 @@ class SGDBBaseHandler:
|
||||
) -> dict[str, Any] | None:
|
||||
httpx_client = ctx_httpx_client.get()
|
||||
game_covers = []
|
||||
|
||||
for page in itertools.count(start=0):
|
||||
url = f"{self.grid_endpoint}/{game_id}"
|
||||
params = {
|
||||
"dimensions": f"{STEAMVERTICAL},{GALAXY342},{GALAXY660},{SQUARE512},{SQUARE1024}",
|
||||
"types": f"{STATIC},{ANIMATED}",
|
||||
"limit": SGDB_API_COVER_LIMIT,
|
||||
"page": page,
|
||||
}
|
||||
|
||||
log.debug(
|
||||
"API request: Method=GET, URL=%s, Headers=%s, Params=%s, Timeout=%s",
|
||||
url,
|
||||
self.masked_headers,
|
||||
params,
|
||||
self.timeout,
|
||||
)
|
||||
|
||||
covers_response = (
|
||||
await httpx_client.get(
|
||||
f"{self.grid_endpoint}/{game_id}",
|
||||
url,
|
||||
headers=self.headers,
|
||||
timeout=120,
|
||||
params={
|
||||
"dimensions": f"{STEAMVERTICAL},{GALAXY342},{GALAXY660},{SQUARE512},{SQUARE1024}",
|
||||
"types": f"{STATIC},{ANIMATED}",
|
||||
"limit": SGDB_API_COVER_LIMIT,
|
||||
"page": page,
|
||||
},
|
||||
timeout=self.timeout,
|
||||
params=params,
|
||||
)
|
||||
).json()
|
||||
page_covers = covers_response["data"]
|
||||
|
||||
page_covers = covers_response["data"]
|
||||
game_covers.extend(page_covers)
|
||||
|
||||
if len(page_covers) < SGDB_API_COVER_LIMIT:
|
||||
break
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ from handler.filesystem.roms_handler import FSRom
|
||||
from handler.metadata import meta_igdb_handler, meta_moby_handler
|
||||
from handler.metadata.igdb_handler import IGDBPlatform, IGDBRom
|
||||
from handler.metadata.moby_handler import MobyGamesPlatform, MobyGamesRom
|
||||
from logger.formatter import BLUE
|
||||
from logger.formatter import highlight as hl
|
||||
from logger.logger import log
|
||||
from models.assets import Save, Screenshot, State
|
||||
from models.firmware import Firmware
|
||||
@@ -61,7 +63,7 @@ async def scan_platform(
|
||||
Platform object
|
||||
"""
|
||||
|
||||
log.info(f"· {fs_slug}")
|
||||
log.info(f"· {hl(fs_slug)}")
|
||||
|
||||
if metadata_sources is None:
|
||||
metadata_sources = ["igdb", "moby"]
|
||||
@@ -106,10 +108,14 @@ async def scan_platform(
|
||||
|
||||
if platform_attrs["igdb_id"] or platform_attrs["moby_id"]:
|
||||
log.info(
|
||||
emoji.emojize(f" Identified as {platform_attrs['name']} :video_game:")
|
||||
emoji.emojize(
|
||||
f" Identified as {hl(platform_attrs['name'], color=BLUE)} :video_game:"
|
||||
)
|
||||
)
|
||||
else:
|
||||
log.warning(emoji.emojize(f" {platform_attrs['slug']} not found :cross_mark:"))
|
||||
log.warning(
|
||||
emoji.emojize(f" {platform_attrs['slug']} not identified :cross_mark:")
|
||||
)
|
||||
|
||||
return Platform(**platform_attrs)
|
||||
|
||||
@@ -171,7 +177,7 @@ async def scan_rom(
|
||||
|
||||
roms_path = fs_rom_handler.get_roms_fs_structure(platform.fs_slug)
|
||||
|
||||
log.info(f"\t · {fs_rom['file_name']}")
|
||||
log.info(f"\t · {hl(fs_rom['file_name'])}")
|
||||
|
||||
if fs_rom.get("multi", False):
|
||||
for file in fs_rom["files"]:
|
||||
@@ -293,7 +299,7 @@ async def scan_rom(
|
||||
# If not found in IGDB or MobyGames
|
||||
if not igdb_handler_rom.get("igdb_id") and not moby_handler_rom.get("moby_id"):
|
||||
log.warning(
|
||||
emoji.emojize(f"\t {rom_attrs['file_name']} not found :cross_mark:")
|
||||
emoji.emojize(f"\t {rom_attrs['file_name']} not identified :cross_mark:")
|
||||
)
|
||||
return Rom(**rom_attrs)
|
||||
|
||||
|
||||
86
backend/logger/formatter.py
Normal file
86
backend/logger/formatter.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
RED = Fore.RED
|
||||
GREEN = Fore.GREEN
|
||||
LIGHTYELLOW = Fore.LIGHTYELLOW_EX
|
||||
YELLOW = Fore.YELLOW
|
||||
BLUE = Fore.BLUE
|
||||
|
||||
|
||||
def should_strip_ansi():
|
||||
"""Determine if ANSI escape codes should be stripped."""
|
||||
# Check if an explicit environment variable is set to control color behavior
|
||||
if os.getenv("FORCE_COLOR", "false").lower() == "true":
|
||||
return False
|
||||
if os.getenv("NO_COLOR", "false").lower() == "true":
|
||||
return True
|
||||
|
||||
# For other environments, strip colors if not a TTY
|
||||
return not os.isatty(1)
|
||||
|
||||
|
||||
# Initialize Colorama once, considering different environments
|
||||
init(strip=should_strip_ansi())
|
||||
|
||||
|
||||
class Formatter(logging.Formatter):
|
||||
"""
|
||||
Logger formatter.
|
||||
"""
|
||||
|
||||
def format(self, record):
|
||||
"""
|
||||
Formats a log record with color-coded output based on the log level.
|
||||
|
||||
Args:
|
||||
record: The log record to format.
|
||||
|
||||
Returns:
|
||||
The formatted log record as a string.
|
||||
"""
|
||||
level: str = "%(levelname)s"
|
||||
dots: str = f"{Fore.RESET}:"
|
||||
identifier = (
|
||||
f"\t {Fore.BLUE}[RomM]{Fore.LIGHTMAGENTA_EX}[{str(record.module_name).lower()}]"
|
||||
if hasattr(record, "module_name")
|
||||
else f"\t {Fore.BLUE}[RomM]{Fore.LIGHTMAGENTA_EX}[%(module)s]"
|
||||
)
|
||||
identifier_warning = (
|
||||
f" {Fore.BLUE}[RomM]{Fore.LIGHTMAGENTA_EX}[{str(record.module_name).lower()}]"
|
||||
if hasattr(record, "module_name")
|
||||
else f" {Fore.BLUE}[RomM]{Fore.LIGHTMAGENTA_EX}[%(module)s]"
|
||||
)
|
||||
identifier_critical = (
|
||||
f" {Fore.BLUE}[RomM]{Fore.LIGHTMAGENTA_EX}[{str(record.module_name).lower()}]"
|
||||
if hasattr(record, "module_name")
|
||||
else f" {Fore.BLUE}[RomM]{Fore.LIGHTMAGENTA_EX}[%(module)s]"
|
||||
)
|
||||
msg: str = f"{Style.RESET_ALL}%(message)s"
|
||||
date: str = f"{Fore.CYAN}[%(asctime)s] "
|
||||
formats: dict = {
|
||||
logging.DEBUG: f"{Fore.LIGHTMAGENTA_EX}{level}{dots}{identifier}{date}{msg}",
|
||||
logging.INFO: f"{Fore.GREEN}{level}{dots}{identifier}{date}{msg}",
|
||||
logging.WARNING: f"{Fore.YELLOW}{level}{dots}{identifier_warning}{date}{msg}",
|
||||
logging.ERROR: f"{Fore.LIGHTRED_EX}{level}{dots}{identifier}{date}{msg}",
|
||||
logging.CRITICAL: f"{Fore.RED}{level}{dots}{identifier_critical}{date}{msg}",
|
||||
}
|
||||
log_fmt = formats.get(record.levelno)
|
||||
formatter = logging.Formatter(fmt=log_fmt, datefmt="%Y-%m-%d %H:%M:%S")
|
||||
return formatter.format(record)
|
||||
|
||||
|
||||
def highlight(msg: str = "", color=Fore.YELLOW) -> str:
|
||||
"""
|
||||
Highlights the message to send to the fancylog.
|
||||
|
||||
Args:
|
||||
msg: Message to log.
|
||||
color: Highlight with specific color. Available colors: RED, GREEN, YELLOW, BLUE.
|
||||
|
||||
Returns:
|
||||
The highlighted message as a string.
|
||||
"""
|
||||
return f"{color}{msg}{Style.RESET_ALL}"
|
||||
@@ -1,19 +1,20 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from logger.stdout_formatter import StdoutFormatter
|
||||
from config import LOGLEVEL
|
||||
from logger.formatter import Formatter
|
||||
|
||||
# Set up logger
|
||||
log = logging.getLogger("romm")
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.setLevel(LOGLEVEL)
|
||||
|
||||
# Set up sqlachemy logger
|
||||
# sql_log = logging.getLogger("sqlalchemy.engine")
|
||||
# sql_log.setLevel(logging.DEBUG)
|
||||
# sql_log.setLevel(LOGLEVEL)
|
||||
|
||||
# Define stdout handler
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
stdout_handler.setFormatter(StdoutFormatter())
|
||||
stdout_handler.setFormatter(Formatter())
|
||||
log.addHandler(stdout_handler)
|
||||
# sql_log.addHandler(stdout_handler)
|
||||
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import logging
|
||||
|
||||
COLORS: dict = {
|
||||
"grey": "\033[92m",
|
||||
"pink": "\033[95m",
|
||||
"blue": "\033[94m",
|
||||
"cyan": "\033[96m",
|
||||
"orange": "\033[93m",
|
||||
"orange_i": "\033[3;93m",
|
||||
"red": "\033[91m",
|
||||
"bold_red": "\033[1;91m",
|
||||
"reset": "\033[0m",
|
||||
}
|
||||
|
||||
|
||||
class StdoutFormatter(logging.Formatter):
|
||||
level: str = "%(levelname)s"
|
||||
dots: str = ":"
|
||||
identifier: str = "\t [RomM]"
|
||||
identifier_warning: str = " [RomM]"
|
||||
identifier_critical: str = " [RomM]"
|
||||
msg: str = "%(message)s"
|
||||
date: str = "[%(asctime)s] "
|
||||
FORMATS: dict = {
|
||||
logging.DEBUG: COLORS["pink"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.INFO: COLORS["grey"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.WARNING: COLORS["orange"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier_warning
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.ERROR: COLORS["red"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.CRITICAL: COLORS["bold_red"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier_critical
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
formatter = logging.Formatter(fmt=log_fmt, datefmt="%Y-%m-%d %H:%M:%S")
|
||||
return formatter.format(record)
|
||||
@@ -42,3 +42,9 @@ SCHEDULED_UPDATE_SWITCH_TITLEDB_CRON=0 4 * * *
|
||||
# In-browser emulation
|
||||
DISABLE_EMULATOR_JS=false
|
||||
DISABLE_RUFFLE_RS=false
|
||||
|
||||
# Disable CSRF protection for development and testing purposes
|
||||
DISABLE_CSRF_PROTECTION=true
|
||||
|
||||
# Logging
|
||||
LOGLEVEL=DEBUG
|
||||
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
volumes:
|
||||
- romm_resources:/romm/resources # Resources fetched from IGDB (covers, screenshots, etc.)
|
||||
- romm_redis_data:/redis-data # Cached data for background tasks
|
||||
- /path/to/library:/romm/library # Your game library
|
||||
- /path/to/library:/romm/library # Your game library. Check https://github.com/rommapp/romm?tab=readme-ov-file#folder-structure for more details.
|
||||
- /path/to/assets:/romm/assets # Uploaded saves, states, etc.
|
||||
- /path/to/config:/romm/config # Path where config.yml is stored
|
||||
ports:
|
||||
|
||||
@@ -220,23 +220,20 @@ async function stopScan() {
|
||||
no-gutters
|
||||
>
|
||||
<v-btn
|
||||
:disabled="scanning || metadataSources.length == 0"
|
||||
:disabled="scanning"
|
||||
rounded="4"
|
||||
height="40"
|
||||
:loading="scanning"
|
||||
@click="scan()"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon
|
||||
:color="
|
||||
scanning || metadataSources.length == 0 ? '' : 'romm-accent-1'
|
||||
"
|
||||
<v-icon :color="scanning ? '' : 'romm-accent-1'"
|
||||
>mdi-magnify-scan</v-icon
|
||||
>
|
||||
</template>
|
||||
<span
|
||||
:class="{
|
||||
'text-romm-accent-1': !(scanning || metadataSources.length == 0),
|
||||
'text-romm-accent-1': !scanning,
|
||||
}"
|
||||
>Scan</span
|
||||
>
|
||||
@@ -278,7 +275,10 @@ async function stopScan() {
|
||||
>
|
||||
<v-list-item class="text-caption text-yellow py-0">
|
||||
<v-icon>mdi-alert</v-icon
|
||||
><span class="ml-2">Please select at least one metadata source.</span>
|
||||
><span class="ml-2"
|
||||
>Please select at least one metadata source if you want to enrich your
|
||||
library with artwork and metadata.</span
|
||||
>
|
||||
</v-list-item>
|
||||
</v-row>
|
||||
|
||||
|
||||
45
poetry.lock
generated
45
poetry.lock
generated
@@ -147,6 +147,10 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
|
||||
@@ -159,8 +163,14 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
|
||||
@@ -171,8 +181,24 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"},
|
||||
{file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
|
||||
@@ -182,6 +208,10 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
|
||||
@@ -193,6 +223,10 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
|
||||
@@ -205,6 +239,10 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
|
||||
@@ -217,6 +255,10 @@ files = [
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
|
||||
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
|
||||
@@ -2991,6 +3033,7 @@ description = "Automatically mock your HTTP interactions to simplify and speed u
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "vcrpy-6.0.1-py2.py3-none-any.whl", hash = "sha256:621c3fb2d6bd8aa9f87532c688e4575bcbbde0c0afeb5ebdb7e14cac409edfdd"},
|
||||
{file = "vcrpy-6.0.1.tar.gz", hash = "sha256:9e023fee7f892baa0bbda2f7da7c8ac51165c1c6e38ff8688683a12a4bde9278"},
|
||||
]
|
||||
|
||||
@@ -3366,4 +3409,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "139c463ccbca490c44093aa3c6c9a74d2908c60e279cdbc59848141b34fb46e6"
|
||||
content-hash = "d2352d0cdb1ca85311e8dd9158b835d3dade9d52bbac25537095067e7a218b86"
|
||||
|
||||
@@ -46,6 +46,7 @@ python-magic = "^0.4.27"
|
||||
py7zr = { git = "https://github.com/adamantike/py7zr.git", rev = "54b68426" }
|
||||
streaming-form-data = "^1.16.0"
|
||||
zipfile-deflate64 = "^0.2.0"
|
||||
colorama = "^0.4.6"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
fakeredis = "^2.21.3"
|
||||
|
||||
Reference in New Issue
Block a user