Skip to content

feat!: support DIDWW API 2026-10-22 - #47

Merged
Skumring merged 2 commits into
prereleasefrom
feat/next-api-version
Oct 26, 2026
Merged

Skumring merged 2 commits into
prereleasefrom
feat/next-api-version

Conversation

@Skumring

Copy link
Copy Markdown
Contributor

Support for DIDWW API version 2026-10-22. This release of the SDK becomes v4.0.0; applications on API 2026-04-16 stay on 3.x (release-3).

This PR targets prerelease, which collects the work for the new API version until it is released. prerelease is merged into main and tagged on the release day of the API.

What changed

  • Authentication. Every request carries Authorization: Bearer <token>. The SDK mints the token from the API key with POST /token on auth.didww.com (sandbox auth-sandbox.didww.com): one token per audience, minted on first use, cached in a TokenStore (one MemoryTokenStore per process by default; subclass TokenStore to share tokens between processes). Concurrent callers share one mint, and locks are reset after fork(). A 401 replaces the token once and retries the request once; a 403 is returned as is; a 429 holds further mints until Retry-After; 5xx answers, connection failures and timeouts are retried with jittered exponential backoff; a TLS failure is raised at once. Api-Key is sent only to the token service, and X-DIDWW-API-Version is no longer sent. DidwwClient gains api_version, scope, token_store, logger (logging, silent by default), auth_url, the three prices URLs and connect_timeout / read_timeout.
  • Prices. voice_in_prices(), voice_out_prices(), sms_out_a2p_prices(), sms_out_p2p_prices(), sms_out_otp_prices() and sms_in_prices(), on the api-vin, api-vout and api-sms hosts: lookups with filters checked before the request and typed rows (Decimal money), and the gzipped price files written unchanged, dated with Last-Modified and fetched with If-Modified-Since; PriceFileReader reads a downloaded file.
  • SMS. SmsTrunk (configurations sms_to_email, http_in, http_out, smpp_esme, smpp_smsc, plus GenericSmsTrunkConfiguration), SmsTrunkGroup, SmsTrunkRegenerateCredential, the sms_trunk / sms_trunk_group relationships of Did, sms_in / sms_out exports with their filters, and SenderIdVerification (list, read, and update of the name and the SMS trunk).
  • Other resources. number_type on DID groups, requirements and emergency calling services, DidGroup.is_demo, Identity.company_website. DidGroupType and Pop resources are removed; VoiceInTrunk.pop is an enum attribute. Relationships can be set to None, read-only relationships are never sent, and resource meta is kept after create and update.
  • Errors. DidwwAuthError / AuthErrorKind and DidwwPricesError / PricesErrorKind, both derived from DidwwError; transport failures raise DidwwClientError. The SDK's own exceptions and reprs carry neither the API key nor a token.
  • Numbers. JSON floats are read as Decimal, and Decimal values are sent exactly, so values read and sent back keep their precision.
  • Hardening. Export downloads stream to a path or a file object, accept only 200, follow no redirects and only reach the origin of the base URL or of the environment; a path is replaced only once the file is complete; upload_encrypted_file goes through the SDK session with the token; error bodies are bounded in size and time; body sizes are checked against Content-Length on urllib3 1.26 too; Generic SMS configurations and SmsTrunk configurations mask their values in repr(); the wait after a price file 503 and the token 429 window add a small random delay.
  • Docs. README sections on authentication, logging, SDK design decisions, prices, SMS trunks, exports, error handling and migrating from 3.x; FILTERS.md; new examples.
  • CI. The examples are compiled too.

Compatibility

Breaking:

  • API versions earlier than 2026-10-22, and API keys, scopes, timeouts or service URLs that cannot be used, are rejected by the constructor; DidwwClient.API_VERSION is replaced by DEFAULT_API_VERSION, and client.api_key is read-only;
  • HTTP defaults: connect 10 s, read 60 s, no redirects (a 3xx is an error), no cookies; a passed session's auth, headers and cookies are not used;
  • JSON floats are Decimal; transport failures raise DidwwClientError; an invalid key raises DidwwAuthError from the token service;
  • export downloads: other statuses raise DidwwApiError, a path is replaced atomically;
  • Pop, DidGroupType, client.pops() and client.did_group_types() are removed.

