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 ducpy as duc
8from ducpy.builders.style_builders import (create_fill_and_stroke_style,
9 create_simple_styles,
10 create_solid_content)
11
12
13def demo_basic_elements():
14 """Demo basic elements using the builders API."""
15 print("=== Basic Elements Demo ===")
16
17 rect = (duc.ElementBuilder()
18 .at_position(0, 0)
19 .with_size(100, 50)
20 .with_label("Sample Rectangle")
21 .with_styles(create_fill_and_stroke_style(
22 fill_content=create_solid_content("#FF6B6B"),
23 stroke_content=create_solid_content("#2C3E50"),
24 stroke_width=2.0,
25 roundness=5.0
26 ))
27 .build_rectangle()
28 .build())
29
30 ellipse = (duc.ElementBuilder()
31 .at_position(120, 0)
32 .with_size(60, 40)
33 .with_label("Sample Ellipse")
34 .with_styles(create_fill_and_stroke_style(
35 fill_content=create_solid_content("#4ECDC4"),
36 stroke_content=create_solid_content("#34495E"),
37 stroke_width=1.5
38 ))
39 .build_ellipse()
40 .build())
41
42 poly = (duc.ElementBuilder()
43 .at_position(200, 0)
44 .with_size(50, 50)
45 .with_label("Hexagon")
46 .with_styles(create_fill_and_stroke_style(
47 fill_content=create_solid_content("#45B7D1"),
48 stroke_content=create_solid_content("#2C3E50"),
49 stroke_width=1.0,
50 roundness=0.0
51 ))
52 .build_polygon()
53 .with_sides(6)
54 .build())
55
56 print(f"Rectangle ID: {rect.element.base.id}")
57 print(f"Ellipse ID: {ellipse.element.base.id}")
58 print(f"Polygon sides: {poly.element.sides}")
59
60 # Demonstrate mutation with random versioning
61 duc.mutate_element(rect, x=10, label="Moved Rectangle")
62
63 return [rect, ellipse, poly]
64
65
66def demo_linear_elements():
67 """Demo linear and arrow elements with styles."""
68 print("\n=== Linear Elements Demo ===")
69
70 line_points = [(0, 0), (50, 25), (100, 0)]
71 line = (duc.ElementBuilder()
72 .with_label("Sample Line")
73 .with_styles(create_simple_styles(
74 strokes=[duc.create_stroke(duc.create_solid_content("#E74C3C"), width=3.0)]
75 ))
76 .build_linear_element()
77 .with_points(line_points)
78 .build())
79 print(f"Line has {len(line.element.linear_base.points)} points")
80
81 arrow_points = [(0, 50), (75, 100)]
82 arrow = (duc.ElementBuilder()
83 .with_label("Sample Arrow")
84 .with_styles(create_simple_styles(
85 strokes=[duc.create_stroke(duc.create_solid_content("#8E44AD"), width=2.5)]
86 ))
87 .build_arrow_element()
88 .with_points(arrow_points)
89 .build())
90 print(f"Arrow element type: {type(arrow.element).__name__}")
91
92 return [line, arrow]
93
94
95def demo_text_elements():
96 """Demo text elements with styles and document formatting."""
97 print("\n=== Text Elements Demo ===")
98
99 text = (duc.ElementBuilder()
100 .at_position(0, 100)
101 .with_size(150, 25)
102 .with_label("Sample Text")
103 .with_styles(create_simple_styles(opacity=0.9))
104 .build_text_element()
105 .with_text("Hello, DucPy!")
106 .build())
107 print(f"Text content: '{text.element.text}'")
108
109 return [text]
110
111
112def demo_stack_elements():
113 """Demo new stack-based elements with styles."""
114 print("\n=== Stack Elements Demo ===")
115
116 frame = (duc.ElementBuilder()
117 .at_position(0, 150)
118 .with_size(200, 100)
119 .with_label("Technical Frame")
120 .with_styles(create_fill_and_stroke_style(
121 fill_content=create_solid_content("#F8F9FA"),
122 stroke_content=create_solid_content("#495057"),
123 stroke_width=2.0,
124 roundness=3.0
125 ))
126 .build_frame_element()
127 .build())
128 print(f"Frame stack label: {frame.element.stack_element_base.stack_base.label}")
129
130 plot = (duc.ElementBuilder()
131 .at_position(220, 150)
132 .with_size(180, 120)
133 .with_label("Engineering Plot")
134 .with_styles(create_fill_and_stroke_style(
135 fill_content=create_solid_content("#E9ECEF"),
136 stroke_content=create_solid_content("#6C757D"),
137 stroke_width=1.5
138 ))
139 .build_plot_element()
140 .with_margins(duc.Margins(top=5, right=5, bottom=5, left=5))
141 .build())
142
143 return [frame, plot]
144
145
146def demo_custom_stack_base():
147 """Demo custom stack base creation."""
148 print("\n=== Custom Stack Base Demo ===")
149
150 custom_frame = (duc.ElementBuilder()
151 .at_position(50, 280)
152 .with_size(150, 80)
153 .with_label("Custom Container")
154 .build_frame_element()
155 .with_stack_base(duc.StateBuilder().build_stack_base()
156 .with_is_collapsed(False)
157 .with_styles(duc.DucStackLikeStyles(opacity=0.8))
158 .build())
159 .build())
160
161 return [custom_frame]
162
163
164def main():
165 """Run all element creation demos."""
166 print("DucPy Element Creation Demo")
167 print("=" * 40)
168
169 elements = []
170 elements.extend(demo_basic_elements())
171 elements.extend(demo_linear_elements())
172 elements.extend(demo_text_elements())
173 elements.extend(demo_stack_elements())
174 elements.extend(demo_custom_stack_base())
175
176 duc_bytes = duc.serialize_duc(
177 name="element_creation_example",
178 elements=elements,
179 )
180
181 print(f"\nCreated {len(elements)} elements → serialized {len(duc_bytes)} bytes.")
182 print("✅ Element creation demo complete!")
183 return duc_bytes
184
185
186if __name__ == "__main__":
187 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 raw `.duc` byte string,
12 matching the pattern used by the other example scripts.
13"""
14
15import ducpy as duc
16
17
18def main():
19 print("Mutation Demo")
20 print("=" * 30)
21
22 # ------------------------------------------------------------------
23 # 1. Build the initial elements + state using the existing builders.
24 # ------------------------------------------------------------------
25 rect = (duc.ElementBuilder()
26 .at_position(0, 0)
27 .with_size(100, 50)
28 .with_label("Initial Rectangle")
29 .build_rectangle()
30 .build())
31
32 ellipse = (duc.ElementBuilder()
33 .at_position(140, 0)
34 .with_size(60, 40)
35 .with_label("Initial Ellipse")
36 .build_ellipse()
37 .build())
38
39 elements = [rect, ellipse]
40
41 duc_global_state = (duc.StateBuilder()
42 .build_global_state()
43 .with_name("mutation_demo")
44 .with_main_scope("mm")
45 .build())
46
47 duc_local_state = (duc.StateBuilder()
48 .build_local_state()
49 .build())
50
51 # A sample external file entry to exercise mutate_external_file.
52 external_file = (duc.StateBuilder()
53 .build_external_file()
54 .with_key("logo")
55 .with_mime_type("image/png")
56 .with_data(b"\x89PNG\r\n\x1a\n" + b"\x00" * 16)
57 .build())
58
59 # ------------------------------------------------------------------
60 # 2. Apply mutations using the duc.mutate_* API.
61 # Each helper mutates in place and also stamps fresh versioning
62 # metadata (seed, updated, version, version_nonce) where
63 # applicable.
64 # ------------------------------------------------------------------
65
66 # 2a. Mutate the rectangle: move it, resize it, rename, hide it.
67 duc.mutate_element(
68 rect,
69 x=20,
70 y=30,
71 width=150,
72 label="Mutated Rectangle",
73 is_visible=False,
74 )
75
76 # 2b. Mutate the ellipse: rename and move (size stays the same).
77 duc.mutate_element(
78 ellipse,
79 x=200,
80 y=75,
81 label="Mutated Ellipse",
82 )
83
84 # 2c. Mutate the global state (zoom level, background, name).
85 duc.mutate_global_state(
86 duc_global_state,
87 view_background_color="#1E1E2E",
88 name="mutation_demo_updated",
89 )
90
91 # 2d. Mutate the local state (scroll position, grid mode).
92 duc.mutate_local_state(
93 duc_local_state,
94 scroll_x=42.0,
95 scroll_y=17.5,
96 grid_mode_enabled=False,
97 )
98
99 # 2e. Mutate the external file entry's metadata.
100 duc.mutate_external_file(
101 external_file,
102 version=2,
103 )
104
105 # ------------------------------------------------------------------
106 # 3. Serialize the mutated objects into a raw .duc byte string,
107 # mirroring the pattern used by the other example scripts.
108 # ------------------------------------------------------------------
109 duc_bytes = duc.serialize_duc(
110 name="mutation_demo",
111 elements=elements,
112 duc_global_state=duc_global_state,
113 duc_local_state=duc_local_state,
114 external_files=[external_file],
115 )
116
117 print(f" Mutated {len(elements)} elements.")
118 print(f" Global state main scope -> {duc_global_state.main_scope!r}")
119 print(f" Local state scroll -> ({duc_local_state.scroll_x}, {duc_local_state.scroll_y})")
120 print(f" External file version -> {external_file.version}")
121 print(f" Serialized {len(duc_bytes)} bytes.")
122 print("\n✅ Mutation demo complete!")
123 return duc_bytes
124
125
126if __name__ == "__main__":
127 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 bytes
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_bytes_roundtrip():
88 print("\n=== Bytes 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 raw = db.to_bytes()
97
98 print(f" Serialised to {len(raw):,} bytes")
99
100 with DucSQL.from_bytes(raw) as db:
101 row = db.sql("SELECT label FROM elements WHERE id = 't1'")[0]
102 print(f" Restored label: '{row['label']}'")
103
104 return raw
105
106
107def demo_advanced_connection():
108 print("\n=== Advanced: direct connection access ===")
109
110 with DucSQL.new() as db:
111 records = [
112 (f"el{i}", "rectangle", i * 110, 0, 100, 60, f"Box {i}", 1.0)
113 for i in range(5)
114 ]
115 db.conn.executemany(
116 "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) "
117 "VALUES (?,?,?,?,?,?,?,?)",
118 records,
119 )
120
121 total = db.sql("SELECT COUNT(*) AS n FROM elements")[0]["n"]
122 print(f" Bulk-inserted {total} elements")
123
124 tables = [
125 row["name"]
126 for row in db.sql(
127 "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
128 )
129 ]
130 print(f" Schema tables: {', '.join(tables[:6])} …")
131
132
133def demo_serialize_via_sql():
134 print("\n=== Build with SQL, serialize with the high-level API ===")
135
136 with DucSQL.new() as db:
137 db.sql(
138 "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) "
139 "VALUES (?,?,?,?,?,?,?,?)",
140 "s1", "rectangle", 0, 0, 100, 50, "From SQL", 1.0,
141 )
142 raw = db.to_bytes()
143
144 parsed = duc.parse_duc(raw)
145 print(f" Parsed {len(parsed.elements)} element(s) built via raw SQL.")
146
147 return raw
148
149
150def main():
151 print("DucSQL Builder Demo")
152 print("=" * 40)
153
154 saved_path = demo_create_new()
155 demo_open_existing(saved_path)
156 raw_bytes = demo_bytes_roundtrip()
157 demo_advanced_connection()
158 demo_serialize_via_sql()
159
160 print(f"\nAll DucSQL demos completed successfully! ({len(raw_bytes):,} byte round-trip payload)")
161 return raw_bytes
162
163
164if __name__ == "__main__":
165 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 writing them to a raw `.duc` binary blob.
7"""
8
9import ducpy as duc
10from ducpy.builders.style_builders import create_fill_and_stroke_style, create_solid_content
11
12def main():
13 print("Serialization Demo")
14 print("=" * 30)
15
16 print("1. Creating elements via Builder API...")
17 elements = []
18
19 # Create some basic elements
20 rect = (duc.ElementBuilder()
21 .at_position(0, 0)
22 .with_size(100, 50)
23 .with_label("Sample 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 elements.append(rect)
32
33 ellipse = (duc.ElementBuilder()
34 .at_position(120, 0)
35 .with_size(60, 40)
36 .with_label("Sample Ellipse")
37 .with_styles(create_fill_and_stroke_style(
38 fill_content=create_solid_content("#4ECDC4"),
39 stroke_content=create_solid_content("#34495E"),
40 stroke_width=1.5
41 ))
42 .build_ellipse()
43 .build())
44 elements.append(ellipse)
45
46 print(f" Created {len(elements)} elements.")
47
48 print("2. Serializing to .duc format...")
49 # NOTE: The serialize_duc function takes keyword arguments for elements,
50 # blocks, global state, etc. and bridges to the Rust native backend.
51 duc_bytes = duc.serialize_duc(
52 name="serialization_example",
53 elements=elements
54 )
55
56 print(f" Successfully serialized {len(duc_bytes)} bytes.")
57 print("\n✅ Serialization demo complete!")
58 return duc_bytes
59
60if __name__ == "__main__":
61 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` binary blob or 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 duc_bytes = duc.serialize_duc(name="parsing_example", elements=elements)
33
34 with tempfile.NamedTemporaryFile(suffix=".duc", delete=False) as tmp:
35 tmp.write(duc_bytes)
36 tmp_path = tmp.name
37
38 print("1. Parsing a .duc file from a file path...")
39
40 # You can pass a string path directly to parse_duc
41 parsed_data = duc.parse_duc(tmp_path)
42
43 print(f" Document Source: {parsed_data.source}")
44 print(f" Parsed {len(parsed_data.elements)} elements.")
45
46 print("\n2. Accessing element attributes (snake_case)...")
47
48 # Element properties are accessible via dot-notation with snake_case keys
49 # because parse_duc returns a DucData object.
50 first_element = parsed_data.elements[0]
51 print(f" Element ID: {first_element.id}")
52 print(f" Element Type: {first_element.type}")
53 print(f" Element Label: {first_element.label}")
54 print(f" Element Position: (X: {first_element.x}, Y: {first_element.y})")
55
56 print("\n3. Parsing directly from raw bytes...")
57
58 # You can also pass raw bytes directly to parse_duc
59 parsed_from_bytes = duc.parse_duc(duc_bytes)
60 print(f" Parsed successfully from bytes. Found {len(parsed_from_bytes.elements)} elements.")
61
62 # Clean up the temporary file
63 os.unlink(tmp_path)
64
65 print("\n✅ Parsing demo complete!")
66
67if __name__ == "__main__":
68 main()