xtce-lib 0.1.0a1__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 (55) hide show
  1. xtce_lib/__init__.py +33 -0
  2. xtce_lib/common/__init__.py +1 -0
  3. xtce_lib/common/validation.py +71 -0
  4. xtce_lib/common/xtce_database.py +286 -0
  5. xtce_lib/common/xtce_file.py +207 -0
  6. xtce_lib/common/xtce_path.py +344 -0
  7. xtce_lib/common/xtce_registry.py +96 -0
  8. xtce_lib/common/xtce_version.py +45 -0
  9. xtce_lib/exceptions.py +44 -0
  10. xtce_lib/generated/__init__.py +5 -0
  11. xtce_lib/generated/xtce_1_0/__init__.py +249 -0
  12. xtce_lib/generated/xtce_1_0/models.py +5637 -0
  13. xtce_lib/generated/xtce_1_1/__init__.py +277 -0
  14. xtce_lib/generated/xtce_1_1/models.py +6265 -0
  15. xtce_lib/generated/xtce_1_2/__init__.py +633 -0
  16. xtce_lib/generated/xtce_1_2/models.py +9964 -0
  17. xtce_lib/generated/xtce_1_3/__init__.py +645 -0
  18. xtce_lib/generated/xtce_1_3/models.py +10570 -0
  19. xtce_lib/py.typed +0 -0
  20. xtce_lib/xsd/dtc-05-01-05.xsd +2798 -0
  21. xtce_lib/xsd/dtc-06-11-06.xsd +3229 -0
  22. xtce_lib/xsd/dtc-18-02-04.xsd +5918 -0
  23. xtce_lib/xsd/dtc-25-02-18.xsd +6363 -0
  24. xtce_lib/xsd/xml.xsd +117 -0
  25. xtce_lib/xtce/__init__.py +602 -0
  26. xtce_lib/xtce/_base.py +400 -0
  27. xtce_lib/xtce/_pattern.py +15 -0
  28. xtce_lib/xtce/_type_aliases.py +8 -0
  29. xtce_lib/xtce/_type_aliases_ext.py +69 -0
  30. xtce_lib/xtce/_util.py +157 -0
  31. xtce_lib/xtce/alarm.py +1710 -0
  32. xtce_lib/xtce/algorithm.py +1276 -0
  33. xtce_lib/xtce/argument.py +481 -0
  34. xtce_lib/xtce/array.py +231 -0
  35. xtce_lib/xtce/calibrator.py +1037 -0
  36. xtce_lib/xtce/codec.py +2064 -0
  37. xtce_lib/xtce/command.py +2188 -0
  38. xtce_lib/xtce/common.py +459 -0
  39. xtce_lib/xtce/condition.py +1497 -0
  40. xtce_lib/xtce/container.py +2717 -0
  41. xtce_lib/xtce/datatype.py +2695 -0
  42. xtce_lib/xtce/enum.py +1065 -0
  43. xtce_lib/xtce/parameter.py +1429 -0
  44. xtce_lib/xtce/range.py +556 -0
  45. xtce_lib/xtce/reference.py +725 -0
  46. xtce_lib/xtce/space_system.py +652 -0
  47. xtce_lib/xtce/stream.py +914 -0
  48. xtce_lib/xtce/telemetry.py +554 -0
  49. xtce_lib/xtce/time.py +424 -0
  50. xtce_lib/xtce/trigger.py +410 -0
  51. xtce_lib/xtce/verifier.py +807 -0
  52. xtce_lib-0.1.0a1.dist-info/METADATA +153 -0
  53. xtce_lib-0.1.0a1.dist-info/RECORD +55 -0
  54. xtce_lib-0.1.0a1.dist-info/WHEEL +4 -0
  55. xtce_lib-0.1.0a1.dist-info/licenses/LICENSE +21 -0
