Skip to main content

Modern Python SDK for the Eskiz.uz SMS gateway

Project description

eskiz-sms

Modern Python SDK for the Eskiz.uz SMS gateway. Sync and async clients, full type hints, Pydantic v2 models.

Downloads Downloads

Discussion group: https://t.me/+xFkMROBeFp45ZmQ0

v1.0 is a ground-up rewrite. The import path and public API changed. See Migrating from v0.x below. v0.x is preserved on the master branch.

Installation

Requires Python 3.11+.

pip install eskiz-sms

Optional extras:

pip install "eskiz-sms[dotenv]"   # for DotenvTokenStorage

Quickstart

from eskiz import EskizSMS

with EskizSMS(email="you@example.com", password="your-password") as client:
    result = client.sms.send(
        mobile_phone="998901234567",
        message="Hello from Eskiz!",
        from_whom="4546",
    )
    print(result.id, result.status)

Async

import asyncio
from eskiz import AsyncEskizSMS

async def main() -> None:
    async with AsyncEskizSMS(email="you@example.com", password="your-password") as client:
        result = await client.sms.send(
            mobile_phone="998901234567",
            message="Hello!",
        )
        print(result.id, result.status)

asyncio.run(main())

Resources

The client exposes four resource namespaces. Every method has the same signature on the sync and async clients.

client.auth

Method Returns Notes
me() User Current account info.

client.sms

Method Returns
send(...) SendResult
send_global(...) SendResult
send_batch(messages=, ...) BatchSendResult
list_messages(start_date=, ...) PaginatedMessages
list_by_dispatch(dispatch_id=...) PaginatedMessages
dispatch_status(dispatch_id=, user_id=) list[DispatchStatusRow]
status(sms_id) SmsStatusDetail
nicks() list[str]
normalize(message) list[NormalizerCharacter]
check(message) SmsCheckResult

client.templates

Method Returns
create(template) TemplateCreated
list_all() TemplateList

client.reports

Method Returns
balance() LimitInfo
prices() PriceList
totals(year=, month=, is_global=) list[Total]
by_month(year) list[TotalByMonth]
by_smsc(year=, month=) list[SmscTotal]
by_range(start_date=, to_date=, ...) list[RangeExpense]
by_dispatch(dispatch_id=, ...) list[DispatchExpense]
export(year=, month=, ...) str (CSV)
logs(sms_id) SmsLogResponse

Sending a batch

from eskiz import EskizSMS
from eskiz.models import BatchMessage

with EskizSMS(email=..., password=...) as client:
    result = client.sms.send_batch(
        dispatch_id=42,
        messages=[
            BatchMessage(user_sms_id="s1", to="998990000001", text="Hi A"),
            BatchMessage(user_sms_id="s2", to="998990000002", text="Hi B"),
        ],
    )

Plain dicts work too — to is normalized (+, spaces, dashes stripped) and ints are coerced:

client.sms.send_batch(
    dispatch_id=42,
    messages=[{"user_sms_id": "s1", "to": 998990000001, "text": "Hi"}],
)

Token storage

Tokens are cached in memory by default. To persist across runs, pass a TokenStorage to the client:

from eskiz import DotenvTokenStorage, EskizSMS

with EskizSMS(
    email="you@example.com",
    password="your-password",
    token_storage=DotenvTokenStorage(env_path=".env"),
) as client:
    client.sms.send(mobile_phone="998901234567", message="Hi")
# Token written to .env; next run reuses it without re-logging in.

DotenvTokenStorage requires pip install "eskiz-sms[dotenv]". You can also implement your own storage (Redis, DB, etc.) by conforming to the TokenStorage Protocol:

class TokenStorage(Protocol):
    def get(self) -> str | None: ...
    def set(self, token: str) -> None: ...
    def clear(self) -> None: ...

Token refresh

The SDK automatically refreshes expired tokens via PATCH /auth/refresh. If refresh fails (revoked token, etc.), it falls back to a fresh POST /auth/login. Concurrent callers share a single in-flight refresh — under load you'll never hammer /auth/login even if 100 requests hit a 401 simultaneously.

Default callback URL

Pass callback_url= to the client and every send uses it unless overridden per-call:

client = EskizSMS(
    email="you@example.com",
    password="your-password",
    callback_url="https://your-app.com/eskiz/callback",
)

Errors

All errors derive from EskizError:

