kicad-sch-api 0.4.1__py3-none-any.whl → 0.5.1__py3-none-any.whl

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 (66) hide show
  1. kicad_sch_api/__init__.py +67 -2
  2. kicad_sch_api/cli/kicad_to_python.py +169 -0
  3. kicad_sch_api/collections/__init__.py +23 -8
  4. kicad_sch_api/collections/base.py +369 -59
  5. kicad_sch_api/collections/components.py +1376 -187
  6. kicad_sch_api/collections/junctions.py +129 -289
  7. kicad_sch_api/collections/labels.py +391 -287
  8. kicad_sch_api/collections/wires.py +202 -316
  9. kicad_sch_api/core/__init__.py +37 -2
  10. kicad_sch_api/core/component_bounds.py +34 -12
  11. kicad_sch_api/core/components.py +146 -7
  12. kicad_sch_api/core/config.py +25 -12
  13. kicad_sch_api/core/connectivity.py +692 -0
  14. kicad_sch_api/core/exceptions.py +175 -0
  15. kicad_sch_api/core/factories/element_factory.py +3 -1
  16. kicad_sch_api/core/formatter.py +24 -7
  17. kicad_sch_api/core/geometry.py +94 -5
  18. kicad_sch_api/core/managers/__init__.py +4 -0
  19. kicad_sch_api/core/managers/base.py +76 -0
  20. kicad_sch_api/core/managers/file_io.py +3 -1
  21. kicad_sch_api/core/managers/format_sync.py +3 -2
  22. kicad_sch_api/core/managers/graphics.py +3 -2
  23. kicad_sch_api/core/managers/hierarchy.py +661 -0
  24. kicad_sch_api/core/managers/metadata.py +4 -2
  25. kicad_sch_api/core/managers/sheet.py +52 -14
  26. kicad_sch_api/core/managers/text_elements.py +3 -2
  27. kicad_sch_api/core/managers/validation.py +3 -2
  28. kicad_sch_api/core/managers/wire.py +112 -54
  29. kicad_sch_api/core/parsing_utils.py +63 -0
  30. kicad_sch_api/core/pin_utils.py +103 -9
  31. kicad_sch_api/core/schematic.py +343 -29
  32. kicad_sch_api/core/types.py +79 -7
  33. kicad_sch_api/exporters/__init__.py +10 -0
  34. kicad_sch_api/exporters/python_generator.py +610 -0
  35. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  36. kicad_sch_api/geometry/__init__.py +15 -3
  37. kicad_sch_api/geometry/routing.py +211 -0
  38. kicad_sch_api/parsers/elements/label_parser.py +30 -8
  39. kicad_sch_api/parsers/elements/symbol_parser.py +255 -83
  40. kicad_sch_api/utils/logging.py +555 -0
  41. kicad_sch_api/utils/logging_decorators.py +587 -0
  42. kicad_sch_api/utils/validation.py +16 -22
  43. kicad_sch_api/wrappers/__init__.py +14 -0
  44. kicad_sch_api/wrappers/base.py +89 -0
  45. kicad_sch_api/wrappers/wire.py +198 -0
  46. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  47. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  48. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  49. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  50. mcp_server/__init__.py +34 -0
  51. mcp_server/example_logging_integration.py +506 -0
  52. mcp_server/models.py +252 -0
  53. mcp_server/server.py +357 -0
  54. mcp_server/tools/__init__.py +32 -0
  55. mcp_server/tools/component_tools.py +516 -0
  56. mcp_server/tools/connectivity_tools.py +532 -0
  57. mcp_server/tools/consolidated_tools.py +1216 -0
  58. mcp_server/tools/pin_discovery.py +333 -0
  59. mcp_server/utils/__init__.py +38 -0
  60. mcp_server/utils/logging.py +127 -0
  61. mcp_server/utils.py +36 -0
  62. kicad_sch_api-0.4.1.dist-info/METADATA +0 -491
  63. kicad_sch_api-0.4.1.dist-info/RECORD +0 -87
  64. kicad_sch_api-0.4.1.dist-info/entry_points.txt +0 -2
  65. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  66. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,51 +1,308 @@
