-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Starting work on the Chromium HTML conversion route
- Loading branch information
Showing
10 changed files
with
304 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import logging | ||
from types import TracebackType | ||
from typing import Dict | ||
from typing import Optional | ||
from typing import Type | ||
|
||
from httpx import Client | ||
|
||
|
||
class GotenbergClient: | ||
def __init__( | ||
self, | ||
*, | ||
gotenerg_url: str, | ||
timeout: float = 30.0, | ||
log_level: int = logging.ERROR, | ||
compress: bool = False, | ||
http2: bool = True, | ||
): | ||
# Configure the client | ||
self._client = Client(base_url=gotenerg_url, timeout=timeout, http2=http2) | ||
|
||
# Set the log level | ||
logging.getLogger("httpx").setLevel(log_level) | ||
logging.getLogger("httpcore").setLevel(log_level) | ||
|
||
# Only JSON responses supported | ||
self._client.headers.update({"Accept": "application/json"}) | ||
|
||
if compress: | ||
self._client.headers.update({"Accept-Encoding": "gzip,br"}) | ||
|
||
# Add the resources | ||
# TODO | ||
|
||
def add_headers(self, header: Dict[str, str]) -> None: # pragma: no cover | ||
""" | ||
Updates the httpx Client headers with the given values | ||
""" | ||
self._client.headers.update(header) | ||
|
||
def __enter__(self) -> "GotenbergClient": | ||
return self | ||
|
||
def __exit__( | ||
self, | ||
exc_type: Optional[Type[BaseException]], | ||
exc_val: Optional[BaseException], | ||
exc_tb: Optional[TracebackType], | ||
) -> None: | ||
self._client.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# SPDX-FileCopyrightText: 2023-present Trenton H <[email protected]> | ||
# | ||
# SPDX-License-Identifier: MPL-2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
import dataclasses | ||
import enum | ||
import json | ||
from pathlib import Path | ||
from typing import Final | ||
from typing import Optional | ||
from urllib.parse import quote | ||
|
||
from httpx import Client | ||
|
||
from gotenberg_client.convert.common import PageOrientationOptions | ||
from gotenberg_client.convert.common import PageRangeType | ||
from gotenberg_client.convert.common import PdfAFormatOptions | ||
from gotenberg_client.convert.common import optional_to_form | ||
|
||
|
||
@dataclasses.dataclass | ||
class PageSize: | ||
width: float | int | None = None | ||
height: float | int | None = None | ||
|
||
def to_form(self) -> dict[str, str]: | ||
data = optional_to_form(self.width, "paperWidth") | ||
data.update(optional_to_form(self.height, "paperHeight")) | ||
return data | ||
|
||
|
||
# Define common paper sizes as shortcuts | ||
A0: Final = PageSize(width=33.1, height=46.8) | ||
A1: Final = PageSize(width=23.4, height=33.1) | ||
A2: Final = PageSize(width=16.54, height=23.4) | ||
A3: Final = PageSize(width=11.7, height=16.54) | ||
A4: Final = PageSize(width=8.5, height=11) | ||
A5: Final = PageSize(width=5.83, height=8.27) | ||
A6: Final = PageSize(width=4.13, height=5.83) | ||
Letter = A4 | ||
Legal: Final = PageSize(width=8.5, height=14) | ||
Tabloid: Final = PageSize(width=11, height=17) | ||
Ledge: Final = PageSize(width=17, height=11) | ||
|
||
|
||
@dataclasses.dataclass | ||
class Margin: | ||
top: float | int | None = None | ||
bottom: float | int | None = None | ||
left: float | int | None = None | ||
right: float | int | None = None | ||
|
||
def to_form(self) -> dict[str, str]: | ||
data = optional_to_form(self.top, "marginTop") | ||
data.update(optional_to_form(self.bottom, "marginBottom")) | ||
data.update(optional_to_form(self.left, "marginLeft")) | ||
data.update(optional_to_form(self.right, "marginRight")) | ||
return data | ||
|
||
|
||
Gotenberg_Default_Margines: Final = Margin(0.39, 0.39, 0.39, 0.39) | ||
Word_Default_Margins: Final = Margin(top=1.0, bottom=1.0, left=1.0, right=1.0) | ||
Word_Narrow_Margins: Final = Margin(top=0.5, bottom=0.5, left=0.5, right=0.5) | ||
|
||
|
||
@dataclasses.dataclass | ||
class RenderControl: | ||
delay: int | float | None = None | ||
expression: str | None = None | ||
|
||
def to_form(self) -> dict[str, str]: | ||
data = optional_to_form(self.delay, "waitDelay") | ||
data.update(optional_to_form(self.expression, "waitForExpression")) | ||
return data | ||
|
||
|
||
@dataclasses.dataclass | ||
class HttpOptions: | ||
user_agent: str | None = None | ||
headers: dict[str, str] | None = None | ||
|
||
def to_form(self) -> dict[str, str]: | ||
data = optional_to_form(self.user_agent, "userAgent") | ||
if self.headers is not None: | ||
json_str = json.dumps(self.headers) | ||
# TODO: Need to check this | ||
data.update({"extraHttpHeaders": quote(json_str)}) | ||
return data | ||
|
||
|
||
@enum.unique | ||
class EmulatedMediaTypeOptions(str, enum.Enum): | ||
Print = "print" | ||
Screen = "screen" | ||
|
||
def to_form(self) -> dict[str, str]: | ||
return {"emulatedMediaType": self.value} | ||
|
||
|
||
class ChromiumRoutes: | ||
_URL_CONVERT_ENDPOINT = "/forms/chromium/convert/url" | ||
_HTML_CONVERT_ENDPOINT = "/forms/chromium/convert/html" | ||
_MARKDOWN_CONVERT_ENDPOINT = "/forms/chromium/convert/markdown" | ||
|
||
def __init__(self, client: Client) -> None: | ||
self._client = client | ||
|
||
def convert_url( | ||
self, | ||
url: str, | ||
*, | ||
page_size: Optional[PageSize] = None, | ||
margins: Optional[Margin] = None, | ||
prefer_css_page_size: Optional[bool] = None, | ||
print_background: Optional[bool] = None, | ||
omit_background: Optional[bool] = None, | ||
orientation: Optional[PageOrientationOptions] = None, | ||
scale: Optional[int | float] = None, | ||
page_ranges: Optional[PageRangeType] = None, | ||
header: Optional[Path] = None, | ||
footer: Optional[Path] = None, | ||
render_control: Optional[RenderControl] = None, | ||
media_type_emulation: Optional[EmulatedMediaTypeOptions] = None, | ||
http_control: Optional[HttpOptions] = None, | ||
fail_on_console_exceptions: Optional[bool] = None, | ||
pdf_a_output: Optional[PdfAFormatOptions] = None, | ||
) -> None: | ||
pass | ||
|
||
def convert_html( | ||
self, | ||
index_file: Path, | ||
*, | ||
additional_files: Optional[list[Path]] = None, | ||
page_size: Optional[PageSize] = None, | ||
margins: Optional[Margin] = None, | ||
prefer_css_page_size: Optional[bool] = None, | ||
print_background: Optional[bool] = None, | ||
omit_background: Optional[bool] = None, | ||
orientation: Optional[PageOrientationOptions] = None, | ||
scale: Optional[int | float] = None, | ||
page_ranges: Optional[PageRangeType] = None, | ||
header: Optional[Path] = None, | ||
footer: Optional[Path] = None, | ||
render_control: Optional[RenderControl] = None, | ||
media_type_emulation: Optional[EmulatedMediaTypeOptions] = None, | ||
http_control: Optional[HttpOptions] = None, | ||
fail_on_console_exceptions: Optional[bool] = None, | ||
pdf_a_output: Optional[PdfAFormatOptions] = None, | ||
) -> None: | ||
data = {} | ||
data.update(page_size.to_form()) | ||
data.update(margins.to_form()) | ||
data.update(optional_to_form(prefer_css_page_size)) | ||
data.update(optional_to_form(print_background)) | ||
data.update(optional_to_form(omit_background)) | ||
if orientation is not None: | ||
data.update(orientation.to_form()) | ||
data.update(optional_to_form(scale)) | ||
# TODO page ranges | ||
# TODO header & footer | ||
data.update(render_control.to_form()) | ||
data.update(optional_to_form(omit_background)) | ||
|
||
def convert_markdown( | ||
self, | ||
index_file: Path, | ||
markdown_files: list[Path], | ||
*, | ||
additional_files: Optional[list[Path]] = None, | ||
page_size: Optional[PageSize] = None, | ||
margins: Optional[Margin] = None, | ||
prefer_css_page_size: Optional[bool] = None, | ||
print_background: Optional[bool] = None, | ||
omit_background: Optional[bool] = None, | ||
orientation: Optional[PageOrientationOptions] = None, | ||
scale: Optional[int | float] = None, | ||
page_ranges: Optional[PageRangeType] = None, | ||
header: Optional[Path] = None, | ||
footer: Optional[Path] = None, | ||
render_control: Optional[RenderControl] = None, | ||
media_type_emulation: Optional[EmulatedMediaTypeOptions] = None, | ||
http_control: Optional[HttpOptions] = None, | ||
fail_on_console_exceptions: Optional[bool] = None, | ||
pdf_a_output: Optional[PdfAFormatOptions] = None, | ||
) -> None: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import enum | ||
from functools import lru_cache | ||
from typing import Optional | ||
|
||
|
||
@enum.unique | ||
class PageOrientationOptions(enum.Enum): | ||
Landscape = enum.auto() | ||
Potrait = enum.auto() | ||
|
||
def to_form(self) -> dict[str, str]: | ||
if self.value == PageOrientationOptions.Landscape: | ||
return {"landscape": "true"} | ||
else: | ||
return {"landscape": "false"} | ||
|
||
|
||
@enum.unique | ||
class PdfAFormatOptions(enum.Enum): | ||
A1a = enum.auto() | ||
A2a = enum.auto() | ||
A3b = enum.auto() | ||
|
||
|
||
PageRangeType = list[list[int]] | None | ||
|
||
|
||
@lru_cache | ||
def optional_to_form(value: Optional[bool | int | float | str], name: str) -> dict[str, str]: | ||
""" | ||
Quick helper to convert an optional type into a form data field | ||
with the given name or no changes if the value is None | ||
""" | ||
if value is None: | ||
return {} | ||
else: | ||
return {name: str(value).lower()} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import dataclasses | ||
|
||
from gotenberg_client.convert.common import PageOrientationOptions | ||
from gotenberg_client.convert.common import PageRangeType | ||
from gotenberg_client.convert.common import PdfAFormatOptions | ||
|
||
|
||
@dataclasses.dataclass | ||
class PageProperties: | ||
""" | ||
Defines possible page properties for the Libre Office routes, along | ||
with the default values. | ||
Documentation: | ||
- https://gotenberg.dev/docs/routes#page-properties-libreoffice | ||
""" | ||
|
||
orientation: PageOrientationOptions = PageOrientationOptions.Potrait | ||
pages: PageRangeType = None | ||
|
||
|
||
@dataclasses.dataclass | ||
class LibreOfficeRouteOptions: | ||
page_properties: PageProperties | None = None | ||
merge: bool | None = None | ||
pdf_a_format: PdfAFormatOptions | None = None |
Empty file.
Empty file.
Empty file.
Empty file.