stepxmlclient 1.0.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.
Files changed (70) hide show
  1. stepxmlclient/__init__.py +22 -0
  2. stepxmlclient/client.py +373 -0
  3. stepxmlclient/creator.py +317 -0
  4. stepxmlclient/model/__init__.py +418 -0
  5. stepxmlclient/model/asset.py +250 -0
  6. stepxmlclient/model/attribute.py +288 -0
  7. stepxmlclient/model/business.py +233 -0
  8. stepxmlclient/model/classification.py +409 -0
  9. stepxmlclient/model/complex_elements.py +260 -0
  10. stepxmlclient/model/deletion.py +87 -0
  11. stepxmlclient/model/entity.py +368 -0
  12. stepxmlclient/model/metadata.py +199 -0
  13. stepxmlclient/model/organization.py +167 -0
  14. stepxmlclient/model/product.py +519 -0
  15. stepxmlclient/model/relations.py +105 -0
  16. stepxmlclient/model/replacement.py +417 -0
  17. stepxmlclient/parser.py +2805 -0
  18. stepxmlclient/rest_client/__init__.py +16 -0
  19. stepxmlclient/rest_client/base.py +388 -0
  20. stepxmlclient/rest_client/client.py +127 -0
  21. stepxmlclient/rest_client/exceptions.py +97 -0
  22. stepxmlclient/rest_client/models/__init__.py +175 -0
  23. stepxmlclient/rest_client/models/asset.py +253 -0
  24. stepxmlclient/rest_client/models/attribute.py +112 -0
  25. stepxmlclient/rest_client/models/background_process.py +23 -0
  26. stepxmlclient/rest_client/models/batch.py +41 -0
  27. stepxmlclient/rest_client/models/classification.py +57 -0
  28. stepxmlclient/rest_client/models/collection.py +26 -0
  29. stepxmlclient/rest_client/models/common.py +192 -0
  30. stepxmlclient/rest_client/models/context.py +16 -0
  31. stepxmlclient/rest_client/models/data_type_group.py +15 -0
  32. stepxmlclient/rest_client/models/entity.py +63 -0
  33. stepxmlclient/rest_client/models/event_processor.py +15 -0
  34. stepxmlclient/rest_client/models/import_export.py +43 -0
  35. stepxmlclient/rest_client/models/integration_endpoint.py +16 -0
  36. stepxmlclient/rest_client/models/product.py +200 -0
  37. stepxmlclient/rest_client/models/relations.py +280 -0
  38. stepxmlclient/rest_client/models/system_information.py +11 -0
  39. stepxmlclient/rest_client/models/user.py +28 -0
  40. stepxmlclient/rest_client/models/workflow.py +30 -0
  41. stepxmlclient/rest_client/operations/__init__.py +54 -0
  42. stepxmlclient/rest_client/operations/assets.py +652 -0
  43. stepxmlclient/rest_client/operations/attributes.py +252 -0
  44. stepxmlclient/rest_client/operations/background_processes.py +84 -0
  45. stepxmlclient/rest_client/operations/batch.py +28 -0
  46. stepxmlclient/rest_client/operations/classifications.py +248 -0
  47. stepxmlclient/rest_client/operations/collection_groups.py +46 -0
  48. stepxmlclient/rest_client/operations/collections.py +67 -0
  49. stepxmlclient/rest_client/operations/contexts.py +88 -0
  50. stepxmlclient/rest_client/operations/data_type_groups.py +68 -0
  51. stepxmlclient/rest_client/operations/entities.py +295 -0
  52. stepxmlclient/rest_client/operations/event_processors.py +109 -0
  53. stepxmlclient/rest_client/operations/exports.py +153 -0
  54. stepxmlclient/rest_client/operations/gateway.py +53 -0
  55. stepxmlclient/rest_client/operations/inbound.py +472 -0
  56. stepxmlclient/rest_client/operations/outbound.py +64 -0
  57. stepxmlclient/rest_client/operations/products.py +489 -0
  58. stepxmlclient/rest_client/operations/relations.py +349 -0
  59. stepxmlclient/rest_client/operations/reports.py +27 -0
  60. stepxmlclient/rest_client/operations/system_information.py +28 -0
  61. stepxmlclient/rest_client/operations/user_groups.py +79 -0
  62. stepxmlclient/rest_client/operations/users.py +119 -0
  63. stepxmlclient/rest_client/operations/workflow_tasks.py +61 -0
  64. stepxmlclient/rest_client/operations/workflows.py +50 -0
  65. stepxmlclient/upload.py +293 -0
  66. stepxmlclient-1.0.0.dist-info/METADATA +666 -0
  67. stepxmlclient-1.0.0.dist-info/RECORD +70 -0
  68. stepxmlclient-1.0.0.dist-info/WHEEL +5 -0
  69. stepxmlclient-1.0.0.dist-info/licenses/LICENSE +21 -0
  70. stepxmlclient-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,418 @@