urllib3>=1.26 is declared (requests already required it). No other new dependencies.

Checks

pytest passes locally on Python 3.9, 3.10 and 3.13, and with urllib3 1.26 + vcrpy 7 as CI resolves them for 3.9 (1038 tests).

Authentication
- Every request carries a short-lived bearer token minted from the API key
  with POST /token on the token service of the environment. The API key is
  sent only to the token service; X-DIDWW-API-Version is no longer sent, the
  version is requested in the token.
- One token per audience (api, api-vin, api-vout, api-sms), minted on first
  use and cached in a TokenStore (one MemoryTokenStore per process by
  default); concurrent callers share one mint, and locks are reset after
  fork.
- A 401 re-mints once and retries the request once; a 403 is returned as is.
- Token service errors raise DidwwAuthError with an AuthErrorKind; 429
  opens a Retry-After window, 5xx and timeouts back off with jitter, TLS
  failures are not retried.
- DidwwClient gains api_version, scope, token_store, logger (logging,
  silent by default), auth_url, the three prices URLs and connect/read
  timeouts; it can be used as a context manager.

Prices
- voice_in_prices(), voice_out_prices(), sms_out_a2p_prices(),
  sms_out_p2p_prices(), sms_out_otp_prices() and sms_in_prices() with
  lookup(), head(), download(), download_to_file() and fetch(), typed rows
  with Decimal money, DidwwPricesError with a PricesErrorKind, and
  PriceFileReader to read a downloaded file. Price files are written
  gzipped as served.

Resources
- New: SmsTrunk (five configuration types, Generic for unknown ones),
  SmsTrunkGroup, SmsTrunkRegenerateCredential, SenderIdVerification.
- Did: sms_trunk and sms_trunk_group relationships.
- DidGroup, AddressRequirement, EmergencyRequirement, EmergencyCallingService:
  number_type replaces the did_group_type relationship; DidGroup.is_demo.
- VoiceInTrunk pop is an enum attribute; Identity company_website; export
  types sms_in and sms_out with their filters.
- A relationship can be set to None; read-only relationships are never
  sent; resource meta is kept after create and update.
- Removed: Pop and DidGroupType resources.

Behaviour changes
- The constructor validates the API key, API version (2026-10-22 or later),
  scope, timeouts and service URLs and raises DidwwClientError on invalid
  values; DidwwClient.API_VERSION is replaced by DEFAULT_API_VERSION and
  client.api_key is read-only.
- HTTP defaults: connect 10 s, read 60 s, no redirects, no cookies; a
  passed session's auth, headers and cookies are not used.
- JSON floats are read as Decimal and Decimal values are sent exactly.
- Transport failures raise DidwwClientError.
- Export downloads stream to the destination (a path or a file object),
  accept only 200, follow no redirects and only reach the origin of the base
  URL or of the environment; a path is replaced only once the file is
  complete. upload_encrypted_file() goes through the same session with the
  token.
- The SDK's exceptions carry neither the API key nor a token.

urllib3 is declared as a dependency (already required by requests). CI
also compiles the examples.
@Skumring Skumring self-assigned this Oct 26, 2026
@Skumring
Skumring requested a balanced review from Copilot October 26, 2026 12:49
@Skumring

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f78287e8-920f-4cb2-844a-e8ed2a69e244

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review skipped: 175 files exceed the limit of 100.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Included-resource metadata is lost, and JSON range validation can accept NaN when callers disable Decimal traps.

2 open issues
What changed in this PR

This PR prepares SDK v4.0.0 for DIDWW API 2026-10-22, introducing bearer authentication, prices services, and SMS resources while retiring older resource representations.

Changes:

  • Adds SMS routing, trunk management, sender verification, and price-file support.
  • Updates resource fields, exact JSON number handling, authentication configuration, and error types.
  • Expands regression tests, fixtures, and examples; adds example compilation to CI.
