Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pikepdf for openSUSE:Factory 
checked in at 2023-12-28 23:02:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pikepdf (Old)
 and      /work/SRC/openSUSE:Factory/.python-pikepdf.new.28375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pikepdf"

Thu Dec 28 23:02:03 2023 rev:21 rq:1135318 version:8.10.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pikepdf/python-pikepdf.changes    
2023-12-14 22:03:30.796873948 +0100
+++ /work/SRC/openSUSE:Factory/.python-pikepdf.new.28375/python-pikepdf.changes 
2023-12-28 23:03:47.335541986 +0100
@@ -1,0 +2,11 @@
+Wed Dec 27 14:00:51 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 8.10.1:
+  * Rebuilt with QPDF 11.6.4.
+  * Replaced use of a custom C++ logger with sharing QPDF's.
+    It is still relayed to the Python logger.
+  * Added a simpler API for adding attachments from bytes data.
+  * Deprecated use of Object.parse(str) in favor of
+    Object.parse(bytes).
+
+-------------------------------------------------------------------

Old:
----
  pikepdf-8.9.0.tar.gz

New:
----
  pikepdf-8.10.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pikepdf.spec ++++++
--- /var/tmp/diff_new_pack.F5kIK0/_old  2023-12-28 23:03:48.007566547 +0100
+++ /var/tmp/diff_new_pack.F5kIK0/_new  2023-12-28 23:03:48.007566547 +0100
@@ -19,7 +19,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-pikepdf
-Version:        8.9.0
+Version:        8.10.1
 Release:        0
 Summary:        Read and write PDFs with Python, powered by qpdf
 License:        MPL-2.0

++++++ pikepdf-8.9.0.tar.gz -> pikepdf-8.10.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/PKG-INFO new/pikepdf-8.10.1/PKG-INFO
--- old/pikepdf-8.9.0/PKG-INFO  2023-12-11 00:26:49.663727000 +0100
+++ new/pikepdf-8.10.1/PKG-INFO 2023-12-17 10:33:05.458814400 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pikepdf
-Version: 8.9.0
+Version: 8.10.1
 Summary: Read and write PDFs with Python, powered by qpdf
 Author-email: "James R. Barlow" <ja...@purplerock.ca>
 License: MPL-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/docs/conf.py 
new/pikepdf-8.10.1/docs/conf.py
--- old/pikepdf-8.9.0/docs/conf.py      2023-12-11 00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/docs/conf.py     2023-12-17 10:31:00.000000000 +0100
@@ -92,7 +92,7 @@
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 
-release = "8.9.0"
+release = "8.10.1"
 version = '.'.join(release.split('.')[:2])
 
 # The language for content autogenerated by Sphinx. Refer to documentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/docs/releasenotes/version8.rst 
new/pikepdf-8.10.1/docs/releasenotes/version8.rst
--- old/pikepdf-8.9.0/docs/releasenotes/version8.rst    2023-12-11 
00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/docs/releasenotes/version8.rst   2023-12-17 
10:31:00.000000000 +0100
@@ -1,3 +1,25 @@
+v8.10.1
+=======
+
+- Rebuilt with QPDF 11.6.4.
+- Replaced use of a custom C++ logger with sharing QPDF's. It is still relayed 
to
+  the Python logger.
+- Added a simpler API for adding attachments from bytes data.
+- Deprecated use of Object.parse(str) in favor of Object.parse(bytes).
+
+v8.10.0
+=======
+
+- Fixed a performance regression when appending thousands of pages from one 
PDF to
+  another.
+- Fixed some obscure issues with iterators over ``Pdf.pages`` that would have 
led
+  to incorrect or unintuitive behavior, like partial iteration not being 
accounted
+  for.
+- Using the ``Pdf.pages`` API to insert objects other ``pikepdf.Pdf`` is now
+  deprecated. Previously, we accepted ``pikepdf.Dictionary`` that had its 
``/Type``
+  set to ``/Page``. Now, one must wrap these dictionaries in 
``pikepdf.Page()``.
+- Added type hints that ``pikepdf.Object`` can be implicitly converted to float
+  and int.
 
 v8.9.0
 ======
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/pyproject.toml 
new/pikepdf-8.10.1/pyproject.toml
--- old/pikepdf-8.9.0/pyproject.toml    2023-12-11 00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/pyproject.toml   2023-12-17 10:31:00.000000000 +0100
@@ -7,7 +7,7 @@
 
 [project]
 name = "pikepdf"
-version = "8.9.0"
+version = "8.10.1"
 description = "Read and write PDFs with Python, powered by qpdf"
 readme = "README.md"
 requires-python = ">=3.8"
@@ -114,7 +114,7 @@
 
 [tool.cibuildwheel.environment]
 QPDF_MIN_VERSION = "11.5.0"
-QPDF_VERSION = "11.6.3"
+QPDF_VERSION = "11.6.4"
 QPDF_PATTERN = 
"https://github.com/qpdf/qpdf/releases/download/vVERSION/qpdf-VERSION.tar.gz";
 
 [tool.cibuildwheel.linux]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/embeddedfiles.cpp 
new/pikepdf-8.10.1/src/core/embeddedfiles.cpp
--- old/pikepdf-8.9.0/src/core/embeddedfiles.cpp        2023-12-11 
00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/src/core/embeddedfiles.cpp       2023-12-17 
10:31:00.000000000 +0100
@@ -15,6 +15,33 @@
 #include "pikepdf.h"
 #include "pipeline.h"
 
