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,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
|
stepxmlclient/upload.py
ADDED
|
@@ -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')
|