Examples

The source for all examples lives in packages/ducpy/src/examples/.


Element Creation

Demonstrates building rectangles, ellipses, polygons, lines, arrows, text, frames and plots using the fluent builder DSL.

  1#!/usr/bin/env python3
  2"""
  3Example demonstrating the element creation functionality using the builders API.
  4This demo shows how to create various types of elements using the modern builders pattern.
  5"""
  6
  7import tempfile
  8
  9import ducpy as duc
 10from ducpy.builders.style_builders import (create_fill_and_stroke_style,
 11                                           create_simple_styles,
 12                                           create_solid_content)
 13
 14
 15def demo_basic_elements():
 16    """Demo basic elements using the builders API."""
 17    print("=== Basic Elements Demo ===")
 18
 19    rect = (duc.ElementBuilder()
 20        .at_position(0, 0)
 21        .with_size(100, 50)
 22        .with_label("Sample Rectangle")
 23        .with_styles(create_fill_and_stroke_style(
 24            fill_content=create_solid_content("#FF6B6B"),
 25            stroke_content=create_solid_content("#2C3E50"),
 26            stroke_width=2.0,
 27            roundness=5.0
 28        ))
 29        .build_rectangle()
 30        .build())
 31
 32    ellipse = (duc.ElementBuilder()
 33        .at_position(120, 0)
 34        .with_size(60, 40)
 35        .with_label("Sample Ellipse")
 36        .with_styles(create_fill_and_stroke_style(
 37            fill_content=create_solid_content("#4ECDC4"),
 38            stroke_content=create_solid_content("#34495E"),
 39            stroke_width=1.5
 40        ))
 41        .build_ellipse()
 42        .build())
 43
 44    poly = (duc.ElementBuilder()
 45        .at_position(200, 0)
 46        .with_size(50, 50)
 47        .with_label("Hexagon")
 48        .with_styles(create_fill_and_stroke_style(
 49            fill_content=create_solid_content("#45B7D1"),
 50            stroke_content=create_solid_content("#2C3E50"),
 51            stroke_width=1.0,
 52            roundness=0.0
 53        ))
 54        .build_polygon()
 55        .with_sides(6)
 56        .build())
 57
 58    print(f"Rectangle ID: {rect.element.base.id}")
 59    print(f"Ellipse ID: {ellipse.element.base.id}")
 60    print(f"Polygon sides: {poly.element.sides}")
 61
 62    # Demonstrate mutation with random versioning
 63    duc.mutate_element(rect, x=10, label="Moved Rectangle")
 64
 65    return [rect, ellipse, poly]
 66
 67
 68def demo_linear_elements():
 69    """Demo linear and arrow elements with styles."""
 70    print("\n=== Linear Elements Demo ===")
 71
 72    line_points = [(0, 0), (50, 25), (100, 0)]
 73    line = (duc.ElementBuilder()
 74        .with_label("Sample Line")
 75        .with_styles(create_simple_styles(
 76            strokes=[duc.create_stroke(duc.create_solid_content("#E74C3C"), width=3.0)]
 77        ))
 78        .build_linear_element()
 79        .with_points(line_points)
 80        .build())
 81    print(f"Line has {len(line.element.linear_base.points)} points")
 82
 83    arrow_points = [(0, 50), (75, 100)]
 84    arrow = (duc.ElementBuilder()
 85        .with_label("Sample Arrow")
 86        .with_styles(create_simple_styles(
 87            strokes=[duc.create_stroke(duc.create_solid_content("#8E44AD"), width=2.5)]
 88        ))
 89        .build_arrow_element()
 90        .with_points(arrow_points)
 91        .build())
 92    print(f"Arrow element type: {type(arrow.element).__name__}")
 93
 94    return [line, arrow]
 95
 96
 97def demo_text_elements():
 98    """Demo text elements with styles and document formatting."""
 99    print("\n=== Text Elements Demo ===")
