Repository: watson-developer-cloud/python-sdk

Issue:
Conversation - Error on .delete_example when example is "#"
When deleting an intent example with text "#" the following error raises:

`DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "DELETE /conversation/api/v1/workspaces/00e477c9-790b-47e7-ee11-48fcx1c22496c/intents/what_is_conversational_experience/examples/?version=2017-05-26 HTTP/1.1" 404 None`

Python 3.5.2 on Linux
watson-developer-cloud==0.26.1

Discussion:

Patch:
diff --git a/examples/conversation_tone_analyzer_integration/tone_detection.py b/examples/conversation_tone_analyzer_integration/tone_detection.py
index b46444f9..d3ac374f 100644
--- a/examples/conversation_tone_analyzer_integration/tone_detection.py
+++ b/examples/conversation_tone_analyzer_integration/tone_detection.py
@@ -31,21 +31,20 @@ EMOTION_TONE_LABEL = 'emotion_tone'
 WRITING_TONE_LABEL = 'writing_tone'
 SOCIAL_TONE_LABEL = 'social_tone'
 
-"""
-updateUserTone processes the Tone Analyzer payload to pull out the emotion,
-writing and social tones, and identify the meaningful tones (i.e.,
-those tones that meet the specified thresholds).
-The conversationPayload json object is updated to include these tones.
-@param conversationPayload json object returned by the Watson Conversation
-Service
-@param toneAnalyzerPayload json object returned by the Watson Tone Analyzer
-Service
-@returns conversationPayload where the user object has been updated with tone
-information from the toneAnalyzerPayload
-"""
-
 
 def updateUserTone(conversationPayload, toneAnalyzerPayload, maintainHistory):
+    """
+    updateUserTone processes the Tone Analyzer payload to pull out the emotion,
+    writing and social tones, and identify the meaningful tones (i.e.,
+    those tones that meet the specified thresholds).
+    The conversationPayload json object is updated to include these tones.
+    @param conversationPayload json object returned by the Watson Conversation
+    Service
+    @param toneAnalyzerPayload json object returned by the Watson Tone Analyzer
+    Service
+    @returns conversationPayload where the user object has been updated with tone
+    information from the toneAnalyzerPayload
+    """
     emotionTone = None
     writingTone = None
     socialTone = None
@@ -80,18 +79,15 @@ def updateUserTone(conversationPayload, toneAnalyzerPayload, maintainHistory):
     return conversationPayload
 
 
-'''
- initToneContext initializes a user object containing tone data (from the
- Watson Tone Analyzer)
- @returns user json object with the emotion, writing and social tones.  The
- current
- tone identifies the tone for a specific conversation turn, and the history
- provides the conversation for
- all tones up to the current tone for a conversation instance with a user.
- '''
-
-
 def initUser():
+    """
+    initUser initializes a user object containing tone data (from the
+    Watson Tone Analyzer)
+    @returns user json object with the emotion, writing and social tones.  The
+    current tone identifies the tone for a specific conversation turn, and the
+    history provides the conversation for all tones up to the current tone for a
+    conversation instance with a user.
+    """
     return {
         'user': {
             'tone': {
@@ -109,19 +105,18 @@ def initUser():
     }
 
 
-'''
- updateEmotionTone updates the user emotion tone with the primary emotion -
- the emotion tone that has
- a score greater than or equal to the EMOTION_SCORE_THRESHOLD; otherwise
- primary emotion will be 'neutral'
- @param user a json object representing user information (tone) to be used in
- conversing with the Conversation Service
- @param emotionTone a json object containing the emotion tones in the payload
- returned by the Tone Analyzer
- '''
 
 
 def updateEmotionTone(user, emotionTone, maintainHistory):
+    """
+    updateEmotionTone updates the user emotion tone with the primary emotion -
+    the emotion tone that has a score greater than or equal to the
+    EMOTION_SCORE_THRESHOLD; otherwise primary emotion will be 'neutral'
+    @param user a json object representing user information (tone) to be used in
+    conversing with the Conversation Service
+    @param emotionTone a json object containing the emotion tones in the payload
+    returned by the Tone Analyzer
+    """
     maxScore = 0.0
     primaryEmotion = None
     primaryEmotionScore = None
@@ -148,17 +143,15 @@ def updateEmotionTone(user, emotionTone, maintainHistory):
             })
 
 
-'''
- updateWritingTone updates the user with the writing tones interpreted based
- on the specified thresholds
- @param: user a json object representing user information (tone) to be used
- in conversing with the Conversation Service
- @param: writingTone a json object containing the writing tones in the
- payload returned by the Tone Analyzer
-'''
-
-
 def updateWritingTone(user, writingTone, maintainHistory):
+    """
+    updateWritingTone updates the user with the writing tones interpreted based
+    on the specified thresholds
+    @param: user a json object representing user information (tone) to be used
+    in conversing with the Conversation Service
+    @param: writingTone a json object containing the writing tones in the
+    payload returned by the Tone Analyzer
+    """
     currentWriting = []
     currentWritingObject = []
 
@@ -189,21 +182,18 @@ def updateWritingTone(user, writingTone, maintainHistory):
     if maintainHistory:
         if 'history' not in user['tone']['writing']:
             user['tone']['writing']['history'] = []
-    user['tone']['writing']['history'].append(currentWritingObject)  # TODO -
-    # is this the correct location??? AW
-
-
-"""
- updateSocialTone updates the user with the social tones interpreted based on
- the specified thresholds
- @param user a json object representing user information (tone) to be used in
- conversing with the Conversation Service
- @param socialTone a json object containing the social tones in the payload
- returned by the Tone Analyzer
-"""
+    user['tone']['writing']['history'].append(currentWritingObject)
 
 
 def updateSocialTone(user, socialTone, maintainHistory):
+    """
+    updateSocialTone updates the user with the social tones interpreted based on
+    the specified thresholds
+    @param user a json object representing user information (tone) to be used in
+    conversing with the Conversation Service
+    @param socialTone a json object containing the social tones in the payload
+    returned by the Tone Analyzer
+    """
     currentSocial = []
     currentSocialObject = []
 
