kicad-sch-api 0.3.2__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {kicad_sch_api-0.3.2/kicad_sch_api.egg-info → kicad_sch_api-0.3.5}/PKG-INFO +1 -1
  2. kicad_sch_api-0.3.5/examples/parser_demo.py +93 -0
  3. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/__init__.py +2 -2
  4. kicad_sch_api-0.3.5/kicad_sch_api/collections/__init__.py +21 -0
  5. kicad_sch_api-0.3.5/kicad_sch_api/collections/base.py +296 -0
  6. kicad_sch_api-0.3.5/kicad_sch_api/collections/components.py +422 -0
  7. kicad_sch_api-0.3.5/kicad_sch_api/collections/junctions.py +378 -0
  8. kicad_sch_api-0.3.5/kicad_sch_api/collections/labels.py +412 -0
  9. kicad_sch_api-0.3.5/kicad_sch_api/collections/wires.py +407 -0
  10. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/formatter.py +31 -0
  11. kicad_sch_api-0.3.5/kicad_sch_api/core/labels.py +348 -0
  12. kicad_sch_api-0.3.5/kicad_sch_api/core/nets.py +310 -0
  13. kicad_sch_api-0.3.5/kicad_sch_api/core/no_connects.py +274 -0
  14. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/parser.py +72 -0
  15. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/schematic.py +185 -9
  16. kicad_sch_api-0.3.5/kicad_sch_api/core/texts.py +343 -0
  17. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/types.py +26 -0
  18. kicad_sch_api-0.3.5/kicad_sch_api/geometry/__init__.py +26 -0
  19. kicad_sch_api-0.3.5/kicad_sch_api/geometry/font_metrics.py +20 -0
  20. kicad_sch_api-0.3.5/kicad_sch_api/geometry/symbol_bbox.py +589 -0
  21. kicad_sch_api-0.3.5/kicad_sch_api/interfaces/__init__.py +17 -0
  22. kicad_sch_api-0.3.5/kicad_sch_api/interfaces/parser.py +76 -0
  23. kicad_sch_api-0.3.5/kicad_sch_api/interfaces/repository.py +70 -0
  24. kicad_sch_api-0.3.5/kicad_sch_api/interfaces/resolver.py +117 -0
  25. kicad_sch_api-0.3.5/kicad_sch_api/parsers/__init__.py +14 -0
  26. kicad_sch_api-0.3.5/kicad_sch_api/parsers/base.py +148 -0
  27. kicad_sch_api-0.3.5/kicad_sch_api/parsers/label_parser.py +254 -0
  28. kicad_sch_api-0.3.5/kicad_sch_api/parsers/registry.py +153 -0
  29. kicad_sch_api-0.3.5/kicad_sch_api/parsers/symbol_parser.py +227 -0
  30. kicad_sch_api-0.3.5/kicad_sch_api/parsers/wire_parser.py +99 -0
  31. kicad_sch_api-0.3.5/kicad_sch_api/symbols/__init__.py +18 -0
  32. kicad_sch_api-0.3.5/kicad_sch_api/symbols/cache.py +470 -0
  33. kicad_sch_api-0.3.5/kicad_sch_api/symbols/resolver.py +367 -0
  34. kicad_sch_api-0.3.5/kicad_sch_api/symbols/validators.py +453 -0
  35. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5/kicad_sch_api.egg-info}/PKG-INFO +1 -1
  36. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/SOURCES.txt +31 -0
  37. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/pyproject.toml +1 -1
  38. kicad_sch_api-0.3.5/tests/test_geometry.py +566 -0
  39. kicad_sch_api-0.3.5/tests/test_image_support.py +119 -0
  40. kicad_sch_api-0.3.5/tests/test_issue_13_public_properties.py +412 -0
  41. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/dead-code-analysis.md +0 -0
  42. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/publish-pypi.md +0 -0
  43. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/review-implementation.md +0 -0
  44. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/run-tests.md +0 -0
  45. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/update-and-commit.md +0 -0
  46. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/update-memory-bank.md +0 -0
  47. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/test/run-reference-tests.md +0 -0
  48. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/CHANGELOG.md +0 -0
  49. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/LICENSE +0 -0
  50. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/MANIFEST.in +0 -0
  51. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/README.md +0 -0
  52. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/advanced_usage.py +0 -0
  53. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/basic_usage.py +0 -0
  54. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/mcp_basic_example.py +0 -0
  55. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/mcp_integration.py +0 -0
  56. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/pin_to_pin_wiring_demo.py +0 -0
  57. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/simple_circuit_with_pin_wiring.py +0 -0
  58. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/simple_two_resistor_routing.py +0 -0
  59. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/cli.py +0 -0
  60. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/__init__.py +0 -0
  61. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/component_bounds.py +0 -0
  62. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/components.py +0 -0
  63. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/config.py +0 -0
  64. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/geometry.py +0 -0
  65. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/ic_manager.py +0 -0
  66. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/junctions.py +0 -0
  67. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/manhattan_routing.py +0 -0
  68. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/pin_utils.py +0 -0
  69. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/simple_manhattan.py +0 -0
  70. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/wire_routing.py +0 -0
  71. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/wires.py +0 -0
  72. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/discovery/__init__.py +0 -0
  73. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/discovery/search_index.py +0 -0
  74. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/library/__init__.py +0 -0
  75. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/library/cache.py +0 -0
  76. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/py.typed +0 -0
  77. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/utils/__init__.py +0 -0
  78. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/utils/validation.py +0 -0
  79. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
  80. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/entry_points.txt +0 -0
  81. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/requires.txt +0 -0
  82. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/top_level.txt +0 -0
  83. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/setup.cfg +0 -0
  84. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
  85. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
  86. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +0 -0
  87. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
  88. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
  89. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
  90. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
  91. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
  92. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
  93. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
  94. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
  95. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
  96. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
  97. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
  98. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
  99. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
  100. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
  101. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
  102. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
  103. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
  104. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
  105. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
  106. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
  107. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
  108. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
  109. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
  110. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
  111. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
  112. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
  113. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_sch +0 -0
  114. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_bounding_box_rectangles.py +0 -0
  115. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_component_removal.py +0 -0
  116. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_element_removal.py +0 -0
  117. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_grid_snapping.py +0 -0
  118. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_kicad_validation.py +0 -0
  119. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_manhattan_routing.py +0 -0
  120. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_parse_reference_rectangles.py +0 -0
  121. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_pin_positioning.py +0 -0
  122. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_pin_to_pin_wiring.py +0 -0
  123. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_rectangle.py +0 -0
  124. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_rectangle_roundtrip.py +0 -0
  125. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_removal_against_references.py +0 -0
  126. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_wire_operations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kicad-sch-api
