kicad-sch-api 0.3.5__py3-none-any.whl → 0.4.0__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.

Potentially problematic release.


This version of kicad-sch-api might be problematic. Click here for more details.

Files changed (47) hide show
  1. kicad_sch_api/collections/__init__.py +2 -2
  2. kicad_sch_api/collections/base.py +5 -7
  3. kicad_sch_api/collections/components.py +24 -12
  4. kicad_sch_api/collections/junctions.py +31 -43
  5. kicad_sch_api/collections/labels.py +19 -27
  6. kicad_sch_api/collections/wires.py +17 -18
  7. kicad_sch_api/core/components.py +5 -0
  8. kicad_sch_api/core/formatter.py +3 -1
  9. kicad_sch_api/core/labels.py +2 -2
  10. kicad_sch_api/core/managers/__init__.py +26 -0
  11. kicad_sch_api/core/managers/file_io.py +243 -0
  12. kicad_sch_api/core/managers/format_sync.py +501 -0
  13. kicad_sch_api/core/managers/graphics.py +579 -0
  14. kicad_sch_api/core/managers/metadata.py +268 -0
  15. kicad_sch_api/core/managers/sheet.py +454 -0
  16. kicad_sch_api/core/managers/text_elements.py +536 -0
  17. kicad_sch_api/core/managers/validation.py +474 -0
  18. kicad_sch_api/core/managers/wire.py +346 -0
  19. kicad_sch_api/core/nets.py +1 -1
  20. kicad_sch_api/core/no_connects.py +5 -3
  21. kicad_sch_api/core/parser.py +75 -41
  22. kicad_sch_api/core/schematic.py +779 -1083
  23. kicad_sch_api/core/texts.py +1 -1
  24. kicad_sch_api/core/types.py +1 -4
  25. kicad_sch_api/geometry/font_metrics.py +3 -1
  26. kicad_sch_api/geometry/symbol_bbox.py +40 -21
  27. kicad_sch_api/interfaces/__init__.py +1 -1
  28. kicad_sch_api/interfaces/parser.py +1 -1
  29. kicad_sch_api/interfaces/repository.py +1 -1
  30. kicad_sch_api/interfaces/resolver.py +1 -1
  31. kicad_sch_api/parsers/__init__.py +2 -2
  32. kicad_sch_api/parsers/base.py +7 -10
  33. kicad_sch_api/parsers/label_parser.py +7 -7
  34. kicad_sch_api/parsers/registry.py +4 -2
  35. kicad_sch_api/parsers/symbol_parser.py +5 -10
  36. kicad_sch_api/parsers/wire_parser.py +2 -2
  37. kicad_sch_api/symbols/__init__.py +1 -1
  38. kicad_sch_api/symbols/cache.py +9 -12
  39. kicad_sch_api/symbols/resolver.py +20 -26
  40. kicad_sch_api/symbols/validators.py +188 -137
  41. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/METADATA +1 -1
  42. kicad_sch_api-0.4.0.dist-info/RECORD +67 -0
  43. kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
  44. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/WHEEL +0 -0
  45. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/entry_points.txt +0 -0
  46. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/licenses/LICENSE +0 -0
  47. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,243 @@