+QPDFFileSpecObjectHelper create_filespec(QPDF &q,
+    py::bytes data,
+    std::string description,
+    std::string filename,
+    std::string mime_type,
+    std::string creation_date,
+    std::string mod_date,
+    QPDFObjectHandle relationship)
+{
+    auto efstream = QPDFEFStreamObjectHelper::createEFStream(q, 
std::string(data));
+    auto filespec = QPDFFileSpecObjectHelper::createFileSpec(q, filename, 
efstream);
+
+    if (!description.empty())
+        filespec.setDescription(description);
+    if (!mime_type.empty())
+        efstream.setSubtype(mime_type);
+    if (!creation_date.empty())
+        efstream.setCreationDate(creation_date);
+    if (!mod_date.empty())
+        efstream.setModDate(mod_date);
+
+    if (relationship.isName()) {
+        filespec.getObjectHandle().replaceKey("/AFRelationship", relationship);
+    }
+    return filespec;
+}
+
 void init_embeddedfiles(py::module_ &m)
 {
     py::class_<QPDFFileSpecObjectHelper,
@@ -28,24 +55,14 @@
                           std::string creation_date,
                           std::string mod_date,
                           QPDFObjectHandle &relationship) {
-            auto efstream =
-                QPDFEFStreamObjectHelper::createEFStream(q, std::string(data));
-            auto filespec =
-                QPDFFileSpecObjectHelper::createFileSpec(q, filename, 
efstream);
-
-            if (!description.empty())
-                filespec.setDescription(description);
-            if (!mime_type.empty())
-                efstream.setSubtype(mime_type);
-            if (!creation_date.empty())
-                efstream.setCreationDate(creation_date);
-            if (!mod_date.empty())
-                efstream.setModDate(mod_date);
-
-            if (relationship.isName()) {
-                filespec.getObjectHandle().replaceKey("/AFRelationship", 
relationship);
-            }
-            return filespec;
+            return create_filespec(q,
+                data,
+                description,
+                filename,
+                mime_type,
+                creation_date,
+                mod_date,
+                relationship);
         }),
             py::keep_alive<0, 1>(), // LCOV_EXCL_LINE
             py::arg("q"),
@@ -119,6 +136,18 @@
     py::class_<QPDFEmbeddedFileDocumentHelper>(m, "Attachments")
         .def_property_readonly(
             "_has_embedded_files", 
&QPDFEmbeddedFileDocumentHelper::hasEmbeddedFiles)
+        .def("_attach_data",
+            [](QPDFEmbeddedFileDocumentHelper &efdh, py::str key, py::bytes 
data) {
+                auto ef = create_filespec(efdh.getQPDF(),
+                    std::string(data),
+                    std::string(""),
+                    std::string(key),
+                    std::string(""),
+                    std::string(""),
+                    std::string(""),
+                    QPDFObjectHandle::newName("/Unspecified"));
+                efdh.replaceEmbeddedFile(key, ef);
+            })
         .def("_get_all_filespecs",
             &QPDFEmbeddedFileDocumentHelper::getEmbeddedFiles,
             py::return_value_policy::reference_internal)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/logger.cpp 
new/pikepdf-8.10.1/src/core/logger.cpp
--- old/pikepdf-8.9.0/src/core/logger.cpp       2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/core/logger.cpp      2023-12-17 10:31:00.000000000 
+0100
@@ -4,8 +4,6 @@
 #include "pikepdf.h"
 #include <qpdf/QPDFLogger.hh>
 
