From a3c37382a7d6141910d4ce7f34e4d302ac2cebf0 Mon Sep 17 00:00:00 2001 From: Nicolas Pogeant <64933282+npogeant@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:18:19 +0000 Subject: [PATCH 1/5] Replace diagnostic print() calls with logging --- src/deckflow/adders/image_adder.py | 7 ++++-- src/deckflow/content/chart_extractor.py | 7 ++++-- src/deckflow/content/registry.py | 9 ++++--- src/deckflow/content/table_extractor.py | 7 ++++-- src/deckflow/content/text_extractor.py | 7 ++++-- src/deckflow/elements/table.py | 13 ++++++---- src/deckflow/elements/text.py | 13 ++++++---- src/deckflow/formatters/font_properties.py | 7 ++++-- src/deckflow/updaters/chart_updater.py | 7 ++++-- src/deckflow/updaters/table_updater.py | 29 ++++++++++++---------- 10 files changed, 68 insertions(+), 38 deletions(-) diff --git a/src/deckflow/adders/image_adder.py b/src/deckflow/adders/image_adder.py index 64dd6bc..d2d71c7 100644 --- a/src/deckflow/adders/image_adder.py +++ b/src/deckflow/adders/image_adder.py @@ -1,6 +1,9 @@ +import logging from typing import Any from PIL import Image +logger = logging.getLogger(__name__) + class ImageAdder: """Adder for adding images to slides.""" @@ -49,6 +52,6 @@ def add_image_from_text(slide: Any, text_element: Any, image_path: str, keep_hei slide.shapes.add_picture(image_path, left=left, top=top, width=width, height=height) return True - except Exception as e: - print(f"Error adding image: {e}") + except Exception: + logger.exception("Error adding image") return False diff --git a/src/deckflow/content/chart_extractor.py b/src/deckflow/content/chart_extractor.py index 50cb911..8b28906 100644 --- a/src/deckflow/content/chart_extractor.py +++ b/src/deckflow/content/chart_extractor.py @@ -1,5 +1,8 @@ +import logging from typing import Any, Dict, List +logger = logging.getLogger(__name__) + def extract_chart_data(chart: Any) -> Dict[str, Any]: """ Extract chart data safely from a pptx chart object.""" data: Dict[str, Any] = {'categories': [], 'series': {}} @@ -19,6 +22,6 @@ def extract_chart_data(chart: Any) -> Dict[str, Any]: if not isinstance(data.get('series'), dict): data['series'] = {} data['series'][name] = values - except Exception as e: - print(f"Error extracting chart data: {e}") + except Exception: + logger.exception("Error extracting chart data") return data \ No newline at end of file diff --git a/src/deckflow/content/registry.py b/src/deckflow/content/registry.py index 6cadf1b..cc84036 100644 --- a/src/deckflow/content/registry.py +++ b/src/deckflow/content/registry.py @@ -1,5 +1,8 @@ +import logging from typing import Any, List, Dict, Optional +logger = logging.getLogger(__name__) + class ContentRegistry: """Registry for managing slide content items.""" @@ -14,12 +17,12 @@ def get_item_by_name(self, items: List[Dict[str, Any]], name: str, item_type: st from .duplicate import DuplicateManager if DuplicateManager.has_duplicates(items, name): - print(f"{name} has duplicates!") + logger.warning("%s has duplicates!", name) return None - + item = next((item for item in items if item['name'] == name), None) if item is None: - print(f"No {item_type} found with this name") + logger.warning("No %s found with this name: %s", item_type, name) return None return item diff --git a/src/deckflow/content/table_extractor.py b/src/deckflow/content/table_extractor.py index e514bc2..6379f65 100644 --- a/src/deckflow/content/table_extractor.py +++ b/src/deckflow/content/table_extractor.py @@ -1,5 +1,8 @@ +import logging from typing import Any, List +logger = logging.getLogger(__name__) + def extract_table_data(table: Any) -> List[List[str]]: """ Extract table data safely from a pptx table object. @@ -14,7 +17,7 @@ def extract_table_data(table: Any) -> List[List[str]]: for cell in row.cells: row_data.append(cell.text) data.append(row_data) - except Exception as e: - print(f"Error extracting table data: {e}") + except Exception: + logger.exception("Error extracting table data") return data \ No newline at end of file diff --git a/src/deckflow/content/text_extractor.py b/src/deckflow/content/text_extractor.py index a89b4ba..fda0ff7 100644 --- a/src/deckflow/content/text_extractor.py +++ b/src/deckflow/content/text_extractor.py @@ -1,5 +1,8 @@ +import logging from typing import Any +logger = logging.getLogger(__name__) + def extract_text(shape: Any) -> str: """ Extract text data safely from a pptx shape.""" try: @@ -9,6 +12,6 @@ def extract_text(shape: Any) -> str: parts = [p.text for p in shape.text_frame.paragraphs] return "\n".join(parts) return "" - except Exception as e: - print(f"Error extracting text: {e}") + except Exception: + logger.exception("Error extracting text") return "" \ No newline at end of file diff --git a/src/deckflow/elements/table.py b/src/deckflow/elements/table.py index b4fe68f..56d7402 100644 --- a/src/deckflow/elements/table.py +++ b/src/deckflow/elements/table.py @@ -1,3 +1,4 @@ +import logging from typing import Any, List, Optional, Union from ..content.table_extractor import extract_table_data @@ -6,6 +7,8 @@ from ..updaters.table_updater import TableUpdater from ..formatters.table_printer import TablePrinter +logger = logging.getLogger(__name__) + class DeckTable: """Class to manage an individual PowerPoint table.""" @@ -33,21 +36,21 @@ def get_cell(self, row: int, col: int) -> Optional[str]: if 0 <= row < self.rows and 0 <= col < self.cols: return self.data[row][col] else: - print(f"Invalid cell indices ({row}, {col}). Table size: {self.rows}x{self.cols}") + logger.warning("Invalid cell indices (%d, %d). Table size: %dx%d", row, col, self.rows, self.cols) return None - + def get_row(self, row_index: int) -> Optional[List[str]]: if 0 <= row_index < self.rows: return self.data[row_index][:] else: - print(f"Invalid row index {row_index}. Table has {self.rows} rows") + logger.warning("Invalid row index %d. Table has %d rows", row_index, self.rows) return None - + def get_column(self, col_index: int) -> Optional[List[str]]: if 0 <= col_index < self.cols: return [row[col_index] for row in self.data] else: - print(f"Invalid column index {col_index}. Table has {self.cols} columns") + logger.warning("Invalid column index %d. Table has %d columns", col_index, self.cols) return None def update_cell(self, row: int, col: int, value: str, color_by_value: bool = False): diff --git a/src/deckflow/elements/text.py b/src/deckflow/elements/text.py index 8bacc56..43f59aa 100644 --- a/src/deckflow/elements/text.py +++ b/src/deckflow/elements/text.py @@ -1,3 +1,4 @@ +import logging from typing import Any from ..content.text_extractor import extract_text @@ -5,6 +6,8 @@ from ..formatters.font_properties import copy_font_properties from ..updaters.text_updater import TextUpdater +logger = logging.getLogger(__name__) + class DeckText: """Class to manage an individual PowerPoint text element.""" @@ -34,7 +37,7 @@ def update(self, new_text: str, color_by_value: bool = False): self.color_by_value = color_by_value if self.current_content == self.original_content: - print(f"No changes to save for shape {self.name}") + logger.debug("No changes to save for shape %s", self.name) return True try: @@ -43,10 +46,10 @@ def update(self, new_text: str, color_by_value: bool = False): elif hasattr(self.shape, "text"): self.shape.text = self.current_content else: - print(f"Text {self.name} has no text support") + logger.warning("Text %s has no text support", self.name) return False - print(f"Text updated for {self.name}") + logger.debug("Text updated for %s", self.name) return True - except Exception as e: - print(f"Error updating text {self.name}: {e}") + except Exception: + logger.exception("Error updating text %s", self.name) return False \ No newline at end of file diff --git a/src/deckflow/formatters/font_properties.py b/src/deckflow/formatters/font_properties.py index f10cc32..4d449f1 100644 --- a/src/deckflow/formatters/font_properties.py +++ b/src/deckflow/formatters/font_properties.py @@ -1,5 +1,8 @@ +import logging from typing import Any +logger = logging.getLogger(__name__) + def copy_font_properties(source_font: Any, target_font: Any): """Copy font properties while tolerating missing attributes.""" @@ -22,5 +25,5 @@ def copy_font_properties(source_font: Any, target_font: Any): target_font.color.brightness = source_font.color.brightness except Exception: pass - except Exception as e: - print(f"Warning: Could not copy some font properties: {e}") \ No newline at end of file + except Exception: + logger.warning("Could not copy some font properties", exc_info=True) \ No newline at end of file diff --git a/src/deckflow/updaters/chart_updater.py b/src/deckflow/updaters/chart_updater.py index c0fe649..9bbd62f 100644 --- a/src/deckflow/updaters/chart_updater.py +++ b/src/deckflow/updaters/chart_updater.py @@ -1,5 +1,8 @@ +import logging from typing import Any +logger = logging.getLogger(__name__) + class ChartUpdater: """Updater for chart elements, preserving formatting/colors.""" @@ -19,6 +22,6 @@ def apply(self, data: dict) -> bool: chart_data.add_series(name, clean_values) self.chart.replace_data(chart_data) return True - except Exception as e: - print(f"Error applying chart data: {e}") + except Exception: + logger.exception("Error applying chart data") return False \ No newline at end of file diff --git a/src/deckflow/updaters/table_updater.py b/src/deckflow/updaters/table_updater.py index b8ea344..c595034 100644 --- a/src/deckflow/updaters/table_updater.py +++ b/src/deckflow/updaters/table_updater.py @@ -1,7 +1,10 @@ +import logging from typing import Any, List from pptx.util import Pt from pptx.dml.color import RGBColor +logger = logging.getLogger(__name__) + class TableUpdater: """Updater for table elements.""" @@ -18,34 +21,34 @@ def __init__(self, table: Any, color_analyzer: Any, formatter: Any): def update_cell(self, row: int, col: int, value: Any) -> bool: if 0 <= row < self.rows and 0 <= col < self.cols: self.table.data[row][col] = "" if value is None else str(value) - print(f"Cell ({row},{col}) -> '{self.table.data[row][col]}'") + logger.debug("Cell (%d,%d) -> '%s'", row, col, self.table.data[row][col]) return True - print(f"Invalid cell indices ({row},{col}) for table {self.rows}x{self.cols}") + logger.warning("Invalid cell indices (%d,%d) for table %dx%d", row, col, self.rows, self.cols) return False def update_row(self, row_index: int, values: List[Any]) -> bool: if not (0 <= row_index < self.rows): - print(f"Invalid row index {row_index}") + logger.warning("Invalid row index %d", row_index) return False for c in range(min(self.cols, len(values))): self.table.data[row_index][c] = "" if values[c] is None else str(values[c]) - print(f"Row {row_index} updated") + logger.debug("Row %d updated", row_index) return True def update_column(self, col_index: int, values: List[Any]) -> bool: if not (0 <= col_index < self.cols): - print(f"Invalid column index {col_index}") + logger.warning("Invalid column index %d", col_index) return False for r in range(min(self.rows, len(values))): self.table.data[r][col_index] = "" if values[r] is None else str(values[r]) - print(f"Column {col_index} updated") + logger.debug("Column %d updated", col_index) return True - + def save_changes(self, color_by_value: bool = False): try: # Check if there are changes to save if self.table.data == self.table.original_data: - print(f"No changes to save for table {self.table.name}") + logger.debug("No changes to save for table %s", self.table.name) return True # Apply changes to the actual PowerPoint table object @@ -79,14 +82,14 @@ def save_changes(self, color_by_value: bool = False): else: cell.text = new_text - except Exception as e: - print(f"Warning: Could not update cell ({row_idx},{col_idx}): {e}") + except Exception: + logger.warning("Could not update cell (%d,%d)", row_idx, col_idx, exc_info=True) # Update the original state self.table.original_data = [row[:] for row in self.table.data] - print(f"Table {self.table.name} changes applied") + logger.debug("Table %s changes applied", self.table.name) return True - except Exception as e: - print(f"Error saving table changes: {e}") + except Exception: + logger.exception("Error saving table changes") return False \ No newline at end of file From 50e1762b3d53fb2e824174034f7738bc0218749e Mon Sep 17 00:00:00 2001 From: Nicolas Pogeant <64933282+npogeant@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:20:02 +0000 Subject: [PATCH 2/5] Declare Pillow as an explicit dependency --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8b14bc1..ae7925b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ readme = "README.md" requires-python = ">=3.9" # Support 3.9+ dependencies = [ "python-pptx>=1.0.2", + "Pillow>=9.0.0", ] license = "MIT" license-files = ["LICEN[CS]E*"] @@ -22,3 +23,6 @@ test = [ [project.urls] Homepage = "https://github.com/npogeant/deckflow" + +[tool.setuptools.package-data] +deckflow = ["py.typed"] From 718e47cbfaa3a1a40e8b07511afcc918d4039aac Mon Sep 17 00:00:00 2001 From: Nicolas Pogeant <64933282+npogeant@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:20:11 +0000 Subject: [PATCH 3/5] Add py.typed marker for downstream type checking --- src/deckflow/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/deckflow/py.typed diff --git a/src/deckflow/py.typed b/src/deckflow/py.typed new file mode 100644 index 0000000..e69de29 From 257613f365da2490e8a2a969da4ee5a2daf1d533 Mon Sep 17 00:00:00 2001 From: Nicolas Pogeant <64933282+npogeant@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:23:13 +0000 Subject: [PATCH 4/5] Batch chart updates into a single replace_data call --- src/deckflow/elements/chart.py | 14 ++++++++++++-- src/deckflow/slide.py | 7 +------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/deckflow/elements/chart.py b/src/deckflow/elements/chart.py index 02379cf..933ce0b 100644 --- a/src/deckflow/elements/chart.py +++ b/src/deckflow/elements/chart.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any, Dict, List from copy import deepcopy from ..content.chart_extractor import extract_chart_data @@ -33,6 +33,16 @@ def update_series(self, series_name: str, new_values: List[float]): def update_categories(self, new_categories: List[str]): self.data['categories'] = list(new_categories) return self._updater.apply(self.data) - + + def update(self, new_data: Dict[str, Any]): + """Merge categories/series from new_data and apply in a single write.""" + if "categories" in new_data: + self.data['categories'] = list(new_data["categories"]) + if "series" in new_data: + series = self.data.setdefault('series', {}) + for series_name, values in new_data["series"].items(): + series[series_name] = [v if v is not None else 0 for v in values] + return self._updater.apply(self.data) + def save_changes(self): return self._updater.apply(self.data) \ No newline at end of file diff --git a/src/deckflow/slide.py b/src/deckflow/slide.py index 144653f..f8f4f49 100644 --- a/src/deckflow/slide.py +++ b/src/deckflow/slide.py @@ -69,12 +69,7 @@ def update_chart(self, name: str, new_data: Dict[str, Any]) -> None: if not chart: raise ValueError(f"Chart '{name}' not found") - if "categories" in new_data: - chart.update_categories(new_data["categories"]) - if "series" in new_data: - for series_name, values in new_data["series"].items(): - chart.update_series(series_name, values) - chart.save_changes() + chart.update(new_data) def update_table(self, name: str, new_data: List, by_rows: bool = True, by_columns: bool = False, color_by_value: bool = False) -> None: """Update a table with new data either by rows or by columns.""" From 2ae1c9ee4585f36d17954960a351301d89fc4d07 Mon Sep 17 00:00:00 2001 From: Nicolas Pogeant <64933282+npogeant@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:23:25 +0000 Subject: [PATCH 5/5] De-duplicate remove_text/remove_chart/remove_table --- src/deckflow/slide.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/deckflow/slide.py b/src/deckflow/slide.py index f8f4f49..2711db9 100644 --- a/src/deckflow/slide.py +++ b/src/deckflow/slide.py @@ -102,32 +102,24 @@ def add_image_from_text(self, text_name: str, image_path: str, keep_height: bool if not success: raise RuntimeError(f"Failed to add image at text element '{text_name}'") - def remove_text(self, name: str) -> bool: - """Remove a text element by its name.""" - text_obj = self.get_text(name) - if not text_obj: - raise ValueError(f"Text element '{name}' not found") - removed = ElementRemover.remove_shape(text_obj.shape) + def _remove_element(self, name: str, getter, label: str) -> bool: + """Look up an element by name via getter and remove its shape from the slide.""" + element = getter(name) + if not element: + raise ValueError(f"{label} '{name}' not found") + removed = ElementRemover.remove_shape(element.shape) if not removed: - raise RuntimeError(f"Failed to remove text element '{name}'") + raise RuntimeError(f"Failed to remove {label.lower()} '{name}'") return True + def remove_text(self, name: str) -> bool: + """Remove a text element by its name.""" + return self._remove_element(name, self.get_text, "Text element") + def remove_chart(self, name: str) -> bool: """Remove a chart element by its name.""" - chart_obj = self.get_chart(name) - if not chart_obj: - raise ValueError(f"Chart '{name}' not found") - removed = ElementRemover.remove_shape(chart_obj.shape) - if not removed: - raise RuntimeError(f"Failed to remove chart '{name}'") - return True + return self._remove_element(name, self.get_chart, "Chart") def remove_table(self, name: str) -> bool: """Remove a table element by its name.""" - table_obj = self.get_table(name) - if not table_obj: - raise ValueError(f"Table '{name}' not found") - removed = ElementRemover.remove_shape(table_obj.shape) - if not removed: - raise RuntimeError(f"Failed to remove table '{name}'") - return True \ No newline at end of file + return self._remove_element(name, self.get_table, "Table") \ No newline at end of file