Spaces:
Sleeping
Sleeping
SilentWraith
commited on
Commit
•
6f8bc75
0
Parent(s):
Initial commit
Browse files- Dockerfile +24 -0
- pyproject.toml +18 -0
- requirements.txt +0 -0
- screenshot/__init__.py +0 -0
- screenshot/main.py +10 -0
- screenshot/routers/__init__.py +0 -0
- screenshot/routers/screenshot.py +93 -0
Dockerfile
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11 AS builder
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
RUN python3 -m venv venv
|
6 |
+
ENV VIRTUAL_ENV=/app/venv
|
7 |
+
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
8 |
+
|
9 |
+
COPY requirements.txt .
|
10 |
+
RUN pip install -r requirements.txt
|
11 |
+
|
12 |
+
# Stage 2
|
13 |
+
FROM python:3.11 AS runner
|
14 |
+
|
15 |
+
WORKDIR /app
|
16 |
+
|
17 |
+
COPY --from=builder /app/venv venv
|
18 |
+
|
19 |
+
ENV VIRTUAL_ENV=/app/venv
|
20 |
+
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
21 |
+
|
22 |
+
EXPOSE 8000
|
23 |
+
|
24 |
+
CMD [ "python screenshot/main.py" ]
|
pyproject.toml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.ruff]
|
2 |
+
line-length = 100
|
3 |
+
|
4 |
+
[tool.ruff.lint]
|
5 |
+
select = ["ALL"]
|
6 |
+
ignore = [
|
7 |
+
"CPY001", # copyright above code
|
8 |
+
"D", # sphinx not support
|
9 |
+
]
|
10 |
+
|
11 |
+
[tool.mypy]
|
12 |
+
disallow_untyped_defs = true
|
13 |
+
show_error_codes = true
|
14 |
+
no_implicit_optional = true
|
15 |
+
warn_return_any = true
|
16 |
+
warn_unused_ignores = true
|
17 |
+
exclude = ["tests"]
|
18 |
+
python_version = "3.10"
|
requirements.txt
ADDED
File without changes
|
screenshot/__init__.py
ADDED
File without changes
|
screenshot/main.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uvicorn
|
2 |
+
from fastapi import FastAPI
|
3 |
+
from routers.screenshot import routers as screenshot_router
|
4 |
+
|
5 |
+
app = FastAPI()
|
6 |
+
|
7 |
+
app.include_router(screenshot_router)
|
8 |
+
|
9 |
+
if __name__ == "__main__":
|
10 |
+
uvicorn.run(app, host="0.0.0.0", port=8000) # noqa: S104
|
screenshot/routers/__init__.py
ADDED
File without changes
|
screenshot/routers/screenshot.py
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
|
3 |
+
import logging
|
4 |
+
from typing import IO, TYPE_CHECKING, AsyncContextManager, Literal
|
5 |
+
|
6 |
+
from fastapi import APIRouter, HTTPException
|
7 |
+
from fastapi.responses import Response
|
8 |
+
from playwright.async_api import BrowserContext, TimeoutError, async_playwright
|
9 |
+
from pydantic import BaseModel, HttpUrl
|
10 |
+
|
11 |
+
if TYPE_CHECKING:
|
12 |
+
from types import TracebackType
|
13 |
+
|
14 |
+
|
15 |
+
router = APIRouter()
|
16 |
+
|
17 |
+
|
18 |
+
class ViewPort(BaseModel):
|
19 |
+
width: int = 1280
|
20 |
+
height: int = 720
|
21 |
+
|
22 |
+
|
23 |
+
class ScreenshotItems(BaseModel):
|
24 |
+
url: HttpUrl
|
25 |
+
full_page: bool | None = False
|
26 |
+
query_selector: str | None = None
|
27 |
+
|
28 |
+
viewport: ViewPort | None = None
|
29 |
+
color_scheme: Literal["light", "dark", "no-preference"] | None = "no-preference"
|
30 |
+
bypass_csp: bool | None = False
|
31 |
+
java_script_enabled: bool | None = True
|
32 |
+
proxy: dict | None = None
|
33 |
+
is_mobile: bool | None = False
|
34 |
+
no_viewport: bool | None = False
|
35 |
+
|
36 |
+
|
37 |
+
class ScreenShot:
|
38 |
+
async def __aenter__(self) -> AsyncContextManager[ScreenShot]:
|
39 |
+
self.playwright = await async_playwright().start()
|
40 |
+
self.browser = await self.playwright.chromium.launch(
|
41 |
+
args=["--disable-extensions"],
|
42 |
+
chromium_sandbox=True,
|
43 |
+
)
|
44 |
+
return self
|
45 |
+
|
46 |
+
async def browser_context(self, items: ScreenshotItems) -> BrowserContext:
|
47 |
+
return await self.browser.new_context(
|
48 |
+
viewport=items.viewport.model_dump() if items.viewport else None,
|
49 |
+
color_scheme=items.color_scheme,
|
50 |
+
bypass_csp=items.bypass_csp,
|
51 |
+
java_script_enabled=items.java_script_enabled,
|
52 |
+
proxy=items.proxy.model_dump() if items.proxy else None,
|
53 |
+
is_mobile=items.is_mobile,
|
54 |
+
no_viewport=items.no_viewport,
|
55 |
+
)
|
56 |
+
|
57 |
+
async def capture(self, items: ScreenshotItems) -> IO[bytes]:
|
58 |
+
context: BrowserContext = await self.browser_context(items)
|
59 |
+
page = await context.new_page()
|
60 |
+
await page.goto(str(items.url))
|
61 |
+
|
62 |
+
if items.query_selector:
|
63 |
+
page = page.locator(items.query_selector)
|
64 |
+
|
65 |
+
screenshot_data = await page.screenshot(full_page=items.full_page)
|
66 |
+
await context.close()
|
67 |
+
return screenshot_data
|
68 |
+
|
69 |
+
async def __aexit__(
|
70 |
+
self,
|
71 |
+
typ: type[BaseException] | None,
|
72 |
+
exc: BaseException | None,
|
73 |
+
tb: TracebackType | None,
|
74 |
+
) -> None:
|
75 |
+
if self.browser:
|
76 |
+
await self.browser.close()
|
77 |
+
if self.playwright:
|
78 |
+
await self.playwright.stop()
|
79 |
+
|
80 |
+
|
81 |
+
@router.post("/screenshot")
|
82 |
+
async def screenshot(data: ScreenshotItems) -> Response:
|
83 |
+
async with ScreenShot() as sc:
|
84 |
+
try:
|
85 |
+
response = await sc.capture(items=data)
|
86 |
+
return Response(content=response, media_type="image/png")
|
87 |
+
except TimeoutError as e:
|
88 |
+
raise HTTPException(
|
89 |
+
status_code=504,
|
90 |
+
detail=f"An error occurred while generating the screenshot: {e}",
|
91 |
+
) from e
|
92 |
+
except Exception:
|
93 |
+
logging.exception("screenshot unhandled error")
|