diff --git a/extensions/albums.py b/extensions/albums.py index 1eb130a..d7cecf3 100644 --- a/extensions/albums.py +++ b/extensions/albums.py @@ -1,5 +1,6 @@ import re -from os import makedirs, path, rename +from os import makedirs, rename +from pathlib import Path from shutil import rmtree from typing import Union @@ -49,9 +50,7 @@ async def album_create( if col_albums.find_one({"name": name}) is not None: raise AlbumAlreadyExistsError(name) - makedirs( - path.join("data", "users", current_user.user, "albums", name), exist_ok=True - ) + makedirs(Path(f"data/users/{current_user.user}/albums/{name}"), exist_ok=True) uploaded = col_albums.insert_one( {"user": current_user.user, "name": name, "title": title, "cover": None} @@ -123,8 +122,8 @@ async def album_patch( if 2 > len(name) > 20: raise AlbumIncorrectError("name", "must be >2 and <20 characters.") rename( - path.join("data", "users", current_user.user, "albums", album["name"]), - path.join("data", "users", current_user.user, "albums", name), + Path(f"data/users/{current_user.user}/albums/{album['name']}"), + Path(f"data/users/{current_user.user}/albums/{name}"), ) col_photos.update_many( {"user": current_user.user, "album": album["name"]}, @@ -186,8 +185,8 @@ async def album_put( cover = image["_id"].__str__() if image is not None else None # type: ignore rename( - path.join("data", "users", current_user.user, "albums", album["name"]), - path.join("data", "users", current_user.user, "albums", name), + Path(f"data/users/{current_user.user}/albums/{album['name']}"), + Path(f"data/users/{current_user.user}/albums/{name}"), ) col_photos.update_many( @@ -222,6 +221,6 @@ async def album_delete( col_photos.delete_many({"album": album["name"]}) - rmtree(path.join("data", "users", current_user.user, "albums", album["name"])) + rmtree(Path(f"data/users/{current_user.user}/albums/{album['name']}")) return Response(status_code=HTTP_204_NO_CONTENT) diff --git a/extensions/pages.py b/extensions/pages.py index d32dad6..b51f692 100644 --- a/extensions/pages.py +++ b/extensions/pages.py @@ -1,4 +1,4 @@ -from os import path +from pathlib import Path import aiofiles from fastapi.responses import HTMLResponse, Response @@ -8,27 +8,21 @@ from modules.app import app @app.get("/pages/matter.css", include_in_schema=False) async def page_matter(): - async with aiofiles.open( - path.join("pages", "matter.css"), "r", encoding="utf-8" - ) as f: + async with aiofiles.open(Path("pages/matter.css"), "r", encoding="utf-8") as f: output = await f.read() return Response(content=output) @app.get("/pages/{page}/{file}", include_in_schema=False) async def page_assets(page: str, file: str): - async with aiofiles.open( - path.join("pages", page, file), "r", encoding="utf-8" - ) as f: + async with aiofiles.open(Path(f"pages/{page}/{file}"), "r", encoding="utf-8") as f: output = await f.read() return Response(content=output) @app.get("/", include_in_schema=False) async def page_home(): - async with aiofiles.open( - path.join("pages", "home", "index.html"), "r", encoding="utf-8" - ) as f: + async with aiofiles.open(Path("pages/home/index.html"), "r", encoding="utf-8") as f: output = await f.read() return HTMLResponse(content=output) @@ -36,7 +30,7 @@ async def page_home(): @app.get("/register", include_in_schema=False) async def page_register(): async with aiofiles.open( - path.join("pages", "register", "index.html"), "r", encoding="utf-8" + Path("pages/register/index.html"), "r", encoding="utf-8" ) as f: output = await f.read() return HTMLResponse(content=output) diff --git a/extensions/photos.py b/extensions/photos.py index a8bb6be..835cbcd 100644 --- a/extensions/photos.py +++ b/extensions/photos.py @@ -1,6 +1,8 @@ +import logging import re from datetime import datetime, timedelta, timezone from os import makedirs, path, remove, system +from pathlib import Path from secrets import token_urlsafe from shutil import move from threading import Thread @@ -42,14 +44,18 @@ from modules.security import ( get_current_active_user, get_user, ) -from modules.utils import configGet, logWrite +from modules.utils import configGet + +logger = logging.getLogger(__name__) async def compress_image(image_path: str): image_type = Magic(mime=True).from_file(image_path) if image_type not in ["image/jpeg", "image/png"]: - logWrite(f"Not compressing {image_path} because its mime is '{image_type}'") + logger.info( + "Not compressing %s because its mime is '%s'", image_path, image_type + ) return size_before = path.getsize(image_path) / 1024 @@ -65,12 +71,15 @@ async def compress_image(image_path: str): return task.start() - logWrite(f"Compressing '{path.split(image_path)[-1]}'...") + logger.info("Compressing '%s'...", Path(image_path).name) task.join() size_after = path.getsize(image_path) / 1024 - logWrite( - f"Compressed '{path.split(image_path)[-1]}' from {size_before} Kb to {size_after} Kb" + logger.info( + "Compressed '%s' from %s Kb to %s Kb", + Path(image_path).name, + size_before, + size_after, ) @@ -109,15 +118,11 @@ async def photo_upload( if col_albums.find_one({"user": current_user.user, "name": album}) is None: raise AlbumNameNotFoundError(album) - makedirs( - path.join("data", "users", current_user.user, "albums", album), exist_ok=True - ) + makedirs(Path(f"data/users/{current_user.user}/albums/{album}"), exist_ok=True) filename = file.filename - if path.exists( - path.join("data", "users", current_user.user, "albums", album, file.filename) - ): + if Path(f"data/users/{current_user.user}/albums/{album}/{file.filename}").exists(): base_name = file.filename.split(".")[:-1] extension = file.filename.split(".")[-1] filename = ( @@ -125,12 +130,12 @@ async def photo_upload( ) async with aiofiles.open( - path.join("data", "users", current_user.user, "albums", album, filename), "wb" + Path(f"data/users/{current_user.user}/albums/{album}/{filename}"), "wb" ) as f: - f.write(await file.read()) + await f.write(await file.read()) file_hash = await get_phash( - path.join("data", "users", current_user.user, "albums", album, filename) + Path(f"data/users/{current_user.user}/albums/{album}/{filename}") ) duplicates = await get_duplicates(file_hash, album) @@ -168,7 +173,7 @@ async def photo_upload( try: coords = extract_location( - path.join("data", "users", current_user.user, "albums", album, filename) + Path(f"data/users/{current_user.user}/albums/{album}/{filename}") ) except (UnpackError, ValueError): coords = {"lng": 0.0, "lat": 0.0, "alt": 0.0} @@ -193,9 +198,7 @@ async def photo_upload( compress_image, trigger="date", run_date=datetime.now() + timedelta(seconds=1), - args=[ - path.join("data", "users", current_user.user, "albums", album, filename) - ], + args=[Path(f"data/users/{current_user.user}/albums/{album}/{filename}")], ) return UJSONResponse( @@ -254,8 +257,8 @@ if configGet("media_token_access") is True: except InvalidId: raise PhotoNotFoundError(id) - image_path = path.join( - "data", "users", user.user, "albums", image["album"], image["filename"] + image_path = Path( + f"data/users/{user.user}/albums/{image['album']}/{image['filename']}" ) mime = Magic(mime=True).from_file(image_path) @@ -299,8 +302,8 @@ async def photo_get( except InvalidId: raise PhotoNotFoundError(id) - image_path = path.join( - "data", "users", current_user.user, "albums", image["album"], image["filename"] + image_path = Path( + f"data/users/{current_user.user}/albums/{image['album']}/{image['filename']}" ) mime = Magic(mime=True).from_file(image_path) @@ -335,11 +338,9 @@ async def photo_move( if col_albums.find_one({"user": current_user.user, "name": album}) is None: raise AlbumNameNotFoundError(album) - if path.exists( - path.join( - "data", "users", current_user.user, "albums", album, image["filename"] - ) - ): + if Path( + f"data/users/{current_user.user}/albums/{album}/{image['filename']}" + ).exists(): base_name = image["filename"].split(".")[:-1] extension = image["filename"].split(".")[-1] filename = ( @@ -360,15 +361,10 @@ async def photo_move( ) move( - path.join( - "data", - "users", - current_user.user, - "albums", - image["album"], - image["filename"], + Path( + f"data/users/{current_user.user}/albums/{image['album']}/{image['filename']}" ), - path.join("data", "users", current_user.user, "albums", album, filename), + Path(f"data/users/{current_user.user}/albums/{album}/{filename}"), ) return UJSONResponse( @@ -441,13 +437,8 @@ async def photo_delete( col_albums.update_one({"name": image["album"]}, {"$set": {"cover": None}}) remove( - path.join( - "data", - "users", - current_user.user, - "albums", - image["album"], - image["filename"], + Path( + f"data/users/{current_user.user}/albums/{image['album']}/{image['filename']}" ) ) diff --git a/extensions/users.py b/extensions/users.py index 6371d8f..4ae71a3 100644 --- a/extensions/users.py +++ b/extensions/users.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime, timedelta from uuid import uuid1 @@ -21,7 +22,9 @@ from modules.security import ( get_user, verify_password, ) -from modules.utils import configGet, logWrite +from modules.utils import configGet + +logger = logging.getLogger(__name__) async def send_confirmation(user: str, email: str): @@ -41,9 +44,11 @@ async def send_confirmation(user: str, email: str): col_emails.insert_one( {"user": user, "email": email, "used": False, "code": confirmation_code} ) - logWrite(f"Sent confirmation email to '{email}' with code {confirmation_code}") + logger.info( + "Sent confirmation email to '%s' with code %s", email, confirmation_code + ) except Exception as exp: - logWrite(f"Could not send confirmation email to '{email}' due to: {exp}") + logger.error("Could not send confirmation email to '%s' due to: %s", email, exp) @app.get("/users/me/", response_model=User) diff --git a/extensions/videos.py b/extensions/videos.py index 2152d0a..91ebffd 100644 --- a/extensions/videos.py +++ b/extensions/videos.py @@ -1,6 +1,7 @@ import re from datetime import datetime, timezone -from os import makedirs, path, remove +from os import makedirs, remove +from pathlib import Path from secrets import token_urlsafe from shutil import move from typing import Union @@ -45,15 +46,11 @@ async def video_upload( if col_albums.find_one({"user": current_user.user, "name": album}) is None: raise AlbumNameNotFoundError(album) - makedirs( - path.join("data", "users", current_user.user, "albums", album), exist_ok=True - ) + makedirs(Path(f"data/users/{current_user.user}/albums/{album}"), exist_ok=True) filename = file.filename - if path.exists( - path.join("data", "users", current_user.user, "albums", album, file.filename) - ): + if Path(f"data/users/{current_user.user}/albums/{album}/{file.filename}").exists(): base_name = file.filename.split(".")[:-1] extension = file.filename.split(".")[-1] filename = ( @@ -61,7 +58,7 @@ async def video_upload( ) async with aiofiles.open( - path.join("data", "users", current_user.user, "albums", album, filename), "wb" + Path(f"data/users/{current_user.user}/albums/{album}/{filename}"), "wb" ) as f: await f.write(await file.read()) @@ -125,8 +122,8 @@ async def video_get( except InvalidId: raise VideoNotFoundError(id) - video_path = path.join( - "data", "users", current_user.user, "albums", video["album"], video["filename"] + video_path = Path( + f"data/users/{current_user.user}/albums/{video['album']}/{video['filename']}" ) mime = Magic(mime=True).from_file(video_path) @@ -161,11 +158,9 @@ async def video_move( if col_albums.find_one({"user": current_user.user, "name": album}) is None: raise AlbumNameNotFoundError(album) - if path.exists( - path.join( - "data", "users", current_user.user, "albums", album, video["filename"] - ) - ): + if Path( + f"data/users/{current_user.user}/albums/{album}/{video['filename']}" + ).exists(): base_name = video["filename"].split(".")[:-1] extension = video["filename"].split(".")[-1] filename = ( @@ -186,15 +181,10 @@ async def video_move( ) move( - path.join( - "data", - "users", - current_user.user, - "albums", - video["album"], - video["filename"], + Path( + f"data/users/{current_user.user}/albums/{video['album']}/{video['filename']}" ), - path.join("data", "users", current_user.user, "albums", album, filename), + Path(f"data/users/{current_user.user}/albums/{album}/{filename}"), ) return UJSONResponse( @@ -264,13 +254,8 @@ async def video_delete( album = col_albums.find_one({"name": video["album"]}) remove( - path.join( - "data", - "users", - current_user.user, - "albums", - video["album"], - video["filename"], + Path( + f"data/users/{current_user.user}/albums/{video['album']}/{video['filename']}" ) ) diff --git a/modules/extensions_loader.py b/modules/extensions_loader.py index e978fe2..32d72bb 100644 --- a/modules/extensions_loader.py +++ b/modules/extensions_loader.py @@ -1,5 +1,6 @@ from importlib.util import module_from_spec, spec_from_file_location from os import getcwd, path, walk +from pathlib import Path # ================================================================================= @@ -12,7 +13,7 @@ def get_py_files(src): for root, dirs, files in walk(src): for file in files: if file.endswith(".py"): - py_files.append(path.join(cwd, root, file)) + py_files.append(Path(f"{cwd}/{root}/{file}")) return py_files @@ -36,7 +37,7 @@ def dynamic_import(module_name, py_path): def dynamic_import_from_src(src, star_import=False): my_py_files = get_py_files(src) for py_file in my_py_files: - module_name = path.split(py_file)[-1][:-3] + module_name = Path(py_file).stem print(f"Importing {module_name} extension...", flush=True) imported_module = dynamic_import(module_name, py_file) if imported_module != None: diff --git a/modules/mailer.py b/modules/mailer.py index 0ff0927..d8e0a92 100644 --- a/modules/mailer.py +++ b/modules/mailer.py @@ -1,8 +1,11 @@ +import logging from smtplib import SMTP, SMTP_SSL from ssl import create_default_context from traceback import print_exc -from modules.utils import configGet, logWrite +from modules.utils import configGet + +logger = logging.getLogger(__name__) try: if configGet("use_ssl", "mailer", "smtp") is True: @@ -10,7 +13,7 @@ try: configGet("host", "mailer", "smtp"), configGet("port", "mailer", "smtp"), ) - logWrite(f"Initialized SMTP SSL connection") + logger.info("Initialized SMTP SSL connection") elif configGet("use_tls", "mailer", "smtp") is True: mail_sender = SMTP( configGet("host", "mailer", "smtp"), @@ -18,21 +21,21 @@ try: ) mail_sender.starttls(context=create_default_context()) mail_sender.ehlo() - logWrite(f"Initialized SMTP TLS connection") + logger.info("Initialized SMTP TLS connection") else: mail_sender = SMTP( configGet("host", "mailer", "smtp"), configGet("port", "mailer", "smtp") ) mail_sender.ehlo() - logWrite(f"Initialized SMTP connection") + logger.info("Initialized SMTP connection") except Exception as exp: - logWrite(f"Could not initialize SMTP connection to: {exp}") + logger.error("Could not initialize SMTP connection to: %s", exp) print_exc() try: mail_sender.login( configGet("login", "mailer", "smtp"), configGet("password", "mailer", "smtp") ) - logWrite(f"Successfully initialized mailer") + logger.info("Successfully initialized mailer") except Exception as exp: - logWrite(f"Could not login into provided SMTP account due to: {exp}") + logger.error("Could not login into provided SMTP account due to: %s", exp) diff --git a/modules/utils.py b/modules/utils.py index 1cf907e..9f1ed5a 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,21 +1,18 @@ -from traceback import print_exc +import logging +from pathlib import Path +from traceback import format_exc from typing import Any, Union from ujson import JSONDecodeError, dumps, loads - -# Print to stdout and then to log -def logWrite(message: str, debug: bool = False) -> None: - # save to log file and rotation is to be done - # logAppend(f'{message}', debug=debug) - print(f"{message}", flush=True) +logger = logging.getLogger(__name__) -def jsonLoad(filepath: str) -> Any: +def jsonLoad(filepath: Union[str, Path]) -> Any: """Load json file ### Args: - * filepath (`str`): Path to input file + * filepath (`Union[str, Path]`): Path to input file ### Returns: * `Any`: Some json deserializable @@ -24,32 +21,36 @@ def jsonLoad(filepath: str) -> Any: try: output = loads(file.read()) except JSONDecodeError: - logWrite( - f"Could not load json file {filepath}: file seems to be incorrect!\n{print_exc()}" + logger.error( + "Could not load json file %s: file seems to be incorrect!\n%s", + filepath, + format_exc(), ) raise except FileNotFoundError: - logWrite( - f"Could not load json file {filepath}: file does not seem to exist!\n{print_exc()}" + logger.error( + "Could not load json file %s: file does not seem to exist!\n%s", + filepath, + format_exc(), ) raise file.close() return output -def jsonSave(contents: Union[list, dict], filepath: str) -> None: +def jsonSave(contents: Union[list, dict], filepath: Union[str, Path]) -> None: """Save contents into json file ### Args: * contents (`Union[list, dict]`): Some json serializable - * filepath (`str`): Path to output file + * filepath (`Union[str, Path]`): Path to output file """ try: with open(filepath, "w", encoding="utf8") as file: file.write(dumps(contents, ensure_ascii=False, indent=4)) file.close() except Exception as exp: - logWrite(f"Could not save json file {filepath}: {exp}\n{print_exc()}") + logger.error("Could not save json file %s: %s\n%s", filepath, exp, format_exc()) return @@ -63,7 +64,7 @@ def configGet(key: str, *args: str) -> Any: ### Returns: * `Any`: Value of provided key """ - this_dict = jsonLoad("config.json") + this_dict = jsonLoad(Path("config.json")) this_key = this_dict for dict_key in args: this_key = this_key[dict_key] diff --git a/photos_api.py b/photos_api.py index 714924b..6d5a497 100644 --- a/photos_api.py +++ b/photos_api.py @@ -1,13 +1,20 @@ -from os import makedirs, path +import logging +from os import makedirs +from pathlib import Path from fastapi.responses import FileResponse from modules.app import app from modules.extensions_loader import dynamic_import_from_src from modules.scheduler import scheduler -from modules.utils import * -makedirs(path.join("data", "users"), exist_ok=True) +makedirs(Path("data/users"), exist_ok=True) + +logging.basicConfig( + level=logging.INFO, + format="%(name)s.%(funcName)s | %(levelname)s | %(message)s", + datefmt="[%X]", +) @app.get("/favicon.ico", response_class=FileResponse, include_in_schema=False)