1
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.
2
+ Base collection infrastructure with centralized index management.
3
+
4
+ Provides:
5
+ - IndexSpec: Index specification and declaration
6
+ - IndexRegistry: Centralized index management with lazy rebuilding
7
+ - PropertyDict: Auto-tracking dictionary for modification detection
8
+ - ValidationLevel: Configurable validation levels
9
+ - BaseCollection: Abstract base class for all collections
6
10
  """
7
11
 
8
12
  import logging
9
13
  from abc import ABC, abstractmethod
10
- from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, TypeVar, Union
14
+ from collections.abc import MutableMapping
15
+ from dataclasses import dataclass
16
+ from enum import Enum
17
+ from functools import total_ordering
18
+ from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, Set, TypeVar, Union
11
19
 
12
20
  logger = logging.getLogger(__name__)
13
21
 
14
22
  T = TypeVar("T") # Type variable for collection items
15
23
 
16
24
 
17
- class IndexedCollection(Generic[T], ABC):
25
+ @total_ordering
26
+ class ValidationLevel(Enum):
27
+ """
28
+ Validation level for collection operations.
29
+
30
+ Controls the amount of validation performed during collection operations.
31
+ Higher levels provide more safety but lower performance.
32
+ """
33
+ NONE = 0 # No validation (maximum performance)
34
+ BASIC = 1 # Basic checks (duplicates, nulls)
35
+ NORMAL = 2 # Standard validation (default)
36
+ STRICT = 3 # Strict validation (referential integrity)
37
+ PARANOID = 4 # Maximum validation (everything, very slow)
38
+
39
+ def __lt__(self, other):
40
+ """Compare validation levels by value."""
41
+ if isinstance(other, ValidationLevel):
42
+ return self.value < other.value
43
+ return NotImplemented
44
+
45
+
46
+ @dataclass
47
+ class IndexSpec:
48
+ """
49
+ Specification for a collection index.
50
+
51
+ Defines how to build and maintain an index for fast lookups.
52
+ """
53
+ name: str
54
+ key_func: Callable[[Any], Any]
55
+ unique: bool = True
56
+ description: str = ""
57
+
58
+ def __post_init__(self):
59
+ """Validate index specification."""
60
+ if not self.name:
61
+ raise ValueError("Index name cannot be empty")
62
+ if not callable(self.key_func):
63
+ raise ValueError("Index key_func must be callable")
64
+
65
+
66
+ class IndexRegistry:
67
+ """
68
+ Centralized registry for managing collection indexes.
69
+
70
+ Provides:
71
+ - Lazy index rebuilding (only when needed)
72
+ - Multiple index support (uuid, reference, lib_id, etc.)
73
+ - Duplicate detection for unique indexes
74
+ - Unified index management API
75
+ """
76
+
77
+ def __init__(self, specs: List[IndexSpec]):
78
+ """
79
+ Initialize index registry.
80
+
81
+ Args:
82
+ specs: List of index specifications to manage
83
+ """
84
+ self.specs = {spec.name: spec for spec in specs}
85
+ self.indexes: Dict[str, Dict[Any, Any]] = {spec.name: {} for spec in specs}
86
+ self._dirty = False
87
+
88
+ logger.debug(f"IndexRegistry initialized with {len(specs)} indexes: {list(self.specs.keys())}")
89
+
90
+ def mark_dirty(self) -> None:
91
+ """Mark all indexes as needing rebuild."""
92
+ self._dirty = True
93
+ logger.debug("Indexes marked dirty")
94
+
95
+ def is_dirty(self) -> bool:
96
+ """Check if indexes need rebuilding."""
97
+ return self._dirty
98
+
99
+ def rebuild(self, items: List[Any]) -> None:
100
+ """
101
+ Rebuild all indexes from items.
102
+
103
+ Args:
104
+ items: List of items to index
105
+
106
+ Raises:
107
+ ValueError: If unique index has duplicates
108
+ """
109
+ logger.debug(f"Rebuilding {len(self.indexes)} indexes for {len(items)} items")
110
+
111
+ # Clear all indexes
112
+ for index_name in self.indexes:
113
+ self.indexes[index_name].clear()
114
+
115
+ # Rebuild each index
116
+ for spec in self.specs.values():
117
+ self._rebuild_index(spec, items)
118
+
119
+ self._dirty = False
120
+ logger.debug("Index rebuild complete")
121
+
122
+ def _rebuild_index(self, spec: IndexSpec, items: List[Any]) -> None:
123
+ """
124
+ Rebuild a single index.
125
+
126
+ Args:
127
+ spec: Index specification
128
+ items: Items to index
129
+
130
+ Raises:
131
+ ValueError: If unique index has duplicates
132
+ """
133
+ index = self.indexes[spec.name]
134
+
135
+ for i, item in enumerate(items):
136
+ try:
137
+ key = spec.key_func(item)
138
+
139
+ if spec.unique:
140
+ if key in index:
141
+ raise ValueError(
142
+ f"Duplicate key '{key}' in unique index '{spec.name}'"
143
+ )
144
+ index[key] = i
145
+ else:
146
+ # Non-unique index: multiple items per key
147
+ if key not in index:
148
+ index[key] = []
149
+ index[key].append(i)
150
+
151
+ except ValueError:
152
+ # Re-raise ValueError (e.g., duplicate key)
153
+ raise
154
+ except Exception as e:
155
+ # Log and skip other errors (e.g., key_func failure)
156
+ logger.warning(f"Failed to index item {i} in '{spec.name}': {e}")
157
+ # Continue indexing other items
158
+
159
+ def get(self, index_name: str, key: Any) -> Optional[Any]:
160
+ """
161
+ Get value from an index.
162
+
163
+ Args:
164
+ index_name: Name of the index
165
+ key: Key to look up
166
+
167
+ Returns:
168
+ Index value if found, None otherwise
169
+ """
170
+ if index_name not in self.indexes:
171
+ raise KeyError(f"Unknown index: {index_name}")
172
+
173
+ return self.indexes[index_name].get(key)
174
+
175
+ def has_key(self, index_name: str, key: Any) -> bool:
176
+ """
177
+ Check if key exists in index.
178
+
179
+ Args:
180
+ index_name: Name of the index
181
+ key: Key to check
182
+
183
+ Returns:
184
+ True if key exists, False otherwise
185
+ """
186
+ if index_name not in self.indexes:
187
+ raise KeyError(f"Unknown index: {index_name}")
188
+
189
+ return key in self.indexes[index_name]
190
+
191
+ def add_spec(self, spec: IndexSpec) -> None:
192
+ """
193
+ Add a new index specification.
194
+
195
+ Args:
196
+ spec: Index specification to add
197
+ """
198
+ if spec.name in self.specs:
199
+ raise ValueError(f"Index '{spec.name}' already exists")
200
+
201
+ self.specs[spec.name] = spec
202
+ self.indexes[spec.name] = {}
203
+ self.mark_dirty()
204
+
205
+ logger.debug(f"Added index spec: {spec.name}")
206
+
207
+
208
+ class PropertyDict(MutableMapping):
18
209
  """
