Skip to content

Latest commit

 

History

History
226 lines (161 loc) · 6.09 KB

File metadata and controls

226 lines (161 loc) · 6.09 KB

Contributing to kwtSMS Python

Thank you for contributing. Please read this guide before opening a pull request.


Code of Conduct

Be respectful and constructive. Treat all contributors as professionals.


Getting Started

1. Clone and set up

git clone https://github.com/boxlinknet/kwtsms-python.git
cd kwtsms_python

2. Install dependencies

# With uv (recommended)
uv sync

# With pip
pip install -e ".[dev]"

Requires Python 3.8+.

3. Configure credentials for integration tests

cp examples/.env.example .env
# Edit .env and set KWTSMS_USERNAME and KWTSMS_PASSWORD

Integration tests are skipped automatically if no credentials are found.


Running Tests

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.

Test structure

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)

Reporting Bugs

Open an issue at https://github.com/boxlinknet/kwtsms-python/issues with:

  1. Python version and OS
  2. Package version (pip show kwtsms)
  3. Minimal code that reproduces the issue
  4. Actual vs expected behavior
  5. JSONL log output if available (remove your password first)

For suspected API bugs, include the relevant kwtSMS error code (ERR001–ERR033).


Feature Suggestions

Open an issue before writing code for new features. Describe:

  1. The use case (what problem does it solve?)
  2. What the API would look like (function signature, return value)
  3. 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.


Development Setup

Project structure

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

Coding standards

  • 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 action field (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 ***)

Style

  • Follow existing naming conventions: snake_case for 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)

Writing Tests

All new features require tests. Follow these patterns:

Unit test (no network)

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.

Integration test

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 True

Integration tests must use test_mode=True.


Submitting a Pull Request

  1. Fork the repository and create a branch: git checkout -b feat/my-feature
  2. Write failing tests first (TDD)
  3. Implement the feature
  4. Run the full test suite: uv run pytest
  5. Commit with a clear message: git commit -m "feat: add X"
  6. Open the pull request with a description of what and why

Commit message format

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)

Versioning

This project uses Semantic Versioning.

  • PATCH (0.7.x): bug fixes, documentation, internal refactoring
  • MINOR (0.x.0): new features, backward-compatible API additions
  • MAJOR (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.