Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ To run the test suite enter in your terminal:
uv run tox
```

#### Pull Requests

Ensure there is an existing issue describing the bug, feature, or improvement. If not, create one and discuss the approach before starting work. Fork the repository and create a feature branch from the `dev` branch.

**PR Requirements:**
- Code builds successfully and all tests pass.
- New functionality includes appropriate tests.
- Documentation is updated if behavior or usage has changed.
- No unnecessary formatting or unrelated refactoring is included.

#### Building

Building can also be done with `uv`! The build system (flit) is specified in the `pyproject.toml` file and should have been installed automatically by `uv`. To build the wheels enter in your terminal:
Expand Down
85 changes: 85 additions & 0 deletions etc/development scripts/load_and_redact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from pathlib import Path
import drawpyo
from drawpyo import load_diagram

# --------------------------------------------------
# Paths
# --------------------------------------------------

base_dir = Path.home() / "Test Drawpyo Charts"
base_dir.mkdir(parents=True, exist_ok=True)

drawio_input_path = (
Path(__file__).parent
/ ".."
/ "reference drawio charts"
/ "Pourover Flowchart.drawio"
).resolve()

redaction_map_path = base_dir / "redaction_map.json"
drawio_redacted_output_path = base_dir / "Redacted Drawio File.drawio"
drawio_restored_output_path = base_dir / "Restored Drawio File.drawio"

# --------------------------------------------------
# Load diagram
# --------------------------------------------------

diagram = load_diagram(drawio_input_path)

# --------------------------------------------------
# Redact diagram + write map
# --------------------------------------------------

redacted_diagram = drawpyo.utils.redact_values(
diagram,
map_file_path=redaction_map_path,
)

# --------------------------------------------------
# Write redacted draw.io file
# --------------------------------------------------

redacted_file = drawpyo.File()
redacted_file.file_path = str(base_dir)
redacted_file.file_name = drawio_redacted_output_path.name

redacted_page = drawpyo.Page(file=redacted_file)

# Add shapes
for shape in redacted_diagram.shapes:
shape.page = redacted_page

# Add edges
for edge in redacted_diagram.edges:
edge.page = redacted_page

redacted_file.write()

# --------------------------------------------------
# Restore diagram from map
# --------------------------------------------------

restored_diagram = drawpyo.utils.restore_values(
redacted_diagram,
map_file_path=redaction_map_path,
)

# --------------------------------------------------
# Write restored (unredacted) draw.io file
# --------------------------------------------------

restored_file = drawpyo.File()
restored_file.file_path = str(base_dir)
restored_file.file_name = drawio_restored_output_path.name

restored_page = drawpyo.Page(file=restored_file)

# Add shapes
for shape in restored_diagram.shapes:
shape.page = restored_page

# Add edges
for edge in restored_diagram.edges:
edge.page = restored_page

restored_file.write()
3 changes: 2 additions & 1 deletion src/drawpyo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .utils.logger import logger
from .utils.page_sizes import PageSize

from .drawio_import import load_diagram
from .drawio_import import load_diagram, ParsedDiagram

from . import utils
from . import diagram
Expand All @@ -27,6 +27,7 @@
diagram_types,
drawio_import,
load_diagram,
ParsedDiagram,
]

__version__ = "0.2.4"
2 changes: 2 additions & 0 deletions src/drawpyo/diagram/edges.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from os import path
from typing import Optional, Dict, Any, List, Union, Tuple
from ..utils.logger import logger
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram/extended_objects.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import List as ListType, Optional, Any, Union
from .objects import Object, object_from_library

Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram/objects.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from os import path
from typing import Optional, Dict, Any, List, Union, Tuple
from ..utils.logger import logger
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram/text_format.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Optional, Dict, Any, Union
from ..utils.logger import logger
from .base_diagram import DiagramBase
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram_types/bar_chart.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Callable, Union, Optional
from copy import deepcopy
from ..diagram.objects import Object, Group
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram_types/class_diagram.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Optional, Any

from .tree import NodeObject, TreeGroup, TreeDiagram
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram_types/legend.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Union, Optional
from copy import deepcopy
from ..diagram.objects import Object, Group
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/diagram_types/pie_chart.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Callable, Union, Optional
from copy import deepcopy
from ..diagram.objects import Object, Group
Expand Down
4 changes: 3 additions & 1 deletion src/drawpyo/drawio_import/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
from .raw import RawMxCell, RawGeometry
from .drawio_parser import load_diagram
from .drawio_parser import load_diagram, ParsedDiagram

__all__ = [ParsedDiagram, RawGeometry, RawMxCell]
4 changes: 3 additions & 1 deletion src/drawpyo/drawio_import/drawio_parser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Dict, List, Optional
from dataclasses import dataclass, field

from .raw import RawMxCell, RawGeometry
from drawpyo import logger
from drawpyo.utils import logger
from drawpyo.diagram import Object, Edge, DiagramBase


Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/drawio_import/raw.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional, List, Tuple

Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/file.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import List, Optional, Any, Union, Dict
from .xml_base import XMLBase
from datetime import datetime
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/page.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import List, Optional, Any, Union, Dict
from .xml_base import XMLBase
from .utils.logger import logger
Expand Down
3 changes: 2 additions & 1 deletion src/drawpyo/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
from .standard_colors import StandardColor
from .color_scheme import ColorScheme
from .page_sizes import PageSize
from .redact import redact_values, restore_values

__all__ = [logger, StandardColor, ColorScheme, PageSize]
__all__ = [logger, StandardColor, ColorScheme, PageSize, redact_values, restore_values]
1 change: 1 addition & 0 deletions src/drawpyo/utils/color_scheme.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

from typing import Union
import re
from .logger import logger
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/utils/logger.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import logging

"""Set the logging level.
Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/utils/page_sizes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from enum import Enum


Expand Down
112 changes: 112 additions & 0 deletions src/drawpyo/utils/redact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from __future__ import annotations

from drawpyo.diagram import Object, Edge
from drawpyo.drawio_import import ParsedDiagram
from drawpyo.utils import logger
from pathlib import Path
import json


def _redact_text(text: str | None) -> str:
"""
Redact a text string by replacing all non-whitespace characters with 'X'.

Args:
text: The input text to redact, or None.

Returns:
The redacted text with non-whitespace characters replaced by 'X',
or None if the input was None.
"""
if text is None:
return ""
return "".join(ch if ch.isspace() else "X" for ch in text)


def redact_values(diagram: ParsedDiagram, map_file_path: Path) -> ParsedDiagram:
"""
Redact all textual values in a diagram.

Original values are stored in a JSON file, keyed by element ID, so they
can later be restored.

Args:
diagram: A diagram instance containing shapes and edges.
map_file_path: File path where the ID-to-original-value mapping
will be written as JSON.

Returns:
The modified diagram with all applicable text fields redacted.
"""
id_value_map: dict[str, str] = {}

for node in diagram.shapes + diagram.edges:
if isinstance(node, Object):
if node.value is None:
continue
id_value_map[node.id] = node.value
node.value = _redact_text(node.value)

elif isinstance(node, Edge):
if node.label is None:
continue
id_value_map[node.id] = node.label
node.label = _redact_text(node.label)

with open(map_file_path, "w", encoding="utf-8") as f:
json.dump(
id_value_map,
f,
indent=2,
ensure_ascii=False,
sort_keys=True,
)

logger.info(f"👤 Reaction map saved to: '{map_file_path}'")
return diagram


def restore_values(diagram: ParsedDiagram, map_file_path: Path) -> ParsedDiagram:
"""
Restore redacted diagram values from a JSON mapping file.

Args:
diagram: A diagram instance containing shapes and edges.
map_file_path: Path to the JSON file created by `redact_values`.

Returns:
The modified diagram with restored text values.
"""
if not map_file_path.exists():
raise FileNotFoundError(f"Restore map not found: {map_file_path}")

with map_file_path.open("r", encoding="utf-8") as f:
id_value_map: dict[str, str] = json.load(f)

restored = 0
missing = 0

for cell_id, original_value in id_value_map.items():
matched_element = None

for element in diagram.shapes + diagram.edges:
if str(element.id) == str(cell_id):
matched_element = element
break

if matched_element is None:
missing += 1
logger.warning(f" No matching object/edge found: '{cell_id}'")
continue

if isinstance(matched_element, Object):
matched_element.value = original_value
restored += 1

elif isinstance(matched_element, Edge):
matched_element.label = original_value
restored += 1

logger.info(f"🔄 Restore complete: restored={restored}, missing={missing}")

return diagram
2 changes: 2 additions & 0 deletions src/drawpyo/utils/standard_colors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from enum import Enum


Expand Down
2 changes: 2 additions & 0 deletions src/drawpyo/xml_base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Dict, Optional, Any, Union

xmlize: Dict[str, str] = {}
Expand Down