Skip to content

Commit a1163c9

Browse files
Merge pull request #305 from plivo/fix/compliance-response-bugs
Fix/compliance response bugs
2 parents 532bc6e + fa7bdb4 commit a1163c9

5 files changed

Lines changed: 136 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Change Log
2+
## [4.60.1](https://github.com/plivo/plivo-python/tree/v4.60.1) (2026-04-17)
3+
**Bug Fix - PhoneNumber Compliance API**
4+
- Fixed Requirements.get() sending None values as query params when not provided
5+
- Fixed create()/update() sending JSON instead of multipart when no document files provided
6+
27
## [4.60.0](https://github.com/plivo/plivo-python/tree/v4.60.0) (2026-04-08)
38
**Feature - PhoneNumber Compliance API support**
49
- Added `phone_number_compliance_requirements` resource for discovering compliance requirements by country, number type, and user type

plivo/resources/phone_number_compliance.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ class PhoneNumberComplianceRequirements(PlivoResourceInterface):
1313

1414
def get(self, country_iso=None, number_type=None, user_type=None):
1515
# GET /PhoneNumber/Compliance/Requirements
16+
params = {}
17+
if country_iso:
18+
params['country_iso'] = country_iso
19+
if number_type:
20+
params['number_type'] = number_type
21+
if user_type:
22+
params['user_type'] = user_type
1623
return self.client.request(
1724
'GET',
1825
('PhoneNumber', 'Compliance', 'Requirements'),
19-
dict(country_iso=country_iso, number_type=number_type, user_type=user_type)
26+
params
2027
)
2128

2229

@@ -114,6 +121,4 @@ def _build_compliance_multipart(data, documents):
114121
for idx, doc_path in enumerate(documents):
115122
field_name = 'documents[{}].file'.format(idx)
116123
files[field_name] = (os.path.basename(doc_path), open(doc_path, 'rb'))
117-
if not files:
118-
files = None
119124
return payload, files

plivo/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = '4.60.0'
2+
__version__ = '4.60.1'

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name='plivo',
13-
version='4.60.0',
13+
version='4.60.1',
1414
description='A Python SDK to make voice calls & send SMS using Plivo and to generate Plivo XML',
1515
long_description=long_description,
1616
url='https://github.com/plivo/plivo-python',

tests/resources/test_phone_number_compliance.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,45 @@ def test_get_requirements_empty_document_types(self):
6868
self.assertEqual(self.client.current_request.method, 'GET')
6969
self.assertEqual(len(response.document_types), 0)
7070

71+
def test_get_requirements_partial_args_no_none_in_url(self):
72+
"""Calling get() with only country_iso should not send
73+
number_type=None or user_type=None in the query string."""
74+
expected_response = {
75+
'requirement_id': 'req_partial',
76+
'document_types': []
77+
}
78+
self.client.set_expected_response(
79+
status_code=200, data_to_return=expected_response)
80+
81+
self.client.phone_number_compliance_requirements.get(
82+
country_iso='IN')
83+
84+
url = self.client.current_request.url
85+
self.assertIn('country_iso=IN', url)
86+
self.assertNotIn('None', url)
87+
self.assertNotIn('number_type', url)
88+
self.assertNotIn('user_type', url)
89+
self.assertEqual(self.client.current_request.method, 'GET')
90+
91+
def test_get_requirements_no_args_no_none_in_url(self):
92+
"""Calling get() with no arguments should produce a clean URL
93+
with no query parameters containing None."""
94+
expected_response = {
95+
'requirement_id': 'req_noargs',
96+
'document_types': []
97+
}
98+
self.client.set_expected_response(
99+
status_code=200, data_to_return=expected_response)
100+
101+
self.client.phone_number_compliance_requirements.get()
102+
103+
url = self.client.current_request.url
104+
self.assertNotIn('None', url)
105+
self.assertNotIn('number_type', url)
106+
self.assertNotIn('user_type', url)
107+
self.assertNotIn('country_iso', url)
108+
self.assertEqual(self.client.current_request.method, 'GET')
109+
71110

72111
class PhoneNumberComplianceApplicationsTest(PlivoResourceTestCase):
73112

@@ -115,6 +154,88 @@ def test_create_multipart_data_structure(self):
115154
self.assertEqual(self.client.current_request.method, 'POST')
116155
self.assertEqual(response.compliance_id, 'comp_def456')
117156

157+
def test_create_without_documents(self):
158+
"""Create without documents parameter should succeed and use
159+
multipart/form-data (not application/json)."""
160+
expected_response = {
161+
'compliance_id': 'comp_nodoc',
162+
'message': 'created'
163+
}
164+
self.client.set_expected_response(
165+
status_code=201, data_to_return=expected_response)
166+
167+
response = self.client.phone_number_compliance.create(
168+
data={
169+
'country_iso': 'IN',
170+
'number_type': 'local',
171+
'alias': 'India Local',
172+
'end_user': {'name': 'Test User'},
173+
})
174+
175+
self.assertEqual(self.client.current_request.method, 'POST')
176+
self.assertEqual(response.compliance_id, 'comp_nodoc')
177+
# Verify the request uses multipart encoding, not JSON
178+
content_type = self.client.current_request.headers['Content-Type']
179+
self.assertTrue(
180+
any([
181+
'multipart' in content_type,
182+
'www-form-urlencoded' in content_type
183+
]))
184+
185+
def test_create_with_documents_none(self):
186+
"""Create with documents=None should succeed and use
187+
multipart/form-data."""
188+
expected_response = {
189+
'compliance_id': 'comp_docnone',
190+
'message': 'created'
191+
}
192+
self.client.set_expected_response(
193+
status_code=201, data_to_return=expected_response)
194+
195+
response = self.client.phone_number_compliance.create(
196+
data={
197+
'country_iso': 'GB',
198+
'number_type': 'mobile',
199+
'alias': 'UK Mobile',
200+
'end_user': {'name': 'Jane Doe'},
201+
},
202+
documents=None)
203+
204+
self.assertEqual(self.client.current_request.method, 'POST')
205+
self.assertEqual(response.compliance_id, 'comp_docnone')
206+
content_type = self.client.current_request.headers['Content-Type']
207+
self.assertTrue(
208+
any([
209+
'multipart' in content_type,
210+
'www-form-urlencoded' in content_type
211+
]))
212+
213+
def test_update_without_documents(self):
214+
"""Update without documents should succeed and use
215+
multipart/form-data."""
216+
expected_response = {
217+
'message': 'updated',
218+
'compliance': {
219+
'compliance_id': 'comp_upd_nodoc',
220+
'status': 'pending'
221+
}
222+
}
223+
self.client.set_expected_response(
224+
status_code=200, data_to_return=expected_response)
225+
226+
response = self.client.phone_number_compliance.update(
227+
'comp_upd_nodoc',
228+
data={'alias': 'New Alias'})
229+
230+
self.assertEqual(self.client.current_request.method, 'PATCH')
231+
self.assertEqual(response.message, 'updated')
232+
content_type = self.client.current_request.headers['Content-Type']
233+
self.assertTrue(
234+
any([
235+
'multipart' in content_type,
236+
'www-form-urlencoded' in content_type
237+
]))
238+
118239
def test_list(self):
119240
expected_response = {
120241
'meta': {

0 commit comments

Comments
 (0)