diff --git a/examples/personality_insights_v3.py b/examples/personality_insights_v3.py
index b3efdbc0..02c806a7 100755
--- a/examples/personality_insights_v3.py
+++ b/examples/personality_insights_v3.py
@@ -1,13 +1,12 @@
+"""
+The example returns a JSON response whose content is the same as that in
+  ../resources/personality-v3-expect2.txt
+"""
 from __future__ import print_function
 import json
 from os.path import join, dirname
 from watson_developer_cloud import PersonalityInsightsV3
 
-"""
-The example returns a JSON response whose content is the same as that in
-   ../resources/personality-v3-expect2.txt
-"""
-
 personality_insights = PersonalityInsightsV3(
     version='2016-10-20',
     username='YOUR SERVICE USERNAME',
diff --git a/pylint.sh b/pylint.sh
index 60cf35e9..4e0862af 100644
--- a/pylint.sh
+++ b/pylint.sh
@@ -4,6 +4,5 @@
 PYTHON_VERSION=$(python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
 echo "Python version: $PYTHON_VERSION"
 if [ $PYTHON_VERSION = '2.7' ]; then
-  pylint watson_developer_cloud
-  pylint test
+  pylint watson_developer_cloud test examples
 fi
diff --git a/watson_developer_cloud/conversation_v1.py b/watson_developer_cloud/conversation_v1.py
index 71867d41..822556bb 100644
--- a/watson_developer_cloud/conversation_v1.py
+++ b/watson_developer_cloud/conversation_v1.py
@@ -134,12 +134,9 @@ class ConversationV1(WatsonService):
             'metadata': metadata,
             'learning_opt_out': learning_opt_out
         }
+        url = '/v1/workspaces'
         response = self.request(
-            method='POST',
-            url='/v1/workspaces',
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_workspace(self, workspace_id):
@@ -154,11 +151,8 @@ class ConversationV1(WatsonService):
         if workspace_id is None:
             raise ValueError('workspace_id must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}'.format(workspace_id),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}'.format(*self._encode_path_vars(workspace_id))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_workspace(self, workspace_id, export=None):
@@ -175,11 +169,9 @@ class ConversationV1(WatsonService):
         if workspace_id is None:
             raise ValueError('workspace_id must be provided')
         params = {'version': self.version, 'export': export}
+        url = '/v1/workspaces/{0}'.format(*self._encode_path_vars(workspace_id))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}'.format(workspace_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_workspaces(self,
@@ -206,8 +198,9 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces'
         response = self.request(
-            method='GET', url='/v1/workspaces', params=params, accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_workspace(self,
@@ -262,12 +255,9 @@ class ConversationV1(WatsonService):
             'metadata': metadata,
             'learning_opt_out': learning_opt_out
         }
+        url = '/v1/workspaces/{0}'.format(*self._encode_path_vars(workspace_id))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}'.format(workspace_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -316,12 +306,10 @@ class ConversationV1(WatsonService):
             'intents': intents,
             'output': output
         }
+        url = '/v1/workspaces/{0}/message'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/message'.format(workspace_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -357,12 +345,10 @@ class ConversationV1(WatsonService):
             'description': description,
             'examples': examples
         }
+        url = '/v1/workspaces/{0}/intents'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/intents'.format(workspace_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_intent(self, workspace_id, intent):
@@ -380,11 +366,9 @@ class ConversationV1(WatsonService):
         if intent is None:
             raise ValueError('intent must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/intents/{1}'.format(workspace_id, intent),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/intents/{1}'.format(*self._encode_path_vars(
+            workspace_id, intent))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_intent(self, workspace_id, intent, export=None):
@@ -404,11 +388,10 @@ class ConversationV1(WatsonService):
         if intent is None:
             raise ValueError('intent must be provided')
         params = {'version': self.version, 'export': export}
+        url = '/v1/workspaces/{0}/intents/{1}'.format(*self._encode_path_vars(
+            workspace_id, intent))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/intents/{1}'.format(workspace_id, intent),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_intents(self,
@@ -442,11 +425,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/intents'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/intents'.format(workspace_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_intent(self,
@@ -481,12 +463,10 @@ class ConversationV1(WatsonService):
             'description': new_description,
             'examples': new_examples
         }
+        url = '/v1/workspaces/{0}/intents/{1}'.format(*self._encode_path_vars(
+            workspace_id, intent))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/intents/{1}'.format(workspace_id, intent),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -513,13 +493,10 @@ class ConversationV1(WatsonService):
             raise ValueError('text must be provided')
         params = {'version': self.version}
         data = {'text': text}
+        url = '/v1/workspaces/{0}/intents/{1}/examples'.format(
+            *self._encode_path_vars(workspace_id, intent))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/intents/{1}/examples'.format(
-                workspace_id, intent),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_example(self, workspace_id, intent, text):
@@ -540,12 +517,9 @@ class ConversationV1(WatsonService):
         if text is None:
             raise ValueError('text must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/intents/{1}/examples/{2}'.format(
-                workspace_id, intent, text),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/intents/{1}/examples/{2}'.format(
+            *self._encode_path_vars(workspace_id, intent, text))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_example(self, workspace_id, intent, text):
@@ -567,12 +541,10 @@ class ConversationV1(WatsonService):
         if text is None:
             raise ValueError('text must be provided')
         params = {'version': self.version}
+        url = '/v1/workspaces/{0}/intents/{1}/examples/{2}'.format(
+            *self._encode_path_vars(workspace_id, intent, text))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/intents/{1}/examples/{2}'.format(
-                workspace_id, intent, text),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_examples(self,
@@ -607,12 +579,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/intents/{1}/examples'.format(
+            *self._encode_path_vars(workspace_id, intent))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/intents/{1}/examples'.format(
-                workspace_id, intent),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_example(self, workspace_id, intent, text, new_text=None):
@@ -636,13 +606,10 @@ class ConversationV1(WatsonService):
             raise ValueError('text must be provided')
         params = {'version': self.version}
         data = {'text': new_text}
+        url = '/v1/workspaces/{0}/intents/{1}/examples/{2}'.format(
+            *self._encode_path_vars(workspace_id, intent, text))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/intents/{1}/examples/{2}'.format(
-                workspace_id, intent, text),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -684,12 +651,10 @@ class ConversationV1(WatsonService):
             'values': values,
             'fuzzy_match': fuzzy_match
         }