File Description
tests/​test_serialization.py Tests relationship clearing and read-only assignment.
tests/​test_json_codec.py Tests exact numbers and JSON validation.
tests/​test_enums.py Tests new enums and round trips.
tests/​test_encrypted_file_upload.py Tests authenticated uploads and failure handling.
tests/​support/​__init__.py Adds test-support package marker.
tests/​resources/​test_voice_in_trunk.py Tests POP as an enum attribute.
tests/​resources/​test_sms_trunk_regenerate_credential.py Tests credential regeneration.
tests/​resources/​test_pop.py Removes obsolete POP resource tests.
tests/​resources/​test_identity.py Tests company website updates.
tests/​resources/​test_export.py Tests SMS exports and filter serialization.
tests/​resources/​test_emergency_requirement.py Checks number type.
tests/​resources/​test_emergency_calling_service.py Checks renewal date and number type.
tests/​resources/​test_did.py Tests SMS routing relationships.
tests/​resources/​test_did_group.py Tests number type and demo flag.
tests/​resources/​test_did_group_type.py Removes obsolete resource tests.
tests/​resources/​test_address_requirement.py Replaces group-type relationship assertions.
tests/​fixtures/​voice_in_trunks/​update_sip.yaml Moves POP into attributes.
tests/​fixtures/​voice_in_trunks/​update_pstn.yaml Moves POP into attributes.
tests/​fixtures/​voice_in_trunks/​update_clear_pop.yaml Adds POP-clearing response.
tests/​fixtures/​voice_in_trunks/​create.yaml Updates POP representation.
tests/​fixtures/​voice_in_trunks/​create_with_pop.yaml Adds explicit POP creation.
tests/​fixtures/​voice_in_trunks/​create_sip_with_rerouting.yaml Updates POP representation.
tests/​fixtures/​token/​server_error.json Adds token-service failure response.
tests/​fixtures/​token/​rate_limit_exceeded.json Adds token rate-limit response.
tests/​fixtures/​token/​invalid_scope.json Adds invalid-scope response.
tests/​fixtures/​token/​invalid_client.json Adds authentication rejection response.
tests/​fixtures/​token/​create.json Adds successful token response.
tests/​fixtures/​token/​create_request.json Adds token request payload.
tests/​fixtures/​token/​create_request_read_scope.json Adds read-scoped token request.
tests/​fixtures/​sms_trunks/​update_unknown_configuration.yaml Covers unknown configuration round trips.
tests/​fixtures/​sms_trunks/​update_name_with_flag_loaded.yaml Covers isolated name updates.
tests/​fixtures/​sms_trunks/​update_loaded_no_changes.yaml Covers unchanged loaded trunks.
tests/​fixtures/​sms_trunks/​update_http_out_from_loaded.yaml Covers writable configuration updates.
tests/​fixtures/​sms_trunks/​update_http_in.yaml Adds HTTP-in update fixture.
tests/​fixtures/​sms_trunks/​update_generic_nulls.yaml Covers explicit configuration nulls.
tests/​fixtures/​sms_trunks/​update_clear_relationships.yaml Covers clearing trunk relationships.
tests/​fixtures/​sms_trunks/​update_allow_any_did_as_source_links_only.yaml Covers source-flag updates.
tests/​fixtures/​sms_trunks/​show.yaml Adds HTTP-out trunk response.
tests/​fixtures/​sms_trunks/​show_not_found.yaml Adds missing-trunk response.
tests/​fixtures/​sms_trunks/​list_with_sms_trunk_group.yaml Adds included group response.
tests/​fixtures/​sms_trunks/​delete.yaml Adds successful trunk deletion.
tests/​fixtures/​sms_trunks/​delete_error.yaml Adds in-use deletion rejection.
tests/​fixtures/​sms_trunks/​create_with_relationships.yaml Adds creation with relationships.
tests/​fixtures/​sms_trunks/​create_sms_to_email.yaml Adds email trunk creation.
tests/​fixtures/​sms_trunks/​create_smpp_smsc.yaml Adds SMPP SMSC creation.
tests/​fixtures/​sms_trunks/​create_smpp_esme.yaml Adds SMPP ESME creation.
tests/​fixtures/​sms_trunks/​create_http_out.yaml Adds HTTP-out creation.
tests/​fixtures/​sms_trunks/​create_http_in.yaml Adds HTTP-in creation.
tests/​fixtures/​sms_trunks/​create_error.yaml Adds configuration validation failure.
tests/​fixtures/​sms_trunk_regenerate_credentials/​create.yaml Adds credential regeneration success.
tests/​fixtures/​sms_trunk_regenerate_credentials/​create_error.yaml Adds credential regeneration rejection.
tests/​fixtures/​sms_trunk_groups/​update_sms_trunks.yaml Covers membership replacement.
tests/​fixtures/​sms_trunk_groups/​update_external_reference_id.yaml Covers external reference updates.
tests/​fixtures/​sms_trunk_groups/​update_clear_sms_trunks.yaml Covers clearing membership.
tests/​fixtures/​sms_trunk_groups/​show_not_found.yaml Adds missing-group response.
tests/​fixtures/​sms_trunk_groups/​list.yaml Adds group listing and counts.
tests/​fixtures/​sms_trunk_groups/​delete.yaml Adds group deletion success.
tests/​fixtures/​sms_trunk_groups/​delete_error.yaml Adds in-use group rejection.
tests/​fixtures/​sms_trunk_groups/​create.yaml Adds group creation with membership.
tests/​fixtures/​sender_id_verifications/​update_name.yaml Covers verification renaming.
tests/​fixtures/​sender_id_verifications/​update_closed_error.yaml Covers closed-verification rejection.
tests/​fixtures/​sender_id_verifications/​unassign_sms_trunk.yaml Covers trunk unassignment.
tests/​fixtures/​sender_id_verifications/​show_with_sms_trunk.yaml Adds included SMS trunk response.
tests/​fixtures/​sender_id_verifications/​assign_sms_trunk.yaml Covers trunk assignment.
tests/​fixtures/​prices/​voice_out/​lookup/​200.json Adds Voice OUT price rows.
tests/​fixtures/​prices/​voice_in/​lookup/​200.json Adds Voice IN price rows.
tests/​fixtures/​prices/​sms_out/​lookup/​200.json Adds SMS OUT price rows.
tests/​fixtures/​prices/​sms_in/​lookup/​200.json Adds SMS IN price rows.
tests/​fixtures/​prices/​errors/​serving_failure.json Adds unavailable price-file response.
tests/​fixtures/​prices/​errors/​price_plan_not_assigned.json Adds missing-plan response.
tests/​fixtures/​prices/​errors/​price_not_found.json Adds missing-price response.
tests/​fixtures/​prices/​errors/​filter_not_allowed.json Adds invalid-filter response.
tests/​fixtures/​pops/​list.yaml Removes obsolete POP fixture.
tests/​fixtures/​identities/​update_company_website.yaml Adds website update response.
tests/​fixtures/​exports/​create_sms_out.yaml Adds SMS OUT export creation.
tests/​fixtures/​exports/​create_sms_in.yaml Adds SMS IN export creation.
tests/​fixtures/​emergency_requirements/​show.yaml Adds number type.
tests/​fixtures/​emergency_requirements/​list.yaml Replaces group-type relationship.
tests/​fixtures/​emergency_calling_services/​show.yaml Adds number type.
tests/​fixtures/​emergency_calling_services/​show_with_includes.yaml Adds number types to included response.
tests/​fixtures/​emergency_calling_services/​show_with_address.yaml Adds service number type.
tests/​fixtures/​emergency_calling_services/​list.yaml Replaces group-type relationship.
tests/​fixtures/​dids/​update_unassign_sms.yaml Covers clearing both SMS routes.
tests/​fixtures/​dids/​update_unassign_sms_trunk.yaml Covers clearing trunk routing.
tests/​fixtures/​dids/​update_unassign_sms_trunk_group.yaml Covers clearing group routing.
tests/​fixtures/​dids/​update_assign_sms_trunk.yaml Covers exclusive trunk assignment.
tests/​fixtures/​dids/​update_assign_sms_trunk_group.yaml Covers exclusive group assignment.
tests/​fixtures/​dids/​show_with_sms_trunk_group.yaml Adds included SMS group response.
tests/​fixtures/​did_groups/​list.yaml Adds number type and demo flag.
tests/​fixtures/​did_group_types/​show.yaml Removes obsolete resource fixture.
tests/​fixtures/​did_group_types/​list.yaml Removes obsolete listing fixture.
tests/​conftest.py Adds token seeding and local-server fixtures.
src/​didww/​resources/​voice_in_trunk.py Replaces POP relationship with enum attribute.
src/​didww/​resources/​sms_trunk.py Adds SMS trunk model and repository.
src/​didww/​resources/​sms_trunk_regenerate_credential.py Adds credential regeneration resource.
src/​didww/​resources/​sms_trunk_group.py Adds SMS group model and repository.
src/​didww/​resources/​sender_id_verification.py Adds readable, updatable verification resource.
src/​didww/​resources/​pop.py Removes obsolete POP resource.
src/​didww/​resources/​identity.py Adds writable company website.
src/​didww/​resources/​export.py Documents CDR and SMS export filters.
src/​didww/​resources/​emergency_requirement.py Replaces group type with number type.
src/​didww/​resources/​emergency_calling_service.py Adds number type attribute.
src/​didww/​resources/​did.py Adds exclusive SMS routing relationships.
src/​didww/​resources/​did_group.py Adds number type and demo flag.
src/​didww/​resources/​did_group_type.py Removes obsolete group-type resource.
src/​didww/​resources/​address_requirement.py Replaces group type with number type.
src/​didww/​prices/​_reader.py Adds streaming gzip CSV reader.
src/​didww/​prices/​_headers.py Parses price-service HTTP headers.
src/​didww/​prices/​__init__.py Exposes public prices interfaces.
src/​didww/​exceptions.py Adds authentication and prices errors.
src/​didww/​enums.py Adds SMS, number-type, and POP enums.
src/​didww/​configuration.py Adds service endpoints, scopes, and validation.
src/​didww/​_json.py Adds exact Decimal JSON encoding.
src/​didww/​__init__.py Adds default null logging handler.
pyproject.toml Sets v4.0.0 and declares urllib3 dependency.
examples/​voice_in_trunks.py Uses POP attribute and enum values.
examples/​upload_file.py Uses single-file encrypted upload.
examples/​sms_trunk_groups.py Demonstrates group lifecycle and cleanup.
examples/​sender_id_verifications.py Demonstrates listing and reversible renaming.
examples/​prices_lookup.py Demonstrates typed price lookups.
examples/​prices_download.py Demonstrates conditional downloads and CSV reading.
examples/​exports.py Uses yesterday’s CDR date range.
examples/​exports_sms.py Demonstrates SMS exports and downloads.
examples/​emergency_scenario.py Migrates emergency lookup to number type.
examples/​emergency_requirements.py Displays requirement number types.
examples/​emergency_calling_services.py Displays service number types.
examples/​did_sms_routing.py Demonstrates routing and restoration.
examples/​did_groups.py Displays number type and demo flag.
examples/​client_factory.py Accepts additional client options.
.github/​workflows/​ci.yml Compiles examples in CI.

Review effort: Balanced

Comment thread src/didww/_json.py Resolved
Comment thread src/didww/resources/sms_trunk.py Resolved
- A TLS handshake that the peer closes is retried on Python 3.9 with
  OpenSSL 3 too, where it surfaces as SSLZeroReturnError.
- JSON numbers out of Decimal range raise whatever the caller's decimal
  context traps.
- Included resources keep their resource meta, e.g. the host of an
  included http_out SMS trunk and sms_trunks_count of an included group.
- The prices download example writes to the user cache directory only.
- Long functions split into helpers, a repeated literal as a constant, and
  tests for paths the refactor touches.
@Skumring
Skumring requested a balanced review from Copilot October 26, 2026 13:23
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The major-version authentication, transport and pricing migration needs final human validation beyond the supplied diffs.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

@Skumring
Skumring merged commit 368a6b8 into prerelease Oct 26, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants