پیکربندی
سه راه برای ساخت یک کلاینت، همهٔ گزینهها، و آنچه پیش از فرستادن درخواست رد میکند.
گزینهها
import os from openemail import AsyncOpenEmail, OpenEmail, init, openemail init(os.environ['OPENEMAIL_API_KEY'])openemail.me.ping() billing = OpenEmail(os.environ['BILLING_API_KEY']) pinned = OpenEmail('oe_live_...', base_url='https://api.openemail.uk') background = AsyncOpenEmail()| نقطهٔ ورود | چه چیزی به شما میدهد |
|---|---|
| init(...) | کلاینت مشترک را پیکربندی میکند و همان را برمیگرداند. از آن پس openemail در هر ماژول همان کلاینت است، و هر چیزی که ندهید از محیط خوانده میشود. |
| openemail | کلاینت مشترک. اگر پیش از init استفاده شود، در نخستین فراخوانی خودش را از OPENEMAIL_API_KEY و OPENEMAIL_BASE_URL میسازد. |
| OpenEmail(...) | کلاینتی جدا، برای کلیدی دوم در کنار کلید مشترک، یا برای ساختن نمونهای که ماژول خودتان آن را export میکند. هر چیزی که ندهید از محیط خوانده میشود، همانطور که init میخواند، و create_client همین کلاس است با نامی دیگر. |
| AsyncOpenEmail(...) | کلاینت ناهمگام جدایی با همان گزینهها که هر متدش با await فراخوانی میشود. openemail مشترک همگام است، پس این یکی را خودتان میسازید. |
| get_client() و reset_client() | get_client خودِ کلاینت مشترک را برمیگرداند و اگر init اجرا نشده باشد آن را از محیط میسازد. reset_client آن را فراموش میکند، پس استفادهٔ بعدی کلاینت تازهای میسازد. |
import httpxfrom openemail import init init( 'oe_live_...', base_url='https://api.openemail.uk', timeout=30, max_retries=2, http_client=httpx.Client(proxy='http://proxy.internal:3128'), headers={'X-Team': 'billing'}, user_agent='billing-service/1.4', disable_update_notice=True,)| گزینه | پیشفرض | توضیحات |
|---|---|---|
| api_key | OPENEMAIL_API_KEY | توسط init و OpenEmail از محیط خوانده میشود. باید با oe_live_ یا oe_test_ آغاز شود. |
| access_token | OPENEMAIL_ACCESS_TOKEN | یک توکن دسترسی OAuth، یا تابعی که آن را برمیگرداند، بهجای api_key. بخش توکنهای دسترسی OAuth را در پایین ببینید. |
| base_url | https://api.openemail.uk | یا OPENEMAIL_BASE_URL. اسلش پایانی حذف میشود، و init و OpenEmail جلوی یک نام میزبان خالی https:// میگذارند، یا جلوی میزبانی روی همین دستگاه http://: localhost، یک نشانی 127.x.x.x یا ::1. اعتبارنامه هرگز با http ساده به میزبان دیگری فرستاده نمیشود، و 0.0.0.0 یا [::] هنگام ساختن کلاینت خطا raise میکند، چون اینها نشانیهاییاند که سرور روی آنها گوش میدهد، نه نشانیهایی برای فرستادن درخواست. |
| timeout | 30 | بر حسب ثانیه، برای هر تلاش، نه برای هر فراخوانی. خواندن بدنه را هم در بر میگیرد، نه فقط سرآیندها را. 0 آن را غیرفعال میکند. files.upload دستکم 600 ثانیه مهلت میدهد، مگر اینکه فراخوانی timeout خودش را بدهد. |
| max_retries | 2 | تلاشهای اضافی پس از تلاش نخست، روی فراخوانیهایی که تکرارشان بیخطر است. روی کلاینت تنظیم میشود، نه برای هر فراخوانی. |
| http_client | یک httpx.Client تازه | برای یک پراکسی، تنظیمات TLS خودتان، یک transport سوارشده یا یک بدل آزمایشی، کلاینت خودتان را بدهید: یک httpx.Client به OpenEmail و یک httpx.AsyncClient به AsyncOpenEmail. بستن کلاینت، کلاینتی را که خودتان دادهاید باز میگذارد. |
| headers | {} | با هر درخواست فرستاده میشود. |
| user_agent | openemail-python/<version> | با هر درخواست فرستاده میشود. |
| disable_update_notice | False | بررسی یکبار در هر فرایند برای یافتن نسخهٔ تازهتر روی PyPI را رد میکند. این بررسی فقط وقتی اجرا میشود که خروجی به یک پایانه برود، و OPENEMAIL_DISABLE_UPDATE_NOTICE هم آن را خاموش میکند. |
پیش از ارسال چه چیزی را رد میکند
اینها پیش از فرستادن هر درخواستی ValueError، یا جایی که جدول میگوید TypeError، را raise میکنند، بهجای آنکه بهصورت شکستی گیجکننده در نخستین ارسال شما ظاهر شوند. پیام میگوید چه چیزی نادرست بوده و بهجایش چه باید داد.
| رد میشود | چرا |
|---|---|
| هیچ کلیدی وجود ندارد | نه api_key داده شده بود و نه OPENEMAIL_API_KEY تنظیم شده بود، پس چیزی برای احراز هویت نیست. |
| یک کوکی نشست یا توکن نشست | تنها oe_live_ و oe_test_ اینجا احراز هویت میکنند، و API هم همین را میگوید. این بررسی فقط یک پیشوند است و نه بیشتر، پس کلید باطلشده همچنان روی سیم شکست میخورد. |
| base_url ای که یک URL با http یا https نیست | چیز دیگری قابل fetch نیست، پس کلاینت همان هنگام ساخته شدن آن را رد میکند، بهجای آنکه در نخستین درخواست شکست بخورد. |
| base_url ای روی 0.0.0.0 یا [::] | نشانیای که سرور روی آن گوش میدهد، نه نشانیای برای فرستادن درخواست. پیام بهجای آن 127.0.0.1 یا [::1] را با همان پورت نام میبرد. |
| اعتبارنامه روی http ساده | هنگام فراخوانی و پیش از آنکه درخواست بیرون برود رد میشود، مگر اینکه سرور روی همین دستگاه باشد. هر کسی روی شبکه میتوانست آن را بخواند. |
| id خالی یا تماماً نقطه در هر متدی | هنگام فراخوانی متد raise میشود. بخشی از مسیر که فقط نقطه باشد را هر تجزیهگر URL حذف میکند، پس درخواست به نقطهٔ پایانی دیگری میرسید. |
| http_client از نوع نادرست | یک TypeError هنگام ساختن کلاینت. OpenEmail یک httpx.Client میگیرد و AsyncOpenEmail یک httpx.AsyncClient. |
| مقداری در بدنه که JSON نمیتواند حملش کند | یک TypeError که نوع آن را نام میبرد. نوعهای JSON بیتغییر رد میشوند، و یک datetime، date یا set برایتان تبدیل میشود. |
گزینهای به نام test_mode وجود ندارد و نخواهد داشت. طرح کلید بخشی از خودِ اعتبارنامه است نه یک اشاره، پس حالت، ویژگیِ کلید است. client.mode پیشوند را میخواند و دربارهٔ چیزی تصمیم نمیگیرد.
یک کلاینت، چند کلید
کلاینت را یک بار بسازید و به اشتراک بگذارید. نمونهٔ تازه برای هر درخواست، بیهیچ سودی استخر اتصالها و پیکربندی را دور میریزد، و هیچیک از وضعیتهای روی آن مخصوص یک فراخوان نیست.
یک کلاینت را میتوان بیخطر میان threadها به اشتراک گذاشت. close() یا پایان یک بلوک with استخر اتصالی را که خود کلاینت باز کرده میبندد، و http_client ای که دادهاید باز میماند تا خودتان آن را ببندید.
برای موردی که در غیر این صورت به ازای هر کلید یک نمونه لازم میکرد، مثلاً کاری که از طرف چند فضای کاری ارسال میکند، api_key را روی همان فراخوانی بدهید. برای آن درخواست جایگزین سرآیند Authorization میشود و چیزی روی کلاینت باقی نمیگذارد.
from openemail.types import EmailSend workspace_key = 'oe_live_...' message: EmailSend = {'from': sender, 'to': recipient, 'subject': subject, 'text': text} client.emails.send(message) client.emails.send(message, api_key=workspace_key) client.threads.list(folder='inbox', api_key=workspace_key)client.webhooks.list(api_key=workspace_key)هر متدی بیرون از temp_mail آن را بهصورت آرگومان کلیدواژهای، در کنار timeout، میگیرد. پیش از فرستادن درخواست و با همان قاعدهای که سازنده به کار میبرد بررسی میشود، پس یک غلط تایپی بهجای 401 دربارهٔ اعتبارنامهای که باید بگردید و پیدایش کنید، یک ValueError را raise میکند که api_key= on this call را نام میبرد. فراخوانیای که دوباره تلاش میشود همان کلیدی را نگه میدارد که به آن داده شده بود.
timeout بر حسب ثانیه است و فقط برای همان یک فراخوانی، در هر یک از تلاشهایش، جای تایماوت کلاینت را میگیرد.
client.mode کلیدی را توصیف میکند که کلاینت با آن ساخته شده و از بازنویسیهای موردی پیروی نمیکند. وقتی یک کلاینت به چند کلید خدمت میکند دیگر یک حالت یکتا برای گزارش وجود ندارد، پس آن را از روی کلیدی که دادهاید بخوانید.
اندپوینتی که هیچ متدی آن را نمیپوشاند
client.raw.request درخواستی را با اعتبارنامه، URL پایه، تایماوت و سیاست تلاش دوبارهٔ کلاینت میفرستد و JSON تجزیهشده را برمیگرداند. method، query، body، api_key و timeout را میگیرد. یک GET را دوباره تلاش میکند و هر چیز دیگری را یک بار میفرستد، مگر اینکه repeatable=True بدهید. idempotent=True یک Idempotency-Key میافزاید، همان که بهصورت idempotency_key میدهید یا یکی تازه.
result = client.raw.request('/something-new', method='POST', body={'name': 'Invoices'})مسیر باید با یک / تنها آغاز شود. هر چیز دیگری، مانند //host/x، پیش از فرستادن درخواست خطا raise میکند، و همینطور مسیری که URL نهاییاش از origin مربوط به URL پایه بیرون برود، تا اعتبارنامهای که با خود دارد هرگز به میزبان دیگری نرسد.
صندوقهای یکبارمصرف
create_temp_mail() کلاینتی میسازد که هیچ کلید API حمل نمیکند، و create_async_temp_mail() همتای ناهمگام آن است. صندوقها را ناشناس میسازد، و هر خواندن توکن صندوقی را میفرستد که create بازگردانده بود، یا توکن تازهتری را که extend بازگرداند، خواه برای هر فراخوانی بهصورت inbox_token خواه یک بار بهصورت create_temp_mail(inbox_token=...).
from openemail import create_temp_mail temp_mail = create_temp_mail() inbox = temp_mail.create()messages = temp_mail.list_messages(inbox['id'], inbox_token=inbox['token']) print(messages['items'], messages['expiresAt'])توکنهای دسترسی OAuth
هنوز منتشر نشدهبرنامهای که شخصی با OAuth متصل کرده، مثل یک ابزار خط فرمان یا یک عامل، بهجای کلید API یک توکن دسترسی دارد. آن را بهصورت access_token بدهید: یا خودِ توکن، یا تابعی که آن را برمیگرداند و روی AsyncOpenEmail میتواند async باشد. تابع برای هر فراخوانی یک بار صدا زده میشود و تلاشهای دوبارهٔ همان فراخوانی از چیزی که برگردانده استفاده میکنند، پس وقتی توکن نزدیک انقضاست آن را درون تابع تازه کنید تا هرگز لازم نباشد کلاینت را از نو بسازید.
from openemail import OpenEmail client = OpenEmail(access_token=session.fresh_access_token) me = client.me.get() if me['object'] == 'oauth_token': print(me['clientId'], me['expiresAt'])| حالت | چه رخ میدهد |
|---|---|
| api_key و access_token با هم، یا هیچکدام | سازنده ValueError را raise میکند. وقتی هیچکدام نباشد، پیام OPENEMAIL_API_KEY و OPENEMAIL_ACCESS_TOKEN را نام میبرد. |
| مقداری که توکن نیست | توکن 1 تا 512 نویسه است و با oe_ شروع نمیشود؛ این همان بررسیای است که is_access_token انجام میدهد. رشتهای که از آن رد نشود در سازنده خطا raise میکند، و تابعی که چنین مقداری برگرداند فراخوانی را پیش از فرستادن هر چیزی شکست میدهد. |
| OPENEMAIL_ACCESS_TOKEN | وقتی هیچکدام از دو اعتبار را ندهید و OPENEMAIL_API_KEY تنظیم نشده باشد، init، OpenEmail و openemail مشترک آن را میخوانند؛ پس کلیدی که در محیط باشد مقدم است. |
| تابعی که خطا raise میکند | فراخوانی همان خطا را، بیتغییر، raise میکند و چیزی فرستاده نمیشود. |
| تابعی روی OpenEmail که یک awaitable برمیگرداند | یک ValueError، چون کلاینت همگام نمیتواند منتظر آن بماند. روی AsyncOpenEmail تابع میتواند async باشد. |
| api_key برای هر فراخوانی | فقط برای همان یک درخواست جای توکن را میگیرد و تابع صدا زده نمیشود. |
| mode | با توکن همیشه live. |
| create_temp_mail() | هر چه در محیط باشد، هیچ اعتباری نمیفرستد. |
| me.get() و me.ping() | برای توکن، get با object برابر oauth_token، id و roleId برابر None، clientId برنامهٔ متصل، و expiresAt، یعنی زمانی که تأیید شخص برای برنامه تمام میشود، پاسخ میدهد. ping با kind برابر oauth، keyId برابر None و clientId پاسخ میدهد. KeyResource و PingResource نوع اجتماعی (union) هستند، پس پیش از خواندن clientId یا expiresAt بر اساس object یا kind تشخیص دهید. |
توکن از طرف یک شخص کار میکند و ایمیلش را همانطور که خودش میتواند میخواند، پس آن را مثل کلید روی سرور نگه دارید.
کدهای تأیید هویت
هنوز منتشر نشدهپیش از یک تغییر حساس، مثل حذف یک دامنه یا تغییر یک وبهوک، API از توکن دسترسی همان کد تأیید هویتی را میخواهد که برنامهٔ وب از شخص میخواست. فراخوانی یک OpenEmailApiError را raise میکند که is_step_up_required آن True است، و چیزی تغییر نکرده است. یک کد بخواهید، کدی را که شخص به شما میدهد تأیید کنید، سپس دوباره فراخوانی کنید. از کلید API هرگز خواسته نمیشود.
from openemail import OpenEmailApiError try: client.domains.delete(domain_id)except OpenEmailApiError as error: if not error.is_step_up_required: raise challenge = client.security.begin_step_up() if challenge['method'] == 'email': prompt = f'Enter the code we emailed to {challenge.get("sentTo")}: ' else: prompt = 'Enter the code from your authenticator app, or a backup code: ' client.security.verify_step_up({'code': input(prompt)}) client.domains.delete(domain_id)| متد | چه میکند |
|---|---|
| security.step_up_status() | اینکه برنامه همین حالا تأییدشده است یا نه (elevated، elevatedUntil)، کد بعدی چطور بررسی میشود (method، email یا totp)، و minutes، طول بازهٔ زمانی. چیزی نمیفرستد و توقف را گزارش نمیکند. |
| security.begin_step_up(body=None) | یک تأیید هویت را آغاز میکند. با email یک کد ششرقمی به نشانیای میرود که شخص با آن وارد میشود و sentTo آن را پوشیده نشان میدهد. با totp شخص کدی را از برنامهٔ احراز هویتش میخواند یا یک کد بازیابی به کار میبرد. تأیید هویتی که هنوز باز است و تلاش باقی دارد دوباره به کار میرود، مگر اینکه {'resend': True} بدهید، و تأیید هویت قفلشده یا منقضیشده با یک فراخوانی ساده جایگزین میشود. هر برنامه برای هر شخص میتواند 5 تأیید هویت در ساعت و 20 در 24 ساعت آغاز کند، و بعدی خطای 429 step_up_throttled را raise میکند. |
| security.verify_step_up({'code': code}) | کد را بررسی میکند و تغییرات حساس را برای این برنامه به مدت 60 دقیقه، تا elevatedUntil، از راه REST و از راه ابزارهای MCP که همان تغییرها را انجام میدهند باز میکند. پس از 10 کد نادرست در 24 ساعت از این برنامه، یا 20 کد از همهٔ برنامههای شخص با هم، این فراخوانی و begin_step_up خطای 429 step_up_locked را با پیامی raise میکنند که میگوید تأیید هویت کی از سر گرفته میشود. |
کلاینت هرگز خودش کد نمیخواهد یا فراخوانی را تکرار نمیکند، و نه begin_step_up و نه verify_step_up خودکار دوباره تلاش نمیشوند، چون تلاش دوباره پس از یک پاسخ گمشده ممکن است ایمیل دومی بفرستد یا تلاش دومی را مصرف کند. اسکوپ لازم ندارند، و کلید API که یکی از آنها را فراخوانی کند 400 step_up_not_applicable میگیرد. STEP_UP_ERROR_CODES همهٔ راههایی را که تأیید ممکن است شکست بخورد نام میبرد، و صفحهٔ خطاهای API میگوید برای هر کدام چه باید کرد.