+        url = '/v1/workspaces/{0}/entities'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/entities'.format(workspace_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_entity(self, workspace_id, entity):
@@ -707,11 +672,9 @@ class ConversationV1(WatsonService):
         if entity is None:
             raise ValueError('entity must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/entities/{1}'.format(workspace_id, entity),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/entities/{1}'.format(*self._encode_path_vars(
+            workspace_id, entity))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_entity(self, workspace_id, entity, export=None):
@@ -731,11 +694,10 @@ class ConversationV1(WatsonService):
         if entity is None:
             raise ValueError('entity must be provided')
         params = {'version': self.version, 'export': export}
+        url = '/v1/workspaces/{0}/entities/{1}'.format(*self._encode_path_vars(
+            workspace_id, entity))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/entities/{1}'.format(workspace_id, entity),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_entities(self,
@@ -769,11 +731,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/entities'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/entities'.format(workspace_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_entity(self,
@@ -813,12 +774,10 @@ class ConversationV1(WatsonService):
             'fuzzy_match': new_fuzzy_match,
             'values': new_values
         }
+        url = '/v1/workspaces/{0}/entities/{1}'.format(*self._encode_path_vars(
+            workspace_id, entity))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/entities/{1}'.format(workspace_id, entity),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -862,13 +821,10 @@ class ConversationV1(WatsonService):
             'patterns': patterns,
             'type': value_type
         }
+        url = '/v1/workspaces/{0}/entities/{1}/values'.format(
+            *self._encode_path_vars(workspace_id, entity))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/entities/{1}/values'.format(
-                workspace_id, entity),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_value(self, workspace_id, entity, value):
@@ -889,12 +845,9 @@ class ConversationV1(WatsonService):
         if value is None:
             raise ValueError('value must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}'.format(
-                workspace_id, entity, value),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}'.format(
+            *self._encode_path_vars(workspace_id, entity, value))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_value(self, workspace_id, entity, value, export=None):
@@ -917,12 +870,10 @@ class ConversationV1(WatsonService):
         if value is None:
             raise ValueError('value must be provided')
         params = {'version': self.version, 'export': export}
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}'.format(
+            *self._encode_path_vars(workspace_id, entity, value))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}'.format(
-                workspace_id, entity, value),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_values(self,
@@ -960,12 +911,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/entities/{1}/values'.format(
+            *self._encode_path_vars(workspace_id, entity))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/entities/{1}/values'.format(
-                workspace_id, entity),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_value(self,
@@ -1007,13 +956,10 @@ class ConversationV1(WatsonService):
             'synonyms': new_synonyms,
             'patterns': new_patterns
         }
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}'.format(
+            *self._encode_path_vars(workspace_id, entity, value))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}'.format(
-                workspace_id, entity, value),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -1043,13 +989,10 @@ class ConversationV1(WatsonService):
             raise ValueError('synonym must be provided')
         params = {'version': self.version}
         data = {'synonym': synonym}
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms'.format(
+            *self._encode_path_vars(workspace_id, entity, value))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms'.format(
-                workspace_id, entity, value),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_synonym(self, workspace_id, entity, value, synonym):
@@ -1073,12 +1016,9 @@ class ConversationV1(WatsonService):
         if synonym is None:
             raise ValueError('synonym must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms/{3}'.
-            format(workspace_id, entity, value, synonym),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms/{3}'.format(
+            *self._encode_path_vars(workspace_id, entity, value, synonym))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_synonym(self, workspace_id, entity, value, synonym):
@@ -1103,12 +1043,10 @@ class ConversationV1(WatsonService):
         if synonym is None:
             raise ValueError('synonym must be provided')
         params = {'version': self.version}
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms/{3}'.format(
+            *self._encode_path_vars(workspace_id, entity, value, synonym))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms/{3}'.
-            format(workspace_id, entity, value, synonym),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_synonyms(self,
@@ -1147,12 +1085,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms'.format(
+            *self._encode_path_vars(workspace_id, entity, value))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms'.format(
-                workspace_id, entity, value),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_synonym(self,
@@ -1184,13 +1120,10 @@ class ConversationV1(WatsonService):
             raise ValueError('synonym must be provided')
         params = {'version': self.version}
         data = {'synonym': new_synonym}
+        url = '/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms/{3}'.format(
+            *self._encode_path_vars(workspace_id, entity, value, synonym))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/entities/{1}/values/{2}/synonyms/{3}'.
-            format(workspace_id, entity, value, synonym),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -1261,12 +1194,10 @@ class ConversationV1(WatsonService):
             'event_name': event_name,
             'variable': variable
         }
+        url = '/v1/workspaces/{0}/dialog_nodes'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/dialog_nodes'.format(workspace_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_dialog_node(self, workspace_id, dialog_node):
@@ -1284,12 +1215,9 @@ class ConversationV1(WatsonService):
         if dialog_node is None:
             raise ValueError('dialog_node must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/dialog_nodes/{1}'.format(
-                workspace_id, dialog_node),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/dialog_nodes/{1}'.format(
+            *self._encode_path_vars(workspace_id, dialog_node))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_dialog_node(self, workspace_id, dialog_node):
@@ -1308,12 +1236,10 @@ class ConversationV1(WatsonService):
         if dialog_node is None:
             raise ValueError('dialog_node must be provided')
         params = {'version': self.version}
