الأخطاء
كل إخفاق يرفع استثناءً. صنفان اثنان، ومعرّف طلب على كل خطأ من API.
التقاط خطأ
from openemail import OpenEmailApiError, OpenEmailNetworkError, openemail try: openemail.emails.send({'from': sender, 'to': recipient, 'subject': subject, 'text': text})except OpenEmailApiError as error: if error.is_validation: print(error.code, error.param, error.message) if error.is_permission: print(openemail.addresses.list()) if error.is_rate_limited: print('try again in', error.retry_after_seconds, 'seconds') print(error.status, error.request_id) raiseexcept OpenEmailNetworkError as error: if error.is_timeout: print('no answer in time') raiseالخطأ permission_error عند الإرسال يعود عادةً إلى نطاق الإرسال الخاص بالمفتاح، أي نطاق أو عنوان لم يُمنح له، لا إلى مساحة العمل، ولهذا يطبع المثال ما تقوله addresses.list() عن العناوين التي يجوز لهذا المفتاح الإرسال باسمها.
الأصناف
| الصنف | متى |
|---|---|
| OpenEmailApiError | استجاب API، ولم يكن الرد نجاحًا. يحمل message وstatus وtype وcode وparam وdoc_url وrequest_id وretry_after_seconds وfields وbody. |
| OpenEmailNetworkError | لم تصل أي استجابة: DNS أو TLS أو اتصال انقطع أو انتهاء المهلة. يحمل cause، وهو استثناء httpx الكامن تحته، وتكون is_timeout بقيمة True عندما يكون انتهاء المهلة هو السبب. |
| OpenEmailError | الصنف الأساسي لكليهما، فكتلة except واحدة تلتقط كل إخفاق سببه API أو الشبكة. وWebhookVerificationError، الذي يرفعه verify_webhook_signature، يرث منه أيضًا. |
| ValueError | يُرفع قبل إرسال أي شيء: مفتاح مفقود أو مشوّه، أو base_url غير صالح للاستخدام، أو بيانات اعتماد متجهة إلى http غير مشفّر، أو معرّف فارغ. أما http_client الخاطئ، أو جسم طلب لا يستطيع JSON حمله، فيرفع TypeError بدلًا من ذلك. |
يسرد fields كل إجابة رفضها نموذج الاشتراك، في صورة key وerror، ويكون None في كل خطأ آخر. ويحتفظ body بـ JSON الذي أرسله API، أو يكون None حين لا يكون جسم الاستجابة JSON.
| الخاصية | تكون true عندما |
|---|---|
| is_auth | تكون type بقيمة authentication_error، أي 401: لا مفتاح، أو نوع اعتماد خاطئ، أو مفتاح لم نُصدره. |
| is_permission | permission_error، أي 403: مفتاح حقيقي ينقصه النطاق أو عنوان From الذي يحتاجه. |
| is_scope_missing | تكون code بقيمة insufficient_scope، وهو الخطأ 403 الذي يسمّي نطاقًا مفقودًا. |
| is_invalid_request | invalid_request_error، أي 400: طلب تعذّر فهمه. أما الرسالة التي تتجاوز سقف الحجم فتعود بالخطأ 422 message_too_large، ولذا فإن is_validation هي الخاصية التي تلتقطها. |
| is_validation | validation_error، أي 422: رفضه المخطط، وparam تسمّي الحقل. |
| is_not_found | not_found_error، أي 404: لا وجود لهذا المورد. |
| is_conflict | conflict_error، أي 409: تجاوز المورد النقطة التي كان يمكن عندها تنفيذ هذا عليه. |
| is_rate_limited | rate_limit_error، أي 429. وتحمل retry_after_seconds مدة الانتظار عندما يحدّدها الخادم. |
| is_server_error | تكون status بقيمة 500 أو أعلى. اذكر request_id إن تواصلت مع الدعم. |
| is_retryable | تكون status بقيمة 408 أو 429 أو 500 أو 502 أو 503 أو 504. |
| is_step_up_required | code هو step_up_required، أي الـ 403 الذي يتلقاه رمز وصول OAuth قبل تغيير حساس إلى أن يتحقق الشخص من رمز. |
تقرأ معظم الخصائص الحقل type، وهو النصف المجمَّد من الغلاف. أما code فيبقى str، لأن API يضمن أنه مفتوح وتراكمي، فعامِل أي قيمة لا تعرفها معاملة type الخاص بها. وLiteral المغلق كان سيجعل ترقية SDK ثمنًا لقراءة وضع إخفاق جديد.
المتن الذي لا يطابق غلاف الخطأ الخاص بـ API يصبح مع ذلك OpenEmailApiError، مع استنتاج type من رمز الحالة وضبط code على unrecognised_response. والاستجابة الناجحة التي ليس متنها JSON ترفع الخطأ نفسه.
إلغاء استدعاء على AsyncOpenEmail لا يرفع أي OpenEmailError. بل ينتشر الإلغاء نفسه، سواء وقع أثناء الطلب أو أثناء الانتظار قبل إعادة المحاولة، ولا يُعاد أي شيء بعده.
request_id
يحمل كل OpenEmailApiError معرّف الطلب الذي أرسله الخادم، من متن الخطأ أو من الترويسة x-request-id، وهو الشيء الوحيد الذي يربط إخفاقك بسطر في سجل الخادم. أما الاستجابة الناجحة فتعيد المتن المحلَّل وحده، فلا يوجد فيها معرّف طلب لقراءته.
ينتهي str(error) برمز الحالة ورمز الخطأ ومعرّف الطلب، فسطر السجل الذي يطبع الاستثناء يحتفظ بالثلاثة كلها. ويصمد OpenEmailApiError أيضًا أمام التسلسل بـ pickle بكل حقوله، فالخطأ الذي يُرفع في عملية عاملة يصل إلى العملية الأم سليمًا.