پرش به مطلب اصلی

پکیج Python

پکیج رسمی nestapi-py یک کلاینت پویا و سبک برای Python است که هم به‌صورت همگام (NestApi) و هم ناهمگام (AsyncNestApi) کار می‌کند. نام هیچ سرویسی داخل پکیج نیست: هر متدی که صدا بزنید به مسیر همان سرویس تبدیل می‌شود، پس سرویس‌های جدید بدون به‌روزرسانی پکیج در دسترس هستند.

نصب​

pip install nestapi-py

نیازمندی‌ها: Python 3.10 به بالا. وابستگی httpx خودکار نصب می‌شود.

نام پکیج در PyPI nestapi-py است و با نام nestapi ایمپورت می‌شود.

شروع سریع​

from nestapi import NestApi

with NestApi() as api:
result = api.bmi(age=30, weight=70, height=175)

print(result["bmi"]) # 22.86
print(result.get("input.age")) # 30

نسخهٔ ناهمگام (async)​

AsyncNestApi دقیقاً مثل NestApi کار می‌کند؛ مسیرها، پارامترها، نسخه‌ها، پاسخ و خطاها یکسان هستند. فقط فراخوانی سرویس‌ها و متدهای call()، request() و close() را await کنید. متد version() معمولی است.

import asyncio

from nestapi import AsyncNestApi


async def main() -> None:
async with AsyncNestApi() as api:
result = await api.bmi(age=30, weight=70, height=175)
print(result["bmi"]) # 22.86

# اجرای هم‌زمان چند درخواست
first, second = await asyncio.gather(
api.bmi(age=30, weight=70, height=175),
api.version(2).call("ip-info", {"ip": "8.8.8.8"}),
)


asyncio.run(main())

هر نمونهٔ AsyncNestApi را فقط در یک event loop استفاده کنید، چون اتصال‌هایش به همان loop وابسته‌اند. برای تزریق کلاینت دلخواه، یک httpx.AsyncClient بدهید.

نمونه‌های بعدی با NestApi نوشته شده‌اند؛ در AsyncNestApi کافی است پیش از هر فراخوانی await بگذارید.

تبدیل نام متد به مسیر​

مسیر از نام متد ساخته می‌شود: هر _ به - تبدیل می‌شود، هر حرف بزرگ (به‌جز حرف اول) یک بخش جدید با - شروع می‌کند و نتیجه با حروف کوچک ارسال می‌شود.

متدمسیر
ip_infoip-info
search_shazamsearch-shazam
chatAi، ChatAichat-ai
chatAIchat-a-i

نام‌هایی که با _ شروع می‌شوند سرویس به حساب نمی‌آیند و AttributeError می‌دهند.

ارسال پارامترها​

پارامترها را به شکل keyword argument یا یک dict بفرستید. برای نام‌هایی که کلمهٔ رزرو Python هستند، مثل from یا in، باید از dict استفاده کنید.

api.bmi(age=30, weight=70, height=175)
api.bmi({"age": 30, "weight": 70, "height": 175})

api.convert({"from": "USD", "in": "EUR"})

api.bmi(30, 70) # TypeError: پارامتر باید نام‌دار باشد
api.bmi({"age": 30}, weight=70) # TypeError: فقط یکی از دو روش
api.bmi({1: "a"}) # TypeError: نام پارامتر باید رشته باشد

فراخوانی با نام دقیق مسیر​

# درخواست GET با نام دقیق مسیر و بدون تبدیل
api.call("ip-info", {"ip": "8.8.8.8"})

# هر متد HTTP دیگر؛ پارامترها همچنان در Query String قرار می‌گیرند
api.request("POST", "some-service", {"a": 1})
یادداشت

سرویس‌هایی با نام call، request، version یا close با متدهای خود کلاینت هم‌نام هستند. آن‌ها را با call() صدا بزنید، برای مثال api.call("version").

نحوهٔ ارسال مقدارها​

مقدار Pythonارسال به شکلتوضیح
True / Falsex=true / x=falseسرور مقدارهای 1، 0، True و TRUE را نمی‌پذیرد
Noneارسال نمی‌شودداخل لیست و dict هم نادیده گرفته می‌شود
list / tuplex=1&x=2کلیدهای تکراری
dictx[key]=1همان قالب پکیج PHP

فاصله به شکل %20 و متن غیرانگلیسی با UTF-8 ارسال می‌شود.

نسخه‌ها​

نسخهٔ پیش‌فرض 1 است و پیشوند apis- خودکار اضافه می‌شود. نسخهٔ هر سرویس در مستندات همان سرویس آمده است. متد version() یک کپی برمی‌گرداند و نمونهٔ اصلی تغییر نمی‌کند. نسخهٔ کمتر از 1 خطای ValueError و هر مقداری غیر از int (حتی bool) خطای TypeError می‌دهد.