+        url = '/v1/workspaces/{0}/dialog_nodes/{1}'.format(
+            *self._encode_path_vars(workspace_id, dialog_node))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/dialog_nodes/{1}'.format(
-                workspace_id, dialog_node),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_dialog_nodes(self,
@@ -1344,11 +1270,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/dialog_nodes'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/dialog_nodes'.format(workspace_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_dialog_node(self,
@@ -1419,13 +1344,10 @@ class ConversationV1(WatsonService):
             'variable': new_variable,
             'actions': new_actions
         }
+        url = '/v1/workspaces/{0}/dialog_nodes/{1}'.format(
+            *self._encode_path_vars(workspace_id, dialog_node))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/dialog_nodes/{1}'.format(
-                workspace_id, dialog_node),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -1460,11 +1382,10 @@ class ConversationV1(WatsonService):
             'page_limit': page_limit,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/logs'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/logs'.format(workspace_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     #########################
@@ -1489,12 +1410,10 @@ class ConversationV1(WatsonService):
             raise ValueError('text must be provided')
         params = {'version': self.version}
         data = {'text': text}
+        url = '/v1/workspaces/{0}/counterexamples'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/counterexamples'.format(workspace_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_counterexample(self, workspace_id, text):
@@ -1513,12 +1432,9 @@ class ConversationV1(WatsonService):
         if text is None:
             raise ValueError('text must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/workspaces/{0}/counterexamples/{1}'.format(
-                workspace_id, text),
-            params=params,
-            accept_json=True)
+        url = '/v1/workspaces/{0}/counterexamples/{1}'.format(
+            *self._encode_path_vars(workspace_id, text))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_counterexample(self, workspace_id, text):
@@ -1538,12 +1454,10 @@ class ConversationV1(WatsonService):
         if text is None:
             raise ValueError('text must be provided')
         params = {'version': self.version}
+        url = '/v1/workspaces/{0}/counterexamples/{1}'.format(
+            *self._encode_path_vars(workspace_id, text))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/counterexamples/{1}'.format(
-                workspace_id, text),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_counterexamples(self,
@@ -1575,11 +1489,10 @@ class ConversationV1(WatsonService):
             'sort': sort,
             'cursor': cursor
         }
+        url = '/v1/workspaces/{0}/counterexamples'.format(
+            *self._encode_path_vars(workspace_id))
         response = self.request(
-            method='GET',
-            url='/v1/workspaces/{0}/counterexamples'.format(workspace_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_counterexample(self, workspace_id, text, new_text=None):
@@ -1601,13 +1514,10 @@ class ConversationV1(WatsonService):
             raise ValueError('text must be provided')
         params = {'version': self.version}
         data = {'text': new_text}
+        url = '/v1/workspaces/{0}/counterexamples/{1}'.format(
+            *self._encode_path_vars(workspace_id, text))
         response = self.request(
-            method='POST',
-            url='/v1/workspaces/{0}/counterexamples/{1}'.format(
-                workspace_id, text),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
 
diff --git a/watson_developer_cloud/discovery_v1.py b/watson_developer_cloud/discovery_v1.py
index dd84dfe0..acb264fc 100644
--- a/watson_developer_cloud/discovery_v1.py
+++ b/watson_developer_cloud/discovery_v1.py
@@ -105,12 +105,9 @@ class DiscoveryV1(WatsonService):
             raise ValueError('name must be provided')
         params = {'version': self.version}
         data = {'name': name, 'description': description, 'size': size}
+        url = '/v1/environments'
         response = self.request(
-            method='POST',
-            url='/v1/environments',
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_environment(self, environment_id):
@@ -124,11 +121,10 @@ class DiscoveryV1(WatsonService):
         if environment_id is None:
             raise ValueError('environment_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='DELETE',
-            url='/v1/environments/{0}'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='DELETE', url=url, params=params, accept_json=True)
         return response
 
     def get_environment(self, environment_id):
@@ -142,11 +138,10 @@ class DiscoveryV1(WatsonService):
         if environment_id is None:
             raise ValueError('environment_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_environments(self, name=None):
@@ -160,11 +155,9 @@ class DiscoveryV1(WatsonService):
         :rtype: dict
         """
         params = {'version': self.version, 'name': name}
+        url = '/v1/environments'
         response = self.request(
-            method='GET',
-            url='/v1/environments',
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_fields(self, environment_id, collection_ids):
@@ -190,11 +183,10 @@ class DiscoveryV1(WatsonService):
             ",".join(collection_ids)
             if isinstance(collection_ids, list) else collection_ids
         }
+        url = '/v1/environments/{0}/fields'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/fields'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_environment(self, environment_id, name=None, description=None):
@@ -214,12 +206,10 @@ class DiscoveryV1(WatsonService):
             raise ValueError('environment_id must be provided')
         params = {'version': self.version}
         data = {'name': name, 'description': description}
+        url = '/v1/environments/{0}'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='PUT',
-            url='/v1/environments/{0}'.format(environment_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='PUT', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -272,12 +262,10 @@ class DiscoveryV1(WatsonService):
             'enrichments': enrichments,
             'normalizations': normalizations
         }
+        url = '/v1/environments/{0}/configurations'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='POST',
-            url='/v1/environments/{0}/configurations'.format(environment_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_configuration(self, environment_id, configuration_id):
@@ -301,12 +289,10 @@ class DiscoveryV1(WatsonService):
         if configuration_id is None:
             raise ValueError('configuration_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/configurations/{1}'.format(
+            *self._encode_path_vars(environment_id, configuration_id))
         response = self.request(
-            method='DELETE',
-            url='/v1/environments/{0}/configurations/{1}'.format(
-                environment_id, configuration_id),
-            params=params,
-            accept_json=True)
+            method='DELETE', url=url, params=params, accept_json=True)
         return response
 
     def get_configuration(self, environment_id, configuration_id):
@@ -323,12 +309,10 @@ class DiscoveryV1(WatsonService):
         if configuration_id is None:
             raise ValueError('configuration_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/configurations/{1}'.format(
+            *self._encode_path_vars(environment_id, configuration_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/configurations/{1}'.format(
-                environment_id, configuration_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_configurations(self, environment_id, name=None):
@@ -345,11 +329,10 @@ class DiscoveryV1(WatsonService):
         if environment_id is None:
             raise ValueError('environment_id must be provided')
         params = {'version': self.version, 'name': name}
+        url = '/v1/environments/{0}/configurations'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/configurations'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_configuration(self,
@@ -401,13 +384,10 @@ class DiscoveryV1(WatsonService):
             'enrichments': enrichments,
             'normalizations': normalizations
         }
+        url = '/v1/environments/{0}/configurations/{1}'.format(
+            *self._encode_path_vars(environment_id, configuration_id))
         response = self.request(
-            method='PUT',
-            url='/v1/environments/{0}/configurations/{1}'.format(
-                environment_id, configuration_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='PUT', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -462,9 +442,11 @@ class DiscoveryV1(WatsonService):
         metadata_tuple = None
         if metadata:
             metadata_tuple = (None, metadata, 'text/plain')
+        url = '/v1/environments/{0}/preview'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
             method='POST',
-            url='/v1/environments/{0}/preview'.format(environment_id),
+            url=url,
             params=params,
             files={
                 'configuration': configuration_tuple,
@@ -506,12 +488,10 @@ class DiscoveryV1(WatsonService):
             'configuration_id': configuration_id,
             'language': language
         }
+        url = '/v1/environments/{0}/collections'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='POST',
-            url='/v1/environments/{0}/collections'.format(environment_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_collection(self, environment_id, collection_id):
@@ -528,12 +508,10 @@ class DiscoveryV1(WatsonService):
         if collection_id is None:
             raise ValueError('collection_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='DELETE',
-            url='/v1/environments/{0}/collections/{1}'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+            method='DELETE', url=url, params=params, accept_json=True)
         return response
 
     def get_collection(self, environment_id, collection_id):
@@ -550,12 +528,10 @@ class DiscoveryV1(WatsonService):
         if collection_id is None:
             raise ValueError('collection_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_collection_fields(self, environment_id, collection_id):
@@ -574,12 +550,10 @@ class DiscoveryV1(WatsonService):
         if collection_id is None:
             raise ValueError('collection_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/fields'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}/fields'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_collections(self, environment_id, name=None):
@@ -596,11 +570,10 @@ class DiscoveryV1(WatsonService):
         if environment_id is None:
             raise ValueError('environment_id must be provided')
         params = {'version': self.version, 'name': name}
+        url = '/v1/environments/{0}/collections'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_collection(self,
@@ -630,13 +603,10 @@ class DiscoveryV1(WatsonService):
             'description': description,
             'configuration_id': configuration_id
         }
+        url = '/v1/environments/{0}/collections/{1}'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='PUT',
-            url='/v1/environments/{0}/collections/{1}'.format(
-                environment_id, collection_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='PUT', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -693,10 +663,11 @@ class DiscoveryV1(WatsonService):
         metadata_tuple = None
         if metadata:
             metadata_tuple = (None, metadata, 'text/plain')
+        url = '/v1/environments/{0}/collections/{1}/documents'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
             method='POST',
-            url='/v1/environments/{0}/collections/{1}/documents'.format(
-                environment_id, collection_id),
+            url=url,
             params=params,
             files={'file': file_tuple,
                    'metadata': metadata_tuple},
@@ -724,12 +695,10 @@ class DiscoveryV1(WatsonService):
         if document_id is None:
             raise ValueError('document_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/documents/{2}'.format(
+            *self._encode_path_vars(environment_id, collection_id, document_id))
         response = self.request(
-            method='DELETE',
-            url='/v1/environments/{0}/collections/{1}/documents/{2}'.format(
-                environment_id, collection_id, document_id),
-            params=params,
-            accept_json=True)
+            method='DELETE', url=url, params=params, accept_json=True)
         return response
 
     def get_document_status(self, environment_id, collection_id, document_id):
@@ -754,12 +723,10 @@ class DiscoveryV1(WatsonService):
         if document_id is None:
             raise ValueError('document_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/documents/{2}'.format(
+            *self._encode_path_vars(environment_id, collection_id, document_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}/documents/{2}'.format(
-                environment_id, collection_id, document_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_document(self,
@@ -803,10 +770,11 @@ class DiscoveryV1(WatsonService):
         metadata_tuple = None
         if metadata:
             metadata_tuple = (None, metadata, 'text/plain')
+        url = '/v1/environments/{0}/collections/{1}/documents/{2}'.format(
+            *self._encode_path_vars(environment_id, collection_id, document_id))
         response = self.request(
             method='POST',
-            url='/v1/environments/{0}/collections/{1}/documents/{2}'.format(
-                environment_id, collection_id, document_id),
+            url=url,
             params=params,
             files={'file': file_tuple,
                    'metadata': metadata_tuple},
@@ -888,11 +856,10 @@ class DiscoveryV1(WatsonService):
             'deduplicate.field':
             deduplicate_field
         }
+        url = '/v1/environments/{0}/query'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/query'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def federated_query_notices(self,
@@ -964,11 +931,10 @@ class DiscoveryV1(WatsonService):
             'deduplicate.field':
             deduplicate_field
         }
+        url = '/v1/environments/{0}/notices'.format(
+            *self._encode_path_vars(environment_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/notices'.format(environment_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def query(self,
@@ -1056,12 +1022,10 @@ class DiscoveryV1(WatsonService):
             'deduplicate.field':
             deduplicate_field
         }
+        url = '/v1/environments/{0}/collections/{1}/query'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}/query'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def query_notices(self,
@@ -1147,12 +1111,10 @@ class DiscoveryV1(WatsonService):
             'deduplicate.field':
             deduplicate_field
         }
+        url = '/v1/environments/{0}/collections/{1}/notices'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}/notices'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     #########################
@@ -1189,13 +1151,10 @@ class DiscoveryV1(WatsonService):
             'filter': filter,
             'examples': examples
         }
+        url = '/v1/environments/{0}/collections/{1}/training_data'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='POST',
-            url='/v1/environments/{0}/collections/{1}/training_data'.format(
-                environment_id, collection_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def create_training_example(self,
@@ -1229,14 +1188,10 @@ class DiscoveryV1(WatsonService):
             'cross_reference': cross_reference,
             'relevance': relevance
         }
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}/examples'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id))
         response = self.request(
-            method='POST',
-            url=
-            '/v1/environments/{0}/collections/{1}/training_data/{2}/examples'.
-            format(environment_id, collection_id, query_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     def delete_all_training_data(self, environment_id, collection_id):
@@ -1252,12 +1207,9 @@ class DiscoveryV1(WatsonService):
         if collection_id is None:
             raise ValueError('collection_id must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/environments/{0}/collections/{1}/training_data'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+        url = '/v1/environments/{0}/collections/{1}/training_data'.format(
+            *self._encode_path_vars(environment_id, collection_id))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def delete_training_data(self, environment_id, collection_id, query_id):
@@ -1276,12 +1228,9 @@ class DiscoveryV1(WatsonService):
         if query_id is None:
             raise ValueError('query_id must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v1/environments/{0}/collections/{1}/training_data/{2}'.format(
-                environment_id, collection_id, query_id),
-            params=params,
-            accept_json=True)
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def delete_training_example(self, environment_id, collection_id, query_id,
@@ -1304,13 +1253,10 @@ class DiscoveryV1(WatsonService):
         if example_id is None:
             raise ValueError('example_id must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url=
-            '/v1/environments/{0}/collections/{1}/training_data/{2}/examples/{3}'.
-            format(environment_id, collection_id, query_id, example_id),
-            params=params,
-            accept_json=True)
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}/examples/{3}'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id,
+                                    example_id))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_training_data(self, environment_id, collection_id, query_id):
@@ -1331,12 +1277,10 @@ class DiscoveryV1(WatsonService):
         if query_id is None:
             raise ValueError('query_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}/training_data/{2}'.format(
-                environment_id, collection_id, query_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def get_training_example(self, environment_id, collection_id, query_id,
@@ -1360,13 +1304,11 @@ class DiscoveryV1(WatsonService):
         if example_id is None:
             raise ValueError('example_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}/examples/{3}'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id,
+                                    example_id))
         response = self.request(
-            method='GET',
-            url=
-            '/v1/environments/{0}/collections/{1}/training_data/{2}/examples/{3}'.
-            format(environment_id, collection_id, query_id, example_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_training_data(self, environment_id, collection_id):
@@ -1383,12 +1325,10 @@ class DiscoveryV1(WatsonService):
         if collection_id is None:
             raise ValueError('collection_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/training_data'.format(
+            *self._encode_path_vars(environment_id, collection_id))
         response = self.request(
-            method='GET',
-            url='/v1/environments/{0}/collections/{1}/training_data'.format(
-                environment_id, collection_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_training_examples(self, environment_id, collection_id, query_id):
@@ -1408,13 +1348,10 @@ class DiscoveryV1(WatsonService):
         if query_id is None:
             raise ValueError('query_id must be provided')
         params = {'version': self.version}
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}/examples'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id))
         response = self.request(
-            method='GET',
-            url=
-            '/v1/environments/{0}/collections/{1}/training_data/{2}/examples'.
-            format(environment_id, collection_id, query_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_training_example(self,
@@ -1446,14 +1383,11 @@ class DiscoveryV1(WatsonService):
             raise ValueError('example_id must be provided')
         params = {'version': self.version}
         data = {'cross_reference': cross_reference, 'relevance': relevance}
+        url = '/v1/environments/{0}/collections/{1}/training_data/{2}/examples/{3}'.format(
+            *self._encode_path_vars(environment_id, collection_id, query_id,
+                                    example_id))
         response = self.request(
-            method='PUT',
-            url=
-            '/v1/environments/{0}/collections/{1}/training_data/{2}/examples/{3}'.
-            format(environment_id, collection_id, query_id, example_id),
-            params=params,
-            json=data,
-            accept_json=True)
+            method='PUT', url=url, params=params, json=data, accept_json=True)
         return response
 
 
diff --git a/watson_developer_cloud/language_translator_v2.py b/watson_developer_cloud/language_translator_v2.py
index 060fa1d8..b98bda0c 100644
--- a/watson_developer_cloud/language_translator_v2.py
+++ b/watson_developer_cloud/language_translator_v2.py
@@ -88,8 +88,9 @@ class LanguageTranslatorV2(WatsonService):
             'source': source,
             'target': target
         }
+        url = '/v2/translate'
         response = self.request(
-            method='POST', url='/v2/translate', json=data, accept_json=True)
+            method='POST', url=url, json=data, accept_json=True)
         return response
 
     #########################
@@ -108,9 +109,10 @@ class LanguageTranslatorV2(WatsonService):
             raise ValueError('text must be provided')
         data = text
         headers = {'content-type': 'text/plain'}
+        url = '/v2/identify'
         response = self.request(
             method='POST',
-            url='/v2/identify',
+            url=url,
             headers=headers,
             data=data,
             accept_json=True)
@@ -126,8 +128,8 @@ class LanguageTranslatorV2(WatsonService):
         :return: A `dict` containing the `IdentifiableLanguages` response.
         :rtype: dict
         """
-        response = self.request(
-            method='GET', url='/v2/identifiable_languages', accept_json=True)
+        url = '/v2/identifiable_languages'
+        response = self.request(method='GET', url=url, accept_json=True)
         return response
 
     #########################
@@ -184,9 +186,10 @@ class LanguageTranslatorV2(WatsonService):
             mime_type = 'text/plain'
             monolingual_corpus_tuple = (monolingual_corpus_filename,
                                         monolingual_corpus, mime_type)
+        url = '/v2/models'
         response = self.request(
             method='POST',
-            url='/v2/models',
+            url=url,
             params=params,
             files={
                 'forced_glossary': forced_glossary_tuple,
@@ -206,10 +209,8 @@ class LanguageTranslatorV2(WatsonService):
         """
         if model_id is None:
             raise ValueError('model_id must be provided')
-        response = self.request(
-            method='DELETE',
-            url='/v2/models/{0}'.format(model_id),
-            accept_json=True)
+        url = '/v2/models/{0}'.format(*self._encode_path_vars(model_id))
+        response = self.request(method='DELETE', url=url, accept_json=True)
         return response
 
     def get_model(self, model_id):
@@ -222,10 +223,8 @@ class LanguageTranslatorV2(WatsonService):
         """
         if model_id is None:
             raise ValueError('model_id must be provided')
-        response = self.request(
-            method='GET',
-            url='/v2/models/{0}'.format(model_id),
-            accept_json=True)
+        url = '/v2/models/{0}'.format(*self._encode_path_vars(model_id))
+        response = self.request(method='GET', url=url, accept_json=True)
         return response
 
     def list_models(self, source=None, target=None, default_models=None):
@@ -239,8 +238,9 @@ class LanguageTranslatorV2(WatsonService):
         :rtype: dict
         """
         params = {'source': source, 'target': target, 'default': default_models}
+        url = '/v2/models'
         response = self.request(
-            method='GET', url='/v2/models', params=params, accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
 
diff --git a/watson_developer_cloud/natural_language_classifier_v1.py b/watson_developer_cloud/natural_language_classifier_v1.py
index d33fcba3..47ff3267 100644
--- a/watson_developer_cloud/natural_language_classifier_v1.py
+++ b/watson_developer_cloud/natural_language_classifier_v1.py
@@ -87,11 +87,10 @@ class NaturalLanguageClassifierV1(WatsonService):
         if text is None:
             raise ValueError('text must be provided')
         data = {'text': text}
+        url = '/v1/classifiers/{0}/classify'.format(
+            *self._encode_path_vars(classifier_id))
         response = self.request(
-            method='POST',
-            url='/v1/classifiers/{0}/classify'.format(classifier_id),
-            json=data,
-            accept_json=True)
+            method='POST', url=url, json=data, accept_json=True)
         return response
 
     def create_classifier(self,
@@ -124,9 +123,10 @@ class NaturalLanguageClassifierV1(WatsonService):
             training_data_filename = training_data.name
         mime_type = 'text/csv'
         training_data_tuple = (training_data_filename, training_data, mime_type)
+        url = '/v1/classifiers'
         response = self.request(
             method='POST',
-            url='/v1/classifiers',
+            url=url,
             files={
                 'metadata': metadata_tuple,
                 'training_data': training_data_tuple
@@ -143,10 +143,9 @@ class NaturalLanguageClassifierV1(WatsonService):
         """
         if classifier_id is None:
             raise ValueError('classifier_id must be provided')
-        self.request(
-            method='DELETE',
-            url='/v1/classifiers/{0}'.format(classifier_id),
-            accept_json=True)
+        url = '/v1/classifiers/{0}'.format(
+            *self._encode_path_vars(classifier_id))
+        self.request(method='DELETE', url=url, accept_json=True)
         return None
 
     def get_classifier(self, classifier_id):
@@ -161,10 +160,9 @@ class NaturalLanguageClassifierV1(WatsonService):
         """
         if classifier_id is None:
             raise ValueError('classifier_id must be provided')
-        response = self.request(
-            method='GET',
-            url='/v1/classifiers/{0}'.format(classifier_id),
-            accept_json=True)
+        url = '/v1/classifiers/{0}'.format(
+            *self._encode_path_vars(classifier_id))
+        response = self.request(method='GET', url=url, accept_json=True)
         return response
 
     def list_classifiers(self):
@@ -176,8 +174,8 @@ class NaturalLanguageClassifierV1(WatsonService):
         :return: A `dict` containing the `ClassifierList` response.
         :rtype: dict
         """
-        response = self.request(
-            method='GET', url='/v1/classifiers', accept_json=True)
+        url = '/v1/classifiers'
+        response = self.request(method='GET', url=url, accept_json=True)
         return response
 
 
diff --git a/watson_developer_cloud/natural_language_understanding_v1.py b/watson_developer_cloud/natural_language_understanding_v1.py
index 41710c36..c737ad3d 100644
--- a/watson_developer_cloud/natural_language_understanding_v1.py
+++ b/watson_developer_cloud/natural_language_understanding_v1.py
@@ -169,12 +169,9 @@ class NaturalLanguageUnderstandingV1(WatsonService):
             'language': language,
             'limit_text_characters': limit_text_characters
         }
+        url = '/v1/analyze'
         response = self.request(
-            method='POST',
-            url='/v1/analyze',
-            params=params,
-            json=data,
-            accept_json=True)
+            method='POST', url=url, params=params, json=data, accept_json=True)
         return response
 
     #########################
@@ -194,11 +191,9 @@ class NaturalLanguageUnderstandingV1(WatsonService):
         if model_id is None:
             raise ValueError('model_id must be provided')
         params = {'version': self.version}
+        url = '/v1/models/{0}'.format(*self._encode_path_vars(model_id))
         response = self.request(
-            method='DELETE',
-            url='/v1/models/{0}'.format(model_id),
-            params=params,
-            accept_json=True)
+            method='DELETE', url=url, params=params, accept_json=True)
         return response
 
     def list_models(self):
@@ -213,8 +208,9 @@ class NaturalLanguageUnderstandingV1(WatsonService):
         :rtype: dict
         """
         params = {'version': self.version}
+        url = '/v1/models'
         response = self.request(
-            method='GET', url='/v1/models', params=params, accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
 
diff --git a/watson_developer_cloud/personality_insights_v3.py b/watson_developer_cloud/personality_insights_v3.py
index c51c4686..cb4a359d 100755
--- a/watson_developer_cloud/personality_insights_v3.py
+++ b/watson_developer_cloud/personality_insights_v3.py
@@ -166,9 +166,10 @@ class PersonalityInsightsV3(WatsonService):
             data = json.dumps(content)
         else:
             data = content
+        url = '/v3/profile'
         response = self.request(
             method='POST',
-            url='/v3/profile',
+            url=url,
             headers=headers,
             params=params,
             data=data,
diff --git a/watson_developer_cloud/tone_analyzer_v3.py b/watson_developer_cloud/tone_analyzer_v3.py
index 435bd77b..ee538289 100755
--- a/watson_developer_cloud/tone_analyzer_v3.py
+++ b/watson_developer_cloud/tone_analyzer_v3.py
@@ -166,9 +166,10 @@ class ToneAnalyzerV3(WatsonService):
             data = json.dumps(tone_input)
         else:
             data = tone_input
+        url = '/v3/tone'
         response = self.request(
             method='POST',
-            url='/v3/tone',
+            url=url,
             headers=headers,
             params=params,
             data=data,
@@ -201,9 +202,10 @@ class ToneAnalyzerV3(WatsonService):
         headers = {'Accept-Language': accept_language}
         params = {'version': self.version}
         data = {'utterances': utterances}
+        url = '/v3/tone_chat'
         response = self.request(
             method='POST',
-            url='/v3/tone_chat',
+            url=url,
             headers=headers,
             params=params,
             json=data,
diff --git a/watson_developer_cloud/visual_recognition_v3.py b/watson_developer_cloud/visual_recognition_v3.py
index c620885e..e521e804 100644
--- a/watson_developer_cloud/visual_recognition_v3.py
+++ b/watson_developer_cloud/visual_recognition_v3.py
@@ -106,9 +106,10 @@ class VisualRecognitionV3(WatsonService):
         parameters_tuple = None
         if parameters:
             parameters_tuple = (None, parameters, 'text/plain')
+        url = '/v3/classify'
         response = self.request(
             method='POST',
-            url='/v3/classify',
+            url=url,
             headers=headers,
             params=params,
             files={
@@ -143,9 +144,10 @@ class VisualRecognitionV3(WatsonService):
         parameters_tuple = None
         if parameters:
             parameters_tuple = (None, parameters, 'text/plain')
+        url = '/v3/detect_faces'
         response = self.request(
             method='POST',
-            url='/v3/detect_faces',
+            url=url,
             params=params,
             files={
                 'images_file': images_file_tuple,
@@ -174,9 +176,10 @@ class VisualRecognitionV3(WatsonService):
             raise ValueError('name must be provided')
         params = {'version': self.version}
         data = {'name': name}
+        url = '/v3/classifiers'
         response = self.request(
             method='POST',
-            url='/v3/classifiers',
+            url=url,
             params=params,
             data=data,
             files=kwargs,
@@ -193,11 +196,9 @@ class VisualRecognitionV3(WatsonService):
         if classifier_id is None:
             raise ValueError('classifier_id must be provided')
         params = {'version': self.version}
-        self.request(
-            method='DELETE',
-            url='/v3/classifiers/{0}'.format(classifier_id),
-            params=params,
-            accept_json=True)
+        url = '/v3/classifiers/{0}'.format(
+            *self._encode_path_vars(classifier_id))
+        self.request(method='DELETE', url=url, params=params, accept_json=True)
         return None
 
     def get_classifier(self, classifier_id):
@@ -211,11 +212,10 @@ class VisualRecognitionV3(WatsonService):
         if classifier_id is None:
             raise ValueError('classifier_id must be provided')
         params = {'version': self.version}
+        url = '/v3/classifiers/{0}'.format(
+            *self._encode_path_vars(classifier_id))
         response = self.request(
-            method='GET',
-            url='/v3/classifiers/{0}'.format(classifier_id),
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def list_classifiers(self, verbose=None):
@@ -227,11 +227,9 @@ class VisualRecognitionV3(WatsonService):
         :rtype: dict
         """
         params = {'version': self.version, 'verbose': verbose}
+        url = '/v3/classifiers'
         response = self.request(
-            method='GET',
-            url='/v3/classifiers',
-            params=params,
-            accept_json=True)
+            method='GET', url=url, params=params, accept_json=True)
         return response
 
     def update_classifier(self,
@@ -249,9 +247,11 @@ class VisualRecognitionV3(WatsonService):
         if classifier_id is None:
             raise ValueError('classifier_id must be provided')
         params = {'version': self.version}
+        url = '/v3/classifiers/{0}'.format(
+            *self._encode_path_vars(classifier_id))
         response = self.request(
             method='POST',
-            url='/v3/classifiers/{0}'.format(classifier_id),
+            url=url,
             params=params,
             files=kwargs,
             accept_json=True)
diff --git a/watson_developer_cloud/watson_service.py b/watson_developer_cloud/watson_service.py
index 619ea733..6b703735 100755
--- a/watson_developer_cloud/watson_service.py
+++ b/watson_developer_cloud/watson_service.py
@@ -227,6 +227,10 @@ class WatsonService(object):
             return val._to_dict()
         return val
 
+    @staticmethod
+    def _encode_path_vars(*args):
+        return (requests.utils.quote(x, safe='') for x in args)
+
     @staticmethod
     def _get_error_message(response):
         """


Test patch:
diff --git a/test/test_conversation_v1.py b/test/test_conversation_v1.py
index f696abcd..a70d4160 100644
--- a/test/test_conversation_v1.py
+++ b/test/test_conversation_v1.py
@@ -140,7 +140,7 @@ def test_get_counterexample():
     service = watson_developer_cloud.ConversationV1(
         username='username', password='password', version='2017-02-03')
     counterexample = service.get_counterexample(
-        workspace_id='boguswid', text='What are you wearing%3F')
+        workspace_id='boguswid', text='What are you wearing?')
     assert len(responses.calls) == 1
     assert responses.calls[0].request.url.startswith(url)
     assert counterexample == response
@@ -203,8 +203,8 @@ def test_update_counterexample():
         username='username', password='password', version='2017-02-03')
     counterexample = service.update_counterexample(
         workspace_id='boguswid',
-        text='What are you wearing%3F',
-        new_text='What are you wearing%3F')
+        text='What are you wearing?',
+        new_text='What are you wearing?')
     assert len(responses.calls) == 1
     assert responses.calls[0].request.url.startswith(url)
     assert counterexample == response
diff --git a/test/test_watson_service.py b/test/test_watson_service.py
new file mode 100755
index 00000000..62ff3ee1
--- /dev/null
+++ b/test/test_watson_service.py
@@ -0,0 +1,63 @@
+# coding=utf-8
+import json
+
+from watson_developer_cloud import WatsonService
+
+import responses
+
+##############################################################################
+# Service
+##############################################################################
+
+class AnyServiceV1(WatsonService):
+    default_url = 'https://gateway.watsonplatform.net/test/api'
+
+    def __init__(self, version, url=default_url, username=None, password=None):
+        WatsonService.__init__(
+            self,
+            vcap_services_name='test',
+            url=url,
+            username=username,
+            password=password,
+            use_vcap_services=True)
+        self.version = version
+
+    def op_with_path_params(self, path0, path1):
+        if path0 is None:
+            raise ValueError('path0 must be provided')
+        if path1 is None:
+            raise ValueError('path1 must be provided')
+        params = {'version': self.version}
+        url = '/v1/foo/{0}/bar/{1}/baz'.format(
+            *self._encode_path_vars(path0, path1))
+        response = self.request(
+            method='GET', url=url, params=params, accept_json=True)
+        return response
+
+
+@responses.activate
+def test_url_encoding():
+    service = AnyServiceV1('2017-07-07', username='username', password='password')
+
+    # All characters in path0 _must_ be encoded in path segments
+    path0 = ' \"<>^`{}|/\\?#%[]'
+    path0_encoded = '%20%22%3C%3E%5E%60%7B%7D%7C%2F%5C%3F%23%25%5B%5D'
+    # All non-ASCII chars _must_ be encoded in path segments
+    path1 = u'比萨浇头'.encode('utf8')  # "pizza toppings"
+    path1_encoded = '%E6%AF%94%E8%90%A8%E6%B5%87%E5%A4%B4'
+
+    path_encoded = '/v1/foo/' + path0_encoded + '/bar/' + path1_encoded + '/baz'
+    test_url = service.default_url + path_encoded
+
+    responses.add(responses.GET,
+                  test_url,
+                  status=200,
+                  body=json.dumps({"foobar": "baz"}),
+                  content_type='application/json')
+
+    response = service.op_with_path_params(path0, path1)
+
+    assert response is not None
+    assert len(responses.calls) == 1
+    assert path_encoded in responses.calls[0].request.url
+    assert 'version=2017-07-07' in responses.calls[0].request.url