mobius-error-py 1.0.0__tar.gz

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 (46) hide show
  1. mobius_error_py-1.0.0/LICENSE +21 -0
  2. mobius_error_py-1.0.0/MANIFEST.in +6 -0
  3. mobius_error_py-1.0.0/PKG-INFO +575 -0
  4. mobius_error_py-1.0.0/README.md +540 -0
  5. mobius_error_py-1.0.0/examples/example_app.py +184 -0
  6. mobius_error_py-1.0.0/pyproject.toml +65 -0
  7. mobius_error_py-1.0.0/setup.cfg +4 -0
  8. mobius_error_py-1.0.0/src/mobius_error/__init__.py +60 -0
  9. mobius_error_py-1.0.0/src/mobius_error/config.py +13 -0
  10. mobius_error_py-1.0.0/src/mobius_error/errors/__init__.py +0 -0
  11. mobius_error_py-1.0.0/src/mobius_error/errors/common_errors.py +33 -0
  12. mobius_error_py-1.0.0/src/mobius_error/errors/error.py +17 -0
  13. mobius_error_py-1.0.0/src/mobius_error/errors/error_message.py +37 -0
  14. mobius_error_py-1.0.0/src/mobius_error/exceptions/__init__.py +0 -0
  15. mobius_error_py-1.0.0/src/mobius_error/exceptions/access_violation_exception.py +24 -0
  16. mobius_error_py-1.0.0/src/mobius_error/exceptions/api_exception.py +28 -0
  17. mobius_error_py-1.0.0/src/mobius_error/exceptions/application_exception.py +71 -0
  18. mobius_error_py-1.0.0/src/mobius_error/exceptions/data_type_mismatch_exception.py +11 -0
  19. mobius_error_py-1.0.0/src/mobius_error/exceptions/group_data_retrieval_exception.py +8 -0
  20. mobius_error_py-1.0.0/src/mobius_error/exceptions/invalid_name_exception.py +8 -0
  21. mobius_error_py-1.0.0/src/mobius_error/exceptions/invalid_tenant_exception.py +8 -0
  22. mobius_error_py-1.0.0/src/mobius_error/exceptions/kafka_consumption_exception.py +8 -0
  23. mobius_error_py-1.0.0/src/mobius_error/exceptions/kafka_exception.py +8 -0
  24. mobius_error_py-1.0.0/src/mobius_error/exceptions/object_mapping_exception.py +8 -0
  25. mobius_error_py-1.0.0/src/mobius_error/exceptions/rest_exception.py +11 -0
  26. mobius_error_py-1.0.0/src/mobius_error/exceptions/rest_get_exception.py +11 -0
  27. mobius_error_py-1.0.0/src/mobius_error/exceptions/rest_post_exception.py +11 -0
  28. mobius_error_py-1.0.0/src/mobius_error/exceptions/token_exception.py +26 -0
  29. mobius_error_py-1.0.0/src/mobius_error/exceptions/unsupported_operation_exception.py +8 -0
  30. mobius_error_py-1.0.0/src/mobius_error/exceptions/validation_exception.py +23 -0
  31. mobius_error_py-1.0.0/src/mobius_error/handlers/__init__.py +0 -0
  32. mobius_error_py-1.0.0/src/mobius_error/handlers/exception_handler.py +248 -0
  33. mobius_error_py-1.0.0/src/mobius_error/kafka/__init__.py +0 -0
  34. mobius_error_py-1.0.0/src/mobius_error/kafka/constants.py +5 -0
  35. mobius_error_py-1.0.0/src/mobius_error/kafka/producer.py +161 -0
  36. mobius_error_py-1.0.0/src/mobius_error/py.typed +0 -0
  37. mobius_error_py-1.0.0/src/mobius_error/responses/__init__.py +0 -0
  38. mobius_error_py-1.0.0/src/mobius_error/responses/api_error_response.py +117 -0
  39. mobius_error_py-1.0.0/src/mobius_error/responses/error_response.py +84 -0
  40. mobius_error_py-1.0.0/src/mobius_error_py.egg-info/PKG-INFO +575 -0
  41. mobius_error_py-1.0.0/src/mobius_error_py.egg-info/SOURCES.txt +44 -0
  42. mobius_error_py-1.0.0/src/mobius_error_py.egg-info/dependency_links.txt +1 -0
  43. mobius_error_py-1.0.0/src/mobius_error_py.egg-info/requires.txt +8 -0
  44. mobius_error_py-1.0.0/src/mobius_error_py.egg-info/top_level.txt +1 -0
  45. mobius_error_py-1.0.0/tests/__init__.py +0 -0
  46. mobius_error_py-1.0.0/tests/test_mobius_error.py +341 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AidTaas Mobius Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+ recursive-include src/mobius_error *.py py.typed
