Repository navigation
feat!: support DIDWW API 2026-10-22 - #47
Conversation
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.
|
@coderabbitai review |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
|
There was a problem hiding this comment.
🟡 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
- 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.
|




Support for DIDWW API version 2026-10-22. This release of the SDK becomes v4.0.0; applications on API
2026-04-16stay on 3.x (release-3).This PR targets
prerelease, which collects the work for the new API version until it is released.prereleaseis merged intomainand tagged on the release day of the API.What changed
Authorization: Bearer <token>. The SDK mints the token from the API key withPOST /tokenonauth.didww.com(sandboxauth-sandbox.didww.com): one token per audience, minted on first use, cached in aTokenStore(oneMemoryTokenStoreper process by default; subclassTokenStoreto share tokens between processes). Concurrent callers share one mint, and locks are reset afterfork(). A 401 replaces the token once and retries the request once; a 403 is returned as is; a 429 holds further mints untilRetry-After; 5xx answers, connection failures and timeouts are retried with jittered exponential backoff; a TLS failure is raised at once.Api-Keyis sent only to the token service, andX-DIDWW-API-Versionis no longer sent.DidwwClientgainsapi_version,scope,token_store,logger(logging, silent by default),auth_url, the three prices URLs andconnect_timeout/read_timeout.voice_in_prices(),voice_out_prices(),sms_out_a2p_prices(),sms_out_p2p_prices(),sms_out_otp_prices()andsms_in_prices(), on theapi-vin,api-voutandapi-smshosts: lookups with filters checked before the request and typed rows (Decimalmoney), and the gzipped price files written unchanged, dated withLast-Modifiedand fetched withIf-Modified-Since;PriceFileReaderreads a downloaded file.SmsTrunk(configurationssms_to_email,http_in,http_out,smpp_esme,smpp_smsc, plusGenericSmsTrunkConfiguration),SmsTrunkGroup,SmsTrunkRegenerateCredential, thesms_trunk/sms_trunk_grouprelationships ofDid,sms_in/sms_outexports with their filters, andSenderIdVerification(list, read, and update of the name and the SMS trunk).number_typeon DID groups, requirements and emergency calling services,DidGroup.is_demo,Identity.company_website.DidGroupTypeandPopresources are removed;VoiceInTrunk.popis an enum attribute. Relationships can be set toNone, read-only relationships are never sent, and resource meta is kept after create and update.DidwwAuthError/AuthErrorKindandDidwwPricesError/PricesErrorKind, both derived fromDidwwError; transport failures raiseDidwwClientError. The SDK's own exceptions and reprs carry neither the API key nor a token.Decimal, andDecimalvalues are sent exactly, so values read and sent back keep their precision.upload_encrypted_filegoes through the SDK session with the token; error bodies are bounded in size and time; body sizes are checked againstContent-Lengthon urllib3 1.26 too;GenericSMS configurations andSmsTrunkconfigurations mask their values inrepr(); the wait after a price file 503 and the token 429 window add a small random delay.Compatibility
Breaking:
2026-10-22, and API keys, scopes, timeouts or service URLs that cannot be used, are rejected by the constructor;DidwwClient.API_VERSIONis replaced byDEFAULT_API_VERSION, andclient.api_keyis read-only;auth, headers and cookies are not used;Decimal; transport failures raiseDidwwClientError; an invalid key raisesDidwwAuthErrorfrom the token service;DidwwApiError, a path is replaced atomically;Pop,DidGroupType,client.pops()andclient.did_group_types()are removed.urllib3>=1.26is declared (requests already required it). No other new dependencies.Checks
pytestpasses 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).