Thank you for contributing. Please read this guide before opening a pull request.
Be respectful and constructive. Treat all contributors as professionals.
git clone https://github.com/boxlinknet/kwtsms-python.git
cd kwtsms_python# With uv (recommended)
uv sync
# With pip
pip install -e ".[dev]"Requires Python 3.8+.
cp examples/.env.example .env
# Edit .env and set KWTSMS_USERNAME and KWTSMS_PASSWORDIntegration tests are skipped automatically if no credentials are found.
| Command | What it runs |
|---|---|
uv run pytest |
All tests except integration |
uv run pytest tests/test_integration.py -v |
Live API tests (requires .env) |
uv run pytest tests/ -v |
All tests |
uv run pytest tests/test_phone.py -v |
Phone normalization only |
All tests use test_mode=True. No real SMS messages are sent and no credits
are consumed.
tests/
test_phone.py # normalize_phone(), validate_phone_input()
test_message.py # clean_message(), emoji ranges, edge cases
test_api_errors.py # _enrich_error(), verify(), send() error paths
test_bulk.py # _send_bulk() routing, batching, ERR013 retry
test_env.py # _load_env_file(), from_env(), .env edge cases
test_status.py # KwtSMS.status() delivery report lookup
test_webhook.py # parse_webhook() delivery receipt parser
test_async.py # AsyncKwtSMS async client
test_retry.py # send_with_retry() ERR028 auto-retry
test_integration.py # live API tests (skipped without credentials)
Open an issue at https://github.com/boxlinknet/kwtsms-python/issues with:
- Python version and OS
- Package version (
pip show kwtsms) - Minimal code that reproduces the issue
- Actual vs expected behavior
- JSONL log output if available (remove your password first)
For suspected API bugs, include the relevant kwtSMS error code (ERR001–ERR033).
Open an issue before writing code for new features. Describe:
- The use case (what problem does it solve?)
- What the API would look like (function signature, return value)
- Whether it requires a new API endpoint
Features that add mandatory external dependencies will not be accepted. The core package is zero-dependency. Optional extras (e.g., kwtsms[async] for aiohttp) are acceptable if declared under [project.optional-dependencies] in pyproject.toml.
src/kwtsms/
__init__.py # Public API: KwtSMS, AsyncKwtSMS, clean_message, normalize_phone,
# validate_phone_input, parse_webhook
_core.py # All sync client logic
_async.py # AsyncKwtSMS (requires kwtsms[async] / aiohttp)
tests/ # pytest test suite
examples/ # Runnable example scripts
docs/ # Markdown documentation for each example
- Target Python 3.8+. Do not use syntax or stdlib features introduced after 3.8
- No external dependencies. Standard library only
- All public functions must have type hints and docstrings
send()never raises. Errors are returned as dicts- All error responses must include an
actionfield (use_enrich_error()) - Phone numbers are always normalized before any API call
- Messages are always cleaned before any API call
- Log entries always use UTC ISO-8601 timestamps
- Never log passwords in plaintext (the log sanitizes them to
***)
- Follow existing naming conventions:
snake_casefor functions and variables - Private helpers are prefixed with
_(e.g.,_request,_enrich_error) - Module-level constants are
ALL_CAPS(e.g.,BASE_URL,_API_ERRORS)
All new features require tests. Follow these patterns:
from unittest.mock import patch
from kwtsms import KwtSMS
def _client(**kwargs) -> KwtSMS:
defaults = dict(username="python_username", password="python_password",
sender_id="TEST", log_file="")
defaults.update(kwargs)
return KwtSMS(**defaults)
def test_send_returns_error_on_network_failure():
with patch("kwtsms._core._request", side_effect=RuntimeError("Timeout")):
result = _client().send("96598765432", "Test")
assert result["result"] == "ERROR"
assert result["code"] == "NETWORK"Use patch("kwtsms._core._request", ...) to mock the API. Never make real
HTTP calls in unit tests.
import pytest
from kwtsms import KwtSMS
# Skip if no credentials
pytestmark = pytest.mark.skipif(
not os.environ.get("KWTSMS_USERNAME"),
reason="No credentials"
)
def test_verify_live():
sms = KwtSMS(
username=os.environ["KWTSMS_USERNAME"],
password=os.environ["KWTSMS_PASSWORD"],
test_mode=True,
log_file="",
)
ok, balance, error = sms.verify()
assert ok is TrueIntegration tests must use test_mode=True.
- Fork the repository and create a branch:
git checkout -b feat/my-feature - Write failing tests first (TDD)
- Implement the feature
- Run the full test suite:
uv run pytest - Commit with a clear message:
git commit -m "feat: add X" - Open the pull request with a description of what and why
feat: add X (new feature)
fix: correct Y (bug fix)
docs: update Z (documentation only)
test: add tests for (new or updated tests)
chore: update deps (maintenance)
This project uses Semantic Versioning.
PATCH(0.7.x): bug fixes, documentation, internal refactoringMINOR(0.x.0): new features, backward-compatible API additionsMAJOR(x.0.0): breaking API changes
Version is tracked in two places:
pyproject.toml [project] version = "..."
src/kwtsms/__init__.py __version__ = "..."
Both must be updated together.