5
+ recursive-include examples *.py
6
+ recursive-include tests *.py
@@ -0,0 +1,575 @@
1
+ Metadata-Version: 2.4
2
+ Name: mobius-error-py
3
+ Version: 1.0.0
4
+ Summary: Mobius Error Handling Library for FastAPI — structured error responses ported from Spring Boot
5
+ Author: AidTaas Mobius Team
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/aidtaas/mobius-error-python
8
+ Project-URL: Documentation, https://github.com/aidtaas/mobius-error-python#readme
9
+ Project-URL: Repository, https://github.com/aidtaas/mobius-error-python
10
+ Project-URL: Issues, https://github.com/aidtaas/mobius-error-python/issues
11
+ Keywords: fastapi,error-handling,exception-handler,microservices,mobius,rest-api
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: fastapi>=0.100.0
28
+ Requires-Dist: starlette>=0.27.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: uvicorn[standard]>=0.23.0; extra == "dev"
31
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
32
+ Requires-Dist: pytest>=7.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Mobius Error Services for FastAPI
37
+
38
+ A comprehensive error handling library for FastAPI, ported from the [Spring Boot `error-spring-lib`](https://github.com/aidtaas/error-spring-lib-dev). It provides a structured, consistent error response format across all your microservices — matching the exact JSON contract your existing Java services already produce.
39
+
40
+ ---
41
+
42
+ ## Table of Contents
43
+
44
+ - [Installation](#installation)
45
+ - [Quick Start](#quick-start)
46
+ - [Response Format](#response-format)
47
+ - [Exception Hierarchy](#exception-hierarchy)
48
+ - [Built-in Error Codes](#built-in-error-codes)
49
+ - [Usage Examples](#usage-examples)
50
+ - [Basic: Raising Exceptions](#basic-raising-exceptions)
51
+ - [Custom Error Codes](#custom-error-codes)
52
+ - [Fluent Builder Pattern](#fluent-builder-pattern)
53
+ - [Wrapping Upstream Failures](#wrapping-upstream-failures)
54
+ - [Pydantic Validation (Automatic)](#pydantic-validation-automatic)
55
+ - [Exception Types Reference](#exception-types-reference)
56
+ - [Advanced Usage](#advanced-usage)
57
+ - [Setting the Application Name](#setting-the-application-name)
58
+ - [Custom Exception Classes](#custom-exception-classes)
59
+ - [Combining with FastAPI's HTTPException](#combining-with-fastapis-httpexception)
60
+ - [Java ↔ Python Mapping](#java--python-mapping)
61
+ - [Project Structure](#project-structure)
62
+
63
+ ---
64
+
65
+ ## Installation
66
+
67
+ Copy the `mobius_error/` package into your project, or install it as an editable package:
68
+
69
+ ```bash
70
+ cd error_lib
71
+ pip install -e .
72
+ ```
73
+
74
+ **Requirements:** Python 3.10+, FastAPI >= 0.100.0
75
+
76
+ ---
77
+
78
+ ## Quick Start
79
+
80
+ Two lines to integrate into any FastAPI app:
81
+
82
+ ```python
83
+ from fastapi import FastAPI
84
+ from mobius_error import register_exception_handlers
85
+
86
+ app = FastAPI(title="my-service")
87
+ register_exception_handlers(app, app_name="my-service")
88
+ ```
89
+
90
+ That's it. Every unhandled exception now returns a structured JSON response instead of a raw 500.
91
+
92
+ ---
93
+
94
+ ## Response Format
95
+
96
+ All error responses follow this consistent JSON structure (empty fields are omitted):
97
+
98
+ ```json
99
+ {
100
+ "timestamp": 1706000000000,
101
+ "origin": "my-service",
102
+ "httpStatusCode": 404,
103
+ "errorCode": 404001,
104
+ "errorMessage": "User not found",
105
+ "detailedErrorMessage": "No user with ID 42 exists in the database",
106
+ "subErrors": [
107
+ {
108
+ "message": "Checked primary and replica databases",
109
+ "timestamp": 1706000000
110
+ }
111
+ ],
112
+ "actionsRequired": [
113
+ "Verify the user ID and try again"
114
+ ],
115
+ "cause": {
116
+ "message": "Record not found in table 'users'",
117
+ "origin": "my-service",
118
+ "timestamp": 1706000000
119
+ },
120
+ "docUrl": "https://mobiusdtaas.atlassian.net/wiki/spaces/EN/pages/2360868868/..."
121
+ }
122
+ ```
123
+
124
+ | Field | Type | Description |
125
+ |------------------------|---------------|-------------------------------------------------------|
126
+ | `timestamp` | `int` | Unix timestamp in milliseconds |
127
+ | `origin` | `string` | Name of the service that produced the error |
128
+ | `httpStatusCode` | `int` | HTTP status code |
129
+ | `errorCode` | `int` | Application-specific numeric error code |
130
+ | `errorMessage` | `string` | Human-readable summary |
131
+ | `detailedErrorMessage` | `string` | More context about what went wrong |
132
+ | `subErrors` | `ErrorMessage[]` | List of nested/related error details |
133
+ | `actionsRequired` | `string[]` | Suggested actions the caller can take |
134
+ | `cause` | `ErrorMessage` | Root cause information (e.g., from a wrapped exception) |
135
+ | `docUrl` | `string` | Link to documentation for common issues |
136
+
137
+ ---
138
+
139
+ ## Exception Hierarchy
140
+
141
+ The exception class hierarchy mirrors the Java library exactly:
142
+
143
+ ```
144
+ Exception
145
+ └── ApplicationException (base — caught as 403)
146
+ └── ApiException (uses Error's HTTP status)
147
+ ├── ValidationException (caught as 409)
148
+ │ ├── AccessViolationException (caught as 403)
149
+ │ └── InvalidNameException
150
+ ├── TokenException (uses Error's HTTP status)
151
+ ├── DataTypeMismatchException (caught as 400)
152
+ └── MethodArgumentsNotValidException
153
+ ├── RestException
154
+ ├── RestGetException
155
+ ├── RestPostException
156
+ ├── KafkaException
157
+ ├── KafkaConsumptionException
158
+ ├── ObjectMappingException
159
+ ├── InvalidTenantException
160
+ ├── UnsupportedOperationException
161
+ └── GroupDataRetrievalException
162
+ ```
163
+
164
+ Each exception handler matches the behavior of its Java `@ExceptionHandler` counterpart.
165
+
166
+ ---
167
+
168
+ ## Built-in Error Codes
169
+
170
+ These are defined in `CommonErrors` and match the Java `CommonErrors` class:
171
+
172
+ | Constant | HTTP | Code | Message |
173
+ |------------------------------|------|--------|------------------------------------------------|
174
+ | `INVALID_TENANT_ID` | 403 | 403002 | You're not registered as a tenant yet |
175
+ | `TENANT_NOT_AUTHORIZED` | 401 | 401000 | You're not authorized to access the resource |
176
+ | `UNEXPECTED_ERROR` | 500 | 500000 | Encountered an unexpected error |
177
+ | `SERVICE_UNAVAILABLE` | 500 | 500001 | One or more service(s) are not up and running |
178
+ | `OBJECT_MAPPING_FAILURE` | 500 | 500002 | Failed to convert json to object/map |
179
+ | `GROUP_DATA_RETRIEVAL_FAILURE` | 500 | 500003 | Failed to get group data from data lake |
180
+ | `GET_API_FAILURE` | 500 | 500004 | Failed to GET data from the URL |
181
+ | `POST_API_FAILURE` | 500 | 500005 | Failed to make POST call to an api |
182
+ | `REST_API_FAILURE` | 500 | 500006 | Failed to make REST call to an api |
183
+ | `KAFKA_ERROR` | 400 | 400000 | An error occurred in kafka |
184
+ | `KAFKA_CONSUMPTION_ERROR` | 400 | 400001 | An error occurred while consuming from kafka |
185
+ | `INVALID_NAME` | 400 | 400002 | The name provided is invalid |
186
+ | `UNSUPPORTED_OPERATION` | 400 | 400003 | This operation is not yet supported |
187
+
188
+ ---
189
+
190
+ ## Usage Examples
191
+
192
+ ### Basic: Raising Exceptions
193
+
194
+ ```python
195
+ from mobius_error import ApplicationException, ApiException, CommonErrors
196
+
197
+ # Simple — uses built-in error definition
198
+ @app.get("/items/{item_id}")
199
+ async def get_item(item_id: int):
200
+ item = db.find(item_id)
201
+ if not item:
202
+ raise ApiException(
203
+ error=CommonErrors.GET_API_FAILURE,
204
+ detailed_error_message=f"Item {item_id} not found in database",
205
+ )
206
+ return item
207
+ ```
208
+
209
+ ### Custom Error Codes
210
+
211
+ Define your own errors following the same pattern as `CommonErrors`:
212
+
213
+ ```python
214
+ from mobius_error import Error, ApiException
215
+
216
+ class MyErrors:
217
+ USER_NOT_FOUND = Error(404, 404001, "User not found", "Check the user ID and try again")
218
+ DUPLICATE_EMAIL = Error(409, 409001, "Email already exists", "Use a different email address")
219
+ QUOTA_EXCEEDED = Error(429, 429001, "Rate limit exceeded", "Wait a moment and retry")
220
+
221
+ @app.get("/users/{user_id}")
222
+ async def get_user(user_id: int):
223
+ user = await find_user(user_id)
224
+ if not user:
225
+ raise ApiException(
226
+ error=MyErrors.USER_NOT_FOUND,
227
+ detailed_error_message=f"No user with ID {user_id}",
228
+ )
229
+ return user
230
+ ```
231
+
232
+ ### Fluent Builder Pattern
233
+
234
+ Chain `.add_sub_error()` and `.add_action_required()` for rich error detail:
235
+
236
+ ```python
237
+ from mobius_error import ApplicationException, CommonErrors
238
+
239
+ @app.post("/deploy")
240
+ async def deploy():
241
+ errors = run_preflight_checks()
242
+ if errors:
243
+ raise (
244
+ ApplicationException(
245
+ error=CommonErrors.UNEXPECTED_ERROR,
246
+ detailed_error_message="Deployment preflight checks failed",
247
+ )
248
+ .add_sub_error("Health check: database unreachable")
249
+ .add_sub_error("Health check: cache latency > 500ms")
250
+ .add_action_required("Verify database connectivity")
251
+ .add_action_required("Check Redis cluster status")
252
+ )
253
+ ```
254
+
255
+ This produces a response with all sub-errors and actions listed:
256
+
257
+ ```json
258
+ {
259
+ "errorCode": 500000,
260
+ "errorMessage": "Encountered an unexpected error",
261
+ "detailedErrorMessage": "Deployment preflight checks failed",
262
+ "subErrors": [
263
+ { "message": "Health check: database unreachable" },
264
+ { "message": "Health check: cache latency > 500ms" }
265
+ ],
266
+ "actionsRequired": [
267
+ "Kindly try again after some time or contact the support team",
268
+ "Verify database connectivity",
269
+ "Check Redis cluster status"
270
+ ]
271
+ }
272
+ ```
273
+
274
+ ### Wrapping Upstream Failures
275
+
276
+ Preserve the original cause when catching exceptions from external calls:
277
+
278
+ ```python
279
+ import httpx
280
+ from mobius_error import RestGetException
281
+
282
+ @app.get("/proxy/weather")
283
+ async def proxy_weather():
284
+ try:
285
+ async with httpx.AsyncClient() as client:
286
+ resp = await client.get("https://api.weather.com/forecast")
287
+ resp.raise_for_status()
288
+ return resp.json()
289
+ except httpx.HTTPStatusError as exc:
290
+ raise RestGetException(
291
+ error_message="Failed to fetch weather forecast",
292
+ url="https://api.weather.com/forecast",
293
+ status_code=exc.response.status_code,
294
+ response_body=exc.response.text,
295
+ cause=exc,
296
+ )
297
+ ```
298
+
299
+ ### Pydantic Validation (Automatic)
300
+
301
+ FastAPI/Pydantic validation errors are caught automatically and formatted as sub-errors:
302
+
303
+ ```python
304
+ from pydantic import BaseModel, Field
305
+
306
+ class CreateUserRequest(BaseModel):
307
+ name: str = Field(..., min_length=2, max_length=50)
308
+ email: str = Field(..., pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
309
+ age: int = Field(..., ge=0, le=150)
310
+
311
+ @app.post("/users")
312
+ async def create_user(body: CreateUserRequest):
313
+ # If validation fails, the library auto-returns a 422 with sub-errors:
314
+ # {
315
+ # "errorCode": 400000,
316
+ # "errorMessage": "Validation error",
317
+ # "subErrors": [
318
+ # { "message": "body → name: String should have at least 2 characters" },
319
+ # { "message": "body → email: String should match pattern ..." }
320
+ # ]
321
+ # }
322
+ return {"created": body.model_dump()}
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Exception Types Reference
328
+
329
+ ### `ApplicationException` → HTTP 403
330
+
331
+ The base exception. Use when something unexpected goes wrong.
332
+
333
+ ```python
334
+ raise ApplicationException(
335
+ error=CommonErrors.UNEXPECTED_ERROR, # required
336
+ detailed_error_message="What happened", # optional
337
+ error_object={"debug": "info"}, # optional, included in response
338
+ cause=original_exception, # optional, populates 'cause' field
339
+ )
340
+ ```
341
+
342
+ ### `ApiException` → Uses Error's HTTP status
343
+
344
+ Most common for API-level errors. The HTTP status comes from the `Error` object.
345
+
346
+ ```python
347
+ raise ApiException(
348
+ error=MyErrors.USER_NOT_FOUND, # Error with http_status_code=404
349
+ detailed_error_message="User 42 not found",
350
+ )
351
+ # → Returns HTTP 404
352
+ ```
353
+
354
+ ### `ValidationException` → HTTP 409
355
+
356
+ For data validation failures caught in business logic (not Pydantic).
357
+
358
+ ```python
359
+ raise ValidationException(
360
+ error=MyErrors.DUPLICATE_EMAIL,
361
+ detailed_error_message="user@example.com already registered",
362
+ )
363
+ ```
364
+
365
+ ### `AccessViolationException` → HTTP 403
366
+
367
+ For authorization/permission failures.
368
+
369
+ ```python
370
+ raise AccessViolationException(
371
+ error=CommonErrors.TENANT_NOT_AUTHORIZED,
372
+ detailed_error_message="Tenant xyz cannot access project abc",
373
+ )
374
+ ```
375
+
376
+ ### `TokenException` → Uses Error's HTTP status
377
+
378
+ For authentication/token failures.
379
+
380
+ ```python
381
+ raise TokenException(
382
+ error=CommonErrors.TENANT_NOT_AUTHORIZED,
383
+ detailed_error_message="JWT token expired",
384
+ )
385
+ # → Returns HTTP 401
386
+ ```
387
+
388
+ ### `DataTypeMismatchException` → HTTP 400
389
+
390
+ For type conversion failures.
391
+
392
+ ```python
393
+ raise DataTypeMismatchException(
394
+ error=Error(400, 400004, "Invalid input type", "Provide valid input"),
395
+ detailed_error_message="Expected int for 'age', got string",
396
+ error_object={"field": "age", "received": "twenty-five"},
397
+ )
398
+ ```
399
+
400
+ ### `InvalidNameException` → HTTP 409
401
+
402
+ Shortcut for invalid name validation.
403
+
404
+ ```python
405
+ raise InvalidNameException("Name contains invalid characters: @#$")
406
+ ```
407
+
408
+ ### `InvalidTenantException` → HTTP 409
409
+
410
+ Shortcut for invalid tenant validation.
411
+
412
+ ```python
413
+ raise InvalidTenantException("Tenant ID 'null' is not valid")
414
+ ```
415
+
416
+ ### `ObjectMappingException` → HTTP 403
417
+
418
+ For serialization/deserialization failures.
419
+
420
+ ```python
421
+ raise ObjectMappingException("Failed to parse user payload", cause=json_error)
422
+ ```
423
+
424
+ ### `RestGetException` / `RestPostException` / `RestException` → HTTP 403
425
+
426
+ For upstream HTTP call failures.
427
+
428
+ ```python
429
+ raise RestGetException(
430
+ error_message="Upstream service unavailable",
431
+ url="https://api.example.com/data",
432
+ status_code=502,
433
+ response_body='{"error": "bad gateway"}',
434
+ )
435
+ ```
436
+
437
+ ### `KafkaException` / `KafkaConsumptionException` → HTTP 403
438
+
439
+ For message queue failures.
440
+
441
+ ```python
442
+ raise KafkaException("Failed to publish event to topic 'orders'")
443
+ raise KafkaConsumptionException("Failed to deserialize message", cause=exc)
444
+ ```
445
+
446
+ ### `UnsupportedOperationException` → HTTP 403
447
+
448
+ For operations not yet implemented.
449
+
450
+ ```python
451
+ raise UnsupportedOperationException("PATCH is not supported on this resource")
452
+ ```
453
+
454
+ ---
455
+
456
+ ## Advanced Usage
457
+
458
+ ### Setting the Application Name
459
+
460
+ The `origin` field in every error response is set from the app name. You can configure it in three ways:
461
+
462
+ ```python
463
+ # Option 1: Pass explicitly (recommended)
464
+ register_exception_handlers(app, app_name="my-service")
465
+
466
+ # Option 2: Falls back to app.title
467
+ app = FastAPI(title="my-service")
468
+ register_exception_handlers(app)
469
+
470
+ # Option 3: Set manually at any time
471
+ from mobius_error import App
472
+ App.set_app_name("my-service")
473
+ ```
474
+
475
+ ### Custom Exception Classes
476
+
477
+ Extend the hierarchy for your domain:
478
+
479
+ ```python
480
+ from mobius_error import ApiException, Error
481
+
482
+ class PaymentErrors:
483
+ INSUFFICIENT_FUNDS = Error(402, 402001, "Insufficient funds", "Add funds to your account")
484
+ CARD_DECLINED = Error(402, 402002, "Card declined", "Try a different payment method")
485
+
486
+ class PaymentException(ApiException):
487
+ def __init__(self, error: Error, detail: str, transaction_id: str | None = None):
488
+ super().__init__(error=error, detailed_error_message=detail)
489
+ if transaction_id:
490
+ self.add_sub_error(f"Transaction ID: {transaction_id}")
491
+
492
+ # Register handler for your custom exception
493
+ @app.exception_handler(PaymentException)
494
+ async def handle_payment(request, exc):
495
+ from mobius_error.responses.api_error_response import ApiErrorResponse
496
+ from fastapi.responses import JSONResponse
497
+ resp = ApiErrorResponse.from_application_exception(exc.http_status_code, exc)
498
+ return JSONResponse(status_code=exc.http_status_code, content=resp.to_dict())
499
+
500
+ # Usage
501
+ raise PaymentException(
502
+ PaymentErrors.CARD_DECLINED,
503
+ detail="Visa ending in 4242 was declined",
504
+ transaction_id="txn_abc123",
505
+ )
506
+ ```
507
+
508
+ ### Combining with FastAPI's HTTPException
509
+
510
+ The library catches Starlette/FastAPI `HTTPException` too, wrapping them in the Mobius format. So `raise HTTPException(status_code=404, detail="Not found")` will return:
511
+
512
+ ```json
513
+ {
514
+ "httpStatusCode": 404,
515
+ "errorCode": 500001,
516
+ "errorMessage": "Not found",
517
+ "origin": "my-service"
518
+ }
519
+ ```
520
+
521
+ ---
522
+
523
+ ## Java ↔ Python Mapping
524
+
525
+ | Java (Spring Boot) | Python (FastAPI) |
526
+ |-------------------------------------------|-----------------------------------------------|
527
+ | `@ControllerAdvice` | `register_exception_handlers(app)` |
528
+ | `@ExceptionHandler(ApiException.class)` | `@app.exception_handler(ApiException)` |
529
+ | `new Error(HttpStatus.NOT_FOUND, ...)` | `Error(404, 404001, ...)` |
530
+ | `throw new ApiException(error, msg)` | `raise ApiException(error=error, detailed_error_message=msg)` |
531
+ | `new ApiErrorResponse(status, msg)` | `ApiErrorResponse.from_status(status, msg)` |
532
+ | `ApplicationException.addSubError(msg)` | `.add_sub_error(msg)` |
533
+ | `App.setAppName(name)` | `App.set_app_name(name)` |
534
+ | `@Value("${spring.application.name}")` | `register_exception_handlers(app, app_name=...)` |
535
+ | `ResponseEntity<ApiErrorResponse>` | `JSONResponse(status_code=..., content=resp.to_dict())` |
536
+
537
+ ---
538
+
539
+ ## Project Structure
540
+
541
+ ```
542
+ error_lib/
543
+ ├── pyproject.toml # Package definition
544
+ ├── example_app.py # Full working demo (run with uvicorn)
545
+ ├── test_smoke.py # Smoke tests for all endpoints
546
+ └── mobius_error/
547
+ ├── __init__.py # Public API — import everything from here
548
+ ├── config.py # App.get_app_name() / App.set_app_name()
549
+ ├── errors/
550
+ │ ├── error.py # Error dataclass (status + code + message)
551
+ │ ├── error_message.py # ErrorMessage model (nested cause chain)
552
+ │ └── common_errors.py # Built-in error constants (CommonErrors)
553
+ ├── exceptions/
554
+ │ ├── application_exception.py # Base exception
555
+ │ ├── api_exception.py # API-level exception
556
+ │ ├── validation_exception.py # Validation errors
557
+ │ ├── access_violation_exception.py
558
+ │ ├── token_exception.py
559
+ │ ├── data_type_mismatch_exception.py
560
+ │ ├── rest_exception.py
561
+ │ ├── rest_get_exception.py
562
+ │ ├── rest_post_exception.py
563
+ │ ├── kafka_exception.py
564
+ │ ├── kafka_consumption_exception.py
565
+ │ ├── object_mapping_exception.py
566
+ │ ├── invalid_name_exception.py
567
+ │ ├── invalid_tenant_exception.py
568
+ │ ├── unsupported_operation_exception.py
569
+ │ └── group_data_retrieval_exception.py
570
+ ├── handlers/
571
+ │ └── exception_handler.py # All FastAPI exception handlers + middleware
572
+ └── responses/
573
+ ├── error_response.py # Base response model
574
+ └── api_error_response.py # Full API response with factory methods
575
+ ```