-
Notifications
You must be signed in to change notification settings - Fork 13
Add save_pdf method #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
06b9f8d
452624d
03f5011
c2ac4c6
ba00b04
0d38757
55b1864
064ea73
aa02d9c
3527b81
61e240c
b51385f
2467f4d
03717bc
8499b97
9061a6a
2a41e13
e035e4c
79a3bc2
b408a9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
import pathlib | ||
from pathlib import Path | ||
import pptx | ||
from typing import Union, Iterable, Tuple | ||
from pptx.shapes.base import BaseShape | ||
import subprocess | ||
import tempfile | ||
_Pathlike = Union[str, Path] | ||
|
||
_Pathlike = Union[str, pathlib.Path] | ||
|
||
class LibreOfficeNotFoundError(FileNotFoundError): | ||
pass | ||
|
||
|
||
class Template: | ||
|
@@ -85,8 +90,38 @@ def _find_shapes_in_slide(slide): | |
def save(self, filename: _Pathlike) -> None: | ||
"""Saves the updated pptx to the specified filepath. | ||
|
||
Args: | ||
Args: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please revert to reduce noise on git-diff :-) |
||
filename (path-like): file name or path | ||
""" | ||
# TODO: make sure that the user does not override the self._template_path | ||
pass | ||
|
||
def save_pdf(self, file_path: _Pathlike) -> None: | ||
"""Exports the updated pptx to the specified filepath as pdf file. | ||
|
||
Args: | ||
file_path (path-like) file name or path | ||
""" | ||
|
||
try: | ||
subprocess.run(['libreoffice', '--version'], | ||
stdout=subprocess.DEVNULL) # check if libreoffice is installed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving the |
||
|
||
path = Path(file_path) | ||
outdir = path.parent | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe inline? Declaring/Initializing variables that are use 10 lines below tends to hinder my reading flow, because I instantaneously try to figure out why this variable is needed now... |
||
file_name = path.name | ||
|
||
# create temporary directory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After refactoring to |
||
with tempfile.TemporaryDirectory() as tmpdirname: | ||
template_temporary_path = f'{tmpdirname}/{file_name}' | ||
|
||
# save current Template as pptx in temporary directory | ||
# TODO replace with self.save() method | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do so now, as the implementation of the save method was already merged into master. |
||
self._presentation.save(template_temporary_path) | ||
|
||
export_cmd = ['libreoffice', '--headless', '--convert-to', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a huge fan of defining these kind of commands in the middle of the other code, because this yet again hinders my reading flow. I would prefer to have a |
||
'pdf', '--outdir', outdir, template_temporary_path] | ||
p = subprocess.run( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be cool if we could do some error handling. Unfortunately, on my machine, the exit code is 0 regardless of the file being converted or not. Still, one way would be to capture the stderr of a successful run:
stderr of an erroneous run:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we don't know what to look for in Instead, I propose to check if the intended output file exists. If so, do something like
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, checking whether the PDF file exists is a far simpler (and hence superior) idea. 👍 |
||
export_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | ||
except FileNotFoundError: | ||
raise LibreOfficeNotFoundError("Libre Office not found.") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
# TODO | ||
from pptx_blueprint import Template | ||
from pptx_blueprint import Template, LibreOfficeNotFoundError | ||
from pathlib import Path | ||
import pytest | ||
import os | ||
import subprocess | ||
|
||
|
||
@pytest.fixture | ||
|
@@ -38,3 +40,18 @@ def test_find_shapes_from_one_slide(template): | |
def test_find_shapes_index_out_of_range(template): | ||
with pytest.raises(IndexError): | ||
shapes = template._find_shapes(0, 'logo') | ||
|
||
|
||
def test_save_pdf(template): | ||
try: | ||
subprocess.run(['libreoffice', '--version'], | ||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # check if libreoffice is installed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the comment in the |
||
output_path = 'test/test.pdf' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be handled using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In pytest, this is even simplert with the |
||
template.save_pdf(output_path) | ||
path = Path(output_path) | ||
assert path.exists() == True | ||
if path.exists(): | ||
os.remove(path) | ||
Path.rmdir(path.parent) | ||
except FileNotFoundError: | ||
pytest.skip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I don't really see the advantage of extending
FileNotFoundError
. Looking into the future, I would like to have a common base class for allpptx_blueprint
-related exceptions, as this would allow for a 'catch all' except clause. And as far as I'm concerned, multiple inheritance is off the table ;-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I see the point of a 'catch all' specific for all
pptx_blueprint
errors. In particular, we're also not going to warp every possible exception that may be indirectly raised through our code (e.g. cannot save because disk is full).However, it's ok to just derive from
Exception
here for the moment. We can later decide if we want to refine this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a parent exception does only make sense if we would catch all exceptions and rethrow library-specific exceptions, yes. It would allow the end user to write code like this:
This is not a must-have feature, but as an end user I like the convenience of it. Still, you'd have to catch and rethrow all exceptions, and I could perfectly understand anybody arguing that it's just not worth it :)