From 53a5ce905bb9ec525fbbd720a32454a38cbc472c Mon Sep 17 00:00:00 2001 From: dm <> Date: Tue, 26 Aug 2025 20:11:01 +0300 Subject: [PATCH] added .env + delete file + handle error when file didns saved but added to db --- .gitignore | 1 + app/api.py | 26 +++++++++++++++++++++----- app/db.py | 35 +++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + uv.lock | 24 +++++++++++++++++++++++- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 13fcc76..a61f37c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ docs/_build/ target/ *.db +.env diff --git a/app/api.py b/app/api.py index 5f983fe..165b32e 100644 --- a/app/api.py +++ b/app/api.py @@ -4,6 +4,10 @@ from sqlalchemy import exists import hashlib from . import db import os +from dotenv import load_dotenv +import os +load_dotenv() +FILES_DIR = os.getenv("FILES_DIR") def compute_hash(data: bytes, algorithm="sha256") -> str: h = hashlib.new(algorithm) @@ -12,7 +16,6 @@ def compute_hash(data: bytes, algorithm="sha256") -> str: app = FastAPI() -FILES_DIR = "./files" @app.get("/") def root(): @@ -29,10 +32,13 @@ async def save_file(file: UploadFile = File(...)): if not existed_url: file_url = db.add_file(file.filename, file.content_type, file.size, hash) - print(f"{FILES_DIR}/{file_url}") - with open(f"{FILES_DIR}/{file_url}", "wb") as f: - f.write(contents) - return {"status": "saved", "filename": file_url} + try: + with open(f"{FILES_DIR}/{file_url}", "wb") as f: + f.write(contents) + return {"status": "saved", "filename": file_url} + except Exception as e: + db.remove_file(file_url) + return {"status": "error", "message": f"Could not save file {file_url}: {e}"} else: return {"status": "file_exists", "filename": existed_url} @@ -48,6 +54,16 @@ def get_file(filename: str, raw: bool = False): return FileResponse(file_path, filename=filename) + +@app.delete("/file/{filename}") +def delete_file(filename: str): + if db.remove_file(filename): + file_path = f"{FILES_DIR}/{filename}" + if os.path.exists(file_path): + os.remove(file_path) + return {"status": "deleted"} + return {"status": "error", "message": "no file like that"} + @app.get("/files/") def get_list_of_files(): files = db.get_all_files() diff --git a/app/db.py b/app/db.py index bb66408..1495907 100644 --- a/app/db.py +++ b/app/db.py @@ -4,9 +4,13 @@ from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy import select -PADDING = 5 +from dotenv import load_dotenv +import os +load_dotenv() +PADDING = int(os.getenv("FILES_PADDING")) +DATABASE_NAME = os.getenv("DATABASE_NAME") -engine = create_engine("sqlite:///example.db") +engine = create_engine(f"sqlite:///{DATABASE_NAME}.db") session = Session(bind=engine) class Base(DeclarativeBase): pass @@ -38,6 +42,22 @@ def to_base36(n: int, width: int) -> str: result.append(chars[rem]) return "".join(reversed(result)).rjust(width, "0") + +def from_base36(s: str) -> int: + chars = "0123456789abcdefghijklmnopqrstuvwxyz" + char_to_val = {c: i for i, c in enumerate(chars)} + + s = s.lower().lstrip("0") + if not s: + return 0 + + n = 0 + for ch in s: + if ch not in char_to_val: + raise ValueError(f"Invalid base36 character: {ch}") + n = n * 36 + char_to_val[ch] + return n + Base.metadata.create_all(bind=engine) def get_all_files(): @@ -83,6 +103,17 @@ def add_file(filename: str, content_type, size: int, hash): return url + +def remove_file(file_url: str): + with Session(autoflush=False, bind=engine) as db: + file_id = from_base36(file_url.rsplit(".")[0]) + file = db.get(File, file_id) + if not file: + return None + db.delete(file) + db.commit() + return True + if __name__ == "__main__": for i in get_all_files(): print(f"{i.id} {i.name}.{i.extension} ({i.hash}) {i.content_type} {i.size}") diff --git a/pyproject.toml b/pyproject.toml index 541b9d7..44c7bb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [ + "dotenv>=0.9.9", "fastapi>=0.116.1", "python-multipart>=0.0.20", "sqlalchemy>=2.0.43", diff --git a/uv.lock b/uv.lock index fb9fe12..46a5997 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" [[package]] @@ -46,6 +46,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, +] + [[package]] name = "fastapi" version = "0.116.1" @@ -168,6 +179,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + [[package]] name = "python-multipart" version = "0.0.20" @@ -182,6 +202,7 @@ name = "sfs" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "dotenv" }, { name = "fastapi" }, { name = "python-multipart" }, { name = "sqlalchemy" }, @@ -190,6 +211,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "dotenv", specifier = ">=0.9.9" }, { name = "fastapi", specifier = ">=0.116.1" }, { name = "python-multipart", specifier = ">=0.0.20" }, { name = "sqlalchemy", specifier = ">=2.0.43" },