Repository: line/line-bot-sdk-python
Pull request #48 (2017-05-31 13:47:45)

Support 'user_id' in SourceGroup and SourceRoom
PR for [Obtaining user consent](https://developers.line.me/messaging-api/obtaining-user-consent)

* Add `user_id` in `SourceGroup` and `SourceRoom`
  * And deprecated `sender_id` because it is now confusing...

okdtsk: How about removing abstractproperty `sender_id` from base `Source` class or some comments into its docstring because it is deprecated anymore?
be-hase: I think that `sender_id` of concrete classes and abstract classes should be deleted at the same time.
OK, I added `deprecated ` comments into docstring.
thanks !

Patch:
diff --git a/.gitignore b/.gitignore
index 9b0c6cf..60a7f9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -101,4 +101,4 @@ ENV/
 .idea
 .DS_Store
 tmp/
-
+*.iml
diff --git a/README.rst b/README.rst
index bcfaca3..96fcd16 100644
--- a/README.rst
+++ b/README.rst
@@ -534,9 +534,11 @@ Source
 - SourceGroup
     - type
     - group\_id
+    - user\_id
 - SourceRoom
     - type
     - room\_id
+    - user\_id
 
 Message
 ^^^^^^^
diff --git a/linebot/__about__.py b/linebot/__about__.py
index b6ddd94..1be193d 100644
--- a/linebot/__about__.py
+++ b/linebot/__about__.py
@@ -16,7 +16,7 @@
 
 from __future__ import unicode_literals
 
-__version__ = '1.2.0'
+__version__ = '1.3.0'
 __author__ = 'LINE Corporation'
 __copyright__ = 'Copyright 2016, LINE Corporation'
 __license__ = 'Apache 2.0'
diff --git a/linebot/models/sources.py b/linebot/models/sources.py
index e96a6b4..c08a2c7 100644
--- a/linebot/models/sources.py
+++ b/linebot/models/sources.py
@@ -16,6 +16,7 @@
 
 from __future__ import unicode_literals
 
+import warnings
 from abc import ABCMeta, abstractproperty
 
 from future.utils import with_metaclass
@@ -42,9 +43,12 @@ class Source(with_metaclass(ABCMeta, Base)):
         If SourceGroup, return group_id.
         If SourceRoom, return room_id.
 
+        'sender_id' is deprecated.
+
         :rtype: str
         :return:
         """
+        warnings.warn("'sender_id' is deprecated.", DeprecationWarning, stacklevel=2)
         raise NotImplementedError
 
 
@@ -71,9 +75,12 @@ class SourceUser(Source):
     def sender_id(self):
         """Alias of user_id.
 
+        'sender_id' is deprecated. Use 'user_id' instead.
+
         :rtype: str
         :return:
         """
+        warnings.warn("'sender_id' is deprecated.", DeprecationWarning, stacklevel=2)
         return self.user_id
 
 
@@ -85,24 +92,29 @@ class SourceGroup(Source):
     JSON object which contains the source group of the event.
     """
 
-    def __init__(self, group_id=None, **kwargs):
+    def __init__(self, group_id=None, user_id=None, **kwargs):
         """__init__ method.
 
         :param str group_id: ID of the source group
+        :param str user_id: ID of the source user
         :param kwargs:
         """
         super(SourceGroup, self).__init__(**kwargs)
 
         self.type = 'group'
         self.group_id = group_id
+        self.user_id = user_id
 
     @property
     def sender_id(self):
         """Alias of group_id.
 
+        'sender_id' is deprecated. Use 'group_id' instead.
+
         :rtype: str
         :return:
         """
+        warnings.warn("'sender_id' is deprecated.", DeprecationWarning, stacklevel=2)
         return self.group_id
 
 
@@ -114,22 +126,27 @@ class SourceRoom(Source):
     JSON object which contains the source room of the event.
     """
 
-    def __init__(self, room_id=None, **kwargs):
+    def __init__(self, room_id=None, user_id=None, **kwargs):
         """__init__ method.
 
         :param str room_id: ID of the source room
+        :param str user_id: ID of the source user
         :param kwargs:
         """
         super(SourceRoom, self).__init__(**kwargs)
 
         self.type = 'room'
         self.room_id = room_id
+        self.user_id = user_id
 
     @property
     def sender_id(self):
         """Alias of room_id.
 
+        'sender_id' is deprecated. Use 'room_id' instead.
+
         :rtype: str
         :return:
         """
+        warnings.warn("'sender_id' is deprecated.", DeprecationWarning, stacklevel=2)
         return self.room_id
diff --git a/requirements.txt b/requirements.txt
index da72879..8161732 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
 requests >= 2.0
-future
\ No newline at end of file
+future
diff --git a/tox.ini b/tox.ini
index bebfaa3..bd604c4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,9 @@
 [tox]
-envlist = py27, py33, py34, py35, py36, py36-flake8-src, py36-flake8-tests, py36-flake8-other
+envlist = py27, py33, py34, py35, py36, py36-flake8-src, py36-flake8-other
 
 [testenv]
 deps =
+    six
     -r{toxinidir}/requirements.txt
     -r{toxinidir}/requirements-test.txt
 commands = py.test -v tests/
@@ -15,6 +16,7 @@ commands = py.test -v --cov=linebot tests/
 [testenv:py36-flake8-src]
 basepython = python3.6
 deps =
+    six
     flake8
     flake8-docstrings
 commands = flake8 linebot/
@@ -22,5 +24,6 @@ commands = flake8 linebot/
 [testenv:py36-flake8-other]
 basepython = python3.6
 deps =
+    six
     flake8
 commands = flake8 tests/ examples/


Test patch:
diff --git a/requirements-test.txt b/requirements-test.txt
index 41c6e1e..977dd7d 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,4 +1,4 @@
 pytest
 pytest-pep8
 pytest-cov
-responses
\ No newline at end of file
+responses
diff --git a/tests/test_webhook.py b/tests/test_webhook.py
index bbd0a93..930a98b 100644
--- a/tests/test_webhook.py
+++ b/tests/test_webhook.py
@@ -80,8 +80,9 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[1].timestamp, 1462629479859)
         self.assertIsInstance(events[1].source, SourceRoom)
         self.assertEqual(events[1].source.type, 'room')
-        self.assertEqual(events[1].source.room_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
-        self.assertEqual(events[1].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[1].source.room_id, 'Ra8dbf4673c4c812cd491258042226c99')
+        self.assertEqual(events[1].source.user_id, None)
+        self.assertEqual(events[1].source.sender_id, 'Ra8dbf4673c4c812cd491258042226c99')
         self.assertIsInstance(events[1].message, ImageMessage)
         self.assertEqual(events[1].message.id, '325708')
         self.assertEqual(events[1].message.type, 'image')
@@ -170,8 +171,9 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[8].timestamp, 1462629479859)
         self.assertIsInstance(events[8].source, SourceGroup)
         self.assertEqual(events[8].source.type, 'group')
-        self.assertEqual(events[8].source.group_id, 'cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
-        self.assertEqual(events[8].source.sender_id, 'cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
+        self.assertEqual(events[8].source.group_id, 'Ca56f94637cc4347f90a25382909b24b9')
+        self.assertEqual(events[8].source.user_id, None)
+        self.assertEqual(events[8].source.sender_id, 'Ca56f94637cc4347f90a25382909b24b9')
 
         # LeaveEvent, SourceGroup
         self.assertIsInstance(events[9], LeaveEvent)
@@ -179,8 +181,9 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[9].timestamp, 1462629479859)
         self.assertIsInstance(events[9].source, SourceGroup)
         self.assertEqual(events[9].source.type, 'group')
-        self.assertEqual(events[9].source.group_id, 'cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
-        self.assertEqual(events[9].source.sender_id, 'cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
+        self.assertEqual(events[9].source.group_id, 'Ca56f94637cc4347f90a25382909b24b9')
+        self.assertEqual(events[9].source.user_id, None)
+        self.assertEqual(events[9].source.sender_id, 'Ca56f94637cc4347f90a25382909b24b9')
 
         # PostbackEvent, SourceUser
         self.assertIsInstance(events[10], PostbackEvent)
@@ -221,6 +224,36 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[12].beacon.dm, '1234567890abcdef')
         self.assertEqual(events[12].beacon.device_message, bytearray(b'\x124Vx\x90\xab\xcd\xef'))
 
+        # MessageEvent, SourceGroup with userId, TextMessage
+        self.assertIsInstance(events[13], MessageEvent)
+        self.assertEqual(events[13].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[13].type, 'message')
+        self.assertEqual(events[13].timestamp, 1462629479859)
+        self.assertIsInstance(events[13].source, SourceGroup)
+        self.assertEqual(events[13].source.type, 'group')
+        self.assertEqual(events[13].source.group_id, 'Ca56f94637cc4347f90a25382909b24b9')
+        self.assertEqual(events[13].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[13].source.sender_id, 'Ca56f94637cc4347f90a25382909b24b9')
+        self.assertIsInstance(events[13].message, TextMessage)
+        self.assertEqual(events[13].message.id, '325708')
+        self.assertEqual(events[13].message.type, 'text')
+        self.assertEqual(events[13].message.text, 'Hello, world')
+
+        # MessageEvent, SourceRoom with userId, TextMessage
+        self.assertIsInstance(events[14], MessageEvent)
+        self.assertEqual(events[14].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[14].type, 'message')
+        self.assertEqual(events[14].timestamp, 1462629479859)
+        self.assertIsInstance(events[14].source, SourceRoom)
+        self.assertEqual(events[14].source.type, 'room')
+        self.assertEqual(events[14].source.room_id, 'Ra8dbf4673c4c812cd491258042226c99')
+        self.assertEqual(events[14].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[14].source.sender_id, 'Ra8dbf4673c4c812cd491258042226c99')
+        self.assertIsInstance(events[14].message, TextMessage)
+        self.assertEqual(events[14].message.id, '325708')
+        self.assertEqual(events[14].message.type, 'text')
+        self.assertEqual(events[14].message.text, 'Hello, world')
+
 
 class TestWebhookHandler(unittest.TestCase):
     def setUp(self):
diff --git a/tests/text/webhook.json b/tests/text/webhook.json
index 726af6f..cad9415 100644
--- a/tests/text/webhook.json
+++ b/tests/text/webhook.json
@@ -1,148 +1,192 @@
 {
-  "events": [{
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "message",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+  "events": [
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "text",
+        "text": "Hello, world"
+      }
     },
-    "message": {
-      "id": "325708",
-      "type": "text",
-      "text": "Hello, world"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "message",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "room",
-      "roomId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "room",
+        "roomId": "Ra8dbf4673c4c812cd491258042226c99"
+      },
+      "message": {
+        "id": "325708",
+        "type": "image"
+      }
     },
-    "message": {
-      "id": "325708",
-      "type": "image"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "message",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "video"
+      }
     },
-    "message": {
-      "id": "325708",
-      "type": "video"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "message",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "audio"
+      }
     },
-    "message": {
-      "id": "325708",
-      "type": "audio"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "message",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "location",
+        "title": "my location",
+        "address": "Tokyo",
+        "latitude": 35.65910807942215,
+        "longitude": 139.70372892916203
+      }
     },
-    "message": {
-      "id": "325708",
-      "type": "location",
-      "title": "my location",
-      "address": "Tokyo",
-      "latitude": 35.65910807942215,
-      "longitude": 139.70372892916203
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "message",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "sticker",
+        "packageId": "1",
+        "stickerId": "1"
+      }
     },
-    "message": {
-      "id": "325708",
-      "type": "sticker",
-      "packageId": "1",
-      "stickerId": "1"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "follow",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
-    }
-  }, {
-    "type": "unfollow",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "join",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "group",
-      "groupId": "cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-    }
-  }, {
-    "type": "leave",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "group",
-      "groupId": "cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "postback",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "follow",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      }
     },
-    "postback": {
-      "data": "action=buyItem&itemId=123123&color=red"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "beacon",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "type": "unfollow",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      }
     },
-    "beacon": {
-      "hwid": "d41d8cd98f",
-      "type": "enter"
-    }
-  }, {
-    "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-    "type": "beacon",
-    "timestamp": 1462629479859,
-    "source": {
-      "type": "user",
-      "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "join",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "group",
+        "groupId": "Ca56f94637cc4347f90a25382909b24b9"
+      }
+    },
+    {
+      "type": "leave",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "group",
+        "groupId": "Ca56f94637cc4347f90a25382909b24b9"
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "postback",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "postback": {
+        "data": "action=buyItem&itemId=123123&color=red"
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "beacon",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "beacon": {
+        "hwid": "d41d8cd98f",
+        "type": "enter"
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "beacon",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "beacon": {
+        "hwid": "d41d8cd98f",
+        "type": "enter",
+        "dm": "1234567890abcdef"
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "group",
+        "groupId": "Ca56f94637cc4347f90a25382909b24b9",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "text",
+        "text": "Hello, world"
+      }
     },
-    "beacon": {
-      "hwid": "d41d8cd98f",
-      "type": "enter",
-      "dm": "1234567890abcdef"
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "room",
+        "roomId": "Ra8dbf4673c4c812cd491258042226c99",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "text",
+        "text": "Hello, world"
+      }
     }
-  }]
-}
\ No newline at end of file
+  ]
+}