19
- Base class for all schematic element collections with automatic indexing.
210
+ Dictionary that automatically tracks modifications.
211
+
212
+ Wraps a dictionary and notifies a callback when any changes occur.
213
+ Implements the full MutableMapping interface.
214
+ """
215
+
216
+ def __init__(self, data: Optional[Dict[str, Any]] = None, on_modify: Optional[Callable[[], None]] = None):
217
+ """
218
+ Initialize property dictionary.
219
+
220
+ Args:
221
+ data: Initial dictionary data
222
+ on_modify: Callback to invoke when dict is modified
223
+ """
224
+ self._data = data or {}
225
+ self._on_modify = on_modify
226
+
227
+ def __getitem__(self, key: str) -> Any:
228
+ """Get item by key."""
229
+ return self._data[key]
230
+
231
+ def __setitem__(self, key: str, value: Any) -> None:
232
+ """Set item and trigger modification callback."""
233
+ self._data[key] = value
234
+ if self._on_modify:
235
+ self._on_modify()
236
+
237
+ def __delitem__(self, key: str) -> None:
238
+ """Delete item and trigger modification callback."""
239
+ del self._data[key]
240
+ if self._on_modify:
241
+ self._on_modify()
242
+
243
+ def __iter__(self) -> Iterator[str]:
244
+ """Iterate over keys."""
245
+ return iter(self._data)
246
+
247
+ def __len__(self) -> int:
248
+ """Number of items."""
249
+ return len(self._data)
250
+
251
+ def __repr__(self) -> str:
252
+ """String representation."""
253
+ return f"PropertyDict({self._data!r})"
254
+
255
+ def set_callback(self, on_modify: Callable[[], None]) -> None:
256
+ """Set or update the modification callback."""
257
+ self._on_modify = on_modify
258
+
259
+
260
+ class BaseCollection(Generic[T], ABC):
261
+ """
262
+ Abstract base class for all schematic element collections.
20
263
 
