پکیج 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_info | ip-info |
search_shazam | search-shazam |
chatAi، ChatAi | chat-ai |
chatAI | chat-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 / False | x=true / x=false | سرور مقدارهای 1، 0، True و TRUE را نمیپذیرد |
None | ارسال نمیشود | داخل لیست و dict هم نادیده گرفته میشود |
list / tuple | x=1&x=2 | کلیدهای تکراری |
dict | x[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() بدون هیچ تنظیمی کار میکند. همهٔ گزینهها در سازنده تنظیم میشوند.
| گزینه | پیشفرض | توضیح |
|---|---|---|
version | 1 | نسخهٔ API؛ مقدار N مسیر apis-N را میسازد |
base_url | https://open.nestapi.ir | آدرس پایه |
timeout | 30 | زمان انتظار به ثانیه، فقط برای کلاینت پیشفرض |
client | httpx | یک 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میدهند.