Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
stumpylog committed Oct 17, 2023
2 parents 2485b73 + 19449b4 commit def7db1
Show file tree
Hide file tree
Showing 19 changed files with 194 additions and 69 deletions.
6 changes: 3 additions & 3 deletions .docker/docker-compose.ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

version: "3"
services:
gotenberg:
gotenberg-client-test-server:
image: docker.io/gotenberg/gotenberg:7.9.2
hostname: gotenberg
container_name: gotenberg
hostname: gotenberg-client-test-server
container_name: gotenberg-client-test-server
network_mode: host
restart: unless-stopped
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ jobs:
docker compose --file ${GITHUB_WORKSPACE}/.docker/docker-compose.ci-test.yml up --detach
echo "Wait for container to be started"
sleep 5
-
name: Install poppler-utils
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends poppler-utils
-
name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
Expand All @@ -72,7 +77,9 @@ jobs:
cache: 'pip'
-
name: Install Hatch
run: pip install --upgrade hatch
run: |
python3 -m pip install --upgrade pip
pip install --upgrade hatch
-
name: Run tests
run: hatch run cov
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ repos:
exclude: "(^Pipfile\\.lock$)"
# Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.0.292'
rev: 'v0.1.0'
hooks:
- id: ruff
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.10.0
hooks:
- id: black
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.0] - 2023-10-17

### Added

- Support for the output filename and request tracing for all routes

### Removed

- References to compression and Brotli. Gotenberg doesn't seem to ever compress response data

### Fixed

- An issue with the sorting of merging PDFs. Expanded testing to cover the merged ordering

### Changed

- Multiple merge calls on the same route will maintain the ordering of all files, rather than just per merge call

## [0.2.0] - 2023-10-16