3
- Version: 0.3.2
3
+ Version: 0.3.5
4
4
  Summary: Professional KiCAD schematic manipulation library with exact format preservation
5
5
  Author-email: Circuit-Synth <shane@circuit-synth.com>
6
6
  Maintainer-email: Circuit-Synth <shane@circuit-synth.com>
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Demonstration of the new modular parser system.
4
+
5
+ This example shows how to use the new ElementParserRegistry
6
+ and specialized parsers for different S-expression elements.
7
+ """
8
+
9
+ import logging
10
+ import sexpdata
11
+
12
+ from kicad_sch_api.parsers.registry import ElementParserRegistry
13
+ from kicad_sch_api.parsers.wire_parser import WireParser
14
+ from kicad_sch_api.parsers.symbol_parser import SymbolParser
15
+ from kicad_sch_api.parsers.label_parser import LabelParser, HierarchicalLabelParser
16
+
17
+ # Enable debug logging to see what's happening
18
+ logging.basicConfig(level=logging.WARNING) # Reduce noise
19
+
20
+
21
+ def main():
22
+ """Demonstrate the new parser system."""
23
+ print("🔧 KiCAD Schematic API - New Parser System Demo")
24
+ print("=" * 50)
25
+
26
+ # Create registry and register specialized parsers
27
+ registry = ElementParserRegistry()
28
+ registry.register("wire", WireParser())
29
+ registry.register("symbol", SymbolParser())
30
+ registry.register("label", LabelParser())
31
+ registry.register("hierarchical_label", HierarchicalLabelParser())
32
+
33
+ print(f"📋 Registered parsers: {registry.get_registered_types()}")
34
+ print()
35
+
36
+ # Example S-expression elements (using sexpdata.Symbol for type names)
37
+ test_elements = [
38
+ # Wire element
39
+ [sexpdata.Symbol("wire"),
40
+ [sexpdata.Symbol("pts"), [sexpdata.Symbol("xy"), 100.0, 100.0], [sexpdata.Symbol("xy"), 150.0, 100.0]],
41
+ [sexpdata.Symbol("stroke"), [sexpdata.Symbol("width"), 0.0], [sexpdata.Symbol("type"), sexpdata.Symbol("default")]],
42
+ [sexpdata.Symbol("uuid"), "12345678-1234-5678-9abc-123456789abc"]],
43
+
44
+ # Label element
45
+ [sexpdata.Symbol("label"), "VCC",
46
+ [sexpdata.Symbol("at"), 125.0, 95.0, 0],
47
+ [sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]],
48
+ [sexpdata.Symbol("uuid"), "87654321-4321-8765-cba9-876543210987"]],
49
+
50
+ # Unknown element (will be skipped)
51
+ [sexpdata.Symbol("unknown_element"), "some_data"]
52
+ ]
53
+
54
+ # Parse elements using the registry
55
+ print("🔍 Parsing S-expression elements:")
56
+ print()
57
+
58
+ for i, element in enumerate(test_elements, 1):
59
+ element_type = element[0] if element else "unknown"
60
+ print(f"Element {i}: {element_type}")
61
+
62
+ result = registry.parse_element(element)
63
+ if result:
64
+ print(f" ✅ Successfully parsed")
65
+ print(f" 📊 Result keys: {list(result.keys())}")
66
+ if "points" in result:
67
+ print(f" 📍 Points: {len(result['points'])} points")
68
+ if "text" in result:
69
+ print(f" 📝 Text: '{result['text']}'")
70
+ else:
71
+ print(f" ❌ No parser available")
72
+ print()
73
+
74
+ # Demonstrate batch parsing
75
+ print("📦 Batch parsing multiple elements:")
76
+ results = registry.parse_elements(test_elements)
77
+ print(f" ✅ Successfully parsed {len(results)} of {len(test_elements)} elements")
78
+
79
+ # Show parsing statistics
80
+ successful_types = [r.get("type", "unknown") for r in results if "type" in r]
81
+ print(f" 📊 Parsed types: {set(successful_types)}")
82
+
83
+ print()
84
+ print("✨ New parser system benefits:")
85
+ print(" • Modular and testable parsers")
86
+ print(" • Easy to add new element types")
87
+ print(" • Clear separation of concerns")
88
+ print(" • Comprehensive error handling")
89
+ print(" • Extensible with fallback parsers")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
@@ -42,7 +42,7 @@ Advanced Usage:
42
42
  print(f"Found {len(issues)} validation issues")
43
43
  """