21
264
  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
265
+ - Lazy index rebuilding via IndexRegistry
266
+ - Automatic modification tracking
267
+ - Configurable validation levels
268
+ - Batch mode for performance
269
+ - Consistent collection operations (add, remove, get, filter)
270
+
271
+ Subclasses must implement:
272
+ - _get_item_uuid(item): Extract UUID from item
273
+ - _create_item(**kwargs): Create new item instance
274
+ - _get_index_specs(): Return list of IndexSpec for this collection
27
275
  """
28
276
 
29
- def __init__(self, items: Optional[List[T]] = None):
277
+ def __init__(
278
+ self,
279
+ items: Optional[List[T]] = None,
280
+ validation_level: ValidationLevel = ValidationLevel.NORMAL
281
+ ):
30
282
  """
31
- Initialize indexed collection.
283
+ Initialize base collection.
32
284
 
33
285
  Args:
34
- items: Initial list of items to add to collection
286
+ items: Initial list of items
287
+ validation_level: Validation level for operations
35
288
  """
36
289
  self._items: List[T] = []
37
- self._uuid_index: Dict[str, int] = {}
290
+ self._validation_level = validation_level
38
291
  self._modified = False
39
- self._dirty_indexes = False
292
+ self._batch_mode = False
40
293
 
41
- # Add initial items if provided
294
+ # Set up index registry with subclass-specific indexes
295
+ index_specs = self._get_index_specs()
296
+ self._index_registry = IndexRegistry(index_specs)
297
+
298
+ # Add initial items
42
299
  if items:
43
300
  for item in items:
44
301
  self._add_item_to_collection(item)
45
302
 
46
303
  logger.debug(f"{self.__class__.__name__} initialized with {len(self._items)} items")
47
304
 
48
- # Abstract methods for subclasses to implement
305
+ # Abstract methods for subclasses
49
306
  @abstractmethod
50
307
  def _get_item_uuid(self, item: T) -> str:
51
308
  """
@@ -55,7 +312,7 @@ class IndexedCollection(Generic[T], ABC):
55
312
  item: Item to extract UUID from
56
313
 
57
314
  Returns:
58
- UUID string for the item
315
+ UUID string
59
316
  """
60
317
  pass
61
318
 
@@ -73,12 +330,12 @@ class IndexedCollection(Generic[T], ABC):
73
330
  pass
74
331
 
75
332
  @abstractmethod
76
- def _build_additional_indexes(self) -> None:
333
+ def _get_index_specs(self) -> List[IndexSpec]:
77
334
  """
78
- Build any additional indexes specific to the collection type.
335
+ Get index specifications for this collection.
79
336
 
80
- Called after UUID index is rebuilt. Subclasses should implement
81
- this to maintain their own specialized indexes.
337
+ Returns:
338
+ List of IndexSpec defining indexes for this collection
82
339
  """
83
340
  pass
84
341
 
@@ -96,14 +353,17 @@ class IndexedCollection(Generic[T], ABC):
96
353
  Raises:
97
354
  ValueError: If item with same UUID already exists
98
355
  """
99
- uuid_str = self._get_item_uuid(item)
356
+ # Validation
357
+ if self._validation_level >= ValidationLevel.BASIC:
358
+ if item is None:
359
+ raise ValueError("Cannot add None item to collection")
100
360
 
101
- # Ensure indexes are current before checking for duplicates
102
- self._ensure_indexes_current()
361
+ uuid_str = self._get_item_uuid(item)
103
362
 
