summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames R. Barlow <james@purplerock.ca>2022-07-16 14:41:30 -0700
committerJames R. Barlow <james@purplerock.ca>2022-07-16 14:41:30 -0700
commitdea495efe7fd2dc72cbdb66486b1fc7dd3a53577 (patch)
tree2e709a3301a64251d5626174ab1d72e1c763e2c0
parent243762b4d3e38379133912070bcece5ca2bfe45a (diff)
Further improvements to Job class
-rw-r--r--src/pikepdf/_qpdf.pyi31
-rw-r--r--src/qpdf/job.cpp130
-rw-r--r--src/qpdf/object.cpp1
-rw-r--r--tests/test_job.py36
4 files changed, 133 insertions, 65 deletions
diff --git a/src/pikepdf/_qpdf.pyi b/src/pikepdf/_qpdf.pyi
index 8d24893..ad5176d 100644
--- a/src/pikepdf/_qpdf.pyi
+++ b/src/pikepdf/_qpdf.pyi
@@ -19,6 +19,7 @@ from typing import (
Any,
BinaryIO,
Callable,
+ ClassVar,
Collection,
Dict,
Iterable,
@@ -28,6 +29,7 @@ from typing import (
Mapping,
MutableMapping,
Optional,
+ Sequence,
Set,
Tuple,
TypeVar,
@@ -707,7 +709,34 @@ class ContentStreamInlineImage:
@property
def iimage(self) -> PdfInlineImage: ...
-class Job: ...
+class Job:
+ json_out_schema_v1: ClassVar[str]
+ job_json_schema_v1: ClassVar[str]
+ EXIT_ERROR: ClassVar[int] = 2
+ EXIT_WARNING: ClassVar[int] = 3
+ EXIT_IS_NOT_ENCRYPTED: ClassVar[int] = 2
+ EXIT_CORRECT_PASSWORD: ClassVar[int] = 3
+
+ @overload
+ def __init__(self, json: str) -> None: ...
+ @overload
+ def __init__(self, json_dict: Mapping) -> None: ...
+ @overload
+ def __init__(
+ self, args: Sequence[Union[str, bytes]], *, progname: str = "pikepdf"
+ ) -> None: ...
+ def check_configuration(self) -> None: ...
+ @property
+ def creates_output(self) -> bool: ...
+ @property
+ def message_prefix(self) -> str: ...
+ def run(self) -> None: ...
+ @property
+ def has_warnings(self) -> bool: ...
+ @property
+ def exit_code(self) -> int: ...
+ @property
+ def encryption_status(self) -> Dict[str, bool]: ...
def _Null() -> Any: ...
def _encode(handle: Any) -> Object: ...
diff --git a/src/qpdf/job.cpp b/src/qpdf/job.cpp
index fb1b6ea..78794d3 100644
--- a/src/qpdf/job.cpp
+++ b/src/qpdf/job.cpp
@@ -15,66 +15,49 @@
#include "pikepdf.h"
-using namespace py::literals;
+void set_job_defaults(QPDFJob &job) { job.setMessagePrefix("pikepdf"); }
-struct pyprint_redirect : private std::streambuf, public std::ostream {
- pyprint_redirect(py::object output) : std::ostream(this), output(output) {}
-
- virtual ~pyprint_redirect()
- {
- py::gil_scoped_acquire{};
- // output = py::none();
- }
-
-private:
- int overflow(int c) override
- {
- py::gil_scoped_acquire{};
- py::print(char(c), "file"_a = this->output, "end"_a = "");
- return 0;
- }
- py::object output;
-};
-
-std::unique_ptr<pyprint_redirect> py_stdout;
-std::unique_ptr<pyprint_redirect> py_stderr;
-
-void set_job_defaults(QPDFJob &job)
+QPDFJob job_from_json_str(const std::string &json)
{
- if (!py_stdout) {
- py::gil_scoped_acquire{};
- py_stdout = std::make_unique<pyprint_redirect>(
- py::module_::import("sys").attr("stdout"));
- }
- if (!py_stderr) {
- py::gil_scoped_acquire{};
- py_stderr = std::make_unique<pyprint_redirect>(
- py::module_::import("sys").attr("stderr"));
- }
-
- job.setMessagePrefix("pikepdf");
- job.setOutputStreams(py_stdout.get(), py_stderr.get());
+ QPDFJob job;
+ bool partial = false;
+ job.initializeFromJson(json, partial);
+ set_job_defaults(job);
+ return job;
}
void init_job(py::module_ &m)
{
- py::class_<QPDFJob::Config, std::shared_ptr<QPDFJob::Config>> jobconfig(
- m, "JobConfig");
-
py::class_<QPDFJob>(m, "Job")
- .def_property_readonly_static("json_out_schema_v1",
- [](const py::object &) { return QPDFJob::json_out_schema_v1(); })
- .def_property_readonly_static("job_json_schema_v1",
- [](const py::object &) { return QPDFJob::job_json_schema_v1(); })
- .def(py::init([](const std::string &json, bool partial) {
- QPDFJob job;
- job.initializeFromJson(json, partial);
- set_job_defaults(job);
- return job;
- }),
+ .def_property_readonly_static(
+ "json_out_schema_v1",
+ [](const py::object &) { return QPDFJob::json_out_schema_v1(); },
+ "For reference, the QPDF JSON output schema is built-in.")
+ .def_property_readonly_static(
+ "job_json_schema_v1",
+ [](const py::object &) { return QPDFJob::job_json_schema_v1(); },
+ "For reference, the QPDF job command line schema is built-in.")
+ .def_readonly_static("EXIT_ERROR",
+ &QPDFJob::EXIT_ERROR,
+ "Exit code for a job that had an error.")
+ .def_readonly_static("EXIT_WARNING",
+ &QPDFJob::EXIT_WARNING,
+ "Exit code for a job that had a warrning.")
+ .def_readonly_static("EXIT_IS_NOT_ENCRYPTED",
+ &QPDFJob::EXIT_IS_NOT_ENCRYPTED,
+ "Exit code for a job that provide a password when the input was not "
+ "encrypted.")
+ .def_readonly_static("EXIT_CORRECT_PASSWORD", &QPDFJob::EXIT_CORRECT_PASSWORD)
+ .def(py::init(&job_from_json_str),
py::arg("json"),
- py::kw_only(),
- py::arg("partial") = true)
+ "Create a Job from a string containing QPDF job JSON.")
+ .def(py::init([](py::dict &json_dict) {
+ auto json_dumps = py::module_::import("json").attr("dumps");
+ py::str json_str = json_dumps(json_dict);
+ return job_from_json_str(std::string(json_str));
+ }),
+ py::arg("json_dict"),
+ "Create a Job from a dict in QPDF job JSON schema.")
.def(py::init(
[](const std::vector<std::string> &args, std::string const &progname) {
QPDFJob job;
@@ -92,20 +75,43 @@ void init_job(py::module_ &m)
}),
py::arg("args"),
py::kw_only(),
- py::arg("progname") = "pikepdf")
- .def_property_readonly("config", &QPDFJob::config)
- .def("check_configuration", &QPDFJob::checkConfiguration)
- .def_property_readonly("creates_output", &QPDFJob::createsOutput)
+ py::arg("progname") = "pikepdf",
+ "Create a Job from command line arguments to the qpdf program.")
+ .def("check_configuration",
+ &QPDFJob::checkConfiguration,
+ "Checks if the configuration is valid; raises an exception if not.")
+ .def_property_readonly("creates_output",
+ &QPDFJob::createsOutput,
+ "Returns True if the Job will create some sort of output file.")
.def_property(
"message_prefix",
- []() {
+ [](QPDFJob &job) {
//&QPDFJob::getMessagePrefix
throw py::notimpl_error(
"QPDFJob::getMessagePrefix not available in qpdf 10.6.3");
},
- &QPDFJob::setMessagePrefix)
- .def("run", &QPDFJob::run)
- .def_property_readonly("has_warnings", &QPDFJob::hasWarnings)
- .def_property_readonly("exit_code", &QPDFJob::getExitCode)
- .def_property_readonly("encryption_status", &QPDFJob::getEncryptionStatus);
+ &QPDFJob::setMessagePrefix,
+ "Allows manipulation of the prefix in front of all output messages.")
+ .def("run", &QPDFJob::run, "Executes the job.")
+ .def_property_readonly("has_warnings",
+ &QPDFJob::hasWarnings,
+ "After run(), returns True if there were warnings.")
+ .def_property_readonly("exit_code",
+ &QPDFJob::getExitCode,
+ R"~~~(
+ After run(), returns an integer exit code.
+
+ Some exit codes have integer value. Their applicably is determined by
+ context of the job being run.
+ )~~~")
+ .def_property_readonly(
+ "encryption_status",
+ [](QPDFJob &job) {
+ uint bits = job.getEncryptionStatus();
+ py::dict result;
+ result["encrypted"] = bool(bits & qpdf_es_encrypted);
+ result["password_incorrect"] = bool(bits & qpdf_es_password_incorrect);
+ return result;
+ },
+ "Returns a Python dictionary describing the encryption status.");
} \ No newline at end of file
diff --git a/src/qpdf/object.cpp b/src/qpdf/object.cpp
index 965ce1d..9d9ba2e 100644
--- a/src/qpdf/object.cpp
+++ b/src/qpdf/object.cpp
@@ -803,7 +803,6 @@ void init_object(py::module_ &m)
since it does not take too long for modern CPUs to reconstruct an
entire PDF. pikepdf will consolidate all incremental updates
when saving.
-
)~~~")
.def_static(
"parse",
diff --git a/tests/test_job.py b/tests/test_job.py
index 33dc4ce..deb7146 100644
--- a/tests/test_job.py
+++ b/tests/test_job.py
@@ -1,13 +1,47 @@
+import json
+
+import pytest
+
from pikepdf import Job
def test_job_from_argv(resources, outpdf):
job = Job(['--check', str(resources / 'outlines.pdf'), str(outpdf)])
job.check_configuration()
+ job.message_prefix = 'foo'
+ with pytest.raises(NotImplementedError):
+ _ = job.message_prefix
assert job.creates_output
assert not job.has_warnings
assert job.exit_code == 0
- assert job.encryption_status == 0
+
+ assert not job.encryption_status["encrypted"]
+ assert not job.encryption_status["password_incorrect"]
+
+
+def test_job_from_json(resources, outpdf):
+ job_json = {}
+ job_json['inputFile'] = str(resources / 'outlines.pdf')
+ job_json['outputFile'] = str(outpdf)
+ job = Job(json.dumps(job_json))
+ job.check_configuration()
+ job.run()
+ assert job.exit_code == 0
+
+ job_json = {}
+ job_json['inputFile'] = str(resources / 'outlines.pdf')
+ job_json['outputFile'] = str(outpdf)
+ job = Job(job_json)
+ job.check_configuration()
+ job.run()
+ assert job.exit_code == 0
+
+
+def test_job_from_invalid_json():
+ job_json = {}
+ job_json['invalidJsonSetting'] = '123'
+ with pytest.raises(RuntimeError):
+ _job = Job(job_json)
def test_schemas():