xtce_lib/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """xtce-lib."""
2
+
3
+ import logging
4
+
5
+ log = logging.getLogger(__name__)
6
+ log.addHandler(logging.NullHandler())
7
+
8
+ from . import generated, xtce
9
+ from .common.validation import ValidationReport, XtceSchemaError, XtceSemanticError
10
+ from .common.xtce_database import XtceDatabase
11
+ from .common.xtce_file import XtceFile
12
+ from .common.xtce_path import PathNode, XtcePath
13
+ from .common.xtce_registry import ResolvedReference, XtceRegistry
14
+ from .common.xtce_version import XtceVersion
15
+ from .exceptions import DowngradePolicy, XtceDowngradeError, XtceUnsupportedError
16
+
17
+ __all__ = [
18
+ "generated",
19
+ "xtce",
20
+ "ValidationReport",
21
+ "XtceSchemaError",
22
+ "XtceSemanticError",
23
+ "XtceDatabase",
24
+ "XtceFile",
25
+ "PathNode",
26
+ "XtcePath",
27
+ "ResolvedReference",
28
+ "XtceRegistry",
29
+ "XtceVersion",
30
+ "DowngradePolicy",
31
+ "XtceDowngradeError",
32
+ "XtceUnsupportedError",
33
+ ]
@@ -0,0 +1 @@
1
+ """XTCE common module."""
@@ -0,0 +1,71 @@
1
+ """Common validation utilities."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass, field
5
+ from typing import Generic, TypeVar
6
+
7
+ from .xtce_path import XtcePath
8
+
9
+
10
+ class BaseValidationError(ABC):
11
+ """Abstract base class for all validation errors."""
12
+
13
+ @abstractmethod
14
+ def format(self) -> str:
15
+ """Return a formatted string representation of the error."""
16
+ pass
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class XtceSchemaError(BaseValidationError):
21
+ """An XTCE schema validation error."""
22
+
23
+ line: int
24
+ message: str
25
+
26
+ def format(self) -> str:
27
+ """Return a formatted string representation of the error."""
28
+ return f"Line {self.line}: {self.message}"
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class XtceSemanticError(BaseValidationError):
33
+ """An XTCE semantic validation error."""
34
+
35
+ scope: XtcePath
36
+ message: str
37
+
38
+ def format(self) -> str:
39
+ """Return a formatted string representation of the error."""
40
+ return f"[{self.scope}] {self.message}"
41
+
42
+
43
+ ErrorType = TypeVar("ErrorType", bound=BaseValidationError)
44
+
45
+
46
+ @dataclass
47
+ class ValidationReport(Generic[ErrorType]):
48
+ """A generic report for XTCE validation."""
49
+
50
+ title: str
51
+ errors: list[ErrorType] = field(default_factory=list)
52
+
53
+ @property
54
+ def is_valid(self) -> bool:
55
+ """True if the validation pass found no errors."""
56
+ return len(self.errors) == 0
57
+
58
+ def add_error(self, error: ErrorType) -> None:
59
+ """Append an error to the report."""
60
+ self.errors.append(error)
61
+
62
+ def summary(self) -> str:
63
+ """Generate a console-friendly report."""
64
+ if self.is_valid:
65
+ return f"{self.title}: PASSED"
66
+
67
+ lines = [f"{self.title}: FAILED with {len(self.errors)} errors"]
68
+ for err in self.errors:
69
+ lines.append(f" {err.format()}")
70
+
71
+ return "\n".join(lines)
@@ -0,0 +1,286 @@
1
+ """XTCE database object."""
2
+
3
+ import itertools
4
+ from pathlib import Path
5
+ from typing import Any, Iterable, Self
6
+
7
+ from pydantic import validate_call
8
+ from xsdata.formats.dataclass.serializers import XmlSerializer
9
+ from xsdata.formats.dataclass.serializers.config import SerializerConfig
10
+
11
+ from xtce_lib.common.xtce_version import XtceVersion
12
+ from xtce_lib.exceptions import DowngradePolicy
13
+ from xtce_lib.xtce._type_aliases_ext import ReferenceableXtceObject
14
+ from xtce_lib.xtce.command import CommandMetadata, MetaCommandRef
15
+ from xtce_lib.xtce.common import Alias, AncillaryData
16
+ from xtce_lib.xtce.enum import SystemType
17
+ from xtce_lib.xtce.reference import ParameterRef
18
+ from xtce_lib.xtce.space_system import Header, Service, SpaceSystem
19
+ from xtce_lib.xtce.telemetry import TelemetryMetadata
20
+
21
+ from .validation import ValidationReport, XtceSemanticError
22
+ from .xtce_file import XtceFile
23
+ from .xtce_path import XtcePath
24
+ from .xtce_registry import XtceRegistry
25
+
26
+
27
+ class XtceDatabase:
28
+ """An XTCE database."""
29
+
30
+ @validate_call
31
+ def __init__(self, name: str) -> None:
32
+ """Initialize a new XTCE database."""
33
+ self._root_system = SpaceSystem(name=name)
34
+ self._registry: XtceRegistry | None = None
35
+
36
+ @classmethod
37
+ @validate_call
38
+ def from_space_system(cls, space_system: SpaceSystem) -> Self:
39
+ """Create a new XTCE database from an existing SpaceSystem."""
40
+ # Bypass __init__
41
+ instance = cls.__new__(cls)
42
+ instance._root_system = space_system
43
+ instance._registry = None
44
+ return instance
45
+
46
+ # TODO maybe from_file(), not sure how to handle XtceFile vs XtceDatabase yet
47
+
48
+ # The below methods allow for accessing the properties of the root SpaceSystem
49
+ # directly from the database, makes for a slightly cleaner API
50
+
51
+ @property
52
+ def name(self) -> str:
53
+ """The name of the SpaceSystem."""
54
+ return self._root_system.name
55
+
56
+ @name.setter
57
+ def name(self, value: str) -> None:
58
+ self._root_system.name = value
59
+
60
+ @property
61
+ def short_description(self) -> str | None:
62
+ """The short description of the SpaceSystem."""
63
+ return self._root_system.short_description
64
+
65
+ @short_description.setter
66
+ def short_description(self, value: str | None) -> None:
67
+ self._root_system.short_description = value
68
+
69
+ @property
70
+ def long_description(self) -> str | None:
71
+ """The long description of the SpaceSystem."""
72
+ return self._root_system.long_description
73
+
74
+ @long_description.setter
75
+ def long_description(self, value: str | None) -> None:
76
+ self._root_system.long_description = value
77
+
78
+ @property
79
+ def aliases(self) -> list[Alias]:
80
+ """The aliases of the SpaceSystem."""
81
+ return self._root_system.aliases
82
+
83
+ @aliases.setter
84
+ def aliases(self, value: list[Alias]) -> None:
85
+ self._root_system.aliases = value
86
+
87
+ @property
88
+ def ancillary_data(self) -> list[AncillaryData]:
89
+ """The ancillary data of the SpaceSystem."""
90
+ return self._root_system.ancillary_data
91
+
92
+ @ancillary_data.setter
93
+ def ancillary_data(self, value: list[AncillaryData]) -> None:
94
+ self._root_system.ancillary_data = value
95
+
96
+ @property
97
+ def header(self) -> Header | None:
98
+ """The header of the SpaceSystem."""
99
+ return self._root_system.header
100
+
101
+ @header.setter
102
+ def header(self, value: Header | None) -> None:
103
+ self._root_system.header = value
104
+
105
+ @property
106
+ def telemetry_metadata(self) -> TelemetryMetadata | None:
107
+ """The telemetry metadata of the SpaceSystem."""
108
+ return self._root_system.telemetry_metadata
109
+
110
+ @telemetry_metadata.setter
111
+ def telemetry_metadata(self, value: TelemetryMetadata | None) -> None:
112
+ self._root_system.telemetry_metadata = value
113
+
114
+ @property
115
+ def command_metadata(self) -> CommandMetadata | None:
116
+ """The command metadata of the SpaceSystem."""
117
+ return self._root_system.command_metadata
118
+
119
+ @command_metadata.setter
120
+ def command_metadata(self, value: CommandMetadata | None) -> None:
121
+ self._root_system.command_metadata = value
122
+
123
+ @property
124
+ def services(self) -> list[Service]:
125
+ """The services of the SpaceSystem."""
126
+ return self._root_system.services
127
+
128
+ @services.setter
129
+ def services(self, value: list[Service]) -> None:
130
+ self._root_system.services = value
131
+
132
+ @property
133
+ def space_systems(self) -> list[SpaceSystem]:
134
+ """The child SpaceSystems of the SpaceSystem."""
135
+ return self._root_system.space_systems
136
+
137
+ @space_systems.setter
138
+ def space_systems(self, value: list[SpaceSystem]) -> None:
139
+ self._root_system.space_systems = value
140
+
141
+ @property
142
+ def system_type(self) -> SystemType:
143
+ """The system type of the SpaceSystem."""
144
+ return self._root_system.system_type
145
+
146
+ @system_type.setter
147
+ def system_type(self, value: SystemType) -> None:
148
+ self._root_system.system_type = value
149
+
150
+ @property
151
+ def asset_type(self) -> str:
152
+ """The asset type of the SpaceSystem."""
153
+ return self._root_system.asset_type
154
+
155
+ @asset_type.setter
156
+ def asset_type(self, value: str) -> None:
157
+ self._root_system.asset_type = value
158
+
159
+ @property
160
+ def operational_status(self) -> str | None:
161
+ """The operational status of the SpaceSystem."""
162
+ return self._root_system.operational_status
163
+
164
+ @operational_status.setter
165
+ def operational_status(self, value: str | None) -> None:
166
+ self._root_system.operational_status = value
167
+
168
+ @property
169
+ def base(self) -> str | None:
170
+ """The base of the SpaceSystem."""
171
+ return self._root_system.base
172
+
173
+ @base.setter
174
+ def base(self, value: str | None) -> None:
175
+ self._root_system.base = value
176
+
177
+ @property
178
+ def root_system(self) -> SpaceSystem:
179
+ """The root SpaceSystem of the database."""
180
+ return self._root_system
181
+
182
+ @property
183
+ def registry(self) -> XtceRegistry:
184
+ """Get the registry of all XTCE objects in this database."""
185
+ if self._registry is None:
186
+ self._registry = XtceRegistry()
187
+ self._index_space_system(self.root_system, XtcePath("/"), self._registry)
188
+ return self._registry
189
+
190
+ def rebuild_registry(self) -> None:
191
+ """Force a rebuild of the registry."""
192
+ new_registry = XtceRegistry()
193
+ self._index_space_system(self.root_system, XtcePath("/"), new_registry)
194
+ self._registry = new_registry
195
+
196
+ def validate(self) -> ValidationReport[XtceSemanticError]:
197
+ """Perform semantic validation of this database."""
198
+ report = ValidationReport[XtceSemanticError](title="Semantic Validation")
199
+ self.rebuild_registry() # Ensure registry is up to date before validation
200
+ self.root_system.validate_semantics(report, self.registry, XtcePath("/"))
201
+ return report
202
+
203
+ @validate_call
204
+ def to_file(
205
+ self,
206
+ path: Path,
207
+ xtce_version: XtceVersion,
208
+ *,
209
+ downgrade_policy: DowngradePolicy = DowngradePolicy.STRICT,
210
+ **kwargs: Any,
211
+ ) -> XtceFile:
212
+ """Write the database to an XTCE XML file.
213
+
214
+ Any keyword arguments override the default serializer configuration.
215
+ """
216
+ # Translate the SpaceSystem
217
+ space_system = self._root_system.to_xsdata(
218
+ version=xtce_version,
219
+ policy=downgrade_policy,
220
+ )
221
+
222
+ # Serialize to file
223
+ config_kwargs: dict[str, Any] = {
224
+ "indent": " ",
225
+ }
226
+ config_kwargs.update(kwargs)
227
+ config = SerializerConfig(**config_kwargs)
228
+ serializer = XmlSerializer(config=config)
229
+
230
+ with path.open("w", encoding="utf-8") as f:
231
+ serializer.write( # type: ignore[reportUnknownMemberType]
232
+ f,
233
+ space_system,
234
+ ns_map={"xtce": xtce_version.value.namespace},
235
+ )
236
+
237
+ return XtceFile(path)
238
+
239
+ def _index_space_system(
240
+ self,
241
+ space_system: SpaceSystem,
242
+ parent_path: XtcePath,
243
+ registry: XtceRegistry,
244
+ ) -> None:
245
+ """Recursively walk the SpaceSystem hierarchy and index all elements."""
246
+ current_path = parent_path / space_system.name
247
+ registry.register(current_path, space_system)
248
+
249
+ collections_to_index: list[Iterable[ReferenceableXtceObject] | None] = []
250
+
251
+ if space_system.command_metadata:
252
+ cmd = space_system.command_metadata
253
+ collections_to_index.extend(
254
+ [
255
+ cmd.argument_types,
256
+ [
257
+ meta_command
258
+ for meta_command in cmd.meta_commands
259
+ if not isinstance(meta_command, MetaCommandRef)
260
+ ],
261
+ ]
262
+ )
263
+
264
+ if space_system.telemetry_metadata:
265
+ tlm = space_system.telemetry_metadata
266
+ collections_to_index.extend(
267
+ [
268
+ tlm.parameter_types,
269
+ [
270
+ parameter
271
+ for parameter in tlm.parameters
272
+ if not isinstance(parameter, ParameterRef)
273
+ ],
274
+ tlm.containers,
275
+ tlm.message_set.messages if tlm.message_set else None,
276
+ ]
277
+ )
278
+
279
+ # Iterate through all objects and register in one pass
280
+ valid_collections = (c for c in collections_to_index if c)
281
+ for item in itertools.chain.from_iterable(valid_collections):
282
+ registry.register(current_path / item.name, item)
283
+
284
+ # Recurse into child SpaceSystems
285
+ for child in space_system.space_systems:
286
+ self._index_space_system(child, current_path, registry)
@@ -0,0 +1,207 @@
1
+ """XTCE file object."""
2
+
3
+ import importlib.resources
4
+ import re
5
+ import tempfile
6
+ from importlib.abc import Traversable
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, ClassVar
9
+
10
+ from lxml import etree
11
+ from xsdata.formats.dataclass.parsers import XmlParser
12
+
13
+ from xtce_lib.xtce.space_system import SpaceSystem
14
+
15
+ from .validation import ValidationReport, XtceSchemaError
16
+ from .xtce_version import XtceVersion
17
+
18
+ if TYPE_CHECKING:
19
+ from xtce_lib.generated.xtce_1_1.models import SpaceSystem as SpaceSystem11
20
+ from xtce_lib.generated.xtce_1_2.models import SpaceSystem as SpaceSystem12
21
+ from xtce_lib.generated.xtce_1_3.models import SpaceSystem as SpaceSystem13
22
+
23
+ from .xtce_database import XtceDatabase
24
+
25
+ AnySpaceSystem = SpaceSystem11 | SpaceSystem12 | SpaceSystem13
26
+ else:
27
+ AnySpaceSystem = Any
28
+
29
+
30
+ class XtceFile:
31
+ """XTCE file manager for parsing and validation."""
32
+
33
+ # TODO add unified model
34
+
35
+ # Cache at class level
36
+ _schema_cache: ClassVar[dict[XtceVersion, etree.XMLSchema]] = {}
37
+ _NAMESPACE_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"^\{([^}]+)\}")
38
+
39
+ def __init__(self, file_path: str | Path) -> None:
40
+ """Initialize the XTCE file object."""
41
+ self._file_path = Path(file_path).resolve()
42
+
43
+ self._namespace = self.get_namespace(self._file_path)
44
+ self._version = XtceVersion.from_namespace(self._namespace)
45
+ self._raw_model: AnySpaceSystem | None = None
46
+
47
+ self._schema_resource, self._xml_resource = self._get_xsd_resources()
48
+
49
+ @property
50
+ def file_path(self) -> Path:
51
+ """Get the XTCE file path."""
52
+ return self._file_path
53
+
54
+ @property
55
+ def namespace(self) -> str:
56
+ """Get the XML namespace of the XTCE file."""
57
+ return self._namespace
58
+
59
+ @property
60
+ def version(self) -> str:
61
+ """Get the XTCE version of the file."""
62
+ return self._version.value.version
63
+
64
+ @property
65
+ def database(self) -> "XtceDatabase":
66
+ """Get the XTCE database object for this file."""
67
+ if self._raw_model is None:
68
+ self.parse_raw()
69
+
70
+ from .xtce_database import XtceDatabase
71
+
72
+ return XtceDatabase.from_space_system(
73
+ SpaceSystem.from_xsdata(self._raw_model, self._version)
74
+ )
75
+
76
+ @property
77
+ def raw_model(self) -> Any:
78
+ """Get the raw parsed model of the XTCE file."""
79
+ if self._raw_model is None:
80
+ self.parse_raw()
81
+ return self._raw_model
82
+
83
+ @staticmethod
84
+ def get_namespace(file_path: str | Path) -> str:
85
+ """Extract the namespace from the file path.
86
+
87
+ Args:
88
+ file_path (str | Path): The path to the XTCE XML file.
89
+
90
+ Returns:
91
+ str: The namespace of the XTCE XML file.
92
+
93
+ Raises:
94
+ ValueError: If the XML file is invalid or if no namespace is found.
95
+
96
+ """
97
+ file_path = Path(file_path)
98
+ try:
99
+ for _, element in etree.iterparse(file_path, events=["start"]):
100
+ if isinstance(element.tag, str):
101
+ match = XtceFile._NAMESPACE_PATTERN.match(element.tag)
102
+ if match:
103
+ return match.group(1)
104
+
105
+ except etree.XMLSyntaxError as e:
106
+ raise ValueError(f"Invalid XML file: {file_path}: {e}")
107
+
108
+ raise ValueError(f"No namespace found in XML file: {file_path}")
109
+
110
+ def validate(self) -> ValidationReport[XtceSchemaError]:
111
+ """Validate the XTCE file against its corresponding XSD schema.
112
+
113
+ Returns:
114
+ ValidationReport[XtceSchemaError]: The result of the validation.
115
+
116
+ Raises:
117
+ ValueError: If the XML file is invalid or if validation fails.
118
+
119
+ """
120
+ validator = self._get_validator()
121
+ report = ValidationReport[XtceSchemaError](title="XSD Validation")
122
+
123
+ try:
124
+ xml_doc = etree.parse(self._file_path)
125
+ is_valid = validator.validate(xml_doc)
126
+
127
+ if is_valid:
128
+ return report
129
+
130
+ for error in validator.error_log:
131
+ # Strip the namespace
132
+ clean_message = re.sub(r"\{(?:http|urn)[^}]+\}", "", error.message)
133
+
134
+ report.add_error(
135
+ XtceSchemaError(line=error.line, message=clean_message)
136
+ )
137
+
138
+ return report
139
+
140
+ except etree.XMLSyntaxError as e:
141
+ # Catastrophic syntax error
142
+ report.add_error(XtceSchemaError(line=e.lineno or 0, message=str(e)))
143
+ return report
144
+
145
+ def parse_raw(self, strict: bool = False) -> None:
146
+ """Parse the XML file into the version-specific xsdata dataclasses.
147
+
148
+ Args:
149
+ strict: If True, validates the file against the schema before parsing.
150
+
151
+ Raises:
152
+ ValueError: If validation fails in strict mode or if parsing fails.
153
+
154
+ """
155
+ if strict:
156
+ result = self.validate()
157
+ if not result.is_valid:
158
+ raise ValueError(
159
+ f"Cannot parse invalid XTCE file: {result.errors[0].message}"
160
+ )
161
+
162
+ # Import the correct generated module based on the XTCE version
163
+ module_path = f"xtce_lib.generated.{self._version.value.module_name}.models"
164
+ models_module = importlib.import_module(module_path)
165
+
166
+ # Parse XML into the SpaceSystem root class
167
+ root_class = getattr(models_module, "SpaceSystem")
168
+ parser = XmlParser()
169
+ self._raw_model = parser.parse(str(self._file_path), root_class)
170
+
171
+ # TODO catch exceptions?
172
+
173
+ def _get_validator(self) -> etree.XMLSchema:
174
+ """Get the XMLSchema validator for the XTCE version of this file."""
175
+ if self._version in self._schema_cache:
176
+ return self._schema_cache[self._version]
177
+
178
+ try:
179
+ # Write the XSD resources to a temporary directory so that relative paths
180
+ # like xml.xsd are resolved correctly
181
+ with tempfile.TemporaryDirectory() as temp_dir:
182
+ temp_path = Path(temp_dir)
183
+
184
+ schema_path = temp_path / self._schema_resource.name
185
+ xml_xsd_path = temp_path / self._xml_resource.name
186
+
187
+ schema_path.write_bytes(self._schema_resource.read_bytes())
188
+ xml_xsd_path.write_bytes(self._xml_resource.read_bytes())
189
+
190
+ schema_doc = etree.parse(schema_path)
191
+ xmlschema = etree.XMLSchema(schema_doc)
192
+
193
+ self.__class__._schema_cache[self._version] = xmlschema
194
+ return xmlschema
195
+
196
+ except etree.XMLSchemaParseError as e:
197
+ raise ValueError(
198
+ f"Failed to compile XSD schema for version {self._version}: {e}"
199
+ )
200
+
201
+ def _get_xsd_resources(self) -> tuple[Traversable, Traversable]:
202
+ """Get schema resources for the XTCE version of this file."""
203
+ files = importlib.resources.files("xtce_lib.xsd")
204
+ schema_file = files / self._version.value.xsd_name
205
+ xsd_file = files / "xml.xsd"
206
+
207
+ return schema_file, xsd_file