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,50 @@
1
+ """Workflow operations for STEP REST API v2."""
2
+ from typing import Any
3
+
4
+ from ..base import BaseOperation, handle_rest_errors
5
+ from ..exceptions import NotFoundError, ValidationError
6
+ from ..models.workflow import WorkflowResponse
7
+
8
+
9
+ class WorkflowOperations(BaseOperation):
10
+ def __init__(self, client, logger=None, context=None, workspace=None):
11
+ super().__init__(client, operation_name="WorkflowOperations", logger=logger, context=context, workspace=workspace)
12
+
13
+ @handle_rest_errors
14
+ def get_workflow_ids(self) -> list[str]:
15
+ self._log_operation("getWorkflowIds", {})
16
+ r = self.client.getWorkflowIds(**self._build_context_params())
17
+ return r if isinstance(r, list) else []
18
+
19
+ @handle_rest_errors
20
+ def get_workflow(self, workflow_id: str) -> WorkflowResponse:
21
+ self._validate_required_params({"workflow_id": workflow_id}, ["workflow_id"])
22
+ self._log_operation("getWorkflow", {"id": workflow_id})
23
+ try:
24
+ r = self.client.getWorkflow(id=workflow_id, **self._build_context_params())
25
+ except Exception as e:
26
+ if "404" in str(e): raise NotFoundError(workflow_id, "Workflow")
27
+ raise
28
+ return WorkflowResponse(**r) if isinstance(r, dict) else r
29
+
30
+ @handle_rest_errors
31
+ def create_workflow_instance(self, workflow_id: str, data: dict[str, Any]) -> WorkflowResponse:
32
+ self._validate_required_params({"workflow_id": workflow_id}, ["workflow_id"])
33
+ self._log_operation("createWorkflowInstance", {"id": workflow_id})
34
+ try:
35
+ r = self.client.createWorkflowInstance(id=workflow_id, json=data, **self._build_context_params())
36
+ except Exception as e:
37
+ if "400" in str(e): raise ValidationError(str(e))
38
+ raise
39
+ return WorkflowResponse(**r) if isinstance(r, dict) else r
40
+
41
+ @handle_rest_errors
42
+ def delete_workflow_instance(self, workflow_id: str, instance_id: str) -> bool:
43
+ self._validate_required_params({"workflow_id": workflow_id, "instance_id": instance_id}, ["workflow_id", "instance_id"])
44
+ self._log_operation("deleteWorkflowInstance", {"id": workflow_id, "instanceId": instance_id})
45
+ try:
46
+ self.client.deleteWorkflowInstance(id=workflow_id, instanceId=instance_id, **self._build_context_params())
47
+ return True
48
+ except Exception as e:
49
+ if "404" in str(e): raise NotFoundError(workflow_id, "Workflow")
50
+ raise
@@ -0,0 +1,293 @@
1
+ """
2
+ Asset binary upload module for STEP XML Client.
3
+
4
+ This module provides the AssetUploader class for uploading binary files (images, documents, etc.)
5
+ to STEP MDM system as asset binaries using the REST API v2.
6
+
7
+ Features:
8
+ - Support for file paths and URLs as input
9
+ - Automatic MIME type detection
10
+ - Support for multi-workspace environments
11
+ - Stream-based upload for memory efficiency
12
+ - Binary content download capability
13
+
14
+ Usage Example:
15
+ Upload an asset binary to STEP::
16
+
17
+ from stepxmlclient.client import StiboStepRestClient, BasicAuthorisation
18
+ from stepxmlclient.upload import AssetUploader
19
+
20
+ # 1. Create and configure the STEP REST client
21
+ client = StiboStepRestClient(
22
+ 'https://your-step-server/restapiv2',
23
+ BasicAuthorisation('username', 'password'),
24
+ nocert=True # For development only
25
+ )
26
+
27
+ # 2. Create the uploader
28
+ uploader = AssetUploader(client)
29
+
30
+ # 3. Upload a file to an asset
31
+ result = uploader.upload_file(
32
+ file_path='path/to/image.jpg',
33
+ asset_id='A123',
34
+ filename=None, # Optional: use original filename
35
+ mime_type=None, # Optional: auto-detect
36
+ context_id='Context1', # Optional: STEP context
37
+ workspace_id=None # Optional: workspace
38
+ )
39
+
40
+ print(result)
41
+ """
42
+
43
+ import os
44
+ from typing import Optional, Dict, Any, BinaryIO, Union
45
+ from pathlib import Path
46
+
47
+
48
+ class AssetUploader:
49
+ """
50
+ Handles uploading binary files to STEP as asset binaries.
51
+
52
+ Uses the STEP REST API v2 to upload files that will be associated
53
+ with Asset objects in the MDM system.
54
+ """
55
+
56
+ def __init__(self, client):
57
+ """
58
+ Initialize the asset uploader.
59
+
60
+ Args:
61
+ client: StiboStepRestClient instance for API communication
62
+ """
63
+ self.client = client
64
+
65
+ def upload_file(
66
+ self,
67
+ file_path: Union[str, Path],
68
+ asset_id: str,
69
+ filename: Optional[str] = None,
70
+ mime_type: Optional[str] = None,
71
+ context_id: str = "Context1",
72
+ workspace_id: Optional[str] = None
73
+ ) -> Dict[str, Any]:
74
+ """
75
+ Upload a binary file to STEP as an asset binary.
76
+
77
+ Args:
78
+ file_path: Path to the file to upload or URL of the file to transfer
79
+ asset_id: ID of the asset in STEP to associate with this binary
80
+ filename: Optional custom filename (defaults to original filename)
81
+ mime_type: Optional MIME type (will be detected if not provided)
82
+ context_id: STEP context ID (default: "Context1")
83
+ workspace_id: Optional workspace ID for multi-workspace environments
84
+
85
+ Returns:
86
+ Dictionary with upload response details
87
+
88
+ Raises:
89
+ FileNotFoundError: If the file doesn't exist
90
+ ValueError: If asset_id is empty
91
+ Exception: If the upload fails
92
+ """
93
+ import tempfile
94
+ import requests
95
+
96
+ # Check if file_path is a URL
97
+ file_path_str = str(file_path)
98
+ is_url = file_path_str.startswith('http://') or file_path_str.startswith('https://')
99
+
100
+ if is_url:
101
+ # Download file to temp location
102
+ response = requests.get(file_path_str, stream=True)
103
+ if response.status_code != 200:
104
+ raise FileNotFoundError(f"Could not download file from URL: {file_path_str}")
105
+ # Use filename from URL if not provided
106
+ if filename is None:
107
+ filename = Path(file_path_str).name or 'downloaded_file'
108
+ # Detect MIME type if not provided
109
+ if mime_type is None:
110
+ mime_type = response.headers.get('Content-Type', 'application/octet-stream')
111
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
112
+ for chunk in response.iter_content(chunk_size=8192):
113
+ tmp_file.write(chunk)
114
+ tmp_file_path = Path(tmp_file.name)
115
+ with open(tmp_file_path, 'rb') as file_handle:
116
+ result = self.upload_stream(
117
+ file_handle,
118
+ asset_id,
119
+ filename,
120
+ mime_type,
121
+ context_id,
122
+ workspace_id
123
+ )
124
+ os.unlink(tmp_file_path)
125
+ return result
126
+ else:
127
+ file_path = Path(file_path)
128
+ if not file_path.exists():
129
+ raise FileNotFoundError(f"File not found: {file_path}")
130
+ if not asset_id:
131
+ raise ValueError("asset_id cannot be empty")
132
+ if filename is None:
133
+ filename = file_path.name
134
+ if mime_type is None:
135
+ mime_type = self._detect_mime_type(file_path)
136
+ with open(file_path, 'rb') as file_handle:
137
+ return self.upload_stream(
138
+ file_handle,
139
+ asset_id,
140
+ filename,
141
+ mime_type,
142
+ context_id,
143
+ workspace_id
144
+ )
145
+
146
+ def upload_stream(
147
+ self,
148
+ file_stream: BinaryIO,
149
+ asset_id: str,
150
+ filename: str,
151
+ mime_type: str,
152
+ context_id: str = "Context1",
153
+ workspace_id: Optional[str] = None
154
+ ) -> Dict[str, Any]:
155
+ """
156
+ Upload a binary stream to STEP as an asset binary.
157
+
158
+ Args:
159
+ file_stream: Binary file stream to upload
160
+ asset_id: ID of the asset in STEP
161
+ filename: Filename for the uploaded binary
162
+ mime_type: MIME type of the source file (returned in metadata)
163
+ context_id: STEP context ID (default: "Context1")
164
+ workspace_id: Optional workspace ID
165
+
166
+ Returns:
167
+ Dictionary with upload response details
168
+
169
+ Raises:
170
+ Exception: If the upload fails
171
+ """
172
+ # Prepare query parameters according to STEP REST API v2 spec
173
+ params = {
174
+ 'fileName': filename,
175
+ 'context': context_id
176
+ }
177
+
178
+ if workspace_id:
179
+ params['workspace'] = workspace_id
180
+
181
+ # Read binary data from stream
182
+ binary_data = file_stream.read()
183
+
184
+ # STEP OpenAPI defines request body media type as application/octet-stream
185
+ request_content_type = 'application/octet-stream'
186
+
187
+ # Use the correct REST API v2 endpoint: PUT /assets/{id}/content
188
+ # Binary content is sent as application/octet-stream in request body
189
+ try:
190
+ # Access the underlying requests Session from requests-openapi client
191
+ endpoint = f'/assets/{asset_id}/content'
192
+ response = self.client.client.requestor.put(
193
+ f'{self.client.client.server.url}{endpoint}',
194
+ params=params,
195
+ data=binary_data,
196
+ headers={'Content-Type': request_content_type}
197
+ )
198
+ response.raise_for_status()
199
+ return {
200
+ 'success': True,
201
+ 'asset_id': asset_id,
202
+ 'filename': filename,
203
+ 'mime_type': mime_type,
204
+ 'content_type': request_content_type,
205
+ 'status_code': response.status_code
206
+ }
207
+ except Exception as e:
208
+ raise Exception(f"Failed to upload asset binary: {str(e)}") from e
209
+
210
+ def get_content(
211
+ self,
212
+ asset_id: str,
213
+ context_id: str = "Context1",
214
+ workspace_id: Optional[str] = None
215
+ ) -> bytes:
216
+ """
217
+ Download binary content from a STEP asset.
218
+
219
+ Args:
220
+ asset_id: ID of the asset in STEP
221
+ context_id: STEP context ID (default: "Context1")
222
+ workspace_id: Optional workspace ID
223
+
224
+ Returns:
225
+ Binary content as bytes
226
+
227
+ Raises:
228
+ Exception: If the download fails
229
+ """
230
+ params = {
231
+ 'context': context_id
232
+ }
233
+
234
+ if workspace_id:
235
+ params['workspace'] = workspace_id
236
+
237
+ try:
238
+ endpoint = f'/assets/{asset_id}/content'
239
+ response = self.client.client.requestor.get(
240
+ f'{self.client.client.server.url}{endpoint}',
241
+ params=params
242
+ )
243
+ response.raise_for_status()
244
+ return response.content
245
+ except Exception as e:
246
+ raise Exception(f"Failed to download asset content: {str(e)}") from e
247
+
248
+ @staticmethod
249
+ def _detect_mime_type(file_path: Path) -> str:
250
+ """
251
+ Detect MIME type based on file extension.
252
+
253
+ Args:
254
+ file_path: Path to the file
255
+
256
+ Returns:
257
+ MIME type string
258
+ """
259
+ extension = file_path.suffix.lower()
260
+
261
+ mime_types = {
262
+ # Images
263
+ '.jpg': 'image/jpeg',
264
+ '.jpeg': 'image/jpeg',
265
+ '.png': 'image/png',
266
+ '.gif': 'image/gif',
267
+ '.bmp': 'image/bmp',
268
+ '.tiff': 'image/tiff',
269
+ '.webp': 'image/webp',
270
+ '.svg': 'image/svg+xml',
271
+ # Documents
272
+ '.pdf': 'application/pdf',
273
+ '.doc': 'application/msword',
274
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
275
+ '.xls': 'application/vnd.ms-excel',
276
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
277
+ '.ppt': 'application/vnd.ms-powerpoint',
278
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
279
+ # Text
280
+ '.txt': 'text/plain',
281
+ '.csv': 'text/csv',
282
+ '.xml': 'application/xml',
283
+ '.json': 'application/json',
284
+ # Archives
285
+ '.zip': 'application/zip',
286
+ '.rar': 'application/x-rar-compressed',
287
+ # Video
288
+ '.mp4': 'video/mp4',
289
+ '.avi': 'video/x-msvideo',
290
+ '.mov': 'video/quicktime',
291
+ }
292
+
293
+ return mime_types.get(extension, 'application/octet-stream')