44
44
 
45
- __version__ = "0.3.2"
45
+ __version__ = "0.3.3"
46
46
  __author__ = "Circuit-Synth"
47
47
  __email__ = "info@circuit-synth.com"
48
48
 
@@ -55,7 +55,7 @@ from .library.cache import SymbolLibraryCache, get_symbol_cache
55
55
  from .utils.validation import ValidationError, ValidationIssue
56
56
 
57
57
  # Version info
58
- VERSION_INFO = (0, 3, 2)
58
+ VERSION_INFO = (0, 3, 3)
59
59
 
60
60
  # Public API
61
61
  __all__ = [
@@ -0,0 +1,21 @@
1
+ """
2
+ Modern collection architecture for KiCAD schematic elements.
3
+
4
+ This module provides a unified collection framework that eliminates code duplication
5
+ across component, wire, and junction collections while providing consistent
6
+ indexing, performance optimization, and management capabilities.
7
+ """
8
+
9
+ from .base import IndexedCollection
10
+ from .components import ComponentCollection
11
+ from .wires import WireCollection
12
+ from .junctions import JunctionCollection
13
+ from .labels import LabelCollection
14
+
15
+ __all__ = [
16
+ "IndexedCollection",
17
+ "ComponentCollection",
18
+ "WireCollection",
19
+ "JunctionCollection",
20
+ "LabelCollection",
21
+ ]
@@ -0,0 +1,296 @@
1
+ """
2
+ Base collection class for unified schematic element management.
3
+
4
+ Provides common functionality for indexing, searching, and managing
5
+ collections of schematic elements with performance optimization.
6
+ """
7
+
8
+ import logging
9
+ from abc import ABC, abstractmethod
10
+ from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, TypeVar, Union
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ T = TypeVar('T') # Type variable for collection items
15
+
16
+
17
+ class IndexedCollection(Generic[T], ABC):
18
+ """
19
+ Base class for all schematic element collections with automatic indexing.
20
+
21
+ Provides unified functionality for:
22
+ - UUID-based fast lookups
23
+ - Automatic index management
24
+ - Modification tracking
25
+ - Consistent iteration interface
26
+ - Performance optimization for large collections
27
+ """
28
+
29
+ def __init__(self, items: Optional[List[T]] = None):
30
+ """
31
+ Initialize indexed collection.
32
+
33
+ Args:
34
+ items: Initial list of items to add to collection
35
+ """
36
+ self._items: List[T] = []
37
+ self._uuid_index: Dict[str, int] = {}
38
+ self._modified = False
39
+ self._dirty_indexes = False
40
+
41
+ # Add initial items if provided
42
+ if items:
43
+ for item in items:
44
+ self._add_item_to_collection(item)
45
+
46
+ logger.debug(f"{self.__class__.__name__} initialized with {len(self._items)} items")
47
+
48
+ # Abstract methods for subclasses to implement
49
+ @abstractmethod
50
+ def _get_item_uuid(self, item: T) -> str:
51
+ """
52
+ Extract UUID from an item.
53
+
54
+ Args:
55
+ item: Item to extract UUID from
56
+
57
+ Returns:
58
+ UUID string for the item
59
+ """
60
+ pass
61
+
62
+ @abstractmethod
63
+ def _create_item(self, **kwargs) -> T:
64
+ """
65
+ Create a new item with given parameters.
66
+
67
+ Args:
68
+ **kwargs: Parameters for item creation
69
+
70
+ Returns:
71
+ Newly created item
72
+ """
73
+ pass
74
+
75
+ @abstractmethod
76
+ def _build_additional_indexes(self) -> None:
77
+ """
78
+ Build any additional indexes specific to the collection type.
79
+
80
+ Called after UUID index is rebuilt. Subclasses should implement
81
+ this to maintain their own specialized indexes.
82
+ """
83
+ pass
84
+
85
+ # Core collection operations
86
+ def add(self, item: T) -> T:
87
+ """
88
+ Add an item to the collection.
89
+
90
+ Args:
91
+ item: Item to add
92
+
93
+ Returns:
94
+ The added item
95
+
96
+ Raises:
97
+ ValueError: If item with same UUID already exists
98
+ """
99
+ uuid_str = self._get_item_uuid(item)
100
+
101
+ # Ensure indexes are current before checking for duplicates
102
+ self._ensure_indexes_current()
103
+
104
+ # Check for duplicate UUID
105
+ if uuid_str in self._uuid_index:
106
+ raise ValueError(f"Item with UUID {uuid_str} already exists")
107
+
108
+ return self._add_item_to_collection(item)
109
+
110
+ def remove(self, identifier: Union[str, T]) -> bool:
111
+ """
112
+ Remove an item from the collection.
113
+
114
+ Args:
115
+ identifier: UUID string or item instance to remove
116
+
117
+ Returns:
118
+ True if item was removed, False if not found
119
+ """
120
+ self._ensure_indexes_current()
121
+
122
+ if isinstance(identifier, str):
123
+ # Remove by UUID
124
+ if identifier not in self._uuid_index:
125
+ return False
126
+
127
+ index = self._uuid_index[identifier]
128
+ item = self._items[index]
129
+ else:
130
+ # Remove by item instance
131
+ item = identifier
132
+ uuid_str = self._get_item_uuid(item)
133
+ if uuid_str not in self._uuid_index:
134
+ return False
135
+
136
+ index = self._uuid_index[uuid_str]
137
+
138
+ # Remove from main list
139
+ self._items.pop(index)
140
+ self._mark_modified()
141
+ self._mark_indexes_dirty()
142
+
143
+ logger.debug(f"Removed item with UUID {self._get_item_uuid(item)}")
144
+ return True
145
+
146
+ def get(self, uuid: str) -> Optional[T]:
147
+ """
148
+ Get an item by UUID.
149
+
150
+ Args:
151
+ uuid: UUID to search for
152
+
153
+ Returns:
154
+ Item if found, None otherwise
155
+ """
156
+ self._ensure_indexes_current()
157
+
158
+ if uuid in self._uuid_index:
159
+ index = self._uuid_index[uuid]
160
+ return self._items[index]
161
+
162
+ return None
163
+
164
+ def find(self, predicate: Callable[[T], bool]) -> List[T]:
165
+ """
166
+ Find all items matching a predicate.
167
+
168
+ Args:
169
+ predicate: Function that returns True for matching items
170
+
171
+ Returns:
172
+ List of matching items
173
+ """
174
+ return [item for item in self._items if predicate(item)]
175
+
176
+ def filter(self, **criteria) -> List[T]:
177
+ """
178
+ Filter items by attribute criteria.
179
+
180
+ Args:
181
+ **criteria: Attribute name/value pairs to match
182
+
183
+ Returns:
184
+ List of matching items
185
+ """
186
+ def matches_criteria(item: T) -> bool:
187
+ for attr, value in criteria.items():
188
+ if not hasattr(item, attr) or getattr(item, attr) != value:
189
+ return False
190
+ return True
191
+
192
+ return self.find(matches_criteria)
193
+
194
+ def clear(self) -> None:
195
+ """Clear all items from the collection."""
196
+ self._items.clear()
197
+ self._uuid_index.clear()
198
+ self._mark_modified()
199
+ logger.debug(f"Cleared all items from {self.__class__.__name__}")
200
+
201
+ # Collection interface methods
202
+ def __len__(self) -> int:
203
+ """Number of items in collection."""
204
+ return len(self._items)
205
+
206
+ def __iter__(self) -> Iterator[T]:
207
+ """Iterate over items in collection."""
208
+ return iter(self._items)
209
+
210
+ def __contains__(self, item: Union[str, T]) -> bool:
211
+ """Check if item or UUID is in collection."""
212
+ if isinstance(item, str):
213
+ # Check by UUID
214
+ self._ensure_indexes_current()
215
+ return item in self._uuid_index
216
+ else:
217
+ # Check by item instance
218
+ uuid_str = self._get_item_uuid(item)
219
+ self._ensure_indexes_current()
220
+ return uuid_str in self._uuid_index
221
+
222
+ def __getitem__(self, index: int) -> T:
223
+ """Get item by index."""
224
+ return self._items[index]
225
+
226
+ # Internal methods
227
+ def _add_item_to_collection(self, item: T) -> T:
228
+ """
229
+ Internal method to add item to collection.
230
+
231
+ Args:
232
+ item: Item to add
233
+
234
+ Returns:
235
+ The added item
236
+ """
237
+ self._items.append(item)
238
+ self._mark_modified()
239
+ self._mark_indexes_dirty()
240
+
241
+ logger.debug(f"Added item with UUID {self._get_item_uuid(item)}")
242
+ return item
243
+
244
+ def _mark_modified(self) -> None:
245
+ """Mark collection as modified."""
246
+ self._modified = True
247
+
248
+ def _mark_indexes_dirty(self) -> None:
249
+ """Mark indexes as needing rebuild."""
250
+ self._dirty_indexes = True
251
+
252
+ def _ensure_indexes_current(self) -> None:
253
+ """Ensure all indexes are current."""
254
+ if self._dirty_indexes:
255
+ self._rebuild_indexes()
256
+
257
+ def _rebuild_indexes(self) -> None:
258
+ """Rebuild all indexes."""
259
+ # Rebuild UUID index
260
+ self._uuid_index = {
261
+ self._get_item_uuid(item): i
262
+ for i, item in enumerate(self._items)
263
+ }
264
+
265
+ # Let subclasses rebuild their additional indexes
266
+ self._build_additional_indexes()
267
+
268
+ self._dirty_indexes = False
269
+ logger.debug(f"Rebuilt indexes for {self.__class__.__name__}")
270
+
271
+ # Collection statistics and debugging
272
+ def get_statistics(self) -> Dict[str, Any]:
273
+ """
274
+ Get collection statistics for debugging and monitoring.
275
+
276
+ Returns:
277
+ Dictionary with collection statistics
278
+ """
279
+ self._ensure_indexes_current()
280
+ return {
281
+ "item_count": len(self._items),
282
+ "uuid_index_size": len(self._uuid_index),
283
+ "modified": self._modified,
284
+ "indexes_dirty": self._dirty_indexes,
285
+ "collection_type": self.__class__.__name__
286
+ }
287
+
288
+ @property
289
+ def is_modified(self) -> bool:
290
+ """Whether collection has been modified."""
291
+ return self._modified
292
+
293
+ def mark_clean(self) -> None:
294
+ """Mark collection as clean (not modified)."""
295
+ self._modified = False
296
+ logger.debug(f"Marked {self.__class__.__name__} as clean")