diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 0000000..cb815b8 --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,22 @@ +name: mypy paste.py 🐍 + +on: + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup PDM + uses: pdm-project/setup-pdm@v3 + with: + python-version: 3.11 + cache: true + cache-dependency-path: "**/pdm.lock" + - name: Install dependencies + run: pdm install + - name: Run mypy + run: pdm mypy diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..132834a --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +ignore_missing_imports = True +disallow_untyped_defs = True \ No newline at end of file diff --git a/pdm.lock b/pdm.lock index 3d45646..8aba24c 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "test", "lint", "hooks"] +groups = ["default", "test", "lint", "hooks", "typing"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:5f062d1449fafa2f6f1dc4d1d0d89c5453c819e95ac94aa028d11ebd2b7b1162" +content_hash = "sha256:c3c10a4cfdf9111aab6e44a030c9b1cb88c074d8b15e0dd2336ff6f1dfdb579e" [[package]] name = "annotated-types" @@ -487,6 +487,36 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "mypy" +version = "1.8.0" +requires_python = ">=3.8" +summary = "Optional static typing for Python" +dependencies = [ + "mypy-extensions>=1.0.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.1.0", +] +files = [ + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, +] + [[package]] name = "mypy-extensions" version = "1.0.0" diff --git a/pyproject.toml b/pyproject.toml index 08644aa..8f9c709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ license = {text = "MIT"} [tool.pdm.scripts] start = "uvicorn src.paste.main:app --host 0.0.0.0 --port 8080 --workers 4" test = "pytest" +mypy = "mypy src/paste" [tool.pdm.dev-dependencies] test = [ @@ -33,3 +34,10 @@ lint = [ hooks = [ "pre-commit>=3.6.0", ] +typing = [ + "mypy>=1.8.0", +] + +[tool.ruff] +line-length = 160 +exclude = ["data/*", ".git"] \ No newline at end of file diff --git a/src/paste/main.py b/src/paste/main.py index 1fc041c..f61a411 100644 --- a/src/paste/main.py +++ b/src/paste/main.py @@ -1,10 +1,24 @@ -from fastapi import File, UploadFile, HTTPException, status, Request, Form, FastAPI, Header -from fastapi.responses import PlainTextResponse, HTMLResponse, RedirectResponse, JSONResponse +from fastapi import ( + File, + UploadFile, + HTTPException, + status, + Request, + Form, + FastAPI, + Header, + Response, +) +from fastapi.responses import ( + PlainTextResponse, + HTMLResponse, + RedirectResponse, + JSONResponse, +) import shutil import os import json from pathlib import Path -from fastapi import FastAPI from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware from slowapi.errors import RateLimitExceeded @@ -16,7 +30,7 @@ from pygments.lexers import get_lexer_by_name, guess_lexer from pygments.formatters import HtmlFormatter from pygments.util import ClassNotFound -from typing import List, Optional, Any +from typing import List, Optional limiter = Limiter(key_func=get_remote_address) app: FastAPI = FastAPI(title="paste.py 🐍") @@ -41,21 +55,24 @@ BASE_DIR: Path = Path(__file__).resolve().parent -templates: Jinja2Templates = Jinja2Templates( - directory=str(Path(BASE_DIR, "templates"))) +templates: Jinja2Templates = Jinja2Templates(directory=str(Path(BASE_DIR, "templates"))) @app.post("/file") @limiter.limit("100/minute") -async def post_as_a_file(request: Request, file: UploadFile = File(...)) -> PlainTextResponse: +async def post_as_a_file( + request: Request, file: UploadFile = File(...) +) -> PlainTextResponse: try: uuid: str = generate_uuid() if uuid in large_uuid_storage: uuid = generate_uuid() # Extract file extension from the filename try: - file_extension: str = Path(file.filename).suffix[1:] - path: str = f"data/{uuid}.{file_extension}" + file_extension: Optional[str] = None + if file.filename is not None: + file_extension = Path(file.filename).suffix[1:] + path: str = f"data/{uuid}{file_extension}" except Exception: path = f"data/{uuid}" finally: @@ -63,7 +80,6 @@ async def post_as_a_file(request: Request, file: UploadFile = File(...)) -> Plai with open(path, "wb") as f: shutil.copyfileobj(file.file, f) large_uuid_storage.append(uuid) - print(large_uuid_storage) except Exception: raise HTTPException( detail="There was an error uploading the file", @@ -75,7 +91,9 @@ async def post_as_a_file(request: Request, file: UploadFile = File(...)) -> Plai @app.get("/paste/{uuid}") -async def get_paste_data(uuid: str, user_agent: Optional[str] = Header(None)) -> Any: +async def get_paste_data( + uuid: str, user_agent: Optional[str] = Header(None) +) -> Response: path: str = f"data/{uuid}" try: with open(path, "rb") as f: @@ -97,13 +115,11 @@ async def get_paste_data(uuid: str, user_agent: Optional[str] = Header(None)) -> try: lexer = get_lexer_by_name(file_extension, stripall=True) except ClassNotFound: - lexer = get_lexer_by_name( - "text", stripall=True) # Default lexer + lexer = get_lexer_by_name("text", stripall=True) # Default lexer formatter = HtmlFormatter( - style="colorful", full=True, linenos="inline", cssclass='code') + style="colorful", full=True, linenos="inline", cssclass="code" + ) highlighted_code: str = highlight(content, lexer, formatter) - - print(highlighted_code) custom_style = """ .code pre span.linenos { color: #999; @@ -154,11 +170,8 @@ async def get_paste_data(uuid: str, user_agent: Optional[str] = Header(None)) -> """ - return HTMLResponse( - content=response_content - ) - except Exception as e: - print(e) + return HTMLResponse(content=response_content) + except Exception: raise HTTPException( detail="404: The Requested Resource is not found", status_code=status.HTTP_404_NOT_FOUND, @@ -166,7 +179,7 @@ async def get_paste_data(uuid: str, user_agent: Optional[str] = Header(None)) -> @app.get("/", response_class=HTMLResponse) -async def indexpage(request: Request) -> HTMLResponse: +async def indexpage(request: Request) -> Response: return templates.TemplateResponse("index.html", {"request": request}) @@ -187,7 +200,7 @@ async def delete_paste(uuid: str) -> PlainTextResponse: @app.get("/web", response_class=HTMLResponse) -async def web(request: Request) -> HTMLResponse: +async def web(request: Request) -> Response: return templates.TemplateResponse("web.html", {"request": request}) @@ -209,8 +222,7 @@ async def web_post( with open(path, "wb") as f: f.write(file_content) large_uuid_storage.append(uuid_) - except Exception as e: - print(e) + except Exception: raise HTTPException( detail="There was an error uploading the file", status_code=status.HTTP_403_FORBIDDEN, diff --git a/src/paste/templates/index.html b/src/paste/templates/index.html index 3bb2389..d6412d2 100644 --- a/src/paste/templates/index.html +++ b/src/paste/templates/index.html @@ -1,15 +1,18 @@ - Codestin Search App - - - - - - - - + Codestin Search App + + + + + + + +