diff options
author | James R. Barlow <james@purplerock.ca> | 2022-07-16 14:41:30 -0700 |
---|---|---|
committer | James R. Barlow <james@purplerock.ca> | 2022-07-16 14:41:30 -0700 |
commit | dea495efe7fd2dc72cbdb66486b1fc7dd3a53577 (patch) | |
tree | 2e709a3301a64251d5626174ab1d72e1c763e2c0 | |
parent | 243762b4d3e38379133912070bcece5ca2bfe45a (diff) |
Further improvements to Job class
-rw-r--r-- | src/pikepdf/_qpdf.pyi | 31 | ||||
-rw-r--r-- | src/qpdf/job.cpp | 130 | ||||
-rw-r--r-- | src/qpdf/object.cpp | 1 | ||||
-rw-r--r-- | tests/test_job.py | 36 |
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(): |