api = NestApi(version=2)

v3 = api.version(3) # api همچنان روی نسخهٔ 2 می‌ماند

فیلد source.version در پاسخ ربطی به نسخهٔ داخل آدرس ندارد.

خواندن پاسخ​

هر فراخوانی موفق یک شیء فقط‌خواندنی nestapi.Response برمی‌گرداند.

عضوتوضیح
result.dataمقدار فیلد data؛ دیکشنری، لیست، رشته یا None
result.metaمقدار فیلد meta یا None
result.sourceمقدار فیلد source
result.get("a.b", default)خواندن از داخل data با مسیر نقطه‌ای
result["key"]خواندن کلیدهای سطح اول data
"key" in resultبررسی وجود کلید در data

مسیر get() از داخل data شروع می‌شود و می‌تواند وارد لیست‌ها هم بشود.

result = api.bmi(age=30, weight=70, height=175)

result.get("input.age") # 30
result.get("input.nope", 0) # 0
result.get("items.0.title") # اندیس لیست در مسیر

result["bmi"] # 22.86
"bmi" in result # True
result["nope"] # KeyError

result.data = {} # AttributeError
result["bmi"] = 1 # TypeError

کلیدی که وجود دارد ولی مقدارش null است، None برمی‌گرداند نه مقدار پیش‌فرض. اگر data دیکشنری یا لیست نباشد، get() مقدار پیش‌فرض را برمی‌گرداند.

مدیریت خطا​

پاسخ‌های ناموفق به Exception تبدیل می‌شوند. برای شرط‌نویسی از error_code استفاده کنید، نه متن پیام، چون متن پیام ممکن است تغییر کند.

کلاسزمان رخ دادن
ServiceNotFoundErrorکد NOT_FOUND؛ سرویس یا نسخهٔ ناموجود
ValidationErrorکدهای PARAM_NOT_FOUND و VALIDATION_ERROR
RateLimitErrorکدهای RATE_LIMITED و RATE_LIMIT_EXCEEDED
ServerErrorسایر کدها با وضعیت ۵۰۰ و بالاتر
ApiErrorکلاس پایهٔ موارد بالا و سایر کدها مثل INVALID_IP
TransportErrorخطای شبکه، بدنهٔ غیر JSON یا پاسخ خارج از قالب API (از ApiError مستقل است)
from nestapi import ApiError, NestApi, TransportError, ValidationError

try:
api.bmi(age=500, weight=70, height=175)
except ValidationError as e:
e.error_code # "VALIDATION_ERROR"
e.status # 422
e.details # {"query.age": "Must be less than or equal to 120"}
except ApiError as e:
... # سایر خطاهای گزارش‌شده توسط API
except TransportError as e:
... # پاسخ قابل استفاده‌ای نرسید؛ علت در e.__cause__ است

مقدار details برای PARAM_NOT_FOUND یک لیست و برای VALIDATION_ERROR یک دیکشنری است.

پیکربندی​

NestApi() بدون هیچ تنظیمی کار می‌کند. همهٔ گزینه‌ها در سازنده تنظیم می‌شوند.

گزینهپیش‌فرضتوضیح
version1نسخهٔ API؛ مقدار N مسیر apis-N را می‌سازد
base_urlhttps://open.nestapi.irآدرس پایه
timeout30زمان انتظار به ثانیه، فقط برای کلاینت پیش‌فرض
clienthttpxیک httpx.Client دلخواه؛ برای AsyncNestApi یک httpx.AsyncClient
import httpx

api = NestApi(
version=1,
base_url="https://open.nestapi.ir",
timeout=30,
client=httpx.Client(),
)

بررسی SSL در کلاینت پیش‌فرض همیشه فعال است و قابل غیرفعال کردن نیست.

بستن اتصال​

برای آزاد کردن اتصال، close() را صدا بزنید یا از with NestApi() as api: استفاده کنید. در نسخهٔ ناهمگام از await api.close() یا async with AsyncNestApi() as api: استفاده کنید.

  • کلاینتی که خودتان تزریق کرده‌اید متعلق به شماست و close() آن را نمی‌بندد.
  • کپی‌های ساخته‌شده با version() کلاینت نمونهٔ اصلی را به اشتراک می‌گذارند. بستن کپی کاری انجام نمی‌دهد و نمونهٔ اصلی همچنان کار می‌کند.
  • بستن نمونهٔ اصلی کلاینت مشترک را می‌بندد و کپی‌ها از آن به بعد TransportError می‌دهند.

لینک‌های بیشتر​