Async Python client for TTLock/Sciener APIs with typed Pydantic schemas and high-level interface wrappers for locks, passcodes, and QR codes.
- Async HTTP client powered by
httpx. - Environment-based configuration via
pydantic-settings. - Typed request/response schemas via
pydantic. - High-level facade object (
Lock) with grouped interfaces:Lockoperations (list/detail/state/control).Passcodeoperations (create/get/list/change/delete).QRoperations (create/get/list/update/delete/clear).
- Provider support for both TTLock and Sciener base URLs.
- Test suite with
pytest+pytest-asyncio.
- Python
>= 3.13 uv(recommended) or a standard Python virtual environment setup
uv pip install --extra-index-url https://test.pypi.org/simple/ --index-strategy unsafe-best-match py-ttlock-clientOr for development:
git clone https://github.com/er1ckr1ck0/py-ttlock-client.git
cd py-ttlock-client
uv syncCreate a .env file in the project root:
TTLOCK_CLIENT_ID=your_client_id
TTLOCK_CLIENT_SECRET=your_client_secret
TTLOCK_USERNAME=your_username
TTLOCK_PASSWORD=your_passwordConfiguration is loaded automatically via pydantic-settings.
import asyncio
from py_ttlock_client import Lock
async def main() -> None:
client = Lock(
client_id="your_client_id",
client_secret="your_client_secret",
username="your_username",
password="your_password",
)
lock_list = await client.Lock.get_list(page=1, page_size=20)
print(lock_list.total)
if lock_list.list_:
detail = await client.Lock.get_detail(lock_id=lock_list.list_[0].lockId)
print(detail.lockAlias)
if __name__ == "__main__":
asyncio.run(main())Use py_ttlock_client.Lock as the main entry point:
client.Lock-> lock management interfaceclient.Passcode-> passcode management interfaceclient.QR-> QR code management interface
get_list(page=1, page_size=20, lock_alias=None, device_type=None, group_id=None)get_detail(lock_id)lock(lock_id)unlock(lock_id)rename(lock_id, alias)delete(lock_id)transfer(lock_id, receiver_username)query_open_state(lock_id)set_auto_lock_time(lock_id, seconds, via_gateway=True)update_battery(lock_id, electric_quantity)freeze(lock_id)unfreeze(lock_id)
create(lock_id, passcode, name=None, start_date, end_date, add_type=2)get(lock_id, keyboard_pwd_version, keyboard_pwd_type, name=None, start_date=None, end_date=None)get_list(lock_id, page=1, page_size=20)delete(lock_id, keyboard_pwd_id, delete_type=2)change(lock_id, keyboard_pwd_id, name=None, new_passcode=None, start_date=None, end_date=None, change_type=2)update(...)(alias forchange)
create(lock_id, qr_type, name=None, start_date=None, end_date=None, cyclic_config=None)get_list(lock_id, page=1, page_size=20, name=None)get(code_id)delete(lock_id, code_id)update(code_id, name=None, start_date=None, end_date=None, cyclic_config=None)clear(lock_id)
The default provider is TTLock.
You can also use provider wrappers:
from py_ttlock_client import TTLockClient, ScienerClientOr pass explicit provider enum to LockClient / Lock using LockProvider.
Available enums:
from py_ttlock_client import PasscodeType, LockState, DeviceTypePasscodeType— ONE_TIME, PERMANENT, PERIOD, ERASELockState— LOCKED, UNLOCKED, UNKNOWNDeviceType— LOCK, LIFT_CONTROLLER
from py_ttlock_client import LockAPIError
try:
...
except LockAPIError as exc:
print(exc.error_code, str(exc))uv run pytestpy_ttlock_client/
├── __init__.py
├── client.py
├── lock.py
├── settings.py
├── providers.py
├── enums.py
├── modules/
│ ├── constants.py
│ ├── exceptions.py
│ └── interface.py
├── interfaces/
│ ├── base_interface.py
│ ├── lock.py
│ ├── passcode.py
│ └── qr_code.py
└── schemas/
├── base.py
├── lock.py
├── passcode.py
└── qr.py