100
101    text = (duc.ElementBuilder()
102        .at_position(0, 100)
103        .with_size(150, 25)
104        .with_label("Sample Text")
105        .with_styles(create_simple_styles(opacity=0.9))
106        .build_text_element()
107        .with_text("Hello, DucPy!")
108        .build())
109    print(f"Text content: '{text.element.text}'")
110
111    return [text]
112
113
114def demo_stack_elements():
115    """Demo new stack-based elements with styles."""
116    print("\n=== Stack Elements Demo ===")
117
118    frame = (duc.ElementBuilder()
119        .at_position(0, 150)
120        .with_size(200, 100)
121        .with_label("Technical Frame")
122        .with_styles(create_fill_and_stroke_style(
123            fill_content=create_solid_content("#F8F9FA"),
124            stroke_content=create_solid_content("#495057"),
125            stroke_width=2.0,
126            roundness=3.0
127        ))
128        .build_frame_element()
129        .build())
130    print(f"Frame stack label: {frame.element.stack_element_base.stack_base.label}")
131
132    plot = (duc.ElementBuilder()
133        .at_position(220, 150)
134        .with_size(180, 120)
135        .with_label("Engineering Plot")
136        .with_styles(create_fill_and_stroke_style(
137            fill_content=create_solid_content("#E9ECEF"),
138            stroke_content=create_solid_content("#6C757D"),
139            stroke_width=1.5
140        ))
141        .build_plot_element()
142        .with_margins(duc.Margins(top=5, right=5, bottom=5, left=5))
143        .build())
144
145    return [frame, plot]
146
147
148def demo_custom_stack_base():
149    """Demo custom stack base creation."""
150    print("\n=== Custom Stack Base Demo ===")
151
152    custom_frame = (duc.ElementBuilder()
153        .at_position(50, 280)
154        .with_size(150, 80)
155        .with_label("Custom Container")
156        .build_frame_element()
157        .with_stack_base(duc.StateBuilder().build_stack_base()
158            .with_is_collapsed(False)
159            .with_styles(duc.DucStackLikeStyles(opacity=0.8))
160            .build())
161        .build())
162
163    return [custom_frame]
164
165
166def main():
167    """Run all element creation demos."""
168    print("DucPy Element Creation Demo")
169    print("=" * 40)
170
171    elements = []
172    elements.extend(demo_basic_elements())
173    elements.extend(demo_linear_elements())
174    elements.extend(demo_text_elements())
175    elements.extend(demo_stack_elements())
176    elements.extend(demo_custom_stack_base())
177
178    output = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
179    output.close()
180    duc_path = duc.serialize_duc(
181        name="element_creation_example",
182        output_path=output.name,
183        elements=elements,
184    )
185
186    print(f"\nCreated {len(elements)} elements → serialized to {duc_path}.")
187    print("✅ Element creation demo complete!")
188    return duc_path
189
190
191if __name__ == "__main__":
192    main()

Mutating Elements