1
+
2
+ """
3
+ Base object model module for STEP XML Client.
4
+
5
+ This module provides the BaseObject class, which is the foundation for all STEP entity models
6
+ (Product, Asset, Attribute, Classification, etc.). BaseObject uses a declarative configuration
7
+ approach to define XML serialization behavior.
8
+
9
+ All STEP entity classes inherit from BaseObject and use class-level configuration attributes
10
+ to define how instances are serialized to XML elements. This approach enables automatic
11
+ serialization without requiring custom to_element() implementations in subclasses.
12
+
13
+ Configuration Attributes (set in subclasses):
14
+ _GROUP_NAME: The collection name in Creator (e.g., 'products', 'assets')
15
+ _TAG_NAME: The XML element tag name (e.g., 'Product', 'Asset')
16
+ _ATTRIBUTE_NAMES: Tuple of attribute names to serialize as XML attributes
17
+ _TEXT_NAMES: Tuple of simple text element names
18
+ _VALUE_NAME: Name of the single text value for this element
19
+ _CHILDREN_NAMES: Tuple of child element names to process recursively
20
+
21
+ Example:
22
+ Defining a custom STEP entity model::
23
+
24
+ from stepxmlclient.model import BaseObject
25
+
26
+ class MyEntity(BaseObject):
27
+ _GROUP_NAME = 'my_entities'
28
+ _TAG_NAME = 'MyEntity'
29
+ _ATTRIBUTE_NAMES = ('ID', 'Type')
30
+ _TEXT_NAMES = ('Name', 'Description')
31
+ _CHILDREN_NAMES = ('Values', 'Relations')
32
+ """
33
+
34
+ import xml.etree.ElementTree as ET
35
+ from typing import Any, Dict, List, Optional, Union, Type
36
+
37
+ class BaseObject:
38
+ """Base class for all STEP entity models.
39
+
40
+ Provides declarative XML serialization for STEP entities. Subclasses define their
41
+ structure through class-level configuration attributes that control how instances
42
+ are converted to XML elements.
43
+
44
+ The to_element() method handles the actual serialization by inspecting these
45
+ configuration attributes and building the appropriate XML structure.
46
+
47
+ Attributes:
48
+ _GROUP_NAME: Collection name for this entity type in Creator
49
+ _TAG_NAME: XML element tag name for this entity
50
+ _ATTRIBUTE_NAMES: Tuple of attribute names to serialize as XML attributes
51
+ _TEXT_NAMES: Tuple of child element names to render as text elements
52
+ _VALUE_NAME: Name of the element value (for elements with single text content)
53
+ _CHILDREN_NAMES: Tuple of child element names to process recursively
54
+ """
55
+
56
+ #: Collection name for grouping in Creator (override in subclasses)
57
+ _GROUP_NAME: Optional[str] = None
58
+
59
+ #: XML element tag name (override in subclasses)
60
+ _TAG_NAME: Optional[str] = None
61
+
62
+ #: Attribute names to serialize as XML attributes (override in subclasses)
63
+ _ATTRIBUTE_NAMES: Optional[List[str]] = None
64
+
65
+ #: Child element names to render as simple text elements (override in subclasses)
66
+ _TEXT_NAMES: Optional[List[str]] = None
67
+
68
+ #: Name of element value for single-value elements (override in subclasses)
69
+ _VALUE_NAME: Optional[str] = None
70
+
71
+ #: Child element names to process and add recursively (override in subclasses)
72
+ _CHILDREN_NAMES: Optional[List[str]] = None
73
+
74
+ def __init__(self, data: Dict[str, Any]) -> None:
75
+ """Initialize a BaseObject with data dictionary.
76
+
77
+ Args:
78
+ data: Dictionary containing the entity data.
79
+ Keys should match configuration attribute names defined in subclasses.
80
+ """
81
+ self._data: Dict[str, Any] = data
82
+ return
83
+
84
+ def to_element(self, parent: Optional[ET.Element] = None) -> ET.Element:
85
+ """Serialize this object to an XML element.
86
+
87
+ Converts the entity to an ElementTree element using the class-level configuration
88
+ attributes. If parent is provided, adds this element as a child; otherwise creates
89
+ a standalone element.
90
+
91
+ The serialization process:
92
+ 1. Creates the element with _TAG_NAME and serializes _ATTRIBUTE_NAMES as attributes
93
+ 2. Sets element text if _VALUE_NAME is defined
94
+ 3. Adds simple text child elements from _TEXT_NAMES
95
+ 4. Recursively processes children from _CHILDREN_NAMES
96
+
97
+ Args:
98
+ parent: Optional parent ElementTree element. If provided, adds this element as a child.
99
+ If None, creates and returns a standalone element.
100
+
101
+ Returns:
102
+ The created ElementTree element
103
+ """
104
+ if parent is None:
105
+ element = ET.Element(
106
+ tag=self._TAG_NAME if self._TAG_NAME else "",
107
+ attrib=self._get_attributes()
108
+ )
109
+ else:
110
+ element = ET.SubElement(
111
+ parent,
112
+ self._TAG_NAME if self._TAG_NAME else "",
113
+ self._get_attributes()
114
+ )
115
+ value = self._get_value()
116
+ if value:
117
+ element.text = value
118
+ self._add_texts(element)
119
+ self._add_children(element)
120
+ return element
121
+
122
+ def _get_value(self) -> Optional[str]:
123
+ """Get the element's text value if configured.
124
+
125
+ Returns the string value from the data dictionary using the _VALUE_NAME key.
126
+ Used for elements that contain simple text content rather than child elements.
127
+
128
+ Returns:
129
+ String representation of the value, or None if not configured or not present
130
+ """
131
+ if self._VALUE_NAME:
132
+ val = self._data.get(self._VALUE_NAME)
133
+ if val is not None:
134
+ return str(val)
135
+ return None
136
+
137
+ def _get_attributes(self) -> Dict[str, str]:
138
+ """Collect XML attributes from configured attribute names.
139
+
140
+ Extracts values from the data dictionary for all names specified in
141
+ _ATTRIBUTE_NAMES and builds an attributes dictionary suitable for ElementTree.
142
+
143
+ Returns:
144
+ Dictionary mapping attribute names to string values
145
+ """
146
+ attributes: Dict[str, str] = {}
147
+ if self._ATTRIBUTE_NAMES:
148
+ for name in self._ATTRIBUTE_NAMES:
149
+ value = self._data.get(name)
150
+ if value is not None:
151
+ attributes[name] = str(value)
152
+ return attributes
153
+
154
+ def _add_texts(self, element: ET.Element) -> None:
155
+ """Add simple text child elements.
156
+
157
+ Creates child elements for each name in _TEXT_NAMES and populates them with
158
+ corresponding values from the data dictionary.
159
+
160
+ Args:
161
+ element: Parent ElementTree element to add text children to
162
+ """
163
+ if self._TEXT_NAMES:
164
+ for name in self._TEXT_NAMES:
165
+ value = self._data.get(name)
166
+ if value is not None:
167
+ child = ET.SubElement(element, name)
168
+ child.text = str(value)
169
+ return
170
+
171
+ def _add_children(self, element: ET.Element) -> None:
172
+ """Add child elements recursively.
173
+
174
+ Processes items from _CHILDREN_NAMES and handles different data types:
175
+ - BaseObject instances: calls to_element() recursively
176
+ - Dictionaries: passes to _add_dict_child() for special handling
177
+ - Lists: processes each item in the list
178
+
179
+ Args:
180
+ element: Parent ElementTree element to add children to
181
+ """
182
+ if self._CHILDREN_NAMES:
183
+ for name in self._CHILDREN_NAMES:
184
+ child = self._data.get(name)
185
+ if child is not None:
186
+ # Handle different child data types
187
+ if hasattr(child, 'to_element'):
188
+ # BaseObject instance
189
+ child.to_element(element)
190
+ elif isinstance(child, dict):
191
+ # Dictionary data (like AssetCrossReference)
192
+ self._add_dict_child(element, name, child)
193
+ elif isinstance(child, list):
194
+ # List of items
195
+ for item in child:
196
+ if hasattr(item, 'to_element'):
197
+ item.to_element(element)
198
+ elif isinstance(item, dict):
199
+ self._add_dict_child(element, name, item)
200
+ elif item is not None:
201
+ # Primitive list item -> simple text child element
202
+ item_element = ET.SubElement(element, name)
203
+ item_element.text = str(item)
204
+ else:
205
+ # Primitive child value -> simple text child element
206
+ child_element = ET.SubElement(element, name)
207
+ child_element.text = str(child)
208
+ return
209
+
210
+ def _add_dict_child(self, parent: ET.Element, tag_name: str, data: Dict[str, Any]) -> ET.Element:
211
+ """Add a child element from dictionary data.
212
+
213
+ Processes dictionary data and creates appropriate XML elements. Handles special cases
214
+ like the Values element with MultiValue/Value nesting structure commonly used in STEP.
215
+
216
+ Args:
217
+ parent: Parent ElementTree element
218
+ tag_name: XML tag name for the child element
219
+ data: Dictionary data to serialize
220
+
221
+ Returns:
222
+ The created child element
223
+ """
224
+ child_element = ET.SubElement(parent, tag_name) # Handle special case for Values (nested structure)
225
+ if tag_name == "Values" and isinstance(data, dict):
226
+ for attribute_id, value_data in data.items():
227
+ if isinstance(value_data, list):
228
+ # MultiValue
229
+ multi_value_elem = ET.SubElement(child_element, "MultiValue", AttributeID=attribute_id)
230
+ for item in value_data:
231
+ self._add_value_element(multi_value_elem, item)
232
+ else:
233
+ # Single Value
234
+ self._add_value_element(child_element, value_data, attribute_id)
235
+ elif tag_name in ("KeyValue", "ParentKeyValue"):
236
+ # KeyValueType is mixed content with required attributes like KeyID.
237
+ text_value = data.get("value", data.get("text"))
238
+ for key, value in data.items():
239
+ if key in ("value", "text"):
240
+ continue
241
+ if value is not None:
242
+ child_element.set(key, str(value))
243
+ if text_value is not None:
244
+ child_element.text = str(text_value)
245
+ else:
246
+ # Simple dictionary - add all as attributes
247
+ for key, value in data.items():
248
+ if value is not None:
249
+ child_element.set(key, str(value))
250
+ return child_element
251
+
252
+ def _add_value_element(self, parent: ET.Element, value_data: Any, attribute_id: Optional[str] = None) -> Optional[ET.Element]:
253
+ """Add a Value element from parsed value data"""
254
+ value_elem: Optional[ET.Element] = None
255
+ if isinstance(value_data, dict):
256
+ attribs: Dict[str, str] = {}
257
+ if attribute_id:
258
+ attribs["AttributeID"] = attribute_id
259
+ if "ID" in value_data:
260
+ attribs["ID"] = str(value_data["ID"])
261
+
262
+ value_elem = ET.SubElement(parent, "Value", attribs)
263
+ if "value" in value_data:
264
+ value_elem.text = str(value_data["value"])
265
+ return value_elem
266
+
267
+
268
+ # Export model classes
269
+ from .product import Product, ProductReplacementRule
270
+ from .asset import Asset
271
+ from .classification import Classification
272
+ from .entity import Entity
273
+ from .attribute import Attribute, AttributeGroup, ListOfValues, ValueGroup, ListOfValuesGroup
274
+ from .organization import Context, Qualifier, Dimension, DimensionPoint
275
+ from .metadata import UserType, LinkType, Tag, TagGroup, Unit
276
+ from .business import BusinessRule, Workflow, Collection, CollectionGroup
277
+ from .deletion import DeleteAsset, DeleteProduct, DeleteAssetCrossReference, DeleteProductCrossReference, DeleteClassificationCrossReference, DeleteEntityCrossReference
278
+ from .replacement import ReplaceValues, ReplaceCrossReferences, ReplaceAttributeLinks, ReplaceClassificationReferences, ReplaceProducts, ReplaceClassifications, ReplaceEntities, ReplaceAssets, ReplaceAttributes
279
+ from .relations import AssetCrossReference, ProductCrossReference, ClassificationReference, ClassificationCrossReference, EntityCrossReference
280
+ from .complex_elements import STEPWorkflowStateBind, SequenceProduct, OverrideSubProduct, PackagingReference, AssetPushLocation, AssetContent, AssetContentSpecification, BinaryContent, AssetBinaryContent, Task, CurrentTasks, Term, Terms, Tables
281
+
282
+ def get_model_class(class_name: str) -> Optional[Type[BaseObject]]:
283
+ """Get model class by its class name as string (case-insensitive).
284
+
285
+ Args:
286
+ class_name: The name of the model class (e.g., 'Product', 'Workflow', 'Collection')
287
+
288
+ Returns:
289
+ The model class type, or None if not found. All returned types are
290
+ subclasses of BaseObject.
291
+
292
+ Example:
293
+ >>> ProductClass = get_model_class('Product')
294
+ >>> product = ProductClass({"ID": "P001", "Name": "Test"})
295
+ >>> WorkflowClass = get_model_class('Workflow')
296
+ >>> workflow = WorkflowClass({"ID": "WF001", "Name": "Approval"})
297
+ """
298
+ model_classes = {
299
+ 'product': Product,
300
+ 'productreplacementrule': ProductReplacementRule,
301
+ 'asset': Asset,
302
+ 'classification': Classification,
303
+ 'entity': Entity,
304
+ 'attribute': Attribute,
305
+ 'attributegroup': AttributeGroup,
306
+ 'listofvalues': ListOfValues,
307
+ 'valuegroup': ValueGroup,
308
+ 'listofvaluesgroup': ListOfValuesGroup,
309
+ 'context': Context,
310
+ 'qualifier': Qualifier,
311
+ 'dimension': Dimension,
312
+ 'dimensionpoint': DimensionPoint,
313
+ 'usertype': UserType,
314
+ 'linktype': LinkType,
315
+ 'tag': Tag,
316
+ 'taggroup': TagGroup,
317
+ 'unit': Unit,
318
+ 'businessrule': BusinessRule,
319
+ 'workflow': Workflow,
320
+ 'collection': Collection,
321
+ 'collectiongroup': CollectionGroup,
322
+ 'deleteasset': DeleteAsset,
323
+ 'deleteproduct': DeleteProduct,
324
+ 'deleteassetcrossreference': DeleteAssetCrossReference,
325
+ 'deleteproductcrossreference': DeleteProductCrossReference,
326
+ 'deleteclassificationcrossreference': DeleteClassificationCrossReference,
327
+ 'deleteentitycrossreference': DeleteEntityCrossReference,
328
+ 'assetcrossreference': AssetCrossReference,
329
+ 'productcrossreference': ProductCrossReference,
330
+ 'classificationreference': ClassificationReference,
331
+ 'classificationcrossreference': ClassificationCrossReference,
332
+ 'entitycrossreference': EntityCrossReference,
333
+ 'stepworkflowstatebind': STEPWorkflowStateBind,
334
+ 'sequenceproduct': SequenceProduct,
335
+ 'overridesubproduct': OverrideSubProduct,
336
+ 'packagingreference': PackagingReference,
337
+ 'assetpushlocation': AssetPushLocation,
338
+ 'assetcontent': AssetContent,
339
+ 'assetcontentspecification': AssetContentSpecification,
340
+ 'binarycontent': BinaryContent,
341
+ 'assetbinarycontent': AssetBinaryContent,
342
+ 'task': Task,
343
+ 'currenttasks': CurrentTasks,
344
+ 'term': Term,
345
+ 'terms': Terms,
346
+ 'tables': Tables,
347
+ 'replacevalues': ReplaceValues,
348
+ 'replacecrossreferences': ReplaceCrossReferences,
349
+ 'replaceattributelinks': ReplaceAttributeLinks,
350
+ 'replaceclassificationreferences': ReplaceClassificationReferences,
351
+ 'replaceproducts': ReplaceProducts,
352
+ 'replaceclassifications': ReplaceClassifications,
353
+ 'replaceentities': ReplaceEntities,
354
+ 'replaceassets': ReplaceAssets,
355
+ 'replaceattributes': ReplaceAttributes,
356
+ }
357
+ return model_classes.get(class_name.lower(), None)
358
+
359
+ __all__ = [
360
+ 'get_model_class',
361
+ 'BaseObject',
362
+ 'Product',
363
+ 'ProductReplacementRule',
364
+ 'Asset',
365
+ 'Classification',
366
+ 'Entity',
367
+ 'Attribute',
368
+ 'AttributeGroup',
369
+ 'ListOfValues',
370
+ 'ValueGroup',
371
+ 'ListOfValuesGroup',
372
+ 'Context',
373
+ 'Qualifier',
374
+ 'Dimension',
375
+ 'DimensionPoint',
376
+ 'UserType',
377
+ 'LinkType',
378
+ 'Tag',
379
+ 'TagGroup',
380
+ 'Unit',
381
+ 'BusinessRule',
382
+ 'Workflow',
383
+ 'Collection',
384
+ 'CollectionGroup',
385
+ 'DeleteAsset',
386
+ 'DeleteProduct',
387
+ 'DeleteAssetCrossReference',
388
+ 'DeleteProductCrossReference',
389
+ 'DeleteClassificationCrossReference',
390
+ 'DeleteEntityCrossReference',
391
+ 'AssetCrossReference',
392
+ 'ProductCrossReference',
393
+ 'ClassificationCrossReference',
394
+ 'EntityCrossReference',
395
+ 'STEPWorkflowStateBind',
396
+ 'SequenceProduct',
397
+ 'OverrideSubProduct',
398
+ 'PackagingReference',
399
+ 'AssetPushLocation',
400
+ 'AssetContent',
401
+ 'AssetContentSpecification',
402
+ 'BinaryContent',
403
+ 'AssetBinaryContent',
404
+ 'Task',
405
+ 'CurrentTasks',
406
+ 'Term',
407
+ 'Terms',
408
+ 'Tables',
409
+ 'ReplaceValues',
410
+ 'ReplaceCrossReferences',
411
+ 'ReplaceAttributeLinks',
412
+ 'ReplaceClassificationReferences',
413
+ 'ReplaceProducts',
414
+ 'ReplaceClassifications',
415
+ 'ReplaceEntities',
416
+ 'ReplaceAssets',
417
+ 'ReplaceAttributes',
418
+ ]
@@ -0,0 +1,250 @@
1
+ """Asset model for STEP XML Client.
2
+
3
+ Defines the Asset entity class for representing binary asset files in STEP MDM.
4
+ Assets represent digital files (images, documents, videos, etc.) that can be associated
5
+ with products and other entities.
6
+ """
7
+
8
+ from typing import List, Optional, Any, Dict, TYPE_CHECKING
9
+ from . import BaseObject
10
+
11
+ if TYPE_CHECKING:
12
+ from .relations import ClassificationReference, EntityCrossReference
13
+ from .complex_elements import AssetPushLocation, AssetContent, AssetBinaryContent
14
+
15
+ class Asset(BaseObject):
16
+ """Asset entity in STEP MDM.
17
+
18
+ This model serializes to the STEP ``Asset`` element and supports helper
19
+ methods for common relationship/content elements.
20
+
21
+ Common top-level ``data`` keys:
22
+ - ``ID`` (str): Asset identifier
23
+ - ``UserTypeID`` (str): STEP object type
24
+ - ``Name`` (str or dict/list): Asset name
25
+ - ``KeyValue`` (dict or list): External key references
26
+ (for example ``{"KeyID": "ExternalKey", "value": "A-100"}``)
27
+ - ``Values`` (dict): Attribute values map
28
+
29
+ Usage examples:
30
+ Dict-first style::
31
+
32
+ asset = Asset({"ID": "A100", "UserTypeID": "UT_ASSET"})
33
+ asset.add_classification_reference(classification_id="C_ASSETS")
34
+ asset.add_asset_push_location({"ConfigurationID": "PUSH_CFG", "Status": "DONE"})
35
+
36
+ Pre-built object style::
37
+
38
+ from stepxmlclient.model import AssetContent, AssetContentSpecification
39
+ asset = Asset({"ID": "A101", "UserTypeID": "UT_ASSET"})
40
+ asset.add_asset_content(content_object=AssetContent({
41
+ "QualifierID": "en",
42
+ "AssetContentSpecification": [
43
+ AssetContentSpecification({"RelativeURL": "/assets/a101.jpg"})
44
+ ],
45
+ }))
46
+ """
47
+ _GROUP_NAME: str = 'assets'
48
+ _TAG_NAME: str = 'Asset'
49
+ _ATTRIBUTE_NAMES: List[str] = ["ID", "UserTypeID", "Changed", "Republished", "Selected", "Referenced"]
50
+ _TEXT_NAMES: List[str] = []
51
+ _CHILDREN_NAMES: List[str] = [
52
+ "KeyValue",
53
+ "Name",
54
+ "AssetPushLocation",
55
+ "DeleteSuppressedClassificationReference",
56
+ "DeleteClassificationReference",
57
+ "SuppressedClassificationReference",
58
+ "ClassificationReference",
59
+ "DeleteSuppressedEntityCrossReference",
60
+ "DeleteEntityCrossReference",
61
+ "SuppressedEntityCrossReference",
62
+ "EntityCrossReference",
63
+ "Values",
64
+ "CurrentTasks",
65
+ "Pages",
66
+ "AssetContent",
67
+ "AssetBinaryContent"
68
+ ]
69
+
70
+ def __init__(self, data: Dict[str, Any]) -> None:
71
+ super().__init__(data)
72
+ # Initialize relation lists if not provided
73
+ for rel_name in [
74
+ "DeleteSuppressedClassificationReference",
75
+ "DeleteClassificationReference",
76
+ "SuppressedClassificationReference",
77
+ "ClassificationReference",
78
+ "DeleteSuppressedEntityCrossReference",
79
+ "DeleteEntityCrossReference",
80
+ "SuppressedEntityCrossReference",
81
+ "EntityCrossReference",
82
+ ]:
83
+ if rel_name not in self._data:
84
+ self._data[rel_name] = []
85
+
86
+ # Backward compatibility: older code may still pass this legacy key.
87
+ legacy_class_refs = self._data.get("ClassificationCrossReference")
88
+ if legacy_class_refs and not self._data.get("ClassificationReference"):
89
+ self._data["ClassificationReference"] = legacy_class_refs
90
+
91
+ # ===== Classification Reference Methods =====
92
+
93
+ def add_classification_reference(self, classification_id: Optional[str] = None,
94
+ type_: Optional[str] = None,
95
+ qualifier_id: Optional[str] = None,
96
+ mount_location: Optional[str] = None,
97
+ ref_object: Optional['ClassificationReference'] = None) -> None:
98
+ """Add a ``ClassificationReference`` child.
99
+
100
+ If a legacy ``ClassificationCrossReference`` object is passed, it is
101
+ normalized to ``ClassificationReference``.
102
+
103
+ Args:
104
+ classification_id: Value for ``ClassificationID``.
105
+ type_: Optional ``Type``.
106
+ qualifier_id: Optional ``QualifierID``.
107
+ mount_location: Optional ``MountLocation``.
108
+ ref_object: Pre-built relation object.
109
+ """
110
+ from .relations import ClassificationReference, ClassificationCrossReference
111
+
112
+ if ref_object is not None:
113
+ # Use provided object
114
+ ref = ref_object
115
+ if isinstance(ref, ClassificationCrossReference):
116
+ # Gracefully normalize legacy object type to the XSD-compliant tag.
117
+ ref = ClassificationReference(dict(ref._data))
118
+ else:
119
+ # Create from parameters
120
+ ref = ClassificationReference({
121
+ "ClassificationID": classification_id,
122
+ "Type": type_,
123
+ "QualifierID": qualifier_id,
124
+ "MountLocation": mount_location
125
+ })
126
+
127
+ if not isinstance(self._data["ClassificationReference"], list):
128
+ self._data["ClassificationReference"] = []
129
+ self._data["ClassificationReference"].append(ref)
130
+
131
+ # ===== Entity Reference Methods =====
132
+
133
+ def add_entity_reference(self, entity_id: Optional[str] = None,
134
+ type_: Optional[str] = None,
135
+ qualifier_id: Optional[str] = None,
136
+ mount_location: Optional[str] = None,
137
+ ref_object: Optional['EntityCrossReference'] = None) -> None:
138
+ """Add an ``EntityCrossReference`` child.
139
+
140
+ Args:
141
+ entity_id: Value for ``EntityID``.
142
+ type_: Optional ``Type``.
143
+ qualifier_id: Optional ``QualifierID``.
144
+ mount_location: Optional ``MountLocation``.
145
+ ref_object: Pre-built ``EntityCrossReference`` instance.
146
+ """
147
+ from .relations import EntityCrossReference
148
+
149
+ if ref_object is not None:
150
+ # Use provided object
151
+ ref = ref_object
152
+ else:
153
+ # Create from parameters
154
+ ref = EntityCrossReference({
155
+ "EntityID": entity_id,
156
+ "Type": type_,
157
+ "QualifierID": qualifier_id,
158
+ "MountLocation": mount_location
159
+ })
160
+
161
+ if not isinstance(self._data["EntityCrossReference"], list):
162
+ self._data["EntityCrossReference"] = []
163
+ self._data["EntityCrossReference"].append(ref)
164
+
165
+ # ===== Additional Asset Content Helpers =====
166
+
167
+ def add_asset_push_location(self, push_location_data: Optional[Dict[str, Any]] = None) -> None:
168
+ """Add an ``AssetPushLocation`` child entry.
169
+
170
+ Args:
171
+ push_location_data: Dictionary with keys:
172
+ - ``ConfigurationID`` (required)
173
+ - ``Status`` (optional)
174
+ - ``QualifierID`` (optional)
175
+ - ``value`` or ``text`` (optional element text)
176
+ """
177
+ if not isinstance(self._data.get("AssetPushLocation"), list):
178
+ self._data["AssetPushLocation"] = []
179
+ self._data["AssetPushLocation"].append(push_location_data or {})
180
+
181
+ def add_asset_push_location_object(self, push_location_object: 'AssetPushLocation') -> None:
182
+ """Add pre-built ``AssetPushLocation`` object."""
183
+ if not isinstance(self._data.get("AssetPushLocation"), list):
184
+ self._data["AssetPushLocation"] = []
185
+ self._data["AssetPushLocation"].append(push_location_object)
186
+
187
+ def add_current_task(self, task_data: Optional[Dict[str, Any]] = None) -> None:
188
+ """Add a ``CurrentTasks`` child entry.
189
+
190
+ Args:
191
+ task_data: Dictionary mapped to ``Task`` attributes. Typical keys:
192
+ - ``STEPWorkflowID``
193
+ - ``StateID``
194
+ - ``AssigneeID``
195
+ - ``AssignedToGroup``
196
+ - ``EntryTime``
197
+ - optional: ``Deadline``, ``Priority``, ``StatusID``
198
+ """
199
+ if not isinstance(self._data.get("CurrentTasks"), list):
200
+ self._data["CurrentTasks"] = []
201
+ self._data["CurrentTasks"].append(task_data or {})
202
+
203
+ def add_page(self, page_data: Optional[Dict[str, Any]] = None) -> None:
204
+ """Add a ``Pages`` child entry.
205
+
206
+ Args:
207
+ page_data: Dictionary with optional top-level key ``Page``.
208
+ ``Page`` objects commonly use attributes ``VersionID`` (required
209
+ by XSD), ``ID`` and ``PageNumber``.
210
+ """
211
+ if not isinstance(self._data.get("Pages"), list):
212
+ self._data["Pages"] = []
213
+ self._data["Pages"].append(page_data or {})
214
+
215
+ def add_asset_content(self,
216
+ content_data: Optional[Dict[str, Any]] = None,
217
+ content_object: Optional['AssetContent'] = None) -> None:
218
+ """Add an ``AssetContent`` child entry.
219
+
220
+ Args:
221
+ content_data: Dictionary with keys:
222
+ - ``QualifierID`` (optional)
223
+ - ``AssetContentSpecification`` (optional list), each entry with
224
+ ``ImageConversionConfigurationID``, ``IncludesBinaryContent``,
225
+ ``RelativeURL``
226
+ content_object: Pre-built ``AssetContent`` object.
227
+ """
228
+ if not isinstance(self._data.get("AssetContent"), list):
229
+ self._data["AssetContent"] = []
230
+ self._data["AssetContent"].append(content_object or content_data or {})
231
+
232
+ def add_asset_binary_content(self,
233
+ binary_content_data: Optional[Dict[str, Any]] = None,
234
+ binary_content_object: Optional['AssetBinaryContent'] = None) -> None:
235
+ """Add an ``AssetBinaryContent`` child entry.
236
+
237
+ Args:
238
+ binary_content_data: Dictionary with keys:
239
+ - ``QualifierID`` (optional)
240
+ - ``ImageConversionConfigurationID`` (optional)
241
+ - ``MIMEType`` (optional)
242
+ - ``Checksum`` (optional)
243
+ - ``Filename`` (optional)
244
+ - ``BinaryContent`` (required for full XSD compliance), e.g.
245
+ ``{"value": "<base64>"}``
246
+ binary_content_object: Pre-built ``AssetBinaryContent`` object.
247
+ """
248
+ if not isinstance(self._data.get("AssetBinaryContent"), list):
249
+ self._data["AssetBinaryContent"] = []
250
+ self._data["AssetBinaryContent"].append(binary_content_object or binary_content_data or {})