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,16 @@
1
+ """REST API client for STEP REST API v2.
2
+
3
+ Provides comprehensive, typed access to STEP REST API operations through
4
+ organized operation groups with shared authentication.
5
+ """
6
+
7
+ from . import exceptions
8
+ from .base import BaseOperation, handle_rest_errors
9
+ from .client import StepRestClient
10
+
11
+ __all__ = [
12
+ 'BaseOperation',
13
+ 'StepRestClient',
14
+ 'exceptions',
15
+ 'handle_rest_errors',
16
+ ]
@@ -0,0 +1,388 @@
1
+ """Base operation class for REST API operations.
2
+
3
+ Provides common functionality for all operation classes including error handling,
4
+ pagination, logging, and request/response processing.
5
+ """
6
+
7
+ import logging
8
+ from functools import wraps
9
+ from typing import Any, Callable, Dict, Optional, TypeVar
10
+
11
+ import requests
12
+ import requests_openapi as roa
13
+
14
+ from . import exceptions as exc
15
+ from .models.common import PageInfo
16
+
17
+ T = TypeVar('T')
18
+
19
+
20
+ def handle_rest_errors(func: Callable) -> Callable:
21
+ """Decorator to handle common REST API errors.
22
+
23
+ Converts HTTP errors to appropriate exception types.
24
+ Allows FileNotFoundError and other file system exceptions to pass through.
25
+
26
+ Also detects STEP API error responses returned as dicts when
27
+ requests_openapi does not raise an exception for non-2xx HTTP responses.
28
+ The STEP REST API returns error payloads with the form
29
+ ``{"httpStatus": <int>, "message": "...", "httpReason": "..."}``.
30
+ """
31
+ @wraps(func)
32
+ def wrapper(*args, **kwargs):
33
+ try:
34
+ result = func(*args, **kwargs)
35
+ # Detect STEP error responses returned as plain dicts instead of
36
+ # raised exceptions (requests_openapi may not call raise_for_status).
37
+ if isinstance(result, dict) and 'httpStatus' in result:
38
+ status = result.get('httpStatus', 0)
39
+ message = result.get('message') or result.get('httpReason', 'API error')
40
+ if status == 401:
41
+ raise exc.AuthenticationError(f"Authentication failed: {message}")
42
+ elif status == 403:
43
+ raise exc.AuthorizationError(f"Access denied: {message}")
44
+ elif status == 404:
45
+ raise exc.NotFoundError(None, details=result)
46
+ elif status == 409:
47
+ raise exc.ConflictError(status, message)
48
+ elif status == 429:
49
+ raise exc.RateLimitError(details=result)
50
+ elif 500 <= status < 600:
51
+ raise exc.ServerError(status, message)
52
+ elif 400 <= status < 500:
53
+ raise exc.RestApiError(status, message)
54
+ return result
55
+ except requests.exceptions.RequestException as e:
56
+ # Handle requests library exceptions
57
+ if hasattr(e, 'response') and e.response is not None:
58
+ status = e.response.status_code
59
+ try:
60
+ error_data = e.response.json()
61
+ except Exception:
62
+ error_data = {"message": e.response.text}
63
+
64
+ error_msg = error_data.get('error_message') or error_data.get('message', str(e))
65
+ error_code = error_data.get('error_code')
66
+
67
+ if status == 401:
68
+ raise exc.AuthenticationError(f"Authentication failed: {error_msg}")
69
+ elif status == 403:
70
+ raise exc.AuthorizationError(f"Access denied: {error_msg}")
71
+ elif status == 404:
72
+ raise exc.NotFoundError(None, details=error_data)
73
+ elif status == 409:
74
+ raise exc.ConflictError(status, error_msg, error_code=error_code)
75
+ elif status == 429:
76
+ retry_after = e.response.headers.get('Retry-After')
77
+ raise exc.RateLimitError(retry_after=retry_after, details=error_data)
78
+ elif 500 <= status < 600:
79
+ raise exc.ServerError(status, error_msg, error_code=error_code)
80
+ else:
81
+ raise exc.RestApiError(status, error_msg, error_code=error_code)
82
+ else:
83
+ raise exc.RestClientError(f"Request failed: {str(e)}")
84
+ except (FileNotFoundError, OSError):
85
+ # Let file system errors pass through unchanged
86
+ raise
87
+ except Exception as e:
88
+ if isinstance(e, exc.RestClientError):
89
+ raise
90
+ raise exc.RestClientError(f"Unexpected error: {e!s}")
91
+
92
+ return wrapper
93
+
94
+
95
+ class BaseOperation:
96
+ """Base class for REST API operation groups.
97
+
98
+ Provides common functionality for all operation classes:
99
+ - Error handling
100
+ - Pagination helpers
101
+ - Logging
102
+ - Response processing
103
+
104
+ Attributes:
105
+ client: The requests_openapi.Client instance
106
+ logger: Logger instance for this operation group
107
+ operation_name: Name of this operation group (for logging)
108
+ context: STEP context ID passed to every API call
109
+ workspace: STEP workspace ID passed to every API call (defaults to "Main" server-side)
110
+ """
111
+
112
+ def __init__(self, client: roa.Client, operation_name: str | None = None,
113
+ logger: Optional[logging.Logger] = None,
114
+ context: str | None = None, workspace: str | None = None):
115
+ """Initialize base operation.
116
+
117
+ Args:
118
+ client: The requests-openapi Client instance
119
+ operation_name: Name of this operation group (defaults to class name)
120
+ logger: Logger instance (defaults to class logger)
121
+ context: STEP context ID to use for all API calls (required by STEP REST API)
122
+ workspace: STEP workspace ID (server defaults to "Main" if not supplied)
123
+ """
124
+ self.client = client
125
+ self.operation_name = operation_name or self.__class__.__name__
126
+ self.context = context
127
+ self.workspace = workspace
128
+
129
+ if logger is None:
130
+ self.logger = logging.getLogger(f"{self.__class__.__module__}.{self.operation_name}")
131
+ else:
132
+ self.logger = logger
133
+
134
+ def _build_context_params(self) -> Dict[str, Any]:
135
+ """Build context and workspace parameters for API calls.
136
+
137
+ Returns:
138
+ Dictionary containing context and/or workspace keys if set.
139
+ """
140
+ params: Dict[str, Any] = {}
141
+ if self.context is not None:
142
+ params['context'] = self.context
143
+ if self.workspace is not None:
144
+ params['workspace'] = self.workspace
145
+ return params
146
+
147
+ def _check_for_api_error(self, response: Any, resource_id: str | None = None,
148
+ resource_type: str | None = None) -> None:
149
+ """Detect STEP error responses returned as dicts instead of raised exceptions.
150
+
151
+ The STEP REST API may return an error body like
152
+ ``{"httpStatus": 404, "message": "...", "httpReason": "..."}`` on a
153
+ transport-level 200 when using requests_openapi without raise_for_status.
154
+ This method raises the appropriate library exception in those cases.
155
+
156
+ Args:
157
+ response: The API response (dict, list, or object)
158
+ resource_id: Resource ID for error messages
159
+ resource_type: Resource type name for error messages
160
+
161
+ Raises:
162
+ AuthenticationError: If httpStatus is 401
163
+ AuthorizationError: If httpStatus is 403
164
+ NotFoundError: If httpStatus is 404
165
+ ConflictError: If httpStatus is 409
166
+ RestApiError: For other 4xx/5xx status codes
167
+ """
168
+ if not isinstance(response, dict) or 'httpStatus' not in response:
169
+ return
170
+ status = response.get('httpStatus', 0)
171
+ message = response.get('message') or response.get('httpReason', 'API error')
172
+ if status == 401:
173
+ raise exc.AuthenticationError(f"Authentication failed: {message}")
174
+ elif status == 403:
175
+ raise exc.AuthorizationError(f"Access denied: {message}")
176
+ elif status == 404:
177
+ raise exc.NotFoundError(resource_id, resource_type)
178
+ elif status == 409:
179
+ raise exc.ConflictError(status, message)
180
+ elif 400 <= status < 600:
181
+ raise exc.RestApiError(status, message)
182
+
183
+ def _check_response_status(self, response: Any, resource_id: str | None = None,
184
+ resource_type: str | None = None) -> None:
185
+ """Check response for HTTP error status codes returned without raising an exception.
186
+
187
+ Some STEP endpoints (e.g. binary content) return an HTTP error response body
188
+ (e.g. an HTML 404 page) with a non-error HTTP status from the transport layer.
189
+ This method inspects the underlying response object and raises the appropriate
190
+ exception when an error status is detected.
191
+
192
+ Args:
193
+ response: The API response object
194
+ resource_id: Resource ID for error messages
195
+ resource_type: Resource type name for error messages
196
+
197
+ Raises:
198
+ NotFoundError: If status is 404
199
+ RestApiError: For other 4xx/5xx status codes
200
+ """
201
+ # First check for STEP error dicts (JSON body with httpStatus field)
202
+ self._check_for_api_error(response, resource_id, resource_type)
203
+
204
+ raw_response = getattr(response, '_response', None) or getattr(response, 'response', None)
205
+ status_code = (
206
+ getattr(raw_response, 'status_code', None)
207
+ or getattr(response, 'status_code', None)
208
+ )
209
+ if status_code is not None and isinstance(status_code, int) and status_code >= 400:
210
+ if status_code == 404:
211
+ raise exc.NotFoundError(resource_id, resource_type)
212
+ raise exc.RestApiError(status_code, f"HTTP {status_code} for {resource_type} {resource_id}")
213
+
214
+ def _log_operation(self, operation_id: str, params: Optional[Dict[str, Any]] = None) -> None:
215
+ """Log an API operation.
216
+
217
+ Args:
218
+ operation_id: The OpenAPI operation ID
219
+ params: Request parameters
220
+ """
221
+ self.logger.debug(
222
+ f"Calling {self.operation_name}.{operation_id}",
223
+ extra={"params": params}
224
+ )
225
+
226
+ @staticmethod
227
+ def _coerce_list_payload(payload: Any) -> list[Any]:
228
+ """Normalize list-like API payloads from various response shapes.
229
+
230
+ Supports:
231
+ - native lists
232
+ - dicts with common wrapper keys such as items/results/data/values
233
+ - response-like objects exposing json() or text
234
+ - single-object payloads, which are returned as a one-item list
235
+ """
236
+ if payload is None:
237
+ return []
238
+
239
+ if isinstance(payload, list):
240
+ return payload
241
+
242
+ if isinstance(payload, dict):
243
+ for key in ("items", "results", "data", "values"):
244
+ value = payload.get(key)
245
+ if isinstance(value, list):
246
+ return value
247
+ if payload:
248
+ return [payload]
249
+ return []
250
+
251
+ if hasattr(payload, "json"):
252
+ try:
253
+ parsed = payload.json()
254
+ except Exception:
255
+ parsed = None
256
+ if parsed is not None:
257
+ return BaseOperation._coerce_list_payload(parsed)
258
+
259
+ if hasattr(payload, "text"):
260
+ try:
261
+ import json
262
+ parsed = json.loads(payload.text)
263
+ except Exception:
264
+ parsed = None
265
+ if parsed is not None:
266
+ return BaseOperation._coerce_list_payload(parsed)
267
+
268
+ return []
269
+
270
+ def _process_response(self, response: Any) -> Any:
271
+ """Process API response (hook for subclasses).
272
+
273
+ Override this method in subclasses to add custom response processing.
274
+
275
+ Args:
276
+ response: Raw API response
277
+
278
+ Returns:
279
+ Processed response
280
+ """
281
+ return response
282
+
283
+ def _build_pagination_params(self, limit: int = 20, offset: int = 0) -> Dict[str, Any]:
284
+ """Build pagination parameters for requests.
285
+
286
+ Args:
287
+ limit: Number of items per page
288
+ offset: Result offset
289
+
290
+ Returns:
291
+ Dictionary of pagination parameters
292
+ """
293
+ return {
294
+ "limit": max(1, min(limit, 1000)), # Clamp between 1 and 1000
295
+ "offset": max(0, offset)
296
+ }
297
+
298
+ def _build_page_info(self, limit: int, offset: int,
299
+ total: Optional[int] = None) -> PageInfo:
300
+ """Build PageInfo object from pagination parameters.
301
+
302
+ Args:
303
+ limit: Items per page
304
+ offset: Current offset
305
+ total: Total items (if known)
306
+
307
+ Returns:
308
+ PageInfo instance
309
+ """
310
+ return PageInfo(
311
+ limit=limit,
312
+ offset=offset,
313
+ total=total
314
+ )
315
+
316
+ def _get_paginated_results(self, operation_id: str, params: Optional[Dict[str, Any]] = None,
317
+ item_key: str = "items",
318
+ total_key: str = "total") -> tuple:
319
+ """Execute paginated API call and extract items and total count.
320
+
321
+ Args:
322
+ operation_id: OpenAPI operation ID to call
323
+ params: Request parameters (should include limit and offset)
324
+ item_key: Key in response containing items list
325
+ total_key: Key in response containing total count
326
+
327
+ Returns:
328
+ Tuple of (items, total) where total may be None if not in response
329
+ """
330
+ self._log_operation(operation_id, params)
331
+
332
+ operation = getattr(self.client, operation_id)
333
+ response = operation(**(params or {}))
334
+
335
+ # Handle different response formats
336
+ if isinstance(response, dict):
337
+ items = response.get(item_key, [])
338
+ total = response.get(total_key)
339
+ else:
340
+ # Handle object responses
341
+ items = getattr(response, item_key, [])
342
+ total = getattr(response, total_key, None)
343
+
344
+ return items, total
345
+
346
+ @handle_rest_errors
347
+ def _call_operation(self, operation_id: str, **params) -> Any:
348
+ """Call an OpenAPI operation with error handling.
349
+
350
+ Args:
351
+ operation_id: OpenAPI operation ID to call
352
+ **params: Operation parameters
353
+
354
+ Returns:
355
+ Operation response
356
+ """
357
+ self._log_operation(operation_id, params)
358
+
359
+ operation = getattr(self.client, operation_id)
360
+ response = operation(**params)
361
+
362
+ return self._process_response(response)
363
+
364
+ def _validate_required_params(self, params: Dict[str, Any],
365
+ required: list) -> None:
366
+ """Validate that required parameters are present.
367
+
368
+ Args:
369
+ params: Parameters to validate
370
+ required: List of required parameter names
371
+
372
+ Raises:
373
+ ValidationError: If required parameters are missing
374
+ """
375
+ missing = [p for p in required if p not in params or params[p] is None]
376
+ if missing:
377
+ raise exc.ValidationError(f"Missing required parameters: {missing}")
378
+
379
+ def _clean_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
380
+ """Remove None values from parameters.
381
+
382
+ Args:
383
+ params: Parameters to clean
384
+
385
+ Returns:
386
+ Cleaned parameters
387
+ """
388
+ return {k: v for k, v in params.items() if v is not None}
@@ -0,0 +1,127 @@
1
+ """Extended STEP REST API client with operation groups.
2
+
3
+ Extends the base StiboStepRestClient to provide organized access to
4
+ operation groups for different STEP entities.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional
9
+
10
+ from ..client import StiboStepRestClient
11
+ from .base import BaseOperation
12
+ from .operations.assets import AssetOperations
13
+ from .operations.attributes import AttributeOperations
14
+ from .operations.background_processes import BackgroundProcessOperations
15
+ from .operations.batch import BatchOperations
16
+ from .operations.classifications import ClassificationOperations
17
+ from .operations.collection_groups import CollectionGroupOperations
18
+ from .operations.collections import CollectionOperations
19
+ from .operations.contexts import ContextOperations
20
+ from .operations.data_type_groups import DataTypeGroupOperations
21
+ from .operations.entities import EntityOperations
22
+ from .operations.event_processors import EventProcessorOperations
23
+ from .operations.exports import ExportOperations
24
+ from .operations.gateway import GatewayOperations
25
+ from .operations.inbound import InboundOperations
26
+ from .operations.outbound import OutboundOperations
27
+ from .operations.products import ProductOperations
28
+ from .operations.relations import RelationOperations
29
+ from .operations.reports import ReportOperations
30
+ from .operations.system_information import SystemInformationOperations
31
+ from .operations.user_groups import UserGroupOperations
32
+ from .operations.users import UserOperations
33
+ from .operations.workflow_tasks import WorkflowTaskOperations
34
+ from .operations.workflows import WorkflowOperations
35
+
36
+
37
+ class StepRestClient(StiboStepRestClient):
38
+ """Extended REST client with organized operation groups.
39
+
40
+ Inherits authentication and base functionality from StiboStepRestClient,
41
+ adding domain-specific operation groups for easier API access.
42
+
43
+ Attributes:
44
+ products: ProductOperations instance
45
+ assets: AssetOperations instance
46
+ classifications: ClassificationOperations instance
47
+ entities: EntityOperations instance
48
+ attributes: AttributeOperations instance
49
+ exports: ExportOperations instance
50
+ relations: RelationOperations instance
51
+ inbound: InboundOperations instance (for IIEP uploads)
52
+ """
53
+
54
+ def __init__(self, location_base, authorisation,
55
+ openapi_file=None, logger=None,
56
+ proxies=None, ssl_version=None,
57
+ cache_openapi=False, nocert=False,
58
+ context: str | None = None, workspace: str | None = None):
59
+ """Initialize StepRestClient with operation groups.
60
+
61
+ Args:
62
+ location_base: Base URL of STEP REST API
63
+ authorisation: Authentication handler (e.g., BasicAuthorisation)
64
+ openapi_file: Path to OpenAPI specification file
65
+ logger: Logger instance
66
+ proxies: Proxy configuration
67
+ ssl_version: SSL version configuration
68
+ cache_openapi: Whether to cache OpenAPI spec
69
+ nocert: Whether to skip SSL certificate verification
70
+ context: STEP context ID passed to all context-aware API calls
71
+ workspace: STEP workspace ID (server defaults to "Main" if not supplied)
72
+ """
73
+ # Initialize parent class
74
+ super().__init__(
75
+ location_base=location_base,
76
+ authorisation=authorisation,
77
+ openapi_file=openapi_file,
78
+ logger=logger,
79
+ proxies=proxies,
80
+ ssl_version=ssl_version,
81
+ cache_openapi=cache_openapi,
82
+ nocert=nocert
83
+ )
84
+
85
+ # Get logger for operation groups
86
+ if logger is None:
87
+ op_logger = logging.getLogger(f"{self.__class__.__module__}.operations")
88
+ else:
89
+ op_logger = logger
90
+
91
+ # Initialize operation groups (context/workspace passed to entity-level operations)
92
+ self.products = ProductOperations(self.client, logger=op_logger, context=context, workspace=workspace)
93
+ self.assets = AssetOperations(self.client, logger=op_logger, context=context, workspace=workspace)
94
+ self.classifications = ClassificationOperations(self.client, logger=op_logger, context=context, workspace=workspace)
95
+ self.entities = EntityOperations(self.client, logger=op_logger, context=context, workspace=workspace)
96
+ self.attributes = AttributeOperations(self.client, logger=op_logger, context=context, workspace=workspace)
97
+ self.relations = RelationOperations(self.client, logger=op_logger, context=context, workspace=workspace)
98
+ self.inbound = InboundOperations(self.client, logger=op_logger, context=context, workspace=workspace)
99
+ self.exports = ExportOperations(self.client, logger=op_logger, context=context, workspace=workspace)
100
+ self.contexts = ContextOperations(self.client, logger=op_logger)
101
+ self.system_information = SystemInformationOperations(self.client, logger=op_logger)
102
+ self.users = UserOperations(self.client, logger=op_logger, context=context, workspace=workspace)
103
+ self.user_groups = UserGroupOperations(self.client, logger=op_logger, context=context, workspace=workspace)
104
+ self.collections = CollectionOperations(self.client, logger=op_logger, context=context, workspace=workspace)
105
+ self.collection_groups = CollectionGroupOperations(self.client, logger=op_logger, context=context, workspace=workspace)
106
+ self.batch = BatchOperations(self.client, logger=op_logger, context=context, workspace=workspace)
107
+ self.data_type_groups = DataTypeGroupOperations(self.client, logger=op_logger)
108
+ self.background_processes = BackgroundProcessOperations(self.client, logger=op_logger)
109
+ self.event_processors = EventProcessorOperations(self.client, logger=op_logger)
110
+ self.outbound = OutboundOperations(self.client, logger=op_logger)
111
+ self.gateway = GatewayOperations(self.client, logger=op_logger)
112
+ self.workflows = WorkflowOperations(self.client, logger=op_logger, context=context, workspace=workspace)
113
+ self.workflow_tasks = WorkflowTaskOperations(self.client, logger=op_logger, context=context, workspace=workspace)
114
+ self.reports = ReportOperations(self.client, logger=op_logger, context=context, workspace=workspace)
115
+
116
+ def get_operation_group(self, group_name: str) -> BaseOperation | None:
117
+ """Get an operation group by name.
118
+
119
+ Useful for dynamic access to operation groups.
120
+
121
+ Args:
122
+ group_name: Name of operation group (e.g., 'products', 'assets')
123
+
124
+ Returns:
125
+ Operation group instance or None if not found
126
+ """
127
+ return getattr(self, group_name, None)
@@ -0,0 +1,97 @@
1
+ """REST API client exceptions.
2
+
3
+ Provides REST-specific exception classes for error handling in rest_client operations.
4
+ """
5
+
6
+
7
+ class RestClientError(Exception):
8
+ """Base exception for REST API client errors."""
9
+ pass # noqa: PIE790
10
+
11
+
12
+ class RestApiError(RestClientError):
13
+ """Exception raised for API response errors.
14
+
15
+ Attributes:
16
+ status_code: HTTP status code from the API response
17
+ error_message: Error message from the API
18
+ error_code: API-specific error code if available
19
+ details: Additional error details from response body
20
+ """
21
+
22
+ def __init__(self, status_code, error_message, error_code=None, details=None):
23
+ self.status_code = status_code
24
+ self.error_message = error_message
25
+ self.error_code = error_code
26
+ self.details = details
27
+ message = f"API Error ({status_code}): {error_message}"
28
+ if error_code:
29
+ message += f" [Code: {error_code}]"
30
+ super().__init__(message)
31
+
32
+
33
+ class AuthenticationError(RestClientError):
34
+ """Exception raised for authentication failures."""
35
+
36
+ pass # noqa: PIE790
37
+
38
+
39
+ class AuthorizationError(RestClientError):
40
+ """Exception raised for authorization failures (insufficient permissions)."""
41
+
42
+ pass # noqa: PIE790 # noqa: PIE790
43
+
44
+
45
+ class NotFoundError(RestApiError):
46
+ """Exception raised when a requested resource is not found (HTTP 404)."""
47
+
48
+ def __init__(self, resource_id, resource_type=None, details=None):
49
+ message = "Resource not found"
50
+ if resource_type:
51
+ message += f": {resource_type}"
52
+ if resource_id:
53
+ message += f" (ID: {resource_id})"
54
+ super().__init__(404, message, error_code="NOT_FOUND", details=details)
55
+ self.resource_id = resource_id
56
+ self.resource_type = resource_type
57
+
58
+
59
+ class ValidationError(RestClientError):
60
+ """Exception raised for request validation errors."""
61
+
62
+ def __init__(self, validation_errors):
63
+ self.validation_errors = validation_errors
64
+ message = f"Validation error: {validation_errors}"
65
+ super().__init__(message)
66
+
67
+
68
+ class ConflictError(RestApiError):
69
+ """Exception raised for resource conflicts (HTTP 409)."""
70
+
71
+ pass # noqa: PIE790
72
+
73
+
74
+ class RateLimitError(RestApiError):
75
+ """Exception raised when rate limit is exceeded (HTTP 429)."""
76
+
77
+ def __init__(self, retry_after=None, details=None):
78
+ super().__init__(429, "Rate limit exceeded", error_code="RATE_LIMITED", details=details)
79
+ self.retry_after = retry_after
80
+
81
+
82
+ class ServerError(RestApiError):
83
+ """Exception raised for server errors (HTTP 5xx)."""
84
+
85
+ pass # noqa: PIE790
86
+
87
+
88
+ class TimeoutError(RestClientError):
89
+ """Exception raised when request times out."""
90
+
91
+ pass # noqa: PIE790
92
+
93
+
94
+ class OperationError(RestClientError):
95
+ """Exception raised for operation-specific errors."""
96
+
97
+ pass # noqa: PIE790