Shows how to update element properties in place and observe version changes.

  1#!/usr/bin/env python3
  2"""
  3Example demonstrating the `mutate` API for elements, global state, and
  4external file entries in DUC.
  5
  6This demo shows how to:
  7  1. Build an initial set of elements and supporting state via the
  8     standard builder API.
  9  2. Apply targeted mutations to the elements, the global state, and an
 10     external file entry using `duc.mutate_*` helpers.
 11  3. Serialize the resulting DUC object into a `.duc` file.
 12"""
 13
 14import tempfile
 15
 16import ducpy as duc
 17
 18
 19def main():
 20    print("Mutation Demo")
 21    print("=" * 30)
 22
 23    # ------------------------------------------------------------------
 24    # 1. Build the initial elements + state using the existing builders.
 25    # ------------------------------------------------------------------
 26    rect = (duc.ElementBuilder()
 27        .at_position(0, 0)
 28        .with_size(100, 50)
 29        .with_label("Initial Rectangle")
 30        .build_rectangle()
 31        .build())
 32
 33    ellipse = (duc.ElementBuilder()
 34        .at_position(140, 0)
 35        .with_size(60, 40)
 36        .with_label("Initial Ellipse")
 37        .build_ellipse()
 38        .build())
 39
 40    elements = [rect, ellipse]
 41
 42    duc_global_state = (duc.StateBuilder()
 43        .build_global_state()
 44        .with_name("mutation_demo")
 45        .with_main_scope("mm")
 46        .build())
 47
 48    duc_local_state = (duc.StateBuilder()
 49        .build_local_state()
 50        .build())
 51
 52    # A sample external file entry to exercise mutate_external_file.
 53    external_file = (duc.StateBuilder()
 54        .build_external_file()
 55        .with_key("logo")
 56        .with_mime_type("image/png")
 57        .with_data(b"\x89PNG\r\n\x1a\n" + b"\x00" * 16)
 58        .build())
 59
 60    # ------------------------------------------------------------------
 61    # 2. Apply mutations using the duc.mutate_* API.
 62    #    Each helper mutates in place and also stamps fresh versioning
 63    #    metadata (seed, updated, version, version_nonce) where
 64    #    applicable.
 65    # ------------------------------------------------------------------
 66
 67    # 2a. Mutate the rectangle: move it, resize it, rename, hide it.
 68    duc.mutate_element(
 69        rect,
 70        x=20,
 71        y=30,
 72        width=150,
 73        label="Mutated Rectangle",
 74        is_visible=False,
 75    )
 76
 77    # 2b. Mutate the ellipse: rename and move (size stays the same).
 78    duc.mutate_element(
 79        ellipse,
 80        x=200,
 81        y=75,
 82        label="Mutated Ellipse",
 83    )
 84
 85    # 2c. Mutate the global state (zoom level, background, name).
 86    duc.mutate_global_state(
 87        duc_global_state,
 88        view_background_color="#1E1E2E",
 89        name="mutation_demo_updated",
 90    )
 91
 92    # 2d. Mutate the local state (scroll position, grid mode).
 93    duc.mutate_local_state(
 94        duc_local_state,
 95        scroll_x=42.0,
 96        scroll_y=17.5,
 97        grid_mode_enabled=False,
 98    )
 99
100    # 2e. Mutate the external file entry's metadata.
101    duc.mutate_external_file(
102        external_file,
103        version=2,
104    )
105
106    # ------------------------------------------------------------------
107    # 3. Serialize the mutated objects into a .duc file.
108    # ------------------------------------------------------------------
109    output = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
110    output.close()
111    duc_path = duc.serialize_duc(
112        name="mutation_demo",
113        output_path=output.name,
114        elements=elements,
115        duc_global_state=duc_global_state,
116        duc_local_state=duc_local_state,
117        external_files=[external_file],
118    )
119
120    print(f"   Mutated {len(elements)} elements.")
121    print(f"   Global state main scope -> {duc_global_state.main_scope!r}")
122    print(f"   Local state scroll -> ({duc_local_state.scroll_x}, {duc_local_state.scroll_y})")
123    print(f"   External file version -> {external_file.version}")
124    print(f"   Serialized to {duc_path}.")
125    print("\n✅ Mutation demo complete!")
126    return duc_path
127
128
129if __name__ == "__main__":
130    main()

External Files