1
+ """
2
+ File I/O Manager for KiCAD schematic operations.
3
+
4
+ Handles all file system interactions including loading, saving, and backup operations
5
+ while maintaining exact format preservation.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from pathlib import Path
11
+ from typing import Any, Dict, Optional, Union
12
+
13
+ from ...utils.validation import ValidationError
14
+ from ..formatter import ExactFormatter
15
+ from ..parser import SExpressionParser
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class FileIOManager:
21
+ """
22
+ Manages file I/O operations for KiCAD schematics.
23
+
24
+ Responsible for:
25
+ - Loading schematic files with validation
26
+ - Saving with format preservation
27
+ - Creating backup files
28
+ - Managing file paths and metadata
29
+ """
30
+
31
+ def __init__(self):
32
+ """Initialize the FileIOManager."""
33
+ self._parser = SExpressionParser(preserve_format=True)
34
+ self._formatter = ExactFormatter()
35
+
36
+ def load_schematic(self, file_path: Union[str, Path]) -> Dict[str, Any]:
37
+ """
38
+ Load a KiCAD schematic file.
39
+
40
+ Args:
41
+ file_path: Path to .kicad_sch file
42
+
43
+ Returns:
44
+ Parsed schematic data
45
+
46
+ Raises:
47
+ FileNotFoundError: If file doesn't exist
48
+ ValidationError: If file is invalid or corrupted
49
+ """
50
+ start_time = time.time()
51
+ file_path = Path(file_path)
52
+
53
+ if not file_path.exists():
54
+ raise FileNotFoundError(f"Schematic file not found: {file_path}")
55
+
56
+ if not file_path.suffix == ".kicad_sch":
57
+ raise ValidationError(f"Not a KiCAD schematic file: {file_path}")
58
+
59
+ logger.info(f"Loading schematic: {file_path}")
60
+
61
+ try:
62
+ schematic_data = self._parser.parse_file(file_path)
63
+ load_time = time.time() - start_time
64
+ logger.info(f"Loaded schematic in {load_time:.3f}s")
65
+
66
+ return schematic_data
67
+
68
+ except Exception as e:
69
+ logger.error(f"Failed to load schematic {file_path}: {e}")
70
+ raise ValidationError(f"Invalid schematic file: {e}") from e
71
+
72
+ def save_schematic(
73
+ self,
74
+ schematic_data: Dict[str, Any],
75
+ file_path: Union[str, Path],
76
+ preserve_format: bool = True,
77
+ ) -> None:
78
+ """
79
+ Save schematic data to file.
80
+
81
+ Args:
82
+ schematic_data: Schematic data to save
83
+ file_path: Target file path
84
+ preserve_format: Whether to preserve exact formatting
85
+
86
+ Raises:
87
+ PermissionError: If file cannot be written
88
+ ValidationError: If data is invalid
89
+ """
90
+ start_time = time.time()
91
+ file_path = Path(file_path)
92
+
93
+ logger.info(f"Saving schematic: {file_path}")
94
+
95
+ try:
96
+ # Ensure parent directory exists
97
+ file_path.parent.mkdir(parents=True, exist_ok=True)
98
+
99
+ # Convert to S-expression format and save
100
+ sexp_data = self._parser._schematic_data_to_sexp(schematic_data)
101
+ formatted_content = self._formatter.format(sexp_data)
102
+
103
+ with open(file_path, "w", encoding="utf-8") as f:
104
+ f.write(formatted_content)
105
+
106
+ save_time = time.time() - start_time
107
+ logger.info(f"Saved schematic in {save_time:.3f}s")
108
+
109
+ except PermissionError as e:
110
+ logger.error(f"Permission denied saving to {file_path}: {e}")
111
+ raise
112
+ except Exception as e:
113
+ logger.error(f"Failed to save schematic to {file_path}: {e}")
114
+ raise ValidationError(f"Save failed: {e}") from e
115
+
116
+ def create_backup(self, file_path: Union[str, Path], suffix: str = ".backup") -> Path:
117
+ """
118
+ Create a backup copy of the schematic file.
119
+
120
+ Args:
121
+ file_path: Source file to backup
122
+ suffix: Backup file suffix
123
+
124
+ Returns:
125
+ Path to backup file
126
+
127
+ Raises:
128
+ FileNotFoundError: If source file doesn't exist
129
+ PermissionError: If backup cannot be created
130
+ """
131
+ file_path = Path(file_path)
132
+
133
+ if not file_path.exists():
134
+ raise FileNotFoundError(f"Cannot backup non-existent file: {file_path}")
135
+
136
+ # Create backup with timestamp if suffix doesn't include one
137
+ if suffix == ".backup":
138
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
139
+ backup_path = file_path.with_suffix(f".{timestamp}.backup")
140
+ else:
141
+ backup_path = file_path.with_suffix(f"{file_path.suffix}{suffix}")
142
+
143
+ try:
144
+ # Copy file content
145
+ backup_path.write_bytes(file_path.read_bytes())
146
+ logger.info(f"Created backup: {backup_path}")
147
+ return backup_path
148
+
149
+ except Exception as e:
150
+ logger.error(f"Failed to create backup {backup_path}: {e}")
151
+ raise PermissionError(f"Backup failed: {e}") from e
152
+
153
+ def validate_file_path(self, file_path: Union[str, Path]) -> Path:
154
+ """
155
+ Validate and normalize a file path for schematic operations.
156
+
157
+ Args:
158
+ file_path: Path to validate
159
+
160
+ Returns:
161
+ Normalized Path object
162
+
163
+ Raises:
164
+ ValidationError: If path is invalid
165
+ """
166
+ file_path = Path(file_path)
167
+
168
+ # Ensure .kicad_sch extension
169
+ if not file_path.suffix:
170
+ file_path = file_path.with_suffix(".kicad_sch")
171
+ elif file_path.suffix != ".kicad_sch":
172
+ raise ValidationError(f"Invalid schematic file extension: {file_path.suffix}")
173
+
174
+ # Validate path characters
175
+ try:
176
+ file_path.resolve()
177
+ except (OSError, ValueError) as e:
178
+ raise ValidationError(f"Invalid file path: {e}") from e
179
+
180
+ return file_path
181
+
182
+ def get_file_info(self, file_path: Union[str, Path]) -> Dict[str, Any]:
183
+ """
184
+ Get file system information about a schematic file.
185
+
186
+ Args:
187
+ file_path: Path to analyze
188
+
189
+ Returns:
190
+ Dictionary with file information
191
+
192
+ Raises:
193
+ FileNotFoundError: If file doesn't exist
194
+ """
195
+ file_path = Path(file_path)
196
+
197
+ if not file_path.exists():
198
+ raise FileNotFoundError(f"File not found: {file_path}")
199
+
200
+ stat = file_path.stat()
201
+
202
+ return {
203
+ "path": str(file_path.resolve()),
204
+ "size": stat.st_size,
205
+ "modified": stat.st_mtime,
206
+ "created": getattr(stat, "st_birthtime", stat.st_ctime),
207
+ "readable": file_path.is_file() and file_path.exists(),
208
+ "writable": file_path.parent.exists() and file_path.parent.is_dir(),
209
+ "extension": file_path.suffix,
210
+ }
211
+
212
+ def create_empty_schematic_data(self) -> Dict[str, Any]:
213
+ """
214
+ Create empty schematic data structure.
215
+
216
+ Returns:
217
+ Empty schematic data dictionary
218
+ """
219
+ return {
220
+ "kicad_sch": {
221
+ "version": 20230819,
222
+ "generator": "kicad-sch-api",
223
+ "uuid": None, # Will be set by calling code
224
+ "paper": "A4",
225
+ "lib_symbols": {},
226
+ "symbol": [],
227
+ "wire": [],
228
+ "junction": [],
229
+ "label": [],
230
+ "hierarchical_label": [],
231
+ "global_label": [],
232
+ "text": [],
233
+ "text_box": [],
234
+ "polyline": [],
235
+ "rectangle": [],
236
+ "circle": [],
237
+ "arc": [],
238
+ "image": [],
239
+ "sheet": [],
240
+ "sheet_instances": [],
241
+ "symbol_instances": [],
242
+ }
243
+ }