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.
- stepxmlclient/__init__.py +22 -0
- stepxmlclient/client.py +373 -0
- stepxmlclient/creator.py +317 -0
- stepxmlclient/model/__init__.py +418 -0
- stepxmlclient/model/asset.py +250 -0
- stepxmlclient/model/attribute.py +288 -0
- stepxmlclient/model/business.py +233 -0
- stepxmlclient/model/classification.py +409 -0
- stepxmlclient/model/complex_elements.py +260 -0
- stepxmlclient/model/deletion.py +87 -0
- stepxmlclient/model/entity.py +368 -0
- stepxmlclient/model/metadata.py +199 -0
- stepxmlclient/model/organization.py +167 -0
- stepxmlclient/model/product.py +519 -0
- stepxmlclient/model/relations.py +105 -0
- stepxmlclient/model/replacement.py +417 -0
- stepxmlclient/parser.py +2805 -0
- stepxmlclient/rest_client/__init__.py +16 -0
- stepxmlclient/rest_client/base.py +388 -0
- stepxmlclient/rest_client/client.py +127 -0
- stepxmlclient/rest_client/exceptions.py +97 -0
- stepxmlclient/rest_client/models/__init__.py +175 -0
- stepxmlclient/rest_client/models/asset.py +253 -0
- stepxmlclient/rest_client/models/attribute.py +112 -0
- stepxmlclient/rest_client/models/background_process.py +23 -0
- stepxmlclient/rest_client/models/batch.py +41 -0
- stepxmlclient/rest_client/models/classification.py +57 -0
- stepxmlclient/rest_client/models/collection.py +26 -0
- stepxmlclient/rest_client/models/common.py +192 -0
- stepxmlclient/rest_client/models/context.py +16 -0
- stepxmlclient/rest_client/models/data_type_group.py +15 -0
- stepxmlclient/rest_client/models/entity.py +63 -0
- stepxmlclient/rest_client/models/event_processor.py +15 -0
- stepxmlclient/rest_client/models/import_export.py +43 -0
- stepxmlclient/rest_client/models/integration_endpoint.py +16 -0
- stepxmlclient/rest_client/models/product.py +200 -0
- stepxmlclient/rest_client/models/relations.py +280 -0
- stepxmlclient/rest_client/models/system_information.py +11 -0
- stepxmlclient/rest_client/models/user.py +28 -0
- stepxmlclient/rest_client/models/workflow.py +30 -0
- stepxmlclient/rest_client/operations/__init__.py +54 -0
- stepxmlclient/rest_client/operations/assets.py +652 -0
- stepxmlclient/rest_client/operations/attributes.py +252 -0
- stepxmlclient/rest_client/operations/background_processes.py +84 -0
- stepxmlclient/rest_client/operations/batch.py +28 -0
- stepxmlclient/rest_client/operations/classifications.py +248 -0
- stepxmlclient/rest_client/operations/collection_groups.py +46 -0
- stepxmlclient/rest_client/operations/collections.py +67 -0
- stepxmlclient/rest_client/operations/contexts.py +88 -0
- stepxmlclient/rest_client/operations/data_type_groups.py +68 -0
- stepxmlclient/rest_client/operations/entities.py +295 -0
- stepxmlclient/rest_client/operations/event_processors.py +109 -0
- stepxmlclient/rest_client/operations/exports.py +153 -0
- stepxmlclient/rest_client/operations/gateway.py +53 -0
- stepxmlclient/rest_client/operations/inbound.py +472 -0
- stepxmlclient/rest_client/operations/outbound.py +64 -0
- stepxmlclient/rest_client/operations/products.py +489 -0
- stepxmlclient/rest_client/operations/relations.py +349 -0
- stepxmlclient/rest_client/operations/reports.py +27 -0
- stepxmlclient/rest_client/operations/system_information.py +28 -0
- stepxmlclient/rest_client/operations/user_groups.py +79 -0
- stepxmlclient/rest_client/operations/users.py +119 -0
- stepxmlclient/rest_client/operations/workflow_tasks.py +61 -0
- stepxmlclient/rest_client/operations/workflows.py +50 -0
- stepxmlclient/upload.py +293 -0
- stepxmlclient-1.0.0.dist-info/METADATA +666 -0
- stepxmlclient-1.0.0.dist-info/RECORD +70 -0
- stepxmlclient-1.0.0.dist-info/WHEEL +5 -0
- stepxmlclient-1.0.0.dist-info/licenses/LICENSE +21 -0
- 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 {})
|