Attaching binary blobs (images, PDFs) to a duc document.

 1"""
 2Example demonstrating the creation and management of external files within a DUC object.
 3"""
 4
 5import ducpy as duc
 6
 7
 8def create_duc_with_external_files():
 9    """
10    Creates a DUC object and adds multiple external file entries to it
11    using the builder pattern.
12    """
13    print("Creating a DUC object with external files...")
14
15    # Create dummy data for external files
16    dummy_image_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDATx\xda\xed\xc1\x01\x01\x00\x00\x00\xc2\xa0\xf7Om\x00\x00\x00\x00IEND\xaeB`\x82"
17    dummy_pdf_data = b"%PDF-1.4\n1 0 obj <</Type/Catalog/Pages 2 0 R>> endobj\n2 0 obj <</Type/Pages/Count 0>> endobj\nxref\n0 3\n0000000000 65535 f\n0000000009 00000 n\n0000000074 00000 n\ntrailer<</Size 3/Root 1 0 R>>startxref\n123\n%%EOF"
18
19    image_file_entry = (duc.StateBuilder()
20        .build_external_file()
21        .with_key("my_image_key")
22        .with_mime_type("image/png")
23        .with_data(dummy_image_data)
24        .build())
25
26    pdf_file_entry = (duc.StateBuilder()
27        .build_external_file()
28        .with_key("document_123")
29        .with_mime_type("application/pdf")
30        .with_data(dummy_pdf_data)
31        .build())
32
33    duc_global_state = (duc.StateBuilder()
34        .build_global_state()
35        .with_main_scope("mm")
36        .build())
37
38    duc_local_state = (duc.StateBuilder()
39        .build_local_state()
40        .build())
41
42    duc_object_files = {image_file_entry.id: image_file_entry, pdf_file_entry.id: pdf_file_entry}
43
44    print("DUC object with external files created successfully!")
45    print(f"Total external files: {len(duc_object_files)}")
46    return duc_object_files, duc_global_state, duc_local_state
47
48
49def main():
50    """Run the external files demo."""
51    print("External Files Demo")
52    print("=" * 30)
53    create_duc_with_external_files()
54    print("\nExternal files demo complete!")
55
56
57if __name__ == "__main__":
58    main()

SQL Builder

Direct SQLite access via DucSQL. Use this when you need raw queries, bulk inserts, schema introspection, or anything beyond what the high-level builders expose.

  1#!/usr/bin/env python3
  2"""
  3Example demonstrating direct SQLite access to .duc files via DucSQL.
  4
  5A .duc file is a plain SQLite database.  DucSQL exposes the raw sqlite3
  6connection so you can run any SQL you want while handling the open/save/
  7export lifecycle for you.
  8
  9Topics covered:
 10  1. Create a new .duc file with the full schema bootstrapped
 11  2. Insert elements and style data
 12  3. Query rows back as dict-like objects
 13  4. Update elements in place
 14  5. Export to / round-trip from a file path
 15  6. Open an existing .duc file
 16  7. Round-trip SQL-built data through the high-level parser
 17"""
 18
 19import os
 20import tempfile
 21
 22import ducpy as duc
 23from ducpy.builders.sql_builder import DucSQL
 24
 25
 26def demo_create_new():
 27    print("=== Create new .duc ===")
 28
 29    with DucSQL.new() as db:
 30        db.sql(
 31            "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) "
 32            "VALUES (?,?,?,?,?,?,?,?)",
 33            "r1", "rectangle", 0, 0, 200, 100, "Main Rectangle", 1.0,
 34        )
 35        db.sql(
 36            "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) "
 37            "VALUES (?,?,?,?,?,?,?,?)",
 38            "e1", "ellipse", 250, 0, 120, 80, "Side Ellipse", 0.9,
 39        )
 40
 41        for owner_id, colour in [("r1", "#4ECDC4"), ("e1", "#FF6B6B")]:
 42            db.sql(
 43                "INSERT INTO backgrounds (owner_type, owner_id, src, opacity) "
 44                "VALUES (?,?,?,?)",
 45                "element", owner_id, colour, 1.0,
 46            )
 47
 48        rows = db.sql("SELECT id, element_type, label FROM elements ORDER BY id")
 49        for row in rows:
 50            print(f"  [{row['id']}] {row['element_type']}{row['label']}")
 51
 52        db.sql_dict(
 53            "UPDATE elements SET label = :label WHERE id = :id",
 54            {"label": "Renamed Rectangle", "id": "r1"},
 55        )
 56
 57        updated = db.sql("SELECT label FROM elements WHERE id = ?", "r1")[0]
 58        print(f"  After rename: '{updated['label']}'")
 59
 60        tmp = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
 61        tmp.close()
 62        db.save(tmp.name)
 63        print(f"  Saved to: {tmp.name}")
 64
 65    return tmp.name
 66
 67
 68def demo_open_existing(path: str):
 69    print("\n=== Open existing .duc ===")
 70
 71    with DucSQL(path) as db:
 72        count = db.sql("SELECT COUNT(*) AS n FROM elements")[0]["n"]
 73        print(f"  Total elements: {count}")
 74
 75        rows = db.sql(
 76            "SELECT e.id, e.label, b.src AS colour "
 77            "FROM elements e "
 78            "LEFT JOIN backgrounds b ON b.owner_type = 'element' AND b.owner_id = e.id "
 79            "ORDER BY e.id"
 80        )
 81        for row in rows:
 82            print(f"  {row['label']} → fill: {row['colour']}")
 83
 84    os.unlink(path)
 85
 86
 87def demo_file_roundtrip():
 88    print("\n=== File round-trip ===")
 89
 90    with DucSQL.new() as db:
 91        db.sql(
 92            "INSERT INTO elements (id, element_type, x, y, width, height, label) "
 93            "VALUES (?,?,?,?,?,?,?)",
 94            "t1", "text", 10, 10, 300, 40, "Hello, DUC!",
 95        )
 96        tmp = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
 97        tmp.close()
 98        db.save(tmp.name)
 99
100    print(f"  Serialised to {tmp.name}")
101
102    with DucSQL(tmp.name) as db:
103        row = db.sql("SELECT label FROM elements WHERE id = 't1'")[0]
104        print(f"  Restored label: '{row['label']}'")
105
106    return tmp.name
107
108
109def demo_advanced_connection():
110    print("\n=== Advanced: direct connection access ===")
111
112    with DucSQL.new() as db:
113        records = [
114            (f"el{i}", "rectangle", i * 110, 0, 100, 60, f"Box {i}", 1.0)
115            for i in range(5)
116        ]
117        db.conn.executemany(
118            "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) "
119            "VALUES (?,?,?,?,?,?,?,?)",
120            records,
121        )
122
123        total = db.sql("SELECT COUNT(*) AS n FROM elements")[0]["n"]
124        print(f"  Bulk-inserted {total} elements")
125
126        tables = [
127            row["name"]
128            for row in db.sql(
129                "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
130            )
131        ]
132        print(f"  Schema tables: {', '.join(tables[:6])} …")
133
134
135def demo_serialize_via_sql():
136    print("\n=== Build with SQL, serialize with the high-level API ===")
137
138    with DucSQL.new() as db:
139        db.sql(
140            "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) "
141            "VALUES (?,?,?,?,?,?,?,?)",
142            "s1", "rectangle", 0, 0, 100, 50, "From SQL", 1.0,
143        )
144        tmp = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
145        tmp.close()
146        db.save(tmp.name)
147
148    parsed = duc.parse_duc(tmp.name)
149    print(f"  Parsed {len(parsed.elements)} element(s) built via raw SQL.")
150
151    return tmp.name
152
153
154def main():
155    print("DucSQL Builder Demo")
156    print("=" * 40)
157
158    saved_path = demo_create_new()
159    demo_open_existing(saved_path)
160    roundtrip_path = demo_file_roundtrip()
161    demo_advanced_connection()
162    demo_serialize_via_sql()
163
164    print(f"\nAll DucSQL demos completed successfully! (round-trip file: {roundtrip_path})")
165    return roundtrip_path
166
167
168if __name__ == "__main__":
169    main()

Serialization

Demonstrates how to serialize builder-created elements directly to a .duc file using duc.serialize_duc.

 1#!/usr/bin/env python3
 2"""
 3Example demonstrating how to serialize elements created by the Builder API into a .duc file.
 4
 5This demo shows the correct pattern for taking in-memory python elements
 6and streaming them to a `.duc` file.
 7"""
 8
 9import ducpy as duc
10import tempfile
11from ducpy.builders.style_builders import create_fill_and_stroke_style, create_solid_content
12
13def main():
14    print("Serialization Demo")
15    print("=" * 30)
16    
17    print("1. Creating elements via Builder API...")
18    elements = []
19    
20    # Create some basic elements
21    rect = (duc.ElementBuilder()
22        .at_position(0, 0)
23        .with_size(100, 50)
24        .with_label("Sample Rectangle")
25        .with_styles(create_fill_and_stroke_style(
26            fill_content=create_solid_content("#FF6B6B"),
27            stroke_content=create_solid_content("#2C3E50"),
28            stroke_width=2.0
29        ))
30        .build_rectangle()
31        .build())
32    elements.append(rect)
33    
34    ellipse = (duc.ElementBuilder()
35        .at_position(120, 0)
36        .with_size(60, 40)
37        .with_label("Sample Ellipse")
38        .with_styles(create_fill_and_stroke_style(
39            fill_content=create_solid_content("#4ECDC4"),
40            stroke_content=create_solid_content("#34495E"),
41            stroke_width=1.5
42        ))
43        .build_ellipse()
44        .build())
45    elements.append(ellipse)
46    
47    print(f"   Created {len(elements)} elements.")
48    
49    print("2. Serializing to .duc format...")
50    output = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
51    output.close()
52    duc_path = duc.serialize_duc(
53        name="serialization_example",
54        output_path=output.name,
55        elements=elements
56    )
57    
58    print(f"   Successfully serialized to {duc_path}.")
59    print("\n✅ Serialization demo complete!")
60    return duc_path
61
62if __name__ == "__main__":
63    main()

Parsing

Demonstrates how to parse a .duc file or raw binary bytes using duc.parse_duc, allowing attribute-style access to the document’s content.

 1#!/usr/bin/env python3
 2"""
 3Example demonstrating how to parse a .duc file using the parsing API.
 4
 5This demo shows how to read a `.duc` file path and access
 6the parsed data using attribute-style access via DucData.
 7"""
 8
 9import os
10import tempfile
11import ducpy as duc
12
13def main():
14    print("Parsing Demo")
15    print("=" * 30)
16
17    # First, let's create a temporary .duc file to parse
18    from ducpy.builders.style_builders import create_fill_and_stroke_style, create_solid_content
19    elements = [
20        duc.ElementBuilder()
21            .at_position(10, 20)
22            .with_size(100, 50)
23            .with_label("Parsed Rectangle")
24            .with_styles(create_fill_and_stroke_style(
25                fill_content=create_solid_content("#FF6B6B"),
26                stroke_content=create_solid_content("#2C3E50"),
27                stroke_width=2.0
28            ))
29            .build_rectangle()
30            .build()
31    ]
32    tmp = tempfile.NamedTemporaryFile(suffix=".duc", delete=False)
33    tmp.close()
34    tmp_path = duc.serialize_duc(name="parsing_example", output_path=tmp.name, elements=elements)
35
36    print("1. Parsing a .duc file from a file path...")
37    
38    # You can pass a string path directly to parse_duc
39    parsed_data = duc.parse_duc(tmp_path)
40    
41    print(f"   Document Source: {parsed_data.source}")
42    print(f"   Parsed {len(parsed_data.elements)} elements.")
43    
44    print("\n2. Accessing element attributes (snake_case)...")
45    
46    # Element properties are accessible via dot-notation with snake_case keys
47    # because parse_duc returns a DucData object.
48    first_element = parsed_data.elements[0]
49    print(f"   Element ID: {first_element.id}")
50    print(f"   Element Type: {first_element.type}")
51    print(f"   Element Label: {first_element.label}")
52    print(f"   Element Position: (X: {first_element.x}, Y: {first_element.y})")
53    
54    print("\n3. Re-parsing from the streamed file path...")
55    
56    parsed_again = duc.parse_duc(tmp_path)
57    print(f"   Parsed successfully from path. Found {len(parsed_again.elements)} elements.")
58    
59    # Clean up the temporary file
60    os.unlink(tmp_path)
61    
62    print("\n✅ Parsing demo complete!")
63
64if __name__ == "__main__":
65    main()