Further improvements for sqlalchemy compatibility

This commit is contained in:
Ozzie Isaacs 2023-02-11 07:43:48 +01:00
parent 63a3edd429
commit dda20eb912
6 changed files with 151 additions and 67 deletions

View File

@ -184,11 +184,11 @@ def migrate():
sql=sql[0].replace(currUniqueConstraint, 'UNIQUE (gdrive_id, path)') sql=sql[0].replace(currUniqueConstraint, 'UNIQUE (gdrive_id, path)')
sql=sql.replace(GdriveId.__tablename__, GdriveId.__tablename__ + '2') sql=sql.replace(GdriveId.__tablename__, GdriveId.__tablename__ + '2')
session.execute(sql) session.execute(sql)
session.execute("INSERT INTO gdrive_ids2 (id, gdrive_id, path) SELECT id, " session.execute(text("INSERT INTO gdrive_ids2 (id, gdrive_id, path) SELECT id, "
"gdrive_id, path FROM gdrive_ids;") "gdrive_id, path FROM gdrive_ids;"))
session.commit() session.commit()
session.execute('DROP TABLE %s' % 'gdrive_ids') session.execute(text('DROP TABLE %s' % 'gdrive_ids'))
session.execute('ALTER TABLE gdrive_ids2 RENAME to gdrive_ids') session.execute(text('ALTER TABLE gdrive_ids2 RENAME to gdrive_ids'))
break break
if not os.path.exists(cli_param.gd_path): if not os.path.exists(cli_param.gd_path):

View File

