diff --git a/linebot/api.py b/linebot/api.py index cbf96798..77f20344 100644 --- a/linebot/api.py +++ b/linebot/api.py @@ -27,7 +27,8 @@ MessageDeliveryBroadcastResponse, MessageDeliveryMulticastResponse, MessageDeliveryPushResponse, MessageDeliveryReplyResponse, InsightMessageDeliveryResponse, InsightFollowersResponse, InsightDemographicResponse, - InsightMessageEventResponse, BroadcastResponse, + InsightMessageEventResponse, BroadcastResponse, NarrowcastResponse, + MessageProgressNarrowcastResponse, ) @@ -210,6 +211,70 @@ def broadcast(self, messages, notification_disabled=False, timeout=None): return BroadcastResponse(request_id=response.headers.get('X-Line-Request-Id')) + def narrowcast(self, messages, recipient=None, filter=None, limit=None, timeout=None): + """Call narrowcast API. + + https://developers.line.biz/en/reference/messaging-api/#send-narrowcast-message + + Sends push messages to multiple users at any time. + Messages cannot be sent to groups or rooms. + + :param messages: Messages. + Max: 5 + :type messages: T <= :py:class:`linebot.models.send_messages.SendMessage` | + list[T <= :py:class:`linebot.models.send_messages.SendMessage`] + :param recipient: audience object of recipient + :type recipient: T <= :py:class:`linebot.models.recipient.AudienceRecipient` + :param filter: demographic filter of recipient + :type filter: T <= :py:class:`linebot.models.filter.DemographicFilter` + :param limit: limit on this narrowcast + :type limit: T <= :py:class:`linebot.models.limit.Limit` + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + :rtype: :py:class:`linebot.models.responses.NarrowcastResponse` + """ + if not isinstance(messages, (list, tuple)): + messages = [messages] + + data = { + 'messages': [message.as_json_dict() for message in messages], + 'recipient': recipient.as_json_dict(), + 'filter': filter.as_json_dict(), + 'limit': limit.as_json_dict(), + } + + response = self._post( + '/v2/bot/message/narrowcast', data=json.dumps(data), timeout=timeout + ) + + return NarrowcastResponse(request_id=response.headers.get('X-Line-Request-Id')) + + def get_progress_status_narrowcast(self, request_id, timeout=None): + """Get progress status of narrowcast messages sent. + + https://developers.line.biz/en/reference/messaging-api/#get-narrowcast-progress-status + + Gets the number of messages sent with the /bot/message/progress/narrowcast endpoint. + + :param str request_id: request ID of narrowcast. + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + :rtype: :py:class:`linebot.models.responses.MessageDeliveryBroadcastResponse` + """ + response = self._get( + '/v2/bot/message/progress/narrowcast?requestId={request_id}'.format( + request_id=request_id), + timeout=timeout + ) + + return MessageProgressNarrowcastResponse.new_from_json_dict(response.json) + def get_message_delivery_broadcast(self, date, timeout=None): """Get number of sent broadcast messages. diff --git a/linebot/models/__init__.py b/linebot/models/__init__.py index 84601b10..e173ec94 100644 --- a/linebot/models/__init__.py +++ b/linebot/models/__init__.py @@ -54,6 +54,16 @@ Beacon, Link, ) +from .filter import( # noqa + Filter, + DemographicFilter, + GenderFilter, + AppTypeFilter, + AreaFilter, + AgeFilter, + SubscriptionPeriodFilter, +) + from .flex_message import ( # noqa FlexSendMessage, FlexContainer, @@ -93,6 +103,11 @@ MessageInsight, ClickInsight, ) + +from .limit import ( # noqa + Limit, +) + from .messages import ( # noqa Message, TextMessage, @@ -103,6 +118,17 @@ StickerMessage, FileMessage, ) + +from .operator import ( # noqa + And, + Or, + Not +) + +from .recipient import ( # noqa + AudienceRecipient +) + from .responses import ( # noqa Profile, MemberIds, @@ -122,6 +148,8 @@ InsightDemographicResponse, InsightMessageEventResponse, BroadcastResponse, + NarrowcastResponse, + MessageProgressNarrowcastResponse, ) from .rich_menu import ( # noqa RichMenu, diff --git a/linebot/models/filter.py b/linebot/models/filter.py new file mode 100644 index 00000000..e755eb2b --- /dev/null +++ b/linebot/models/filter.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""linebot.models.filter module.""" + +from __future__ import unicode_literals + +from abc import ABCMeta + +from future.utils import with_metaclass + +from .base import Base + + +class Filter(with_metaclass(ABCMeta, Base)): + """Filter. + + https://developers.line.biz/en/reference/messaging-api/#narrowcast-demographic-filter + + A filter is the top-level structure of a demographic element. + """ + + def __init__(self, demographic=None, **kwargs): + """__init__ method. + + :param demographic: Combination of different criteria using logical + operator objects. + :type demographic: :py:class:`linebot.model.DemographicFilter` | + :py:class:`linebot.model.Operator` + :param kwargs: + """ + super(Filter, self).__init__(**kwargs) + + self.demographic = demographic + + +class DemographicFilter(Filter): + """DemographicFilter. + + https://developers.line.biz/en/reference/messaging-api/#narrowcast-demographic-filter + + Demographic filter objects represent criteria (e.g. age, gender, OS, region, + and friendship duration) on which to filter the list of recipients. + You can filter recipients based on a combination of different criteria using + logical operator objects. + """ + + def __init__(self, **kwargs): + """__init__ method. + + :param kwargs: + """ + super(DemographicFilter, self).__init__(**kwargs) + + self.type = None + + +class GenderFilter(DemographicFilter): + """GenderFilter.""" + + def __init__(self, one_of=[], **kwargs): + """__init__ method. + + :param one_of: Send messages to users of a given gender. One of: + male: Users who identify as male + female: Users who identify as female + :type one_of: list[str] + """ + super(GenderFilter, self).__init__(**kwargs) + + self.type = "gender" + self.one_of = one_of + + +class AppTypeFilter(DemographicFilter): + """AppTypeFilter.""" + + def __init__(self, one_of=[], **kwargs): + """__init__ method. + + :param one_of: Send messages to users of the specified OS. One of: + ios: Users who using iOS. + android: Users who using Android. + :type one_of: list[str] + """ + super(AppTypeFilter, self).__init__(**kwargs) + + self.type = "appType" + self.one_of = one_of + + +class AreaFilter(DemographicFilter): + """AreaFilter.""" + + def __init__(self, one_of=[], **kwargs): + """__init__ method. + + :param one_of: Send messages to users in the specified region. + :type one_of: list[str] + """ + super(AreaFilter, self).__init__(**kwargs) + + self.type = "area" + self.one_of = one_of + + +class AgeFilter(DemographicFilter): + """AgeFilter. + + This lets you filter recipients with a given age range. + """ + + def __init__(self, gte=None, lt=None, **kwargs): + """__init__ method. + + Be sure to specify either gte, lt, or both. + + :param gte: Send messages to users at least as old as the specified age. + :type gte: str + :param lt: Send messages to users younger than the specified age. + You can specify the same values as for the gte property. + :type lt: str + """ + super(AgeFilter, self).__init__(**kwargs) + + self.type = "age" + self.gte = gte + self.lt = lt + + +class SubscriptionPeriodFilter(DemographicFilter): + """SubscriptionPeriodFilter. + + This lets you filter recipients with a given range of friendship durations. + """ + + def __init__(self, gte=None, lt=None, **kwargs): + """__init__ method. + + Be sure to specify either gte, lt, or both. + + :param gte: Send messages to users who have been friends of yours for + at least the specified number of days + :type gte: str + :param lt: Send messages to users who have been friends of yours for + less than the specified number of days. + You can specify the same values as for the gte property. + :type lt: str + """ + super(SubscriptionPeriodFilter, self).__init__(**kwargs) + + self.type = "subscriptionPeriod" + self.gte = gte + self.lt = lt diff --git a/linebot/models/limit.py b/linebot/models/limit.py new file mode 100644 index 00000000..7f967674 --- /dev/null +++ b/linebot/models/limit.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""linebot.models.recipient module.""" + +from __future__ import unicode_literals + +from abc import ABCMeta + +from future.utils import with_metaclass + +from .base import Base + + +class Limit(with_metaclass(ABCMeta, Base)): + """Limit. + + https://developers.line.biz/en/reference/messaging-api/#send-narrowcast-message + + """ + + def __init__(self, max=None, **kwargs): + """__init__ method. + + :param kwargs: + """ + super(Limit, self).__init__(**kwargs) + + self.max = max diff --git a/linebot/models/operator.py b/linebot/models/operator.py new file mode 100644 index 00000000..e0861e72 --- /dev/null +++ b/linebot/models/operator.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""linebot.models.filter module.""" + +from __future__ import unicode_literals + +from abc import ABCMeta + +from future.utils import with_metaclass + +from .base import Base + + +class Operator(with_metaclass(ABCMeta, Base)): + """Operator. + + https://developers.line.biz/en/reference/messaging-api/#narrowcast-demographic-filter + + Use logical AND, OR, and NOT operators to combine multiple recipient objects or + demographic filter objects together. + You can specify up to 10 recipient objects or demographic filter objects per request. + """ + + def __init__(self, **kwargs): + """__init__ method. + + :param kwargs: + """ + super(Operator, self).__init__(**kwargs) + + self.type = "operator" + + +class And(Operator): + """And. + + Create a new recipient object or demographic filter object by taking the + logical conjunction (AND) of the specified array of objects. + """ + + def __init__(self, *args, **kwargs): + """__init__ method. + + :param args: + :param kwargs: + """ + super(And, self).__init__(**kwargs) + + setattr(self, 'and', args) + + +class Or(Operator): + """Or. + + Create a new recipient object or demographic filter object by taking the + logical disjunction (OR) of the specified array of objects. + """ + + def __init__(self, *args, **kwargs): + """__init__ method. + + :param args: + :param kwargs: + """ + super(Or, self).__init__(**kwargs) + + setattr(self, 'or', args) + + +class Not(Operator): + """Not. + + Create a new recipient object or demographic filter object that excludes + in the specified object. + """ + + def __init__(self, arg, **kwargs): + """__init__ method. + + :param arg: + :param kwargs: + """ + super(Not, self).__init__(**kwargs) + + setattr(self, 'not', arg) diff --git a/linebot/models/recipient.py b/linebot/models/recipient.py new file mode 100644 index 00000000..4855d91c --- /dev/null +++ b/linebot/models/recipient.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""linebot.models.limit module.""" + +from __future__ import unicode_literals + +from abc import ABCMeta + +from future.utils import with_metaclass + +from .base import Base + + +class Recipient(with_metaclass(ABCMeta, Base)): + """Recipient. + + https://developers.line.biz/en/reference/messaging-api/#narrowcast-recipient + + Recipient objects represent audiences. You can specify recipients based on + a combination of criteria using logical operator objects. + """ + + def __init__(self, **kwargs): + """__init__ method. + + :param kwargs: + """ + super(Recipient, self).__init__(**kwargs) + + self.type = None + + +class AudienceRecipient(Recipient): + """AudienceRecipient.""" + + def __init__(self, group_id=None, **kwargs): + """__init__ method. + + :param int group_id: The audience ID. Create audiences with the + Manage Audience API. + :param kwargs: + """ + super(AudienceRecipient, self).__init__(**kwargs) + + self.type = "audience" + self.audience_group_id = group_id diff --git a/linebot/models/responses.py b/linebot/models/responses.py index 7e699709..aad1f494 100644 --- a/linebot/models/responses.py +++ b/linebot/models/responses.py @@ -276,6 +276,34 @@ def __init__(self, status=None, success=None, **kwargs): self.success = success +class MessageProgressNarrowcastResponse(Base): + """MessageProgressNarrowcastResponse.""" + + def __init__(self, phase=None, success_count=None, failure_count=None, + target_count=None, failed_description=None, error_code=None, **kwargs): + """__init__ method. + + :param str phase: Progress status. One of `waiting`, `sending`, + `succeeded`, or `failed`. + :param int success_count: Number of narrowcast messages sent successful. + :param int failure_count: Number of narrowcast messages sent failed. + :param int target_count: Number of targeted messages sent. + :param str failed_description: Reaseon why narrowcast failed, useful when + phase is `failed`. + :param int error_code: Summary of the error. One of `1` or `2`. `1` + means internal error, whereas `2` indicates too few targets. + :param kwargs: + """ + super(MessageProgressNarrowcastResponse, self).__init__(**kwargs) + + self.phase = phase + self.success_count = success_count + self.failure_count = failure_count + self.target_count = target_count + self.failed_description = failed_description + self.error_code = error_code + + class IssueLinkTokenResponse(Base): """IssueLinkTokenResponse. @@ -428,3 +456,20 @@ def __init__(self, overview=None, messages=None, clicks=None, **kwargs): self.overview = self.get_or_new_from_json_dict(overview, MessageStatistics) self.messages = [self.get_or_new_from_json_dict(it, MessageInsight) for it in messages] self.clicks = [self.get_or_new_from_json_dict(it, ClickInsight) for it in clicks] + + +class NarrowcastResponse(Base): + """NarrowcastResponse. + + https://developers.line.biz/en/reference/messaging-api/#send-narrowcast-message + """ + + def __init__(self, request_id=None, **kwargs): + """__init__ method. + + :param str request_id: Request ID. A unique ID is generated for each request + :param kwargs: + """ + super(NarrowcastResponse, self).__init__(**kwargs) + + self.request_id = request_id diff --git a/tests/api/test_narrowcast_message.py b/tests/api/test_narrowcast_message.py new file mode 100644 index 00000000..0f01d06a --- /dev/null +++ b/tests/api/test_narrowcast_message.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import unicode_literals, absolute_import + +import json +import unittest + +import responses + +from linebot import ( + LineBotApi +) +from linebot.models import ( + TextSendMessage, + Limit, + And, + Or, + Not, + Filter, + GenderFilter, + AppTypeFilter, + AreaFilter, + AgeFilter, + AudienceRecipient, + SubscriptionPeriodFilter, +) + + +class TestNarrowcastMessage(unittest.TestCase): + def setUp(self): + self.tested = LineBotApi('channel_secret') + self.maxDiff = None + self.request_id = 'f70dd685-499a-4231-a441-f24b8d4fba21' + + # test data + self.text_message = TextSendMessage(text='Hello, world') + self.message = [{"type": "text", "text": "Hello, world"}] + + @responses.activate + def test_narrowcast_simple_text_message(self): + responses.add( + responses.POST, + LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/narrowcast', + json={}, status=200, + headers={'X-Line-Request-Id': 'request_id_test'}, + ) + + self.tested.narrowcast( + self.text_message, + recipient=AudienceRecipient(group_id=5614991017776), + filter=Filter(demographic=AgeFilter(gte="age_35", lt="age_40")), + limit=Limit(max=10), + ) + + request = responses.calls[0].request + self.assertEqual( + request.url, + LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/narrowcast') + self.assertEqual(request.method, 'POST') + self.assertEqual( + json.loads(request.body), + { + "messages": self.message, + "recipient": { + 'audienceGroupId': 5614991017776, + 'type': 'audience' + }, + "filter": { + "demographic": { + "type": "age", + "gte": "age_35", + "lt": "age_40" + } + }, + "limit": { + "max": 10 + } + } + ) + + @responses.activate + def test_narrowcast_text_message(self): + responses.add( + responses.POST, + LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/narrowcast', + json={}, status=200, + headers={'X-Line-Request-Id': 'request_id_test'}, + ) + + response = self.tested.narrowcast( + self.text_message, + recipient=And( + AudienceRecipient(group_id=5614991017776), + Not(AudienceRecipient(group_id=4389303728991)) + ), + filter=Filter( + demographic=Or( + And( + GenderFilter(one_of=["male", "female"]), + AgeFilter(gte="age_20", lt="age_25"), + AppTypeFilter(one_of=["android", "ios"]), + AreaFilter(one_of=["jp_23", "jp_05"]), + SubscriptionPeriodFilter(gte="day_7", lt="day_30") + ), + And( + AgeFilter(gte="age_35", lt="age_40"), + Not(GenderFilter(one_of=["male"])) + ) + ) + ), + limit=Limit(max=100), + ) + + request = responses.calls[0].request + self.assertEqual( + request.url, + LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/narrowcast') + self.assertEqual(request.method, 'POST') + self.assertEqual( + json.loads(request.body), + { + "messages": self.message, + "recipient": { + "type": "operator", + "and": [ + { + 'audienceGroupId': 5614991017776, + 'type': 'audience' + }, + { + "type": "operator", + "not": { + "type": "audience", + "audienceGroupId": 4389303728991 + } + } + ] + }, + "filter": { + "demographic": { + "type": "operator", + "or": [ + { + "type": "operator", + "and": [ + { + "type": "gender", + "oneOf": [ + "male", + "female" + ] + }, + { + "type": "age", + "gte": "age_20", + "lt": "age_25" + }, + { + "type": "appType", + "oneOf": [ + "android", + "ios" + ] + }, + { + "type": "area", + "oneOf": [ + "jp_23", + "jp_05" + ] + }, + { + "type": "subscriptionPeriod", + "gte": "day_7", + "lt": "day_30" + } + ] + }, + { + "type": "operator", + "and": [ + { + "type": "age", + "gte": "age_35", + "lt": "age_40" + }, + { + "type": "operator", + "not": { + "type": "gender", + "oneOf": [ + "male" + ] + } + } + ] + } + ] + } + }, + "limit": { + "max": 100 + } + } + ) + self.assertEqual('request_id_test', response.request_id) + + @responses.activate + def test_get_progress_status_narrowcast(self): + responses.add( + responses.GET, + LineBotApi.DEFAULT_API_ENDPOINT + + '/v2/bot/message/progress/narrowcast?requestId={request_id}'.format( + request_id=self.request_id), + json={'phase': 'waiting'}, status=200, + ) + responses.add( + responses.GET, + LineBotApi.DEFAULT_API_ENDPOINT + + '/v2/bot/message/progress/narrowcast?requestId={request_id}'.format( + request_id=self.request_id), + json={ + 'phase': 'succeeded', + 'successCount': 10, + 'failureCount': 0, + 'targetCount': 10, + }, status=200, + ) + + res = self.tested.get_progress_status_narrowcast(self.request_id) + request = responses.calls[0].request + self.assertEqual('GET', request.method) + self.assertEqual( + request.url, + LineBotApi.DEFAULT_API_ENDPOINT + + '/v2/bot/message/progress/narrowcast?requestId={request_id}'.format( + request_id=self.request_id) + ) + + self.assertEqual(res.phase, 'waiting') + + res = self.tested.get_progress_status_narrowcast(self.request_id) + request = responses.calls[1].request + self.assertEqual('GET', request.method) + self.assertEqual( + request.url, + LineBotApi.DEFAULT_API_ENDPOINT + + '/v2/bot/message/progress/narrowcast?requestId={request_id}'.format( + request_id=self.request_id) + ) + + self.assertEqual(res.phase, 'succeeded') + self.assertEqual(res.success_count, 10) + self.assertEqual(res.failure_count, 0) + self.assertEqual(res.target_count, 10) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/models/serialize_test_case.py b/tests/models/serialize_test_case.py index d9fdeeac..7784dcdc 100644 --- a/tests/models/serialize_test_case.py +++ b/tests/models/serialize_test_case.py @@ -36,6 +36,11 @@ class SerializeTestCase(unittest.TestCase): URI = 'uri' LOCATION = 'location' FLEX = 'flex' + GENDER = "gender" + APP_TYPE = "appType" + AGE = "age" + AREA = "area" + SUBSCRIPTION_PERIOD = "subscriptionPeriod" SPACER = 'spacer' SPAN = 'span' BUBBLE = 'bubble' diff --git a/tests/models/test_filter.py b/tests/models/test_filter.py new file mode 100644 index 00000000..cd852c9e --- /dev/null +++ b/tests/models/test_filter.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the 'License'); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import unicode_literals, absolute_import + +import unittest + +from linebot.models import ( + GenderFilter, + AppTypeFilter, + AreaFilter, + AgeFilter, + SubscriptionPeriodFilter +) +from tests.models.serialize_test_case import SerializeTestCase + + +class TestFilter(SerializeTestCase): + def test_gender_filter(self): + arg = { + "one_of": ["male", "female"] + } + self.assertEqual( + self.serialize_as_dict(arg, type=self.GENDER), + GenderFilter(**arg).as_json_dict() + ) + + def test_app_type_filter(self): + arg = { + "one_of": ["ios", "android"] + } + self.assertEqual( + self.serialize_as_dict(arg, type=self.APP_TYPE), + AppTypeFilter(**arg).as_json_dict() + ) + + def test_age_filter(self): + arg = { + "gte": "age_35", + "lt": "age_40", + } + self.assertEqual( + self.serialize_as_dict(arg, type=self.AGE), + AgeFilter(**arg).as_json_dict() + ) + + def test_area_filter(self): + arg = { + "one_of": ["jp_34", "jp_05"] + } + self.assertEqual( + self.serialize_as_dict(arg, type=self.AREA), + AreaFilter(**arg).as_json_dict() + ) + + def test_subscription_period_filter(self): + arg = { + "gte": "day_7", + "lt": "day_30", + } + self.assertEqual( + self.serialize_as_dict(arg, type=self.SUBSCRIPTION_PERIOD), + SubscriptionPeriodFilter(**arg).as_json_dict() + ) + + +if __name__ == '__main__': + unittest.main()