From 2165dd5b7b731a8043c2a05658ad67dc1073ee97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Thu, 2 Nov 2023 00:08:42 +0100 Subject: [PATCH] Use Ruff and fix all its warnings --- .pre-commit-config.yaml | 61 +++++++++++++ .vscode/launch.json | 5 +- Dockerfile | 4 +- discord_embed/__init__.py | 1 - discord_embed/generate_html.py | 23 +++-- discord_embed/main.py | 48 +++++++--- discord_embed/settings.py | 43 +++------ discord_embed/video.py | 12 ++- discord_embed/video_file_upload.py | 24 +++-- discord_embed/webhook.py | 16 +++- embed.subdomain.conf | 2 +- poetry.lock | 137 ++++++++++++++++++++++++++++- pyproject.toml | 8 +- static/style.css | 12 --- templates/index.html | 27 +++--- tests/test_discord_embed.py | 54 ++++-------- tests/test_generate_html.py | 62 ++----------- tests/test_video.py | 9 +- tests/test_webhook.py | 2 + 19 files changed, 347 insertions(+), 203 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 static/style.css diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..fde1e57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,61 @@ +default_language_version: + python: python3.12 +repos: + # Apply a consistent format to pyproject.toml files. + # https://pyproject-fmt.readthedocs.io/en/latest/ + - repo: https://github.com/tox-dev/pyproject-fmt + rev: "1.4.0" + hooks: + - id: pyproject-fmt + + - repo: https://github.com/asottile/add-trailing-comma + rev: v3.1.0 + hooks: + - id: add-trailing-comma + + # Some out-of-the-box hooks for pre-commit. + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-ast + - id: check-builtin-literals + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-toml + - id: check-vcs-permalinks + - id: end-of-file-fixer + - id: mixed-line-ending + - id: name-tests-test + args: [--pytest-test-first] + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + exclude_types: + - "html" + + # Run Pyupgrade on all Python files. This will upgrade the code to Python 3.12. + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: ["--py312-plus"] + + # An extremely fast Python linter and formatter. + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.3 + hooks: + - id: ruff-format + - id: ruff + args: ["--fix", "--exit-non-zero-on-fix"] + + # Static checker for GitHub Actions workflow files. + - repo: https://github.com/rhysd/actionlint + rev: v1.6.26 + hooks: + - id: actionlint + + # Optimize .png files. + - repo: https://github.com/shssoichiro/oxipng + rev: v9.0.0 + hooks: + - id: oxipng diff --git a/.vscode/launch.json b/.vscode/launch.json index 0d6942b..80704f8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,7 +1,4 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { @@ -17,4 +14,4 @@ "justMyCode": true } ] -} \ No newline at end of file +} diff --git a/Dockerfile b/Dockerfile index f1cb019..bf68648 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ ENV PYTHONUNBUFFERED 1 # Update the system and install curl, it is needed for downloading Poetry. RUN apt-get update && apt-get install curl ffmpeg -y --no-install-recommends -# 1. Create user so we don't run as root +# 1. Create user so we don't run as root # 2. Create directories that the bot needs that are owned by the user. # /Uploads is used to store the uploaded files. # /home/botuser/discord-embed is where the Python code is stored. @@ -45,4 +45,4 @@ VOLUME ["/Uploads"] # Run the server on all interfaces and on port 5000. # You should run a reverse proxy like nginx infront of this. EXPOSE 5000 -CMD ["poetry", "run", "uvicorn", "discord_embed.main:app", "--host", "0.0.0.0", "--port", "5000"] \ No newline at end of file +CMD ["poetry", "run", "uvicorn", "discord_embed.main:app", "--host", "0.0.0.0", "--port", "5000"] diff --git a/discord_embed/__init__.py b/discord_embed/__init__.py index 5becc17..e69de29 100644 --- a/discord_embed/__init__.py +++ b/discord_embed/__init__.py @@ -1 +0,0 @@ -__version__ = "1.0.0" diff --git a/discord_embed/generate_html.py b/discord_embed/generate_html.py index 836a7e1..0fafba2 100644 --- a/discord_embed/generate_html.py +++ b/discord_embed/generate_html.py @@ -1,11 +1,19 @@ -import os -from datetime import datetime +from __future__ import annotations + +import datetime +from pathlib import Path from urllib.parse import urljoin from discord_embed import settings -def generate_html_for_videos(url: str, width: int, height: int, screenshot: str, filename: str) -> str: +def generate_html_for_videos( + url: str, + width: int, + height: int, + screenshot: str, + filename: str, +) -> str: """Generate HTML for video files. Args: @@ -18,10 +26,13 @@ def generate_html_for_videos(url: str, width: int, height: int, screenshot: str, Returns: Returns HTML for video. """ + time_now: datetime.datetime = datetime.datetime.now(tz=datetime.UTC) + time_now_str: str = time_now.strftime("%Y-%m-%d %H:%M:%S %Z") + video_html: str = f""" - + @@ -39,8 +50,8 @@ def generate_html_for_videos(url: str, width: int, height: int, screenshot: str, # Take the filename and append .html to it. filename += ".html" - file_path: str = os.path.join(settings.upload_folder, filename) - with open(file_path, "w", encoding="utf-8") as f: + file_path = Path(settings.upload_folder, filename) + with Path.open(file_path, "w", encoding="utf-8") as f: f.write(video_html) return html_url diff --git a/discord_embed/main.py b/discord_embed/main.py index 46bf54b..6aa534a 100644 --- a/discord_embed/main.py +++ b/discord_embed/main.py @@ -1,8 +1,10 @@ +from __future__ import annotations + +from pathlib import Path from urllib.parse import urljoin from fastapi import FastAPI, File, Request, UploadFile from fastapi.responses import HTMLResponse, JSONResponse -from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from discord_embed import settings @@ -17,12 +19,11 @@ app: FastAPI = FastAPI( }, ) -app.mount("/static", StaticFiles(directory="static"), name="static") templates: Jinja2Templates = Jinja2Templates(directory="templates") @app.post("/uploadfiles/", description="Where to send a POST request to upload files.") -async def upload_file(file: UploadFile = File()): +async def upload_file(file: UploadFile = File()): # noqa: B008 """Page for uploading files. If it is a video, we need to make an HTML file, and a thumbnail @@ -35,15 +36,22 @@ async def upload_file(file: UploadFile = File()): Returns: Returns a dict with the filename, or a link to the .html if it was a video. """ + if file.filename is None: + send_webhook("Filename is None") + return JSONResponse(content={"error": "Filename is None"}, status_code=500) + if file.content_type is None: + send_webhook("Content type is None") + return JSONResponse(content={"error": "Content type is None"}, status_code=500) + if file.content_type.startswith("video/"): html_url: str = await do_things(file) else: filename: str = await remove_illegal_chars(file.filename) - with open(f"{settings.upload_folder}/{filename}", "wb+") as f: + with Path.open(Path(settings.upload_folder, filename), "wb+") as f: f.write(file.file.read()) - html_url: str = urljoin(settings.serve_domain, filename) # type: ignore + html_url: str = urljoin(settings.serve_domain, filename) send_webhook(f"{html_url} was uploaded.") return JSONResponse(content={"html_url": html_url}) @@ -58,8 +66,7 @@ async def remove_illegal_chars(file_name: str) -> str: Returns: Returns a string with the filename without illegal characters. """ - - filename: str = file_name.replace(" ", ".") # type: ignore + filename: str = file_name.replace(" ", ".") illegal_characters: list[str] = [ "*", '"', @@ -84,13 +91,31 @@ async def remove_illegal_chars(file_name: str) -> str: ",", ] for character in illegal_characters: - filename: str = filename.replace(character, "") # type: ignore + filename: str = filename.replace(character, "") return filename +index_html: str = """ + + + +