@ -565,15 +565,15 @@ def migrate_registration_table(engine, _session):
_session.commit() _session.commit()
except exc.OperationalError: # Database is not compatible, some columns are missing except exc.OperationalError: # Database is not compatible, some columns are missing
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE registration ADD column 'allow' INTEGER") conn.execute(text("ALTER TABLE registration ADD column 'allow' INTEGER"))
conn.execute("update registration set 'allow' = 1") conn.execute(text("update registration set 'allow' = 1"))
_session.commit() _session.commit()
try: try:
# Handle table exists, but no content # Handle table exists, but no content
cnt = _session.query(Registration).count() cnt = _session.query(Registration).count()
if not cnt: if not cnt:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("insert into registration (domain, allow) values('%.%',1)") conn.execute(text("insert into registration (domain, allow) values('%.%',1)"))
_session.commit() _session.commit()
except exc.OperationalError: # Database is not writeable except exc.OperationalError: # Database is not writeable
print('Settings database is not writeable. Exiting...') print('Settings database is not writeable. Exiting...')
@ -597,11 +597,11 @@ def migrate_shelfs(engine, _session):
_session.query(exists().where(Shelf.uuid)).scalar() _session.query(exists().where(Shelf.uuid)).scalar()
except exc.OperationalError: except exc.OperationalError:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE shelf ADD column 'uuid' STRING") conn.execute(text("ALTER TABLE shelf ADD column 'uuid' STRING"))
conn.execute("ALTER TABLE shelf ADD column 'created' DATETIME") conn.execute(text("ALTER TABLE shelf ADD column 'created' DATETIME"))
conn.execute("ALTER TABLE shelf ADD column 'last_modified' DATETIME") conn.execute(text("ALTER TABLE shelf ADD column 'last_modified' DATETIME"))
conn.execute("ALTER TABLE book_shelf_link ADD column 'date_added' DATETIME") conn.execute(text("ALTER TABLE book_shelf_link ADD column 'date_added' DATETIME"))
conn.execute("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false") conn.execute(text("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false"))
for shelf in _session.query(Shelf).all(): for shelf in _session.query(Shelf).all():
shelf.uuid = str(uuid.uuid4()) shelf.uuid = str(uuid.uuid4())
shelf.created = datetime.datetime.now() shelf.created = datetime.datetime.now()
@ -615,14 +615,14 @@ def migrate_shelfs(engine, _session):
except exc.OperationalError: except exc.OperationalError:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false") conn.execute(text("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false"))
_session.commit() _session.commit()
try: try:
_session.query(exists().where(BookShelf.order)).scalar() _session.query(exists().where(BookShelf.order)).scalar()
except exc.OperationalError: # Database is not compatible, some columns are missing except exc.OperationalError: # Database is not compatible, some columns are missing
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1") conn.execute(text("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1"))
_session.commit() _session.commit()
@ -631,11 +631,11 @@ def migrate_readBook(engine, _session):
_session.query(exists().where(ReadBook.read_status)).scalar() _session.query(exists().where(ReadBook.read_status)).scalar()
except exc.OperationalError: except exc.OperationalError:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0") conn.execute(text("ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0"))
conn.execute("UPDATE book_read_link SET 'read_status' = 1 WHERE is_read") conn.execute(text("UPDATE book_read_link SET 'read_status' = 1 WHERE is_read"))
conn.execute("ALTER TABLE book_read_link ADD column 'last_modified' DATETIME") conn.execute(text("ALTER TABLE book_read_link ADD column 'last_modified' DATETIME"))
conn.execute("ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME") conn.execute(text("ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME"))
conn.execute("ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0") conn.execute(text("ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0"))
_session.commit() _session.commit()
test = _session.query(ReadBook).filter(ReadBook.last_modified == None).all() test = _session.query(ReadBook).filter(ReadBook.last_modified == None).all()
for book in test: for book in test:
@ -649,8 +649,8 @@ def migrate_remoteAuthToken(engine, _session):
_session.commit() _session.commit()
except exc.OperationalError: # Database is not compatible, some columns are missing except exc.OperationalError: # Database is not compatible, some columns are missing
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0") conn.execute(text("ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0"))
conn.execute("update remote_auth_token set 'token_type' = 0") conn.execute(text("update remote_auth_token set 'token_type' = 0"))
_session.commit() _session.commit()
# Migrate database to current version, has to be updated after every database change. Currently migration from # Migrate database to current version, has to be updated after every database change. Currently migration from
@ -668,19 +668,19 @@ def migrate_Database(_session):
_session.query(exists().where(User.sidebar_view)).scalar() _session.query(exists().where(User.sidebar_view)).scalar()
except exc.OperationalError: # Database is not compatible, some columns are missing except exc.OperationalError: # Database is not compatible, some columns are missing
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE user ADD column `sidebar_view` Integer DEFAULT 1") conn.execute(text("ALTER TABLE user ADD column `sidebar_view` Integer DEFAULT 1"))
_session.commit() _session.commit()
create = True create = True
try: try:
if create: if create:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("SELECT language_books FROM user") conn.execute(text("SELECT language_books FROM user"))
_session.commit() _session.commit()
except exc.OperationalError: except exc.OperationalError:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("UPDATE user SET 'sidebar_view' = (random_books* :side_random + language_books * :side_lang " conn.execute(text("UPDATE user SET 'sidebar_view' = (random_books* :side_random + language_books * :side_lang "
"+ series_books * :side_series + category_books * :side_category + hot_books * " "+ series_books * :side_series + category_books * :side_category + hot_books * "
":side_hot + :side_autor + :detail_random)", ":side_hot + :side_autor + :detail_random)"),
{'side_random': constants.SIDEBAR_RANDOM, 'side_lang': constants.SIDEBAR_LANGUAGE, {'side_random': constants.SIDEBAR_RANDOM, 'side_lang': constants.SIDEBAR_LANGUAGE,
'side_series': constants.SIDEBAR_SERIES, 'side_category': constants.SIDEBAR_CATEGORY, 'side_series': constants.SIDEBAR_SERIES, 'side_category': constants.SIDEBAR_CATEGORY,
'side_hot': constants.SIDEBAR_HOT, 'side_autor': constants.SIDEBAR_AUTHOR, 'side_hot': constants.SIDEBAR_HOT, 'side_autor': constants.SIDEBAR_AUTHOR,
@ -690,16 +690,16 @@ def migrate_Database(_session):
_session.query(exists().where(User.denied_tags)).scalar() _session.query(exists().where(User.denied_tags)).scalar()
except exc.OperationalError: # Database is not compatible, some columns are missing except exc.OperationalError: # Database is not compatible, some columns are missing
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE user ADD column `denied_tags` String DEFAULT ''") conn.execute(text("ALTER TABLE user ADD column `denied_tags` String DEFAULT ''"))
conn.execute("ALTER TABLE user ADD column `allowed_tags` String DEFAULT ''") conn.execute(text("ALTER TABLE user ADD column `allowed_tags` String DEFAULT ''"))
conn.execute("ALTER TABLE user ADD column `denied_column_value` String DEFAULT ''") conn.execute(text("ALTER TABLE user ADD column `denied_column_value` String DEFAULT ''"))
conn.execute("ALTER TABLE user ADD column `allowed_column_value` String DEFAULT ''") conn.execute(text("ALTER TABLE user ADD column `allowed_column_value` String DEFAULT ''"))
_session.commit() _session.commit()
try: try:
_session.query(exists().where(User.view_settings)).scalar() _session.query(exists().where(User.view_settings)).scalar()
except exc.OperationalError: except exc.OperationalError:
with engine.connect() as conn: with engine.connect() as conn:
conn.execute("ALTER TABLE user ADD column `view_settings` VARCHAR(10) DEFAULT '{}'") conn.execute(text("ALTER TABLE user ADD column `view_settings` VARCHAR(10) DEFAULT '{}'"))
_session.commit() _session.commit()
try: try:
_session.query(exists().where(User.kobo_only_shelves_sync)).scalar() _session.query(exists().where(User.kobo_only_shelves_sync)).scalar()

View File

@ -1,5 +1,5 @@
# GDrive Integration # GDrive Integration
google-api-python-client>=1.7.11,<2.75.0 google-api-python-client>=1.7.11,<2.78.0
gevent>20.6.0,<23.0.0 gevent>20.6.0,<23.0.0
greenlet>=0.4.17,<2.1.0 greenlet>=0.4.17,<2.1.0
httplib2>=0.9.2,<0.22.0 httplib2>=0.9.2,<0.22.0
@ -13,7 +13,7 @@ rsa>=3.4.2,<4.10.0
# Gmail # Gmail
google-auth-oauthlib>=0.4.3,<0.9.0 google-auth-oauthlib>=0.4.3,<0.9.0
google-api-python-client>=1.7.11,<2.75.0 google-api-python-client>=1.7.11,<2.78.0
# goodreads # goodreads
goodreads>=0.3.2,<0.4.0 goodreads>=0.3.2,<0.4.0

View File

@ -7,10 +7,10 @@ Flask-Principal>=0.3.2,<0.5.1
backports_abc>=0.4 backports_abc>=0.4
Flask>=1.0.2,<2.3.0 Flask>=1.0.2,<2.3.0
iso-639>=0.4.5,<0.5.0 iso-639>=0.4.5,<0.5.0
PyPDF>=3.0.0,<3.3.0 PyPDF==3.4.0
pytz>=2016.10 pytz>=2016.10
requests>=2.11.1,<2.28.0 requests>=2.11.1,<2.28.0
SQLAlchemy>=1.3.0,<1.5.0 SQLAlchemy>=1.3.0,<2.0.0
tornado>=4.1,<6.3 tornado>=4.1,<6.3
Wand>=0.4.4,<0.7.0 Wand>=0.4.4,<0.7.0
unidecode>=0.04.19,<1.4.0 unidecode>=0.04.19,<1.4.0

View File

@ -47,10 +47,10 @@ install_requires =
backports_abc>=0.4 backports_abc>=0.4
Flask>=1.0.2,<2.3.0 Flask>=1.0.2,<2.3.0
iso-639>=0.4.5,<0.5.0 iso-639>=0.4.5,<0.5.0
PyPDF>=3.0.0,<3.3.0 PyPDF==3.4.0
pytz>=2016.10 pytz>=2016.10
requests>=2.11.1,<2.28.0 requests>=2.11.1,<2.28.0
SQLAlchemy>=1.3.0,<1.5.0 SQLAlchemy>=1.3.0,<2.0.0
tornado>=4.1,<6.3 tornado>=4.1,<6.3
Wand>=0.4.4,<0.7.0 Wand>=0.4.4,<0.7.0
unidecode>=0.04.19,<1.4.0 unidecode>=0.04.19,<1.4.0

View File

@ -37,20 +37,20 @@
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
<p class='text-justify attribute'><strong>Start Time: </strong>2023-02-09 19:47:18</p> <p class='text-justify attribute'><strong>Start Time: </strong>2023-02-10 18:46:27</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3"> <div class="col-xs-6 col-md-6 col-sm-offset-3">
<p class='text-justify attribute'><strong>Stop Time: </strong>2023-02-10 02:15:25</p> <p class='text-justify attribute'><strong>Stop Time: </strong>2023-02-11 01:04:02</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3"> <div class="col-xs-6 col-md-6 col-sm-offset-3">
<p class='text-justify attribute'><strong>Duration: </strong>5h 32 min</p> <p class='text-justify attribute'><strong>Duration: </strong>5h 20 min</p>
</div> </div>
</div> </div>
</div> </div>
@ -726,11 +726,11 @@
<tr id="su" class="passClass"> <tr id="su" class="failClass">
<td>TestEbookConvertGDriveKepubify</td> <td>TestEbookConvertGDriveKepubify</td>
<td class="text-center">3</td> <td class="text-center">3</td>
<td class="text-center">3</td> <td class="text-center">2</td>
<td class="text-center">0</td> <td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center"> <td class="text-center">
@ -749,11 +749,33 @@
<tr id='pt9.2' class='hiddenRow bg-success'> <tr id="ft9.2" class="none bg-danger">
<td> <td>
<div class='testcase'>TestEbookConvertGDriveKepubify - test_convert_only</div> <div class='testcase'>TestEbookConvertGDriveKepubify - test_convert_only</div>
</td> </td>
<td colspan='6' align='center'>PASS</td> <td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft9.2')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft9.2" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft9.2').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_ebook_convert_kepubify_gdrive.py&#34;, line 176, in test_convert_only
self.assertEqual(ret[-1][&#39;result&#39;], &#39;Finished&#39;)
AssertionError: &#39;Started&#39; != &#39;Finished&#39;
- Started
+ Finished</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
@ -1607,11 +1629,11 @@
<tr id="su" class="passClass"> <tr id="su" class="failClass">
<td>TestLoadMetadata</td> <td>TestLoadMetadata</td>
<td class="text-center">1</td> <td class="text-center">1</td>
<td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center"> <td class="text-center">
@ -1621,22 +1643,42 @@
<tr id='pt15.1' class='hiddenRow bg-success'> <tr id="ft15.1" class="none bg-danger">
<td> <td>
<div class='testcase'>TestLoadMetadata - test_load_metadata</div> <div class='testcase'>TestLoadMetadata - test_load_metadata</div>
</td> </td>
<td colspan='6' align='center'>PASS</td> <td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft15.1')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft15.1" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft15.1').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_books_metadata.py&#34;, line 133, in test_load_metadata
self.assertEqual(20, len(results))
AssertionError: 20 != 10</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
<tr id="su" class="failClass"> <tr id="su" class="errorClass">
<td>TestEditBooksOnGdrive</td> <td>TestEditBooksOnGdrive</td>
<td class="text-center">18</td> <td class="text-center">18</td>
<td class="text-center">17</td> <td class="text-center">15</td>
<td class="text-center">1</td> <td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">2</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center"> <td class="text-center">
<a onclick="showClassDetail('c16', 18)">Detail</a> <a onclick="showClassDetail('c16', 18)">Detail</a>
@ -1744,11 +1786,33 @@
<tr id='pt16.12' class='hiddenRow bg-success'> <tr id="et16.12" class="none bg-info">
<td> <td>
<div class='testcase'>TestEditBooksOnGdrive - test_edit_language</div> <div class='testcase'>TestEditBooksOnGdrive - test_edit_language</div>
</td> </td>
<td colspan='6' align='center'>PASS</td> <td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et16.12')">ERROR</a>
</div>
<!--css div popup start-->
<div id="div_et16.12" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_et16.12').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 571, in test_edit_language
self.edit_book(content={&#39;languages&#39;: &#39;German&#39;})
File &#34;/home/ozzie/Development/calibre-web-test/test/helper_ui.py&#34;, line 1754, in edit_book
ele.send_keys(Keys.CONTROL, &#34;a&#34;)
AttributeError: &#39;bool&#39; object has no attribute &#39;send_keys&#39;</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
@ -1762,11 +1826,31 @@
<tr id='pt16.14' class='hiddenRow bg-success'> <tr id="ft16.14" class="none bg-danger">
<td> <td>
<div class='testcase'>TestEditBooksOnGdrive - test_edit_rating</div> <div class='testcase'>TestEditBooksOnGdrive - test_edit_rating</div>
</td> </td>
<td colspan='6' align='center'>PASS</td> <td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft16.14')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft16.14" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft16.14').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 604, in test_edit_rating
self.assertEqual(1, len(books[1]))
AssertionError: 1 != 0</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
@ -1780,26 +1864,26 @@
<tr id="ft16.16" class="none bg-danger"> <tr id="et16.16" class="none bg-info">
<td> <td>
<div class='testcase'>TestEditBooksOnGdrive - test_edit_title</div> <div class='testcase'>TestEditBooksOnGdrive - test_edit_title</div>
</td> </td>
<td colspan='6'> <td colspan='6'>
<div class="text-center"> <div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft16.16')">FAIL</a> <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et16.16')">ERROR</a>
</div> </div>
<!--css div popup start--> <!--css div popup start-->
<div id="div_ft16.16" class="popup_window test_output" style="display:block;"> <div id="div_et16.16" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'> <div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();" <button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft16.16').style.display='none'"><span onclick="document.getElementById('div_et16.16').style.display='none'"><span
aria-hidden="true">&times;</span></button> aria-hidden="true">&times;</span></button>
</div> </div>
<div class="text-left pull-left"> <div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last): <pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 225, in test_edit_title File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 230, in test_edit_title
self.assertEqual(u&#39;Pipo|;.:&#39;, title.get_attribute(&#39;value&#39;)) self.assertEqual(ele.text, u&#39;Very long extra super turbo cool title without any issue of displaying including ö utf-8 characters&#39;)
AssertionError: &#39;Pipo|;.:&#39; != None</pre> AttributeError: &#39;bool&#39; object has no attribute &#39;text&#39;</pre>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
@ -4769,9 +4853,9 @@ AssertionError: &#39;Pipo|;.:&#39; != None</pre>
<tr id='total_row' class="text-center bg-grey"> <tr id='total_row' class="text-center bg-grey">
<td>Total</td> <td>Total</td>
<td>425</td> <td>425</td>
<td>417</td> <td>413</td>
<td>1</td> <td>3</td>
<td>0</td> <td>2</td>
<td>7</td> <td>7</td>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
@ -4902,7 +4986,7 @@ AssertionError: &#39;Pipo|;.:&#39; != None</pre>
<tr> <tr>
<th>pypdf</th> <th>pypdf</th>
<td>3.2.1</td> <td>3.4.0</td>
<td>Basic</td> <td>Basic</td>
</tr> </tr>
@ -5220,7 +5304,7 @@ AssertionError: &#39;Pipo|;.:&#39; != None</pre>
</div> </div>
<script> <script>
drawCircle(417, 1, 0, 7); drawCircle(413, 3, 2, 7);
showCase(5); showCase(5);
</script> </script>