-static auto pikepdf_logger = QPDFLogger::create();
-
 // Pipeline to relay QPDF log messages to Python logging module
 // This is a sink - cannot pass to other pipeline objects
 class Pl_PythonLogger : public Pipeline {
@@ -47,7 +45,7 @@
 std::shared_ptr<QPDFLogger> get_pikepdf_logger()
 {
     // All QPDFs can use the same logger
-    return pikepdf_logger;
+    return QPDFLogger::defaultLogger();
 }
 
 void init_logger(py::module_ &m)
@@ -61,6 +59,7 @@
     std::shared_ptr<Pipeline> pl_log_error = std::make_shared<Pl_PythonLogger>(
         "QPDF to Python logging pipeline", py_logger, "error");
 
+    auto pikepdf_logger = get_pikepdf_logger();
     pikepdf_logger->setInfo(pl_log_info);
     pikepdf_logger->setWarn(pl_log_warn);
     pikepdf_logger->setError(pl_log_error);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/object.cpp 
new/pikepdf-8.10.1/src/core/object.cpp
--- old/pikepdf-8.9.0/src/core/object.cpp       2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/core/object.cpp      2023-12-17 10:31:00.000000000 
+0100
@@ -758,7 +758,17 @@
         .def_property_readonly("objgen", &object_get_objgen)
         .def_static(
             "parse",
-            [](std::string const &stream, std::string const &description) {
+            [](py::bytes stream, py::str description) {
+                return QPDFObjectHandle::parse(
+                    std::string(stream), std::string(description));
+            },
+            py::arg("stream"),
+            py::arg("description") = "")
+        .def_static(
+            "parse",
+            [](py::str stream, std::string const &description) {
+                python_warning("pikepdf.Object.parse(str) is deprecated; use 
bytes.",
+                    PyExc_DeprecationWarning);
                 return QPDFObjectHandle::parse(stream, description);
             },
             py::arg("stream"),
@@ -863,7 +873,7 @@
         .def(
             "__eq__",
             [](QPDFObjectHelper &self, QPDFObjectHelper &other) {
-                // Pages that are copies
+                // Object helpers are equal if their object handles are equal
                 return objecthandle_equal(
                     self.getObjectHandle(), other.getObjectHandle());
             },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/object_convert.cpp 
new/pikepdf-8.10.1/src/core/object_convert.cpp
--- old/pikepdf-8.9.0/src/core/object_convert.cpp       2023-12-11 
00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/src/core/object_convert.cpp      2023-12-17 
10:31:00.000000000 +0100
@@ -43,13 +43,9 @@
 {
     StackGuard sg(" array_builder");
     std::vector<QPDFObjectHandle> result;
-    int narg = 0;
 
     for (const auto &item : iter) {
-        narg++;
-
-        auto value = objecthandle_encode(item);
-        result.push_back(value);
+        result.emplace_back(objecthandle_encode(item));
     }
     return result;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/parsers.cpp 
new/pikepdf-8.10.1/src/core/parsers.cpp
--- old/pikepdf-8.9.0/src/core/parsers.cpp      2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/core/parsers.cpp     2023-12-17 10:31:00.000000000 
+0100
@@ -58,8 +58,6 @@
         }
         break;
     }
-    case qpdf_object_type_e::ot_stream:
-        throw py::type_error("Streams are not allowed in content stream 
instructions");
     default: {
         throw py::type_error("Only scalar types, arrays, and dictionaries are 
allowed "
                              "in content streams.");
@@ -271,13 +269,11 @@
 {
     py::class_<ContentStreamInstruction>(m, "ContentStreamInstruction")
         .def(py::init<const ContentStreamInstruction &>())
-        .def(py::init([](ObjectList operands, QPDFObjectHandle operator_) {
-            return ContentStreamInstruction(operands, operator_);
-        }))
+        .def(py::init<ObjectList, QPDFObjectHandle>())
         .def(py::init([](py::iterable operands, QPDFObjectHandle operator_) {
             ObjectList newlist;
             for (auto &item : operands) {
-                newlist.push_back(objecthandle_encode(item));
+                newlist.emplace_back(objecthandle_encode(item));
             }
             return ContentStreamInstruction(newlist, operator_);
         }))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/pikepdf.cpp 
new/pikepdf-8.10.1/src/core/pikepdf.cpp
--- old/pikepdf-8.9.0/src/core/pikepdf.cpp      2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/core/pikepdf.cpp     2023-12-17 10:31:00.000000000 
+0100
@@ -136,6 +136,17 @@
     init_rectangle(m);
     init_tokenfilter(m);
 
+    auto m_test = m.def_submodule("_test", "pikepdf._core test functions");
+    m_test
+        .def(
+            "fopen_nonexistent_file",
+            []() -> void { (void)QUtil::safe_fopen("does_not_exist__42", 
"rb"); },
+            "Used to test that C++ system error -> Python exception 
propagation works.")
+        .def(
+            "log_info",
+            [](std::string s) { return get_pikepdf_logger()->info(s); },
+            "Used to test routing of QPDF's logger to Python logging.");
+
     // -- Module level functions --
     m.def("utf8_to_pdf_doc",
          [](py::str utf8, char unknown) {
@@ -148,17 +159,9 @@
                 return py::str(QUtil::pdf_doc_to_utf8(pdfdoc));
             })
         .def(
-            "_test_file_not_found",
-            []() -> void { (void)QUtil::safe_fopen("does_not_exist__42", 
"rb"); },
-            "Used to test that C++ system error -> Python exception 
propagation works.")
-        .def(
             "_translate_qpdf_logic_error",
             [](std::string s) { return translate_qpdf_logic_error(s).first; },
             "Used to test interpretation of QPDF errors.")
-        .def(
-            "_log_info",
-            [](std::string s) { return get_pikepdf_logger()->info(s); },
-            "Used to test routing of QPDF's logger to Python logging.")
         .def("set_decimal_precision",
             [](uint prec) {
                 DECIMAL_PRECISION = prec;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/qpdf_pagelist.cpp 
new/pikepdf-8.10.1/src/core/qpdf_pagelist.cpp
--- old/pikepdf-8.9.0/src/core/qpdf_pagelist.cpp        2023-12-11 
00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/src/core/qpdf_pagelist.cpp       2023-12-17 
10:31:00.000000000 +0100
@@ -44,6 +44,7 @@
     if (!slice.compute(this->count(), &start, &stop, &step, &slicelength))
         throw py::error_already_set(); // LCOV_EXCL_LINE
     std::vector<QPDFPageObjectHelper> result;
+    result.reserve(slicelength);
     for (py::size_t i = 0; i < slicelength; ++i) {
         auto oh = this->get_page(start);
         result.push_back(oh);
@@ -134,8 +135,15 @@
 
 py::size_t PageList::count() { return this->doc.getAllPages().size(); }
 
-void PageList::try_insert_qpdfobject_as_page(py::size_t index, py::handle obj)
+QPDFPageObjectHelper PageList::page_from_object(py::handle obj)
 {
+    try {
+        auto page = obj.cast<QPDFPageObjectHelper>();
+        return page;
+    } catch (py::cast_error &) {
+        // Perhaps obj is a dictionary with Type=Name.Page
+    }
+
     QPDFObjectHandle oh, indirect_oh;
     try {
         oh = obj.cast<QPDFObjectHandle>();
@@ -144,6 +152,10 @@
                              "nor pikepdf.Dictionary with Type=Name.Page");
     }
 
+    python_warning("Implicit conversion of pikepdf.Dictionary to pikepdf.Page 
is "
+                   "deprecated. Use pikepdf.Page(dictionary) instead.",
+        PyExc_DeprecationWarning);
+
     bool copied = false;
     try {
         if (!oh.getOwningQPDF()) {
@@ -161,8 +173,7 @@
                                              "to insert this as a page: ") +
                                  objecthandle_repr(oh));
         }
-        auto page = QPDFPageObjectHelper(indirect_oh);
-        this->insert_page(index, page);
+        return QPDFPageObjectHelper(indirect_oh);
     } catch (std::runtime_error &) {
         // If we created a new temporary indirect object to hold the page, and
         // failed to insert, delete the object we created as best we can.
@@ -181,22 +192,33 @@
         this->insert_page(index, poh);
         return;
     } catch (py::cast_error &) {
-        this->try_insert_qpdfobject_as_page(index, obj);
+        auto page = this->page_from_object(obj);
+        this->insert_page(index, page);
         return;
     }
 }
 
 void PageList::insert_page(py::size_t index, QPDFPageObjectHelper page)
 {
-    auto doc = QPDFPageDocumentHelper(*this->qpdf);
     if (index != this->count()) {
         auto refpage = this->get_page(index);
-        doc.addPageAt(page, true, refpage);
+        this->doc.addPageAt(page, true, refpage);
     } else {
-        doc.addPage(page, false);
+        this->doc.addPage(page, false);
     }
 }
 
+void PageList::append_page(py::handle obj)
+{
+    auto page = this->page_from_object(obj);
+    this->doc.addPage(page, false);
+}
+
+void PageList::append_page(QPDFPageObjectHelper page)
+{
+    this->doc.addPage(page, false);
+}
+
 QPDFPageObjectHelper from_objgen(QPDF &q, QPDFObjGen og)
 {
     auto h = q.getObjectByObjGen(og);
@@ -205,8 +227,22 @@
     return QPDFPageObjectHelper(h);
 }
 
+QPDFPageObjectHelper PageListIterator::next()
+{
+    if (this->index >= this->pages.size()) {
+        throw py::stop_iteration();
+    }
+    auto page = this->pages.at(this->index);
+    this->index++;
+    return page;
+}
+
 void init_pagelist(py::module_ &m)
 {
+    py::class_<PageListIterator>(m, "_PageListIterator")
+        .def("__iter__", [](PageListIterator &it) { return it; })
+        .def("__next__", &PageListIterator::next);
+
     py::class_<PageList>(m, "PageList")
         .def(
             "__getitem__",
@@ -238,13 +274,12 @@
                 return pl.get_page(pnum - 1);
             },
             py::arg("pnum"))
-        .def("__iter__", [](PageList &pl) { return PageList(pl.qpdf, 0); })
-        .def("__next__",
+        .def(
+            "__iter__",
             [](PageList &pl) {
-                if (pl.iterpos < pl.count())
-                    return pl.get_page(pl.iterpos++);
-                throw py::stop_iteration();
-            })
+                return PageListIterator{pl, 0};
+            },
+            py::keep_alive<0, 1>())
         .def(
             "insert",
             [](PageList &pl, py::ssize_t index, py::object obj) {
@@ -256,31 +291,24 @@
         .def("reverse",
             [](PageList &pl) {
                 py::slice ordinary_indices(0, pl.count(), 1);
-                py::int_ step(-1);
-                py::slice reversed = py::reinterpret_steal<py::slice>(
-                    PySlice_New(Py_None, Py_None, step.ptr()));
+                py::slice reversed{{}, {}, -1};
                 py::list reversed_pages = pl.get_pages(reversed);
                 pl.set_pages_from_iterable(ordinary_indices, reversed_pages);
             })
         .def(
             "append",
-            [](PageList &pl, QPDFPageObjectHelper &page) {
-                pl.insert_page(pl.count(), page);
-            },
+            [](PageList &pl, QPDFPageObjectHelper &page) { 
pl.append_page(page); },
             py::arg("page"))
         .def(
             "append",
-            [](PageList &pl, py::handle page) { pl.insert_page(pl.count(), 
page); },
+            [](PageList &pl, py::handle page) { pl.append_page(page); },
             py::arg("page"))
         .def(
             "extend",
             [](PageList &pl, PageList &other) {
-                auto other_count = other.count();
-                for (decltype(other_count) i = 0; i < other_count; i++) {
-                    if (other_count != other.count())
-                        throw py::value_error(
-                            "source page list modified during iteration");
-                    pl.insert_page(pl.count(), other.get_page(i));
+                auto other_pages = other.doc.getAllPages();
+                for (auto &page : other_pages) {
+                    pl.append_page(page);
                 }
             },
             py::arg("other"))
@@ -289,21 +317,30 @@
             [](PageList &pl, py::iterable iterable) {
                 py::iterator it = iterable.attr("__iter__")();
                 while (it != py::iterator::sentinel()) {
-                    // assert_pyobject_is_page_obj(*it);
                     assert_pyobject_is_page_helper(*it);
-                    pl.insert_page(pl.count(), *it);
+                    pl.append_page(*it);
                     ++it;
                 }
             },
             py::arg("iterable"))
         .def("remove",
-            [](PageList &pl, py::kwargs kwargs) {
-                auto pnum = kwargs["p"].cast<py::ssize_t>();
+            [](PageList &pl, QPDFPageObjectHelper &page) {
+                try {
+                    pl.doc.removePage(page);
+                } catch (const QPDFExc &e) {
+                    throw py::value_error("Page is not referenced in the PDF");
+                }
+            })
+        .def(
+            "remove",
+            [](PageList &pl, py::ssize_t pnum) {
                 if (pnum <= 0) // Indexing past end is checked in .get_page
                     throw py::index_error(
                         "page access out of range in 1-based indexing");
                 pl.delete_page(pnum - 1);
-            })
+            },
+            py::kw_only(),
+            py::arg("p"))
         .def("index",
             [](PageList &pl, const QPDFObjectHandle &h) {
                 return page_index(*pl.qpdf, h);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/core/qpdf_pagelist.h 
new/pikepdf-8.10.1/src/core/qpdf_pagelist.h
--- old/pikepdf-8.9.0/src/core/qpdf_pagelist.h  2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/core/qpdf_pagelist.h 2023-12-17 10:31:00.000000000 
+0100
@@ -14,8 +14,7 @@
 
 class PageList { // LCOV_EXCL_LINE
 public:
-    PageList(std::shared_ptr<QPDF> q, py::size_t iterpos = 0)
-        : iterpos(iterpos), qpdf(q), doc(*qpdf){};
+    PageList(std::shared_ptr<QPDF> q) : qpdf(q), doc(*qpdf){};
 
     QPDFPageObjectHelper get_page(py::size_t index);
     py::list get_pages(py::slice slice);
@@ -26,13 +25,26 @@
     py::size_t count();
     void insert_page(py::size_t index, py::handle obj);
     void insert_page(py::size_t index, QPDFPageObjectHelper page);
+    void append_page(py::handle obj);
+    void append_page(QPDFPageObjectHelper page);
 
 public:
-    py::size_t iterpos;
     std::shared_ptr<QPDF> qpdf;
     QPDFPageDocumentHelper doc;
 
 private:
     std::vector<QPDFPageObjectHelper> get_page_objs_impl(py::slice slice);
-    void try_insert_qpdfobject_as_page(py::size_t index, py::handle obj);
+    QPDFPageObjectHelper page_from_object(py::handle obj);
 };
+
+class PageListIterator { // LCOV_EXCL_LINE
+public:
+    PageListIterator(PageList &pl, size_t index)
+        : pl(pl), index(index), pages(pl.doc.getAllPages()){};
+    QPDFPageObjectHelper next();
+
+private:
+    PageList &pl;
+    size_t index;
+    std::vector<QPDFPageObjectHelper> pages;
+};
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/pikepdf/_core.pyi 
new/pikepdf-8.10.1/src/pikepdf/_core.pyi
--- old/pikepdf-8.9.0/src/pikepdf/_core.pyi     2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/pikepdf/_core.pyi    2023-12-17 10:31:00.000000000 
+0100
@@ -467,9 +467,11 @@
     def __delitem__(self, name: str | Name | int) -> None: ...
     def __dir__(self) -> list: ...
     def __eq__(self, other: Any) -> bool: ...
+    def __float__(self) -> float: ...
     def __getattr__(self, name: str) -> Object: ...
     def __getitem__(self, name: str | Name | int) -> Object: ...
     def __hash__(self) -> int: ...
+    def __int__(self) -> int: ...
     def __iter__(self) -> Iterable[Object]: ...
     def __len__(self) -> int: ...
     def __setattr__(self, name: str, value: Any) -> None: ...
@@ -817,15 +819,36 @@
 class Attachments(MutableMapping[str, AttachedFileSpec]):
     """Exposes files attached to a PDF.
 
+    If a file is attached to a PDF, it is exposed through this interface.
+    For example ``p.attachments['readme.txt']`` would return a
+    :class:`pikepdf._core.AttachedFileSpec` that describes the attached file,
+    if a file were attached under that name.
+    ``p.attachments['readme.txt'].get_file()`` would return a
+    :class:`pikepdf._core.AttachedFile`, an archaic intermediate object to 
support
+    different versions of the file for different platforms. Typically one
+    just calls ``p.attachments['readme.txt'].read_bytes()`` to get the
+    contents of the file.
+
     This interface provides access to any files that are attached to this PDF,
     exposed as a Python :class:`collections.abc.MutableMapping` interface.
 
     The keys (virtual filenames) are always ``str``, and values are always
     :class:`pikepdf.AttachedFileSpec`.
 
+    To create a new attached file, use
+    :meth:`pikepdf._core.AttachedFileSpec.from_filepath`
+    to create a :class:`pikepdf._core.AttachedFileSpec` and then assign it to 
the
+    :attr:`pikepdf.Pdf.attachments` mapping. If the file is in memory, use
+    ``p.attachments['test.pdf'] = b'binary data'``.
+
     Use this interface through :attr:`pikepdf.Pdf.attachments`.
 
     .. versionadded:: 3.0
+
+    .. versionchanged:: 8.10.1
+        Added convenience interface for directly loading attached files, e.g.
+        ``pdf.attachments['/test.pdf'] = b'binary data'``. Prior to this 
release,
+        there was no way to attach data in memory as a file.
     """
 
     def __contains__(self, k: object) -> bool: ...
@@ -834,7 +857,7 @@
     def __getitem__(self, k: str) -> AttachedFileSpec: ...
     def __iter__(self) -> Iterator[str]: ...
     def __len__(self) -> int: ...
-    def __setitem__(self, k: str, v: AttachedFileSpec): ...
+    def __setitem__(self, k: str, v: AttachedFileSpec | bytes): ...
     def __init__(self, *args, **kwargs) -> None: ...
     def _add_replace_filespec(self, arg0: str, arg1: AttachedFileSpec) -> 
None: ...
     def _get_all_filespecs(self) -> dict[str, AttachedFileSpec]: ...
@@ -1341,10 +1364,11 @@
     """For accessing pages in a PDF.
 
     A ``list``-like object enumerating a range of pages in a 
:class:`pikepdf.Pdf`.
-    It may be all of the pages or a subset.
+    It may be all of the pages or a subset. Obtain using 
:attr:`pikepdf.Pdf.pages`.
+
+    See :class:`pikepdf.Page` for accessing individual pages.
     """
 
-    def __init__(self, *args, **kwargs) -> None: ...
     def append(self, page: Page) -> None:
         """Add another page to the end.
 
@@ -1372,7 +1396,7 @@
 
         Raises an exception if no page matches.
         """
-    def index(self, page: Page | Object) -> int:
+    def index(self, page: Page) -> int:
         """Given a page, find the index.
 
         That is, returns ``n`` such that ``pdf.pages[n] == this_page``.
@@ -1398,29 +1422,33 @@
         function does not account for that. Use :attr:`pikepdf.Page.label`
         to get the page label for a page.
         """
-    def remove(self, *, p: int) -> None:
-        """Remove a page (using 1-based numbering).
+    def remove(self, page: Page | None = None, *, p: int) -> None:
+        """Remove a page.
 
         Args:
-            p: 1-based page number
+            page: If page is not None, remove that page.
+            p: 1-based page number to remove, if page is None.
         """
     def reverse(self) -> None:
         """Reverse the order of pages."""
     @overload
-    def __delitem__(self, arg0: int) -> None: ...
+    def __delitem__(self, idx: int) -> None: ...
     @overload
-    def __delitem__(self, arg0: slice) -> None: ...
+    def __delitem__(self, sl: slice) -> None: ...
     @overload
-    def __getitem__(self, arg0: int) -> Page: ...
+    def __getitem__(self, idx: int) -> Page: ...
     @overload
-    def __getitem__(self, arg0: slice) -> list[Page]: ...
-    def __iter__(self) -> PageList: ...
+    def __getitem__(self, sl: slice) -> list[Page]: ...
+    def __iter__(self) -> _PageListIterator: ...
     def __len__(self) -> int: ...
-    def __next__(self) -> Page: ...
     @overload
-    def __setitem__(self, arg0: int, arg1: Page) -> None: ...
+    def __setitem__(self, idx: int, page: Page) -> None: ...
     @overload
-    def __setitem__(self, arg0: slice, arg1: Iterable[Page]) -> None: ...
+    def __setitem__(self, sl: slice, pages: Iterable[Page]) -> None: ...
+
+class _PageListIterator:
+    def __iter__(self) -> _PageListIterator: ...
+    def __next__(self) -> Page: ...
 
 class Pdf:
     def _repr_mimebundle_(include: Any = ..., exclude: Any = ...) -> Any:
@@ -2771,7 +2799,6 @@
 def _new_string_utf8(s: str) -> String:
     """Low-level function to construct a PDF String object from UTF-8 bytes."""
 
-def _test_file_not_found(*args, **kwargs) -> Any: ...
 def _translate_qpdf_logic_error(arg0: str) -> str: ...
 def get_decimal_precision() -> int:
     """Set the number of decimal digits to use when converting floats."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/pikepdf/_methods.py 
new/pikepdf-8.10.1/src/pikepdf/_methods.py
--- old/pikepdf-8.9.0/src/pikepdf/_methods.py   2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/pikepdf/_methods.py  2023-12-17 10:31:00.000000000 
+0100
@@ -55,7 +55,7 @@
 Numeric = TypeVar('Numeric', int, float, Decimal)
 
 
-def _single_page_pdf(page) -> bytes:
+def _single_page_pdf(page: Page) -> bytes:
     """Construct a single page PDF from the provided page in memory."""
     pdf = Pdf.new()
     pdf.pages.append(page)
@@ -666,7 +666,7 @@
             bundle = {k for k in bundle if k in include}
         if exclude:
             bundle = {k for k in bundle if k not in exclude}
-        pagedata = _single_page_pdf(self.obj)
+        pagedata = _single_page_pdf(self)
         if 'application/pdf' in bundle:
             data['application/pdf'] = pagedata
         if 'image/png' in bundle:
@@ -700,7 +700,9 @@
             raise KeyError(k)
         return filespec
 
-    def __setitem__(self, k: str, v: AttachedFileSpec) -> None:
+    def __setitem__(self, k: str, v: AttachedFileSpec | bytes) -> None:
+        if isinstance(v, bytes):
+            return self._attach_data(k, v)
         if not v.filename:
             v.filename = k
         return self._add_replace_filespec(k, v)
@@ -715,7 +717,9 @@
         yield from self._get_all_filespecs()
 
     def __repr__(self):
-        return f"<pikepdf._core.Attachments with {len(self)} attached files>"
+        if len(self) == 0:
+            return "<pikepdf._core.Attachments no attached files>"
+        return f"<pikepdf._core.Attachments: {list(self)}>"
 
 
 @augments(AttachedFileSpec)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/pikepdf/_version.py 
new/pikepdf-8.10.1/src/pikepdf/_version.py
--- old/pikepdf-8.9.0/src/pikepdf/_version.py   2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/src/pikepdf/_version.py  2023-12-17 10:31:00.000000000 
+0100
@@ -3,4 +3,4 @@
 
 from __future__ import annotations
 
-__version__ = "8.9.0"
+__version__ = "8.10.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/src/pikepdf.egg-info/PKG-INFO 
new/pikepdf-8.10.1/src/pikepdf.egg-info/PKG-INFO
--- old/pikepdf-8.9.0/src/pikepdf.egg-info/PKG-INFO     2023-12-11 
00:26:49.000000000 +0100
+++ new/pikepdf-8.10.1/src/pikepdf.egg-info/PKG-INFO    2023-12-17 
10:33:05.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pikepdf
-Version: 8.9.0
+Version: 8.10.1
 Summary: Read and write PDFs with Python, powered by qpdf
 Author-email: "James R. Barlow" <ja...@purplerock.ca>
 License: MPL-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_attachments.py 
new/pikepdf-8.10.1/tests/test_attachments.py
--- old/pikepdf-8.9.0/tests/test_attachments.py 2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_attachments.py        2023-12-17 
10:31:00.000000000 +0100
@@ -33,7 +33,7 @@
 
     assert len(pal.attachments) == 1, "attachment count not incremented"
     assert 'rle.pdf' in pal.attachments, "attachment filename not registered"
-    assert 'attached' in repr(pal.attachments)
+    assert 'rle.pdf' in repr(pal.attachments), "attachment filename not 
enumerated"
 
     pal.save(outpdf)
 
@@ -157,3 +157,9 @@
     assert 'foo' not in repr(fs)
     assert 'AttachedFile' in repr(fs.get_file())
     assert fs.relationship == Name.Data
+
+
+def test_attach_direct(pal):
+    data = b'some data'
+    pal.attachments['direct.txt'] = data
+    assert pal.attachments['direct.txt'].get_file().read_bytes() == data
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_errors.py 
new/pikepdf-8.10.1/tests/test_errors.py
--- old/pikepdf-8.9.0/tests/test_errors.py      2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_errors.py     2023-12-17 10:31:00.000000000 
+0100
@@ -46,7 +46,7 @@
 
 def test_system_error():
     with pytest.raises(FileNotFoundError):
-        pikepdf._core._test_file_not_found()
+        pikepdf._core._test.fopen_nonexistent_file()
 
 
 @skip_if_pypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_formxobject.py 
new/pikepdf-8.10.1/tests/test_formxobject.py
--- old/pikepdf-8.9.0/tests/test_formxobject.py 2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_formxobject.py        2023-12-17 
10:31:00.000000000 +0100
@@ -3,7 +3,7 @@
 
 from __future__ import annotations
 
-from pikepdf import Dictionary, Name, Object, Pdf, Stream
+from pikepdf import Dictionary, Name, Object, Page, Pdf, Stream
 
 # pylint: disable=e1137
 
@@ -30,7 +30,7 @@
 
     image = Stream(pdf, image_data)
     image.stream_dict = Object.parse(
-        """
+        b"""
             <<
                 /Type /XObject
                 /Subtype /Image
@@ -71,13 +71,15 @@
 
     contents = Stream(pdf, stream)
 
-    page = pdf.make_indirect(
-        {
-            '/Type': Name('/Page'),
-            '/MediaBox': mediabox,
-            '/Contents': contents,
-            '/Resources': resources,
-        }
+    page = Page(
+        pdf.make_indirect(
+            {
+                '/Type': Name('/Page'),
+                '/MediaBox': mediabox,
+                '/Contents': contents,
+                '/Resources': resources,
+            }
+        )
     )
 
     pdf.pages.append(page)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_io.py 
new/pikepdf-8.10.1/tests/test_io.py
--- old/pikepdf-8.9.0/tests/test_io.py  2023-12-11 00:24:59.000000000 +0100
+++ new/pikepdf-8.10.1/tests/test_io.py 2023-12-17 10:31:00.000000000 +0100
@@ -197,7 +197,7 @@
 
 def test_logging(caplog):
     caplog.set_level(logging.INFO)
-    pikepdf._core._log_info("test log message")
+    pikepdf._core._test.log_info("test log message")
     assert [("pikepdf._core", logging.INFO)] == [
         (rec[0], rec[1]) for rec in caplog.record_tuples
     ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_matrix.py 
new/pikepdf-8.10.1/tests/test_matrix.py
--- old/pikepdf-8.9.0/tests/test_matrix.py      2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_matrix.py     2023-12-17 10:31:00.000000000 
+0100
@@ -11,6 +11,7 @@
 from pikepdf import Array
 from pikepdf._core import Matrix, Rectangle
 from pikepdf.models import PdfMatrix
+from pikepdf.objects import Dictionary
 
 
 def allclose(m1, m2, abs_tol=1e-6):
@@ -69,6 +70,21 @@
     def test_default_is_identity(self):
         assert Matrix() == Matrix(1, 0, 0, 1, 0, 0)
 
+    def test_not_enough_args(self):
+        with pytest.raises(TypeError):
+            Matrix(1, 2, 3, 4, 5)
+
+    def test_tuple(self):
+        assert Matrix() == Matrix((1, 0, 0, 1, 0, 0))
+        with pytest.raises(ValueError):
+            Matrix((1, 2, 3, 4, 5))
+
+    def test_failed_object_conversion(self):
+        with pytest.raises(ValueError):
+            assert Matrix(Array([1, 2, 3]))
+        with pytest.raises(ValueError):
+            assert Matrix(Dictionary(Foo=1))
+
     def test_accessors(self):
         m = Matrix(1, 2, 3, 4, 5, 6)
         assert m.a == 1
@@ -145,3 +161,6 @@
         assert (0, 0) < m.transform((1, 0)) < (1, 1)
         m = Matrix().rotated(-45)
         assert (0, 0) < m.transform((1, 0)) < (1, -1)
+
+    def test_latex(self):
+        assert '\\begin' in Matrix(1, 0, 0, 1, 0, 0)._repr_latex_()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_object.py 
new/pikepdf-8.10.1/tests/test_object.py
--- old/pikepdf-8.9.0/tests/test_object.py      2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_object.py     2023-12-17 10:31:00.000000000 
+0100
@@ -112,14 +112,14 @@
         except InvalidOperation:
             return  # PDF doesn't support exponential notation
         try:
-            py_d = Object.parse(decstr)
+            py_d = Object.parse(decstr.encode('pdfdoc'))
         except RuntimeError as e:
             if 'overflow' in str(e) or 'underflow' in str(e):
-                py_d = Object.parse(str(f))
+                py_d = Object.parse(f.encode('pdfdoc'))
 
         assert isclose(py_d, d, abs_tol=1e-5), (d, f.hex())
     else:
-        with pytest.raises(PdfError):
+        with pytest.raises(PdfError), pytest.deprecated_call():
             Object.parse(str(d))
 
 
@@ -325,11 +325,11 @@
         assert Name('/Foo') != String('/Foo')
 
     def test_numbers(self):
-        self.check(Object.parse('1.0'), 1)
-        self.check(Object.parse('42'), 42)
+        self.check(Object.parse(b'1.0'), 1)
+        self.check(Object.parse(b'42'), 42)
 
     def test_bool_comparison(self):
-        self.check(Object.parse('0.0'), False)
+        self.check(Object.parse(b'0.0'), False)
         self.check(True, 1)
 
     def test_string(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_page.py 
new/pikepdf-8.10.1/tests/test_page.py
--- old/pikepdf-8.9.0/tests/test_page.py        2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_page.py       2023-12-17 10:31:00.000000000 
+0100
@@ -130,14 +130,16 @@
 def test_add_unowned_page():  # issue 174
     pdf = Pdf.new()
     d = Dictionary(Type=Name.Page)
-    pdf.pages.append(d)
+    pdf.pages.append(Page(d))
 
 
 def test_failed_add_page_cleanup():
     pdf = Pdf.new()
     d = Dictionary(Type=Name.NotAPage)
     num_objects = len(pdf.objects)
-    with pytest.raises(TypeError, match="only pages can be inserted"):
+    with pytest.raises(
+        TypeError, match="only pages can be inserted"
+    ), pytest.deprecated_call():
         pdf.pages.append(d)
     assert len(pdf.pages) == 0
 
@@ -148,7 +150,9 @@
 
     # But we'd better not delete an existing object...
     d2 = pdf.make_indirect(Dictionary(Type=Name.StillNotAPage))
-    with pytest.raises(TypeError, match="only pages can be inserted"):
+    with pytest.raises(
+        TypeError, match="only pages can be inserted"
+    ), pytest.deprecated_call():
         pdf.pages.append(d2)
     assert len(pdf.pages) == 0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_pages.py 
new/pikepdf-8.10.1/tests/test_pages.py
--- old/pikepdf-8.9.0/tests/test_pages.py       2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_pages.py      2023-12-17 10:31:00.000000000 
+0100
@@ -202,11 +202,10 @@
         pdf.pages[0::2] = pdf2.pages[0:1]
 
 
-@pytest.mark.timeout(1)
 def test_self_extend(fourpages):
     pdf = fourpages
-    with pytest.raises(ValueError, match="source page list modified during 
iteration"):
-        pdf.pages.extend(pdf.pages)
+    pdf.pages.extend(pdf.pages)
+    assert len(pdf.pages) == 8
 
 
 def test_one_based_pages(fourpages):
@@ -233,7 +232,7 @@
     pdf = fourpages
     with pytest.raises(TypeError):
         pdf.pages.insert(0, 'this is a string not a page')
-    with pytest.raises(TypeError):
+    with pytest.raises(TypeError), pytest.deprecated_call():
         pdf.pages.insert(0, Dictionary(Type=Name.NotAPage, Value="Not a page"))
 
 
@@ -361,10 +360,20 @@
         fourpages.pages.remove(p=-1)
 
 
+def test_remove_by_ref(fourpages):
+    second_page = fourpages.pages[1]
+    assert second_page == fourpages.pages[1]
+    fourpages.pages.remove(second_page)
+    assert second_page not in fourpages.pages
+    assert len(fourpages.pages) == 3
+    with pytest.raises(ValueError):
+        fourpages.pages.remove(second_page)
+
+
 def test_pages_wrong_type(fourpages):
-    with pytest.raises(TypeError):
+    with pytest.raises(TypeError), pytest.deprecated_call():
         fourpages.pages.insert(3, {})
-    with pytest.raises(TypeError):
+    with pytest.raises(TypeError), pytest.deprecated_call():
         fourpages.pages.insert(3, Array([42]))
 
 
@@ -478,7 +487,7 @@
     p = Pdf.new()
     d = Dictionary(Type=Name.Page, MediaBox=[0, 0, 612, 792], 
Resources=Dictionary())
     for n in range(5):
-        p.pages.append(d)
+        p.pages.append(Page(d))
         p.pages[n].Contents = Stream(p, b"BT (Page %s) Tj ET" % 
str(n).encode())
 
     p.Root.PageLabels = p.make_indirect(
@@ -529,3 +538,11 @@
     )
     with pytest.raises(ValueError):
         graph.pages.from_objgen(graph.pages[0].Contents.objgen)
+
+
+def test_page_iteration(graph, fourpages):
+    fourpages_iter = iter(fourpages.pages)
+    next(fourpages_iter)  # Discard
+    next(fourpages_iter)  # Discard
+    graph.pages.extend(fourpages_iter)  # Append remaining two
+    assert len(graph.pages) == 3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pikepdf-8.9.0/tests/test_sanity.py 
new/pikepdf-8.10.1/tests/test_sanity.py
--- old/pikepdf-8.9.0/tests/test_sanity.py      2023-12-11 00:24:59.000000000 
+0100
+++ new/pikepdf-8.10.1/tests/test_sanity.py     2023-12-17 10:31:00.000000000 
+0100
@@ -23,7 +23,7 @@
 from packaging.version import Version
 
 import pikepdf
-from pikepdf import Name, Object, Pdf, Stream
+from pikepdf import Name, Object, Page, Pdf, Stream
 
 
 def test_minimum_qpdf_version():
@@ -106,7 +106,7 @@
         '/Resources': resources,
     }
     qpdf_page_dict = page_dict
-    page = pdf.make_indirect(qpdf_page_dict)
+    page = Page(pdf.make_indirect(qpdf_page_dict))
 
     pdf.pages.append(page)
     pdf.save(outdir / 'hi.pdf')

Reply via email to