discord-nice-embed

+ Swagger UI - API documentation +
+ ReDoc - Alternative API documentation +
+ + +
+ + + +""" + + @app.get("/", response_class=HTMLResponse, include_in_schema=False) -async def main(request: Request): +async def main(request: Request): # noqa: ARG001 """Our index view. You can upload files here. @@ -99,7 +124,6 @@ async def main(request: Request): request: Our request. Returns: - TemplateResponse: Returns HTML for site. + HTMLResponse: Our index.html page. """ - - return templates.TemplateResponse("index.html", {"request": request}) + return index_html diff --git a/discord_embed/settings.py b/discord_embed/settings.py index 8f603e7..105eda3 100644 --- a/discord_embed/settings.py +++ b/discord_embed/settings.py @@ -1,37 +1,14 @@ -import os -import pathlib -import sys +from __future__ import annotations -from dotenv import load_dotenv +import os +from pathlib import Path + +from dotenv import find_dotenv, load_dotenv # Load environment variables -load_dotenv() +load_dotenv(find_dotenv(), verbose=True) -# Check if user has added a domain to the environment. -try: - serve_domain: str = os.environ["SERVE_DOMAIN"] -except KeyError: - sys.exit("discord-embed: Environment variable 'SERVE_DOMAIN' is missing!") - -# Remove trailing slash from domain -if serve_domain.endswith("/"): - serve_domain = serve_domain[:-1] - -# Check if we have a folder for uploads. -try: - upload_folder: str = os.environ["UPLOAD_FOLDER"] -except KeyError: - sys.exit("Environment variable 'UPLOAD_FOLDER' is missing!") - -# Create upload_folder if it doesn't exist. -pathlib.Path(upload_folder).mkdir(parents=True, exist_ok=True) - -# Remove trailing slash from upload_folder -if upload_folder.endswith("/"): - upload_folder = upload_folder[:-1] - -# Discord webhook URL -try: - webhook_url: str = os.environ["WEBHOOK_URL"] -except KeyError: - sys.exit("Environment variable 'WEBHOOK_URL' is missing!") +webhook_url: str = os.environ["WEBHOOK_URL"] +serve_domain: str = os.environ["SERVE_DOMAIN"].removesuffix("/") +upload_folder: str = os.environ["UPLOAD_FOLDER"].removesuffix("/") +Path(upload_folder).mkdir(parents=True, exist_ok=True) diff --git a/discord_embed/video.py b/discord_embed/video.py index 6386a00..35b32f4 100644 --- a/discord_embed/video.py +++ b/discord_embed/video.py @@ -1,3 +1,6 @@ +from __future__ import annotations + +import logging import sys from dataclasses import dataclass @@ -5,6 +8,8 @@ import ffmpeg from discord_embed import settings +logger: logging.Logger = logging.getLogger(__name__) + @dataclass class Resolution: @@ -28,9 +33,12 @@ def video_resolution(path_to_video: str) -> Resolution: Returns height and width. """ probe = ffmpeg.probe(path_to_video) - video_stream = next((stream for stream in probe["streams"] if stream["codec_type"] == "video"), None) + video_stream = next( + (stream for stream in probe["streams"] if stream["codec_type"] == "video"), + None, + ) if video_stream is None: - print("No video stream found", file=sys.stderr) + logger.critical("No video stream found") sys.exit(1) width: int = int(video_stream["width"]) diff --git a/discord_embed/video_file_upload.py b/discord_embed/video_file_upload.py index c498822..b2bf8a2 100644 --- a/discord_embed/video_file_upload.py +++ b/discord_embed/video_file_upload.py @@ -1,13 +1,16 @@ -import os +from __future__ import annotations + from dataclasses import dataclass from pathlib import Path - -from fastapi import UploadFile +from typing import TYPE_CHECKING from discord_embed import settings from discord_embed.generate_html import generate_html_for_videos from discord_embed.video import Resolution, make_thumbnail, video_resolution +if TYPE_CHECKING: + from fastapi import UploadFile + @dataclass class VideoFile: @@ -32,19 +35,23 @@ def save_to_disk(file: UploadFile) -> VideoFile: Returns: VideoFile object with the filename and location. """ + if file.filename is None: + msg = "Filename is None" + raise ValueError(msg) + # Create the folder where we should save the files - folder_video: str = os.path.join(settings.upload_folder, "video") - Path(folder_video).mkdir(parents=True, exist_ok=True) + save_folder_video = Path(settings.upload_folder, "video") + Path(save_folder_video).mkdir(parents=True, exist_ok=True) # Replace spaces with dots in the filename. filename: str = file.filename.replace(" ", ".") # Save the uploaded file to disk. - file_location: str = os.path.join(folder_video, filename) - with open(file_location, "wb+") as f: + file_location = Path(save_folder_video, filename) + with Path.open(file_location, "wb+") as f: f.write(file.file.read()) - return VideoFile(filename, file_location) + return VideoFile(filename, str(file_location)) async def do_things(file: UploadFile) -> str: @@ -56,7 +63,6 @@ async def do_things(file: UploadFile) -> str: Returns: Returns URL for video. """ - video_file: VideoFile = save_to_disk(file) file_url: str = f"{settings.serve_domain}/video/{video_file.filename}" diff --git a/discord_embed/webhook.py b/discord_embed/webhook.py index f586f08..1c85cd6 100644 --- a/discord_embed/webhook.py +++ b/discord_embed/webhook.py @@ -1,7 +1,17 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + from discord_webhook import DiscordWebhook from discord_embed import settings +if TYPE_CHECKING: + from requests import Response + +logger: logging.Logger = logging.getLogger(__name__) + def send_webhook(message: str) -> None: """Send a webhook to Discord. @@ -11,7 +21,9 @@ def send_webhook(message: str) -> None: """ webhook: DiscordWebhook = DiscordWebhook( url=settings.webhook_url, - content=message, + content=message or "discord-nice-embed: No message was provided.", rate_limit_retry=True, ) - webhook.execute() + response: Response = webhook.execute() + if not response.ok: + logger.critical("Webhook failed to send\n %s\n %s", response, message) diff --git a/embed.subdomain.conf b/embed.subdomain.conf index 5d67e56..04258ba 100644 --- a/embed.subdomain.conf +++ b/embed.subdomain.conf @@ -17,4 +17,4 @@ server { set $upstream_proto http; proxy_pass $upstream_proto://$upstream_app:$upstream_port; } -} \ No newline at end of file +} diff --git a/poetry.lock b/poetry.lock index 0eaf2ff..9ff4aca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,6 +42,17 @@ files = [ {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -183,6 +194,17 @@ requests = ">=2.28.1,<3.0.0" [package.extras] async = ["httpx (>=0.23.0,<0.24.0)"] +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + [[package]] name = "fastapi" version = "0.104.1" @@ -220,6 +242,22 @@ future = "*" [package.extras] dev = ["Sphinx (==2.1.0)", "future (==0.17.1)", "numpy (==1.16.4)", "pytest (==4.6.1)", "pytest-mock (==1.10.4)", "tox (==3.12.1)"] +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] name = "future" version = "0.18.3" @@ -333,6 +371,20 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +[[package]] +name = "identify" +version = "2.5.31" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"}, + {file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.4" @@ -441,6 +493,20 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "packaging" version = "23.2" @@ -452,6 +518,21 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.3.0" @@ -467,6 +548,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.5.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "pydantic" version = "2.4.2" @@ -732,6 +831,22 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "sniffio" version = "1.3.0" @@ -857,6 +972,26 @@ files = [ docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +[[package]] +name = "virtualenv" +version = "20.24.6" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, + {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "watchfiles" version = "0.21.0" @@ -1028,4 +1163,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "3eb51d6b10b9c56043cfeaf3cffbe0f30f526ff774b2c4bbd7672f5e3a0d6b03" +content-hash = "939a371df3b2147c6a306b152b4f7011fc0663defba72c79085f705b0eaeef9c" diff --git a/pyproject.toml b/pyproject.toml index 23fa87d..9af91c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,16 +18,20 @@ uvicorn = { extras = ["standard"], version = "^0.23.0" } [tool.poetry.group.dev.dependencies] httpx = "^0.25.0" pytest = "^7.4.3" +pre-commit = "^3.5.0" [build-system] -requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" +requires = [ + "poetry-core>=1", +] [tool.ruff] fix = true +unsafe-fixes = true preview = true select = ["ALL"] -ignore = ["D100", "CPY001"] +ignore = ["D100", "D104", "CPY001", "ANN201"] [tool.ruff.pydocstyle] convention = "google" diff --git a/static/style.css b/static/style.css deleted file mode 100644 index 948a07f..0000000 --- a/static/style.css +++ /dev/null @@ -1,12 +0,0 @@ -body { - background-color: #111111; - color: #DCDDDE; -} - -a:link { - color: #00AFF4; -} - -a:visited { - color: #00AFF4; -} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 9869f1e..8b3b5e7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,21 +1,14 @@ - - - discord-nice-embed - - - - + -

discord-nice-embed

-Swagger UI - API documentation -
-ReDoc - Alternative API documentation -
-
-
- - -
+

discord-nice-embed

+ Swagger UI - API documentation +
+ ReDoc - Alternative API documentation +
+ + +
+ diff --git a/tests/test_discord_embed.py b/tests/test_discord_embed.py index d3749c3..14dc3a3 100644 --- a/tests/test_discord_embed.py +++ b/tests/test_discord_embed.py @@ -1,68 +1,44 @@ -import os +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING -from fastapi import Response from fastapi.testclient import TestClient -from discord_embed import __version__, settings +from discord_embed import settings from discord_embed.main import app +if TYPE_CHECKING: + import httpx + client: TestClient = TestClient(app) TEST_FILE: str = "tests/test.mp4" -def test_version() -> None: - """Test version is correct.""" - assert __version__ == "1.0.0" - - def test_domain_ends_with_slash() -> None: """Test domain ends with a slash.""" assert not settings.serve_domain.endswith("/") -def test_save_to_disk() -> None: - """Test save_to_disk() works.""" - # TODO: Implement this test. I need to mock the UploadFile object. - - -def test_do_things() -> None: - """Test do_things() works.""" - # TODO: Implement this test. I need to mock the UploadFile object. - - def test_main() -> None: """Test main() works.""" - data_without_trailing_nl = "" - response: Response = client.get("/") - - # Check if response is our HTML. - with open("templates/index.html", encoding="utf8") as our_html: - data: str = our_html.read() - - # index.html has a trailing newline that we need to remove. - if data[-1:] == "\n": - data_without_trailing_nl: str = data[:-1] # type: ignore - - assert response.status_code == 200 - assert response.text == data_without_trailing_nl + response: httpx.Response = client.get("/") + assert response.is_success def test_upload_file() -> None: """Test if we can upload files.""" - domain = os.environ["SERVE_DOMAIN"] - - # Remove trailing slash from domain - if domain.endswith("/"): - domain: str = domain[:-1] # type: ignore + domain = os.environ["SERVE_DOMAIN"].removesuffix("/") # Upload our video file and check if it returns the html_url. - with open(TEST_FILE, "rb") as uploaded_file: - response: Response = client.post( + with Path.open(Path(TEST_FILE), "rb") as uploaded_file: + response: httpx.Response = client.post( url="/uploadfiles/", files={"file": uploaded_file}, ) returned_json = response.json() html_url: str = returned_json["html_url"] - assert response.status_code == 200 + assert response.is_success assert html_url == f"{domain}/test.mp4" diff --git a/tests/test_generate_html.py b/tests/test_generate_html.py index 6e37a12..0ed2ecd 100644 --- a/tests/test_generate_html.py +++ b/tests/test_generate_html.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import os +from pathlib import Path from discord_embed.generate_html import generate_html_for_videos @@ -12,8 +15,8 @@ def test_generate_html_for_videos() -> None: domain = domain[:-1] # Delete the old HTML file if it exists - if os.path.exists("Uploads/test_video.mp4.html"): - os.remove("Uploads/test_video.mp4.html") + if Path.exists(Path("Uploads/test_video.mp4.html")): + Path.unlink(Path("Uploads/test_video.mp4.html")) generated_html: str = generate_html_for_videos( url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", @@ -23,58 +26,3 @@ def test_generate_html_for_videos() -> None: filename="test_video.mp4", ) assert generated_html == f"{domain}/test_video.mp4" - - # Open the generated HTML and check if it contains the correct URL, width, height, and screenshot. - - with open("Uploads/test_video.mp4.html", "r") as generated_html_file: - generated_html_lines: list[str] = generated_html_file.readlines() - """ - - - - - - - - - - - - - - """ - - for line, html in enumerate(generated_html_lines): - # Strip spaces and newlines - stripped_html: str = html.strip() - - rick: str = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - - # Check each line - if line == 1: - assert stripped_html == "" - elif line == 2: - assert stripped_html == "" - elif line == 3: - assert stripped_html.startswith("