### Added
Expand Down
29 changes: 22 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = 'A Python client for interfacing with the Gotenberg API'
readme = "README.md"
requires-python = ">=3.8"
license = "MPL-2.0"
keywords = []
keywords = ["api", "pdf", "html", "client"]
authors = [
{ name = "Trenton H", email = "[email protected]" },
]
Expand All @@ -18,7 +18,7 @@ classifiers = [
"Operating System :: OS Independent",
"Intended Audience :: Developers",
"Environment :: Web Environment",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand All @@ -39,7 +39,6 @@ Source = "https://github.com/stumpylog/gotenberg-client/"
Changelog = "https://github.com/stumpylog/gotenberg-client/blob/main/CHANGELOG.md"

[project.optional-dependencies]
compression = ["httpx[http2,brotli] ~= 0.24"]
magic = ["python-magic"]

[tool.hatch.version]
Expand All @@ -60,7 +59,6 @@ dependencies = [
"pytest-httpx ~= 0.22; python_version < '3.9'",
"pikepdf",
"python-magic",
"brotli",
]

[tool.hatch.envs.default.scripts]
Expand Down Expand Up @@ -90,7 +88,7 @@ python = ["3.8", "3.9", "3.10", "3.11", "3.12"]

[tool.hatch.envs.pre-commit]
dependencies = [
"pre-commit>=3.4.0",
"pre-commit>=3.5.0",
]

[tool.hatch.envs.pre-commit.scripts]
Expand All @@ -101,8 +99,8 @@ update = ["pre-commit autoupdate"]
detached = true
dependencies = [
"black>=23.9.1",
"mypy>=1.0.0",
"ruff>=0.0.292",
"mypy>=1.6.0",
"ruff>=0.1.0",
"httpx",
]

Expand All @@ -112,6 +110,7 @@ typing = [
"mypy --install-types --non-interactive {args:src/gotenberg_client}"
]
style = [
"ruff --version",
"ruff {args:.}",
"black --check --diff {args:.}",
]
Expand All @@ -135,25 +134,33 @@ fix = true
output-format = "grouped"
target-version = "py38"
line-length = 120
# https://docs.astral.sh/ruff/rules/
extend-select = [
"A",
"ARG",
"B",
"C",
"C4",
"COM",
"DTZ",
"E",
"EM",
"ERA",
"EXE",
"F",
"FBT",
"FLY",
"I",
"ICN",
"INP",
"INT",
"ISC",
"N",
"PERF",
"PIE",
"PGH",
"PTH",
"PL",
"PLC",
"PLE",
"PLR",
Expand All @@ -164,8 +171,14 @@ extend-select = [
"RUF",
"S",
"SIM",
"SLF",
"T",
"T10",
"T20",
"TCH",
"TD",
"TID",
"TRY",
"UP",
"W",
"YTT",
Expand All @@ -179,6 +192,8 @@ ignore = [
"S105", "S106", "S107",
# Ignore complexity
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
# Ignore no author and missing issue link in TODO tags
"TD002", "TD003"
]

[tool.ruff.isort]
Expand Down
2 changes: 1 addition & 1 deletion src/gotenberg_client/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-present Trenton H <[email protected]>
#
# SPDX-License-Identifier: MPL-2.0
__version__ = "0.2.0"
__version__ = "0.3.0"
20 changes: 14 additions & 6 deletions src/gotenberg_client/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, client: Client, api_route: str) -> None:
self._stack = ExitStack()
self._form_data: Dict[str, str] = {}
self._file_map: Dict[str, Path] = {}
self._headers: Dict[str, str] = {}

def __enter__(self) -> Self:
self.reset()
Expand Down Expand Up @@ -66,11 +67,11 @@ def run(self) -> Response:
Response.
TODO: It would be nice to return a simpler response to the user
"""
resp = self._client.post(url=self._route, data=self._form_data, files=self.get_files())
resp = self._client.post(url=self._route, headers=self._headers, data=self._form_data, files=self._get_files())
resp.raise_for_status()
return resp

def get_files(self) -> RequestFiles:
def _get_files(self) -> RequestFiles:
"""
Deals with opening all provided files for multi-part uploads, including
pushing their new contexts onto the stack to ensure resources like file
Expand All @@ -79,23 +80,22 @@ def get_files(self) -> RequestFiles:
files = {}
for filename in self._file_map:
file_path = self._file_map[filename]
# Gotenberg requires these to have the specific name
filepath_name = filename if filename in {"index.html", "header.html", "footer.html"} else file_path.name

# Helpful but not necessary to provide the mime type when possible
mime_type = guess_mime_type(file_path)
if mime_type is not None:
files.update(
{filepath_name: (filepath_name, self._stack.enter_context(file_path.open("rb")), mime_type)},
{filename: (filename, self._stack.enter_context(file_path.open("rb")), mime_type)},
)
else: # pragma: no cover
files.update({filepath_name: (filepath_name, self._stack.enter_context(file_path.open("rb")))}) # type: ignore
files.update({filename: (filename, self._stack.enter_context(file_path.open("rb")))}) # type: ignore [dict-item]
return files

def _add_file_map(self, filepath: Path, name: Optional[str] = None) -> None:
"""
Small helper to handle bookkeeping of files for later opening. The name is
optional to support those things which are required to have a certain name
generally for ordering or just to be found at all
"""
if name is None:
name = filepath.name
Expand All @@ -111,6 +111,14 @@ def pdf_format(self, pdf_format: PdfAFormat) -> "BaseRoute":
self._form_data.update(pdf_format.to_form())
return self

def trace(self, trace_id: str) -> "BaseRoute":
self._headers["Gotenberg-Trace"] = trace_id
return self

def output_name(self, filename: str) -> "BaseRoute":
self._headers["Gotenberg-Output-Filename"] = filename
return self


class BaseApi:
"""
Expand Down
8 changes: 0 additions & 8 deletions src/gotenberg_client/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#
# SPDX-License-Identifier: MPL-2.0
import logging
from importlib.util import find_spec
from types import TracebackType
from typing import Dict
from typing import Optional
Expand Down Expand Up @@ -37,19 +36,12 @@ def __init__(
logging.getLogger("httpx").setLevel(log_level)
logging.getLogger("httpcore").setLevel(log_level)

# TODO Brotli?
if find_spec("brotli") is not None:
self._client.headers.update({"Accept-Encoding": "gzip,deflate,br"})
else:
self._client.headers.update({"Accept-Encoding": "gzip,deflate"})

# Add the resources
self.chromium = ChromiumApi(self._client)
self.libre_office = LibreOfficeApi(self._client)
self.pdf_a = PdfAApi(self._client)
self.merge = MergeApi(self._client)
self.health = HealthCheckApi(self._client)
# TODO

def add_headers(self, header: Dict[str, str]) -> None: # pragma: no cover
"""
Expand Down
2 changes: 1 addition & 1 deletion src/gotenberg_client/_convert/chromium.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def url(self, url: str) -> Self:
self._form_data["url"] = url
return self

def get_files(self) -> ForceMultipartDict:
def _get_files(self) -> ForceMultipartDict:
return FORCE_MULTIPART


Expand Down
6 changes: 3 additions & 3 deletions src/gotenberg_client/_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ def _extract_status(self, module: ModuleOptions) -> ModuleStatus:
status = StatusOptions(self.data["details"][module.value]["status"])

# mypy is quite wrong here, it's clearly marked as a datetime.datetime, not Any
timestamp = self._extract_datetime(self.data["details"][module.value]["timestamp"]) # type: ignore
# Also wrong here
return ModuleStatus(status, timestamp) # type: ignore
# but ...
timestamp: datetime.datetime = self._extract_datetime(self.data["details"][module.value]["timestamp"])
return ModuleStatus(status, timestamp)

@staticmethod
@no_type_check
Expand Down
11 changes: 9 additions & 2 deletions src/gotenberg_client/_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from pathlib import Path
from typing import List

from httpx import Client

from gotenberg_client._base import BaseApi
from gotenberg_client._base import BaseRoute

Expand All @@ -13,15 +15,20 @@ class MergeRoute(BaseRoute):
Handles the merging of a given set of files
"""

def __init__(self, client: Client, api_route: str) -> None:
super().__init__(client, api_route)
self._next = 1

def merge(self, files: List[Path]) -> "MergeRoute":
"""
Adds the given files into the file mapping. This method will maintain the
ordering of the list. Calling this method multiple times may not merge
in the expected ordering
"""
for idx, filepath in enumerate(files):
for filepath in files:
# Include index to enforce ordering
self._add_file_map(filepath, f"{idx}_{filepath.name}")
self._add_file_map(filepath, f"{self._next}_{filepath.name}")
self._next += 1
return self


Expand Down
4 changes: 2 additions & 2 deletions src/gotenberg_client/_types_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import sys

if sys.version_info >= (3, 11):
if sys.version_info >= (3, 11): # pragma: no cover
from typing import Self
else:
else: # pragma: no cover
from typing_extensions import Self # noqa: F401
6 changes: 3 additions & 3 deletions src/gotenberg_client/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def optional_to_form(value: Optional[Union[bool, int, float, str]], name: str) -
return {name: str(value).lower()}


def guess_mime_type_stdlib(url: Path) -> Optional[str]:
def guess_mime_type_stdlib(url: Path) -> Optional[str]: # pragma: no cover
"""
Uses the standard library to guess a mimetype
"""
Expand All @@ -33,9 +33,9 @@ def guess_mime_type_magic(url: Path) -> Optional[str]:
"""
Uses libmagic to guess the mimetype
"""
import magic # type: ignore
import magic # type: ignore [import-not-found]

return magic.from_file(url, mime=True) # type: ignore
return magic.from_file(url, mime=True) # type: ignore [misc]


# Use the best option
Expand Down
Binary file added tests/samples/a_merge_second.pdf
Binary file not shown.
Empty file modified tests/samples/sample1.pdf
100755 → 100644
Empty file.
Binary file added tests/samples/z_first_merge.pdf
Binary file not shown.
Loading

0 comments on commit def7db1

Please sign in to comment.