diff --git a/README.md b/README.md index c2a622f4..e5f391ea 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The `duc` 2D CAD file format is a cornerstone of our advanced design system, con This documentation aims to provide a comprehensive guide to the format's structure, capabilities, and integration with various applications. ## Tools -[Documentation](https://duc.ducflair.com) | [Playground](https://scopture.com) +[Documentation](https://duc.ducflair.com) | [Playground](https://scopture.com/w/hollow) ## Market Insights diff --git a/packages/ducjs/src/types/elements/index.ts b/packages/ducjs/src/types/elements/index.ts index ce838896..f65f6a26 100644 --- a/packages/ducjs/src/types/elements/index.ts +++ b/packages/ducjs/src/types/elements/index.ts @@ -281,7 +281,28 @@ export const DUC_ELEMENT_SELECTABLE_FIELDS = [ "startBinding", "endBinding", "svgPath", -] as const; + "isPlot", + "isCollapsed", +] as const satisfies readonly DucElementSelectableField[]; + +type DucElementSelectableFieldCoverageExclusion = "strokeColor" | "backgroundColor"; +type _MissingSelectableFields = Exclude< + DucElementSelectableField, + typeof DUC_ELEMENT_SELECTABLE_FIELDS[number] | DucElementSelectableFieldCoverageExclusion +>; +type _AssertAllFieldsCovered = _MissingSelectableFields extends never ? true : { missing: _MissingSelectableFields }; +const _fieldCoverage: _AssertAllFieldsCovered = true; + +export const DUC_ELEMENT_COMMUNICATION_DENY_FIELDS = [ + "seed", + "versionNonce", + "thumbnail", + "svgPath", + "originalText", +] as const satisfies readonly DucElementSelectableField[]; + +export type DucElementCommunicationDenyField = typeof DUC_ELEMENT_COMMUNICATION_DENY_FIELDS[number]; +export const DUC_ELEMENT_COMMUNICATION_DENY_FIELD_SET: ReadonlySet = new Set(DUC_ELEMENT_COMMUNICATION_DENY_FIELDS); export const DUC_ELEMENT_TYPE_VALUES = [ "rectangle", @@ -299,7 +320,10 @@ export const DUC_ELEMENT_TYPE_VALUES = [ "pdf", "doc", "plot", -] as const; +] as const satisfies readonly Exclude[]; + +type _AssertAllTypesCovered = Exclude extends never ? true : { missing: Exclude }; +const _typeCoverage: _AssertAllTypesCovered = true; export type NonDeleted = TElement & { isDeleted: boolean; diff --git a/packages/ducpy/src/ducpy/builders/state_builders.py b/packages/ducpy/src/ducpy/builders/state_builders.py index ce683690..51d34c38 100644 --- a/packages/ducpy/src/ducpy/builders/state_builders.py +++ b/packages/ducpy/src/ducpy/builders/state_builders.py @@ -446,9 +446,9 @@ def create_local_state_from_base(base: BaseStateParams, **kwargs) -> DucLocalSta current_item_stroke=kwargs.get('current_item_stroke', None), current_item_background=kwargs.get('current_item_background', None), current_item_opacity=kwargs.get('current_item_opacity', 1.0), - current_item_font_family=kwargs.get('current_item_font_family', "Virgil"), + current_item_font_family=kwargs.get('current_item_font_family', "Roboto Mono"), current_item_font_size=kwargs.get('current_item_font_size', 20.0), - current_item_text_align=kwargs.get('current_item_text_align', None), + current_item_text_align=kwargs.get('current_item_text_align', TEXT_ALIGN.LEFT), current_item_roundness=kwargs.get('current_item_roundness', 0.0), current_item_start_line_head=kwargs.get('current_item_start_line_head', None), current_item_end_line_head=kwargs.get('current_item_end_line_head', None), @@ -457,7 +457,7 @@ def create_local_state_from_base(base: BaseStateParams, **kwargs) -> DucLocalSta objects_snap_mode_enabled=kwargs.get('objects_snap_mode_enabled', True), grid_mode_enabled=kwargs.get('grid_mode_enabled', True), outline_mode_enabled=kwargs.get('outline_mode_enabled', False), - manual_save_mode=kwargs.get('manual_save_mode', None), + manual_save_mode=kwargs.get('manual_save_mode', False), decimal_places=kwargs.get('decimal_places', 2), ) diff --git a/packages/ducpy/src/ducpy/serialize.py b/packages/ducpy/src/ducpy/serialize.py index 29307e8a..93a25730 100644 --- a/packages/ducpy/src/ducpy/serialize.py +++ b/packages/ducpy/src/ducpy/serialize.py @@ -16,7 +16,7 @@ import ducpy_native from ducpy.utils.convert import (deep_snake_to_camel, snake_to_camel, - to_serializable) + to_serializable, _flatten_dict) logger = logging.getLogger(__name__) @@ -104,22 +104,6 @@ def _read_schema_version_fallback() -> str: "DucModelElement": "model", } -# Keys in the asdict() output that should be flattened (merged into the parent), -# mirroring Rust's #[serde(flatten)] on base / styles / linear_base / stack_element_base. -_FLATTEN_KEYS = frozenset({"base", "styles", "linear_base", "stack_element_base"}) - - -def _flatten_dict(d: dict) -> dict: - """Recursively flatten nested dicts whose key is in ``_FLATTEN_KEYS``.""" - result: dict = {} - for k, v in d.items(): - if k in _FLATTEN_KEYS and isinstance(v, dict): - result.update(_flatten_dict(v)) - else: - result[k] = v - return result - - def _element_to_camel(wrapper_or_element: Any) -> dict: """Convert an element (or ElementWrapper) to the camelCase dict Rust expects.""" el = wrapper_or_element diff --git a/packages/ducpy/src/ducpy/utils/convert.py b/packages/ducpy/src/ducpy/utils/convert.py index ed03eee7..24fa9e0b 100644 --- a/packages/ducpy/src/ducpy/utils/convert.py +++ b/packages/ducpy/src/ducpy/utils/convert.py @@ -65,11 +65,19 @@ def deep_snake_to_camel(obj: Any) -> Any: def _flatten_dict(d: dict) -> dict: - """Recursively flatten keys that Rust serde #[serde(flatten)] would flatten.""" + """Recursively flatten keys that Rust serde #[serde(flatten)] would flatten. + + Walks the entire dict so that nested flatten keys (e.g. ``stack_element_base`` + -> ``stack_base`` -> ``styles``) are all merged into the appropriate level. + """ result: dict = {} for k, v in d.items(): - if k in _FLATTEN_KEYS and isinstance(v, dict): - result.update(_flatten_dict(v)) + if isinstance(v, dict): + v = _flatten_dict(v) + if k in _FLATTEN_KEYS: + result.update(v) + else: + result[k] = v else: result[k] = v return result diff --git a/packages/ducpy/src/examples/element_creation_demo.py b/packages/ducpy/src/examples/element_creation_demo.py index bba354d6..2d7b3323 100644 --- a/packages/ducpy/src/examples/element_creation_demo.py +++ b/packages/ducpy/src/examples/element_creation_demo.py @@ -13,8 +13,7 @@ def demo_basic_elements(): """Demo basic elements using the builders API.""" print("=== Basic Elements Demo ===") - - # Create basic shapes with styles + rect = (duc.ElementBuilder() .at_position(0, 0) .with_size(100, 50) @@ -27,7 +26,7 @@ def demo_basic_elements(): )) .build_rectangle() .build()) - + ellipse = (duc.ElementBuilder() .at_position(120, 0) .with_size(60, 40) @@ -39,7 +38,7 @@ def demo_basic_elements(): )) .build_ellipse() .build()) - + poly = (duc.ElementBuilder() .at_position(200, 0) .with_size(50, 50) @@ -53,22 +52,21 @@ def demo_basic_elements(): .build_polygon() .with_sides(6) .build()) - + print(f"Rectangle ID: {rect.element.base.id}") - print(f"Ellipse ID: {ellipse.element.base.id}") + print(f"Ellipse ID: {ellipse.element.base.id}") print(f"Polygon sides: {poly.element.sides}") - + # Demonstrate mutation with random versioning - original_version = rect.element.base.version duc.mutate_element(rect, x=10, label="Moved Rectangle") - print(f"Version changed: {original_version} -> {rect.element.base.version}") + + return [rect, ellipse, poly] def demo_linear_elements(): """Demo linear and arrow elements with styles.""" print("\n=== Linear Elements Demo ===") - - # Create a styled line + line_points = [(0, 0), (50, 25), (100, 0)] line = (duc.ElementBuilder() .with_label("Sample Line") @@ -79,8 +77,7 @@ def demo_linear_elements(): .with_points(line_points) .build()) print(f"Line has {len(line.element.linear_base.points)} points") - - # Create a styled arrow + arrow_points = [(0, 50), (75, 100)] arrow = (duc.ElementBuilder() .with_label("Sample Arrow") @@ -92,11 +89,13 @@ def demo_linear_elements(): .build()) print(f"Arrow element type: {type(arrow.element).__name__}") + return [line, arrow] + def demo_text_elements(): """Demo text elements with styles and document formatting.""" print("\n=== Text Elements Demo ===") - + text = (duc.ElementBuilder() .at_position(0, 100) .with_size(150, 25) @@ -106,14 +105,14 @@ def demo_text_elements(): .with_text("Hello, DucPy!") .build()) print(f"Text content: '{text.element.text}'") - print(f"Text uses random versioning: {text.element.base.version > 0}") + + return [text] def demo_stack_elements(): """Demo new stack-based elements with styles.""" print("\n=== Stack Elements Demo ===") - - # Create a styled frame + frame = (duc.ElementBuilder() .at_position(0, 150) .with_size(200, 100) @@ -127,8 +126,7 @@ def demo_stack_elements(): .build_frame_element() .build()) print(f"Frame stack label: {frame.element.stack_element_base.stack_base.label}") - - # Create a styled plot with margins + plot = (duc.ElementBuilder() .at_position(220, 150) .with_size(180, 120) @@ -141,48 +139,48 @@ def demo_stack_elements(): .build_plot_element() .with_margins(duc.Margins(top=5, right=5, bottom=5, left=5)) .build()) - print(f"Plot is marked as plot: {plot.element.stack_element_base.stack_base.is_plot}") - print(f"Plot margins: {plot.element.layout.margins.top}mm") - + return [frame, plot] def demo_custom_stack_base(): """Demo custom stack base creation.""" print("\n=== Custom Stack Base Demo ===") - - # Use it in a frame + custom_frame = (duc.ElementBuilder() .at_position(50, 280) .with_size(150, 80) - .with_label("Custom Container") # Moved label to ElementBuilder + .with_label("Custom Container") .build_frame_element() .with_stack_base(duc.StateBuilder().build_stack_base() .with_is_collapsed(False) .with_styles(duc.DucStackLikeStyles(opacity=0.8)) .build()) .build()) - - print(f"Custom stack opacity: {custom_frame.element.stack_element_base.stack_base.styles.opacity}") + + return [custom_frame] def main(): """Run all element creation demos.""" print("DucPy Element Creation Demo") print("=" * 40) - - demo_basic_elements() - demo_linear_elements() - demo_text_elements() - demo_stack_elements() - demo_custom_stack_base() - - print("\n✅ All demos completed successfully!") - print("The refactored code provides:") - print("- Reduced code duplication") - print("- Consistent random versioning") - print("- New stack-based element support") - print("- Improved maintainability") + + elements = [] + elements.extend(demo_basic_elements()) + elements.extend(demo_linear_elements()) + elements.extend(demo_text_elements()) + elements.extend(demo_stack_elements()) + elements.extend(demo_custom_stack_base()) + + duc_bytes = duc.serialize_duc( + name="element_creation_example", + elements=elements, + ) + + print(f"\nCreated {len(elements)} elements → serialized {len(duc_bytes)} bytes.") + print("✅ Element creation demo complete!") + return duc_bytes if __name__ == "__main__": diff --git a/packages/ducpy/src/examples/external_files_demo.py b/packages/ducpy/src/examples/external_files_demo.py index fb38b07f..0a88eb93 100644 --- a/packages/ducpy/src/examples/external_files_demo.py +++ b/packages/ducpy/src/examples/external_files_demo.py @@ -3,7 +3,6 @@ """ import ducpy as duc -from ducpy.classes.DataStateClass import ExportedDataState def create_duc_with_external_files(): @@ -17,7 +16,6 @@ def create_duc_with_external_files(): 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" dummy_pdf_data = b"%PDF-1.4\n1 0 obj <> endobj\n2 0 obj <> endobj\nxref\n0 3\n0000000000 65535 f\n0000000009 00000 n\n0000000074 00000 n\ntrailer<>startxref\n123\n%%EOF" - # 1. Create External File Entries using builders image_file_entry = (duc.StateBuilder() .build_external_file() .with_key("my_image_key") @@ -32,49 +30,27 @@ def create_duc_with_external_files(): .with_data(dummy_pdf_data) .build()) - # 2. Create Global State (minimal for this example) global_state = (duc.StateBuilder() .build_global_state() .with_main_scope("mm") .build()) - # 3. Create Local State (minimal for this example) local_state = (duc.StateBuilder() .build_local_state() .build()) - # 4. Assemble into an ExportedDataState (DUC object) - duc_object = ExportedDataState( - type="duc_example", - version="1.0.0", - source="external_files_demo.py", - thumbnail=b"", - elements=[], - blocks=[], - block_instances=[], - block_collections=[], - groups=[], - regions=[], - layers=[], - dictionary={}, - duc_local_state=local_state, - duc_global_state=global_state, - version_graph=None, # Not focusing on versioning here - files={image_file_entry.id: image_file_entry, pdf_file_entry.id: pdf_file_entry} - ) + duc_object_files = {image_file_entry.id: image_file_entry, pdf_file_entry.id: pdf_file_entry} print("DUC object with external files created successfully!") - print(f"Total external files: {len(duc_object.files)}") - for fid, file in duc_object.files.items(): - active_rev = file.revisions[file.active_revision_id] - print(f"File id: {fid}, MIME type: {active_rev.mime_type}") - return duc_object + print(f"Total external files: {len(duc_object_files)}") + return duc_object_files, global_state, local_state + def main(): """Run the external files demo.""" print("External Files Demo") print("=" * 30) - sample_duc = create_duc_with_external_files() + create_duc_with_external_files() print("\nExternal files demo complete!") diff --git a/packages/ducpy/src/examples/mutation_demo.py b/packages/ducpy/src/examples/mutation_demo.py index 47a1e0fb..8a9b943a 100644 --- a/packages/ducpy/src/examples/mutation_demo.py +++ b/packages/ducpy/src/examples/mutation_demo.py @@ -1,61 +1,126 @@ +#!/usr/bin/env python3 """ -Example demonstrating the mutation of elements within a DUC object. +Example demonstrating the `mutate` API for elements, global state, and +external file entries in DUC. + +This demo shows how to: + 1. Build an initial set of elements and supporting state via the + standard builder API. + 2. Apply targeted mutations to the elements, the global state, and an + external file entry using `duc.mutate_*` helpers. + 3. Serialize the resulting DUC object into a raw `.duc` byte string, + matching the pattern used by the other example scripts. """ import ducpy as duc -from ducpy.classes.DataStateClass import ExportedDataState - -def demonstrate_element_mutation(): - """ - Creates a DUC object with an element, then mutates its properties - and prints the changes. - - """ - print("Demonstrating element mutation...") - - # 1. Create a simple Rectangle element - initial_rect = (duc.ElementBuilder() - .at_position(0, 0).with_size(100, 50) - .with_label("Initial Rectangle") - .build_rectangle() - .build()) - elements = [initial_rect] - - # 2. Create minimal Global and Local States - global_state = (duc.StateBuilder().build_global_state().build()) - local_state = (duc.StateBuilder().build_local_state().build()) - - print("Initial Rectangle Properties:") - print(f" X: {initial_rect.element.base.x}, Y: {initial_rect.element.base.y}") - print(f" Width: {initial_rect.element.base.width}, Height: {initial_rect.element.base.height}") - print(f" Label: {initial_rect.element.base.label}") - print(f" Version: {initial_rect.element.base.version}") - print() - - # 4. Mutate the rectangle element - print("Mutating the rectangle element...") - duc.mutate_element(initial_rect, - x=20, - y=30, - width=150, - label="Mutated Rectangle", - is_visible=False) - - print("Mutated Rectangle Properties:") - print(f" X: {initial_rect.element.base.x}, Y: {initial_rect.element.base.y}") - print(f" Width: {initial_rect.element.base.width}, Height: {initial_rect.element.base.height}") - print(f" Label: {initial_rect.element.base.label}") - print(f" Is Visible: {initial_rect.element.base.is_visible}") - print(f" New Version: {initial_rect.element.base.version}") # Version should have changed - - print("\nElement mutation demo complete!") def main(): - """Run the mutation demo.""" print("Mutation Demo") print("=" * 30) - demonstrate_element_mutation() + + # ------------------------------------------------------------------ + # 1. Build the initial elements + state using the existing builders. + # ------------------------------------------------------------------ + rect = (duc.ElementBuilder() + .at_position(0, 0) + .with_size(100, 50) + .with_label("Initial Rectangle") + .build_rectangle() + .build()) + + ellipse = (duc.ElementBuilder() + .at_position(140, 0) + .with_size(60, 40) + .with_label("Initial Ellipse") + .build_ellipse() + .build()) + + elements = [rect, ellipse] + + global_state = (duc.StateBuilder() + .build_global_state() + .with_name("mutation_demo") + .with_main_scope("mm") + .build()) + + local_state = (duc.StateBuilder() + .build_local_state() + .build()) + + # A sample external file entry to exercise mutate_external_file. + external_file = (duc.StateBuilder() + .build_external_file() + .with_key("logo") + .with_mime_type("image/png") + .with_data(b"\x89PNG\r\n\x1a\n" + b"\x00" * 16) + .build()) + + # ------------------------------------------------------------------ + # 2. Apply mutations using the duc.mutate_* API. + # Each helper mutates in place and also stamps fresh versioning + # metadata (seed, updated, version, version_nonce) where + # applicable. + # ------------------------------------------------------------------ + + # 2a. Mutate the rectangle: move it, resize it, rename, hide it. + duc.mutate_element( + rect, + x=20, + y=30, + width=150, + label="Mutated Rectangle", + is_visible=False, + ) + + # 2b. Mutate the ellipse: rename and move (size stays the same). + duc.mutate_element( + ellipse, + x=200, + y=75, + label="Mutated Ellipse", + ) + + # 2c. Mutate the global state (zoom level, background, name). + duc.mutate_global_state( + global_state, + view_background_color="#1E1E2E", + name="mutation_demo_updated", + ) + + # 2d. Mutate the local state (scroll position, grid mode). + duc.mutate_local_state( + local_state, + scroll_x=42.0, + scroll_y=17.5, + grid_mode_enabled=False, + ) + + # 2e. Mutate the external file entry's metadata. + duc.mutate_external_file( + external_file, + version=2, + ) + + # ------------------------------------------------------------------ + # 3. Serialize the mutated objects into a raw .duc byte string, + # mirroring the pattern used by the other example scripts. + # ------------------------------------------------------------------ + duc_bytes = duc.serialize_duc( + name="mutation_demo", + elements=elements, + duc_global_state=global_state, + duc_local_state=local_state, + external_files=[external_file], + ) + + print(f" Mutated {len(elements)} elements.") + print(f" Global state name -> {global_state.name!r}") + print(f" Local state scroll -> ({local_state.scroll_x}, {local_state.scroll_y})") + print(f" External file version -> {external_file.version}") + print(f" Serialized {len(duc_bytes)} bytes.") + print("\n✅ Mutation demo complete!") + return duc_bytes if __name__ == "__main__": diff --git a/packages/ducpy/src/examples/sql_builder_demo.py b/packages/ducpy/src/examples/sql_builder_demo.py index c7f62fc6..f2efca86 100644 --- a/packages/ducpy/src/examples/sql_builder_demo.py +++ b/packages/ducpy/src/examples/sql_builder_demo.py @@ -13,22 +13,20 @@ 4. Update elements in place 5. Export to / round-trip from bytes 6. Open an existing .duc file + 7. Round-trip SQL-built data through the high-level parser """ import os import tempfile +import ducpy as duc from ducpy.builders.sql_builder import DucSQL -# --------------------------------------------------------------------------- -# 1. Create a new .duc from scratch -# --------------------------------------------------------------------------- def demo_create_new(): print("=== Create new .duc ===") with DucSQL.new() as db: - # Insert two elements db.sql( "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) " "VALUES (?,?,?,?,?,?,?,?)", @@ -40,7 +38,6 @@ def demo_create_new(): "e1", "ellipse", 250, 0, 120, 80, "Side Ellipse", 0.9, ) - # Attach fill colours for owner_id, colour in [("r1", "#4ECDC4"), ("e1", "#FF6B6B")]: db.sql( "INSERT INTO backgrounds (owner_type, owner_id, src, opacity) " @@ -48,12 +45,10 @@ def demo_create_new(): "element", owner_id, colour, 1.0, ) - # Verify inserts rows = db.sql("SELECT id, element_type, label FROM elements ORDER BY id") for row in rows: print(f" [{row['id']}] {row['element_type']} — {row['label']}") - # Use named parameters for an update db.sql_dict( "UPDATE elements SET label = :label WHERE id = :id", {"label": "Renamed Rectangle", "id": "r1"}, @@ -62,7 +57,6 @@ def demo_create_new(): updated = db.sql("SELECT label FROM elements WHERE id = ?", "r1")[0] print(f" After rename: '{updated['label']}'") - # Save to a temporary file tmp = tempfile.NamedTemporaryFile(suffix=".duc", delete=False) tmp.close() db.save(tmp.name) @@ -71,10 +65,6 @@ def demo_create_new(): return tmp.name -# --------------------------------------------------------------------------- -# 2. Open an existing .duc file -# --------------------------------------------------------------------------- - def demo_open_existing(path: str): print("\n=== Open existing .duc ===") @@ -94,14 +84,9 @@ def demo_open_existing(path: str): os.unlink(path) -# --------------------------------------------------------------------------- -# 3. Round-trip via bytes (in-memory, no disk I/O needed) -# --------------------------------------------------------------------------- - def demo_bytes_roundtrip(): print("\n=== Bytes round-trip ===") - # Build in memory with DucSQL.new() as db: db.sql( "INSERT INTO elements (id, element_type, x, y, width, height, label) " @@ -112,21 +97,17 @@ def demo_bytes_roundtrip(): print(f" Serialised to {len(raw):,} bytes") - # Restore from bytes and query with DucSQL.from_bytes(raw) as db: row = db.sql("SELECT label FROM elements WHERE id = 't1'")[0] print(f" Restored label: '{row['label']}'") + return raw -# --------------------------------------------------------------------------- -# 4. Access the underlying connection for advanced operations -# --------------------------------------------------------------------------- def demo_advanced_connection(): print("\n=== Advanced: direct connection access ===") with DucSQL.new() as db: - # Use executemany for bulk inserts via the raw connection records = [ (f"el{i}", "rectangle", i * 110, 0, 100, 60, f"Box {i}", 1.0) for i in range(5) @@ -140,7 +121,6 @@ def demo_advanced_connection(): total = db.sql("SELECT COUNT(*) AS n FROM elements")[0]["n"] print(f" Bulk-inserted {total} elements") - # Run a schema introspection query tables = [ row["name"] for row in db.sql( @@ -150,16 +130,35 @@ def demo_advanced_connection(): print(f" Schema tables: {', '.join(tables[:6])} …") +def demo_serialize_via_sql(): + print("\n=== Build with SQL, serialize with the high-level API ===") + + with DucSQL.new() as db: + db.sql( + "INSERT INTO elements (id, element_type, x, y, width, height, label, opacity) " + "VALUES (?,?,?,?,?,?,?,?)", + "s1", "rectangle", 0, 0, 100, 50, "From SQL", 1.0, + ) + raw = db.to_bytes() + + parsed = duc.parse_duc(raw) + print(f" Parsed {len(parsed.elements)} element(s) built via raw SQL.") + + return raw + + def main(): print("DucSQL Builder Demo") print("=" * 40) saved_path = demo_create_new() demo_open_existing(saved_path) - demo_bytes_roundtrip() + raw_bytes = demo_bytes_roundtrip() demo_advanced_connection() + demo_serialize_via_sql() - print("\nAll DucSQL demos completed successfully!") + print(f"\nAll DucSQL demos completed successfully! ({len(raw_bytes):,} byte round-trip payload)") + return raw_bytes if __name__ == "__main__": diff --git a/packages/ducpy/src/tests/src/test_examples.py b/packages/ducpy/src/tests/src/test_examples.py index 6923444e..f9080910 100644 --- a/packages/ducpy/src/tests/src/test_examples.py +++ b/packages/ducpy/src/tests/src/test_examples.py @@ -72,17 +72,15 @@ def test_element_creation_demo_runs_successfully(self, test_output_dir): class TestMutationDemo: """Test the mutation demo.""" - + def test_mutation_demo_runs_successfully(self, test_output_dir): """Test that the mutation demo runs without errors.""" - output_text, _ = _run_demo(mutation_demo, test_output_dir) + output_text, duc_bytes = _run_demo(mutation_demo, test_output_dir) assert "Mutation Demo" in output_text - assert "Demonstrating element mutation..." in output_text - assert "Initial Rectangle Properties:" in output_text - assert "Mutating the rectangle element..." in output_text - assert "Mutated Rectangle Properties:" in output_text - assert "Element mutation demo complete!" in output_text + assert "Mutation demo complete" in output_text + assert duc_bytes is not None + assert os.path.isfile(os.path.join(test_output_dir, f"{mutation_demo.__name__}.duc")) class TestExternalFilesDemo: @@ -104,14 +102,17 @@ class TestSQLBuilderDemo: def test_sql_builder_demo_runs_successfully(self, test_output_dir): """Test that the SQL builder demo runs without errors.""" - output_text, _ = _run_demo(sql_builder_demo, test_output_dir) + output_text, duc_bytes = _run_demo(sql_builder_demo, test_output_dir) assert "DucSQL Builder Demo" in output_text assert "Create new .duc" in output_text assert "Open existing .duc" in output_text assert "Bytes round-trip" in output_text assert "Advanced" in output_text + assert "Build with SQL, serialize with the high-level API" in output_text assert "All DucSQL demos completed successfully!" in output_text + assert duc_bytes is not None + assert os.path.isfile(os.path.join(test_output_dir, f"{sql_builder_demo.__name__}.duc")) class TestSerializationDemo: diff --git a/packages/ducpy/src/tests/src/test_stack_elements.py b/packages/ducpy/src/tests/src/test_stack_elements.py index 6827019a..5cf9fa74 100644 --- a/packages/ducpy/src/tests/src/test_stack_elements.py +++ b/packages/ducpy/src/tests/src/test_stack_elements.py @@ -201,9 +201,5 @@ def test_stack_elements_via_sql(): assert len(db2.sql("SELECT * FROM element_plot")) == 1 -# Legacy builder/native serialization demo now covered by SQL-first variant. -test_serialize_stack_elements_demo = test_stack_elements_via_sql - - if __name__ == "__main__": pytest.main([__file__]) diff --git a/packages/ducrs/Cargo.toml b/packages/ducrs/Cargo.toml index 7b53b176..02e835ab 100644 --- a/packages/ducrs/Cargo.toml +++ b/packages/ducrs/Cargo.toml @@ -7,6 +7,17 @@ license = "MIT" authors = ["Ducflair support@ducflair.com"] repository = "https://github.com/ducflair/duc/tree/main/packages/ducrs" readme = "README.md" +include = [ + "src/**", + "build.rs", + "Cargo.toml", + "README.md", + "LICENSE", + "schema/duc.sql", + "schema/version_control.sql", + "schema/search.sql", + "schema/migrations/*.sql", +] [dependencies] base64 = "0.21.5"