104
- # Check for duplicate UUID
105
- if uuid_str in self._uuid_index:
106
- raise ValueError(f"Item with UUID {uuid_str} already exists")
363
+ # Check for duplicate UUID
364
+ self._ensure_indexes_current()
365
+ if self._index_registry.has_key("uuid", uuid_str):
366
+ raise ValueError(f"Item with UUID {uuid_str} already exists")
107
367
 
108
368
  return self._add_item_to_collection(item)
109
369
 
@@ -121,24 +381,22 @@ class IndexedCollection(Generic[T], ABC):
121
381
 
122
382
  if isinstance(identifier, str):
123
383
  # Remove by UUID
124
- if identifier not in self._uuid_index:
384
+ index = self._index_registry.get("uuid", identifier)
385
+ if index is None:
125
386
  return False
126
-
127
- index = self._uuid_index[identifier]
128
387
  item = self._items[index]
129
388
  else:
130
389
  # Remove by item instance
131
390
  item = identifier
132
391
  uuid_str = self._get_item_uuid(item)
133
- if uuid_str not in self._uuid_index:
392
+ index = self._index_registry.get("uuid", uuid_str)
393
+ if index is None:
134
394
  return False
135
395
 
136
- index = self._uuid_index[uuid_str]
137
-
138
396
  # Remove from main list
139
397
  self._items.pop(index)
140
398
  self._mark_modified()
141
- self._mark_indexes_dirty()
399
+ self._index_registry.mark_dirty()
142
400
 
143
401
  logger.debug(f"Removed item with UUID {self._get_item_uuid(item)}")
144
402
  return True
@@ -155,8 +413,8 @@ class IndexedCollection(Generic[T], ABC):
155
413
  """
156
414
  self._ensure_indexes_current()
157
415
 
158
- if uuid in self._uuid_index:
159
- index = self._uuid_index[uuid]
416
+ index = self._index_registry.get("uuid", uuid)
417
+ if index is not None:
160
418
  return self._items[index]
161
419
 
162
420
  return None
@@ -183,7 +441,6 @@ class IndexedCollection(Generic[T], ABC):
183
441
  Returns:
184
442
  List of matching items
185
443
  """
186
-
187
444
  def matches_criteria(item: T) -> bool:
188
445
  for attr, value in criteria.items():
189
446
  if not hasattr(item, attr) or getattr(item, attr) != value:
@@ -195,10 +452,25 @@ class IndexedCollection(Generic[T], ABC):
195
452
  def clear(self) -> None:
196
453
  """Clear all items from the collection."""
197
454
  self._items.clear()
198
- self._uuid_index.clear()
455
+ self._index_registry.mark_dirty()
199
456
  self._mark_modified()
200
457
  logger.debug(f"Cleared all items from {self.__class__.__name__}")
201
458
 
459
+ # Batch mode operations
460
+ def batch_mode(self):
461
+ """
462
+ Context manager for batch operations.
463
+
464
+ Defers index rebuilding until the batch is complete.
465
+
466
+ Example:
467
+ with collection.batch_mode():
468
+ for i in range(1000):
469
+ collection.add(create_item(i))
470
+ # Indexes rebuilt only once here
471
+ """
472
+ return BatchContext(self)
473
+
202
474
  # Collection interface methods
203
475
  def __len__(self) -> int:
204
476
  """Number of items in collection."""
@@ -213,12 +485,12 @@ class IndexedCollection(Generic[T], ABC):
213
485
  if isinstance(item, str):
214
486
  # Check by UUID
215
487
  self._ensure_indexes_current()
216
- return item in self._uuid_index
488
+ return self._index_registry.has_key("uuid", item)
217
489
  else:
218
490
  # Check by item instance
219
491
  uuid_str = self._get_item_uuid(item)
220
492
  self._ensure_indexes_current()
221
- return uuid_str in self._uuid_index
493
+ return self._index_registry.has_key("uuid", uuid_str)
222
494
 
223
495
  def __getitem__(self, index: int) -> T:
224
496
  """Get item by index."""
