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()