diff --git a/README.md b/README.md index a8247c3..1de6ec4 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ from gotenberg_client.options import PdfAFormat with GotenbergClient("http://localhost:3000") as client: with client.chromium.html_to_pdf() as route: - response = route.index("my-index.html").resource("image.png").resource("style.css").pdf_format(PdfAFormat.A1a).run() + response = route.index("my-index.html").resources(["image.png", "style.css"]).pdf_format(PdfAFormat.A1a).run() Path("my-index.pdf").write_bytes(response.content) ``` diff --git a/src/gotenberg_client/base.py b/src/gotenberg_client/base.py index e0b1e80..d5bb799 100644 --- a/src/gotenberg_client/base.py +++ b/src/gotenberg_client/base.py @@ -3,7 +3,6 @@ # SPDX-License-Identifier: MPL-2.0 import logging from contextlib import ExitStack -from importlib.util import find_spec from pathlib import Path from types import TracebackType from typing import Dict @@ -15,6 +14,7 @@ from httpx._types import RequestFiles from gotenberg_client.options import PdfAFormat +from gotenberg_client.utils import guess_mime_type logger = logging.getLogger(__name__) @@ -119,26 +119,3 @@ class BaseApi: def __init__(self, client: Client) -> None: self._client = client - - -def guess_mime_type_stdlib(url: Path) -> Optional[str]: - """ - Uses the standard library to guess a mimetype - """ - import mimetypes - - mime_type, _ = mimetypes.guess_type(url) - return mime_type - - -def guess_mime_type_magic(url: Path) -> Optional[str]: - """ - Uses libmagic to guess the mimetype - """ - import magic # type: ignore - - return magic.from_file(url, mime=True) # type: ignore - - -# Use the best option -guess_mime_type = guess_mime_type_magic if find_spec("magic") is not None else guess_mime_type_stdlib diff --git a/src/gotenberg_client/convert/chromium.py b/src/gotenberg_client/convert/chromium.py index 85775cf..378c422 100644 --- a/src/gotenberg_client/convert/chromium.py +++ b/src/gotenberg_client/convert/chromium.py @@ -47,6 +47,10 @@ def __bool__(self) -> bool: class ChromiumBaseRoute(ConvertBaseRoute): + """ + https://gotenberg.dev/docs/routes#convert-with-chromium + """ + def header(self, header: Path) -> "ChromiumBaseRoute": self._add_file_map(header, "header.html") return self @@ -140,10 +144,16 @@ def index(self, index: Path) -> "_FileBasedRoute": class HtmlRoute(_FileBasedRoute): - pass + """ + https://gotenberg.dev/docs/routes#html-file-into-pdf-route + """ class UrlRoute(ChromiumBaseRoute): + """ + https://gotenberg.dev/docs/routes#url-into-pdf-route + """ + def url(self, url: str) -> "UrlRoute": self._form_data["url"] = url return self @@ -153,6 +163,10 @@ def get_files(self) -> ForceMultipartDict: class MarkdownRoute(_FileBasedRoute): + """ + https://gotenberg.dev/docs/routes#markdown-files-into-pdf-route + """ + def markdown_file(self, markdown_file: Path) -> "MarkdownRoute": self._add_file_map(markdown_file) return self diff --git a/src/gotenberg_client/convert/common.py b/src/gotenberg_client/convert/common.py index 3166477..bbc7599 100644 --- a/src/gotenberg_client/convert/common.py +++ b/src/gotenberg_client/convert/common.py @@ -2,10 +2,6 @@ # # SPDX-License-Identifier: MPL-2.0 import logging -from functools import lru_cache -from typing import Dict -from typing import Optional -from typing import Union from gotenberg_client.base import BaseRoute from gotenberg_client.options import PageOrientation @@ -14,22 +10,21 @@ class ConvertBaseRoute(BaseRoute): - def orient(self, orient: PageOrientation) -> "BaseRoute": + """ + All 3 convert routes provide control over orientation and page ranges + """ + + def orient(self, orient: PageOrientation) -> "ConvertBaseRoute": + """ + Sets the page orientation, either Landscape or portrait + """ self._form_data.update(orient.to_form()) return self - def page_ranges(self, ranges: str) -> "BaseRoute": + def page_ranges(self, ranges: str) -> "ConvertBaseRoute": + """ + Sets the page range string, allowing either some range or just a + few pages + """ self._form_data.update({"nativePageRanges": ranges}) return self - - -@lru_cache # type: ignore -def optional_to_form(value: Optional[Union[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()} diff --git a/src/gotenberg_client/convert/libre_office.py b/src/gotenberg_client/convert/libre_office.py index 02d29c3..a75f4a5 100644 --- a/src/gotenberg_client/convert/libre_office.py +++ b/src/gotenberg_client/convert/libre_office.py @@ -9,20 +9,37 @@ class LibreOfficeConvertRoute(ConvertBaseRoute): + """ + https://gotenberg.dev/docs/routes#convert-with-libreoffice + """ + def convert(self, file_path: Path) -> "LibreOfficeConvertRoute": + """ + Adds a single file to be converted to PDF. Can be called multiple times, + resulting in a ZIP of the PDFs, unless merged + """ self._add_file_map(file_path) return self def convert_files(self, file_paths: List[Path]) -> "LibreOfficeConvertRoute": + """ + Adds all provided files for conversion + """ for x in file_paths: self.convert(x) return self def merge(self) -> "LibreOfficeConvertRoute": + """ + Merge the resulting PDFs into one + """ self._form_data.update({"merge": "true"}) return self def no_merge(self) -> "LibreOfficeConvertRoute": + """ + Don't merge the resulting PDFs + """ self._form_data.update({"merge": "false"}) return self @@ -31,4 +48,7 @@ class LibreOfficeApi(BaseApi): _CONVERT_ENDPOINT = "/forms/libreoffice/convert" def to_pdf(self) -> LibreOfficeConvertRoute: + """ + Returns the LibreOffice conversion route + """ return LibreOfficeConvertRoute(self._client, self._CONVERT_ENDPOINT) diff --git a/src/gotenberg_client/convert/pdfa.py b/src/gotenberg_client/convert/pdfa.py index 49ac54c..dd5715e 100644 --- a/src/gotenberg_client/convert/pdfa.py +++ b/src/gotenberg_client/convert/pdfa.py @@ -9,7 +9,14 @@ class PdfAConvertRoute(ConvertBaseRoute): + """ + https://gotenberg.dev/docs/routes#convert-into-pdfa-route + """ + def convert(self, file_path: Path) -> "PdfAConvertRoute": + """ + Convert a single PDF into the provided PDF/A format + """ self._add_file_map(file_path) return self diff --git a/src/gotenberg_client/options.py b/src/gotenberg_client/options.py index 7825fdc..8f58d8e 100644 --- a/src/gotenberg_client/options.py +++ b/src/gotenberg_client/options.py @@ -7,7 +7,7 @@ from typing import Optional from typing import Union -from gotenberg_client.convert.common import optional_to_form +from gotenberg_client.utils import optional_to_form @enum.unique @@ -30,12 +30,12 @@ def to_form(self) -> Dict[str, str]: @enum.unique class PageOrientation(enum.Enum): Landscape = enum.auto() - Potrait = enum.auto() + Portrait = enum.auto() def to_form(self) -> Dict[str, str]: if self.value == PageOrientation.Landscape.value: return {"landscape": "true"} - elif self.value == PageOrientation.Potrait.value: + elif self.value == PageOrientation.Portrait.value: return {"landscape": "false"} else: # pragma: no cover raise NotImplementedError(self.value) diff --git a/src/gotenberg_client/utils.py b/src/gotenberg_client/utils.py new file mode 100644 index 0000000..eec25ff --- /dev/null +++ b/src/gotenberg_client/utils.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2023-present Trenton H +# +# SPDX-License-Identifier: MPL-2.0 +from importlib.util import find_spec +from pathlib import Path +from typing import Dict +from typing import Optional +from typing import Union + + +def optional_to_form(value: Optional[Union[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()} + + +def guess_mime_type_stdlib(url: Path) -> Optional[str]: + """ + Uses the standard library to guess a mimetype + """ + import mimetypes + + mime_type, _ = mimetypes.guess_type(url) + return mime_type + + +def guess_mime_type_magic(url: Path) -> Optional[str]: + """ + Uses libmagic to guess the mimetype + """ + import magic # type: ignore + + return magic.from_file(url, mime=True) # type: ignore + + +# Use the best option +guess_mime_type = guess_mime_type_magic if find_spec("magic") is not None else guess_mime_type_stdlib diff --git a/tests/test_convert_chromium_html.py b/tests/test_convert_chromium_html.py index dcdabd0..026f410 100644 --- a/tests/test_convert_chromium_html.py +++ b/tests/test_convert_chromium_html.py @@ -117,7 +117,7 @@ def test_convert_render_control(self, client: GotenbergClient, httpx_mock: HTTPX @pytest.mark.parametrize( ("orientation"), - [PageOrientation.Landscape, PageOrientation.Potrait], + [PageOrientation.Landscape, PageOrientation.Portrait], ) def test_convert_orientation( self, diff --git a/tests/test_convert_libre_office.py b/tests/test_convert_libre_office.py index b71d6cd..dfe62cd 100644 --- a/tests/test_convert_libre_office.py +++ b/tests/test_convert_libre_office.py @@ -6,9 +6,9 @@ import pytest from httpx import codes -from gotenberg_client.base import guess_mime_type_stdlib from gotenberg_client.client import GotenbergClient from gotenberg_client.options import PdfAFormat +from gotenberg_client.utils import guess_mime_type_stdlib from tests.conftest import SAMPLE_DIR from tests.conftest import SAVE_DIR from tests.conftest import SAVE_OUTPUTS @@ -86,7 +86,7 @@ def test_libre_office_convert_multiples_format_merged(self, client: GotenbergCli (SAVE_DIR / "test_libre_office_convert_multiples_format.zip").write_bytes(resp.content) def test_libre_office_convert_std_lib_mime(self, client: GotenbergClient): - with patch("gotenberg_client.base.guess_mime_type") as mocked_guess_mime_type: + with patch("gotenberg_client.utils.guess_mime_type") as mocked_guess_mime_type: mocked_guess_mime_type.side_effect = guess_mime_type_stdlib with client.libre_office.to_pdf() as route: resp = route.convert_files([SAMPLE_DIR / "sample.docx", SAMPLE_DIR / "sample.odt"]).no_merge().run()