@@ -237,7 +509,10 @@ class IndexedCollection(Generic[T], ABC):
237
509
  """
238
510
  self._items.append(item)
239
511
  self._mark_modified()
240
- self._mark_indexes_dirty()
512
+
513
+ # Always mark indexes as dirty when items change
514
+ # Batch mode just defers the rebuild, not the dirty flag
515
+ self._index_registry.mark_dirty()
241
516
 
242
517
  logger.debug(f"Added item with UUID {self._get_item_uuid(item)}")
243
518
  return item
@@ -246,24 +521,14 @@ class IndexedCollection(Generic[T], ABC):
246
521
  """Mark collection as modified."""
247
522
  self._modified = True
248
523
 
249
- def _mark_indexes_dirty(self) -> None:
250
- """Mark indexes as needing rebuild."""
251
- self._dirty_indexes = True
252
-
253
524
  def _ensure_indexes_current(self) -> None:
254
- """Ensure all indexes are current."""
255
- if self._dirty_indexes:
525
+ """Ensure all indexes are current (unless in batch mode)."""
526
+ if not self._batch_mode and self._index_registry.is_dirty():
256
527
  self._rebuild_indexes()
257
528
 
258
529
  def _rebuild_indexes(self) -> None:
259
530
  """Rebuild all indexes."""
260
- # Rebuild UUID index
261
- self._uuid_index = {self._get_item_uuid(item): i for i, item in enumerate(self._items)}
262
-
263
- # Let subclasses rebuild their additional indexes
264
- self._build_additional_indexes()
265
-
266
- self._dirty_indexes = False
531
+ self._index_registry.rebuild(self._items)
267
532
  logger.debug(f"Rebuilt indexes for {self.__class__.__name__}")
268
533
 
269
534
  # Collection statistics and debugging
@@ -277,10 +542,12 @@ class IndexedCollection(Generic[T], ABC):
277
542
  self._ensure_indexes_current()
278
543
  return {
279
544
  "item_count": len(self._items),
280
- "uuid_index_size": len(self._uuid_index),
545
+ "index_count": len(self._index_registry.indexes),
281
546
  "modified": self._modified,
282
- "indexes_dirty": self._dirty_indexes,
547
+ "indexes_dirty": self._index_registry.is_dirty(),
283
548
  "collection_type": self.__class__.__name__,
549
+ "validation_level": self._validation_level.name,
550
+ "batch_mode": self._batch_mode,
284
551
  }
285
552
 
286
553
  @property
@@ -292,3 +559,46 @@ class IndexedCollection(Generic[T], ABC):
292
559
  """Mark collection as clean (not modified)."""
293
560
  self._modified = False
294
561
  logger.debug(f"Marked {self.__class__.__name__} as clean")
562
+
563
+ @property
564
+ def validation_level(self) -> ValidationLevel:
565
+ """Current validation level."""
566
+ return self._validation_level
567
+
568
+ def set_validation_level(self, level: ValidationLevel) -> None:
569
+ """
570
+ Set validation level.
571
+
572
+ Args:
573
+ level: New validation level
574
+ """
575
+ self._validation_level = level
576
+ logger.debug(f"Set validation level to {level.name}")
577
+
578
+
579
+ class BatchContext:
580
+ """Context manager for batch operations."""
581
+
582
+ def __init__(self, collection: BaseCollection):
583
+ """
584
+ Initialize batch context.
585
+
586
+ Args:
587
+ collection: Collection to batch operations on
588
+ """
589
+ self.collection = collection
590
+
591
+ def __enter__(self):
592
+ """Enter batch mode - defers index rebuilds."""
593
+ self.collection._batch_mode = True
594
+ logger.debug(f"Entered batch mode for {self.collection.__class__.__name__}")
595
+ return self.collection
596
+
597
+ def __exit__(self, exc_type, exc_val, exc_tb):
598
+ """Exit batch mode and rebuild indexes if needed."""
599
+ self.collection._batch_mode = False
600
+ # Indexes are already marked dirty by add operations
601
+ # Just ensure they're rebuilt now
602
+ self.collection._ensure_indexes_current()
603
+ logger.debug(f"Exited batch mode for {self.collection.__class__.__name__}")
604
+ return False