EskizError
├── EskizHTTPError         network / TLS / transport failure
├── AuthError
│   ├── InvalidCredentials login email or password is wrong
│   ├── TokenExpired       (rarely surfaced; auto-handled)
│   └── TokenInvalid       token revoked or refresh failed
├── EskizBadRequest        API rejected the request (validation, etc.)
└── EskizValidationError   local input failed validation (bad URL, ...)

The Eskiz* prefix on the exception names is deliberate — BadRequest, HTTPError, and ValidationError collide with web frameworks, urllib, and pydantic respectively.

from eskiz import EskizBadRequest, EskizSMS, InvalidCredentials

try:
    client.sms.send(mobile_phone="998901234567", message="Hi")
except InvalidCredentials:
    print("check your email/password")
except EskizBadRequest as e:
    print(f"API error: {e.message} (status={e.status_code})")

Migrating from v0.x

v0.x v1.0
from eskiz_sms import EskizSMS from eskiz import EskizSMS
EskizSMS(email, password, save_token=True) EskizSMS(email=, password=, token_storage=DotenvTokenStorage())
eskiz.send_sms(...) client.sms.send(...)
eskiz.send_global_sms(...) client.sms.send_global(...)
eskiz.send_batch(...) client.sms.send_batch(...)
eskiz.get_user_messages(...) client.sms.list_messages(...)
eskiz.get_dispatch_status(...) client.sms.dispatch_status(...)
eskiz.get_template(...) / get_templates() client.templates.list_all()
eskiz.create_template(...) client.templates.create(...)
eskiz.totals(...) client.reports.totals(...)
eskiz.total_by_month(...) client.reports.by_month(...)
eskiz.total_by_smsc(...) client.reports.by_smsc(...)
eskiz.message_export(...) client.reports.export(...)
eskiz.get_limit() client.reports.balance()
eskiz.logs_sms(...) client.reports.logs(...)
eskiz.user client.auth.me()
from eskiz_sms.async_ import EskizSMS from eskiz import AsyncEskizSMS

Other notable changes:

  • Python 3.11+ required (was 3.8+).
  • All endpoint methods take keyword-only arguments.
  • All return values are typed Pydantic models, not raw dicts.
  • Contact endpoints (add_contact, get_contact, etc.) are no longer exposed — they were not in the official Postman collection.
  • client.token.set(...) removed; use a custom TokenStorage instead.

Integration tests

The unit suite uses respx to mock the HTTP layer. A separate, opt-in integration suite under tests/integration/ exercises the SDK against the real Eskiz API. It is skipped by default — pass --run-integration to run it.

Set credentials in .env.integration (see .env.integration.example):

cp .env.integration.example .env.integration
# fill in ESKIZ_EMAIL / ESKIZ_PASSWORD

Run only the read-only smokes (no SMS sent, no credits used):

pytest --run-integration tests/integration/test_readonly.py tests/integration/test_async.py

Run the live send test as well — set ESKIZ_TEST_PHONE to a number you control. Eskiz moderates SMS bodies per-account; if the default Eskiz Test body isn't approved on yours, set ESKIZ_TEST_BODY to one that is:

ESKIZ_TEST_PHONE=998901234567 pytest --run-integration tests/integration

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eskiz_sms-1.0.1.tar.gz (26.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eskiz_sms-1.0.1-py3-none-any.whl (38.6 kB view details)

Uploaded Python 3

File details

Details for the file eskiz_sms-1.0.1.tar.gz.

File metadata

  • Download URL: eskiz_sms-1.0.1.tar.gz
  • Upload date:
  • Size: 26.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for eskiz_sms-1.0.1.tar.gz
Algorithm Hash digest
SHA256 e9f673a873dd218a11a739dcada2204a59c31b07db6fa096e3c9ccd7ff1d2c7d
MD5 b5a4a3a87d222044e1ccda5d480066fc
BLAKE2b-256 af13fa6083bf5ddf2280490827b28d60c032336fd3829f2d5f0f2a55dd6dc601

See more details on using hashes here.

File details

Details for the file eskiz_sms-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: eskiz_sms-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 38.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for eskiz_sms-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cc8787b1ec93a8cec5ef821119ee6387944280025a7601128e398a96fd8b1480
MD5 46f8db8e426c37bc1722b39a6e6e5fd0
BLAKE2b-256 f720a0d8b51fa7e6ba32826fc016bf312f337a1d3c01d8c6ccf23a18347e1ff6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page