diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed737cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope +!.vscode/*.code-snippets +.vscode + +# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode \ No newline at end of file diff --git a/onesignal/device_notification.py b/onesignal/device_notification.py index 93cb65b..a8dc27e 100644 --- a/onesignal/device_notification.py +++ b/onesignal/device_notification.py @@ -42,9 +42,8 @@ def __init__(self, self.include_android_reg_ids = include_android_reg_ids self.include_external_user_ids = include_external_user_ids - def get_data(self): + def get_instance_data(self): return { - **self.get_common_data(), "include_player_ids": self.include_player_ids, "include_email_tokens": self.include_email_tokens, "include_ios_tokens": self.include_ios_tokens, diff --git a/onesignal/filter_notification.py b/onesignal/filter_notification.py index 1219c5c..4eaa209 100644 --- a/onesignal/filter_notification.py +++ b/onesignal/filter_notification.py @@ -23,8 +23,5 @@ def __init__(self, filters, **kwargs): else: self.filters.append(filter.data) - def get_data(self): - return { - **self.get_common_data(), - "filters": self.filters - } + def get_instance_data(self): + return {"filters": self.filters} diff --git a/onesignal/notification.py b/onesignal/notification.py index aafbfb3..5799d27 100644 --- a/onesignal/notification.py +++ b/onesignal/notification.py @@ -1,7 +1,8 @@ import datetime import re +from itertools import chain - +fields_to_ignore_if_no_set = ["delayed_option"] common_notification_paramenters = """contents headings subtitle @@ -72,7 +73,9 @@ class Notification: Attributes: {common_notification_paramenters} - """.format(common_notification_paramenters=common_notification_paramenters) + """.format( + common_notification_paramenters=common_notification_paramenters + ) ANDROID_VISIBILITY_PUBLIC = 1 ANDROID_VISIBILITY_PRIVATE = 0 @@ -83,70 +86,72 @@ class Notification: DELAYED_OPTION_TIMEZONE = "timezone" DELAYED_OPTION_LAST_ACTIVE = "last-active" - def __init__(self, - contents=None, - headings=None, - subtitle=None, - template_id=None, - content_available=None, - mutable_content=None, - email_body=None, - email_subject=None, - email_from_name=None, - email_from_address=None, - data=None, - url=None, - ios_attachments=None, - big_picture=None, - adm_big_picture=None, - chrome_big_picture=None, - buttons=None, - web_buttons=None, - ios_category=None, - android_channel_id=None, - existing_android_channel_id=None, - android_background_layout=None, - small_icon=None, - large_icon=None, - adm_small_icon=None, - adm_large_icon=None, - chrome_web_icon=None, - chrome_web_image=None, - chrome_web_badge=None, - firefox_icon=None, - chrome_icon=None, - ios_sound=None, - android_sound=None, - adm_sound=None, - wp_sound=None, - wp_wns_sound=None, - android_led_color=None, - android_accent_color=None, - android_visibility=None, - ios_badge_type=None, - ios_badge_count=None, - collapse_id=None, - apns_alert=None, - send_after=None, - delayed_option=None, - delivery_time_of_day=None, - ttl=None, - priority=None, - android_group=None, - android_group_message=None, - adm_group=None, - adm_group_message=None, - is_ios=None, - is_android=None, - is_any_web=None, - is_email=None, - is_chrome_web=None, - is_firefox=None, - is_safari=None, - is_wp=None, - is_wp_wns=None, - is_adm=None, - is_chrome=None): + def __init__( + self, + contents=None, + headings=None, + subtitle=None, + template_id=None, + content_available=None, + mutable_content=None, + email_body=None, + email_subject=None, + email_from_name=None, + email_from_address=None, + data=None, + url=None, + ios_attachments=None, + big_picture=None, + adm_big_picture=None, + chrome_big_picture=None, + buttons=None, + web_buttons=None, + ios_category=None, + android_channel_id=None, + existing_android_channel_id=None, + android_background_layout=None, + small_icon=None, + large_icon=None, + adm_small_icon=None, + adm_large_icon=None, + chrome_web_icon=None, + chrome_web_image=None, + chrome_web_badge=None, + firefox_icon=None, + chrome_icon=None, + ios_sound=None, + android_sound=None, + adm_sound=None, + wp_sound=None, + wp_wns_sound=None, + android_led_color=None, + android_accent_color=None, + android_visibility=None, + ios_badge_type=None, + ios_badge_count=None, + collapse_id=None, + apns_alert=None, + send_after=None, + delayed_option=None, + delivery_time_of_day=None, + ttl=None, + priority=None, + android_group=None, + android_group_message=None, + adm_group=None, + adm_group_message=None, + is_ios=None, + is_android=None, + is_any_web=None, + is_email=None, + is_chrome_web=None, + is_firefox=None, + is_safari=None, + is_wp=None, + is_wp_wns=None, + is_adm=None, + is_chrome=None, + ): self.id = None @@ -157,9 +162,10 @@ def __init__(self, self.check_type(content_available, "content_available", bool) self.check_type(mutable_content, "mutable_content", bool) - assert contents or content_available is True or template_id, \ - ("'contents' is required unless content_available=True " - "or template_id is set") + assert contents or content_available is True or template_id, ( + "'contents' is required unless content_available=True " + "or template_id is set" + ) self.content_data = { "contents": contents, @@ -176,16 +182,14 @@ def __init__(self, self.check_type(email_from_address, "email_from_address", str) assert not email_from_address or re.search( - r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", - email_from_address - ), \ - "'email_from_address' is not a valid mail address" + r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email_from_address + ), "'email_from_address' is not a valid mail address" self.email_content_data = { "email_body": email_body, "email_subject": email_subject, "email_from_name": email_from_name, - "email_from_address": email_from_address + "email_from_address": email_from_address, } self.check_type(data, "data", dict) @@ -201,7 +205,7 @@ def __init__(self, "ios_attachments": ios_attachments, "big_picture": big_picture, "adm_big_picture": adm_big_picture, - "chrome_big_picture": chrome_big_picture + "chrome_big_picture": chrome_big_picture, } self.check_type(buttons, "buttons", list) @@ -211,13 +215,11 @@ def __init__(self, self.action_buttons_data = { "buttons": buttons, "web_buttons": web_buttons, - "ios_category": ios_category + "ios_category": ios_category, } - self.check_type(existing_android_channel_id, - "existing_android_channel_id", str) - self.check_type(android_background_layout, - "android_background_layout", dict) + self.check_type(existing_android_channel_id, "existing_android_channel_id", str) + self.check_type(android_background_layout, "android_background_layout", dict) self.check_type(small_icon, "small_icon", str) self.check_type(large_icon, "large_icon", str) self.check_type(adm_small_icon, "adm_small_icon", str) @@ -232,8 +234,9 @@ def __init__(self, self.check_type(android_led_color, "android_led_color", str) self.check_type(android_accent_color, "android_accent_color", str) self.check_type(android_visibility, "android_visibility", int) - assert android_visibility in [1, 0, -1] or not android_visibility, \ - "'android_visibility' has to 1, 0 or -1" + assert ( + android_visibility in [1, 0, -1] or not android_visibility + ), "'android_visibility' has to 1, 0 or -1" self.check_type(ios_badge_type, "ios_badge_type", str) self.check_type(ios_badge_count, "ios_badge_count", int) self.check_type(collapse_id, "collapse_id", str) @@ -263,13 +266,14 @@ def __init__(self, "ios_badgeType": ios_badge_type, "ios_badgeCount": ios_badge_count, "collapse_id": collapse_id, - "apns_alert": apns_alert + "apns_alert": apns_alert, } - assert isinstance(send_after, datetime.datetime) or not send_after, \ - "'send_after' has to be an instance of datetime.datetime" + assert ( + isinstance(send_after, datetime.datetime) or not send_after + ), "'send_after' has to be an instance of datetime.datetime" if send_after: - send_after = send_after.strftime('%Y-%m-%d %H:%M:%S %Z') + send_after = send_after.strftime("%Y-%m-%d %H:%M:%S %Z") self.check_type(delayed_option, "delayed_option", str) self.check_type(delivery_time_of_day, "delivery_time_of_day", str) self.check_type(ttl, "ttl", int) @@ -280,7 +284,7 @@ def __init__(self, "delayed_option": delayed_option, "delivery_time_of_day": delivery_time_of_day, "ttl": ttl, - "priority": priority + "priority": priority, } self.check_type(android_group, "android_group", str) @@ -292,7 +296,7 @@ def __init__(self, "android_group": android_group, "android_group_message": android_group_message, "adm_group": adm_group, - "adm_group_message": adm_group_message + "adm_group_message": adm_group_message, } self.check_type(is_ios, "is_ios", bool) @@ -317,16 +321,16 @@ def __init__(self, "isWP": is_wp, "isWP_WNS": is_wp_wns, "isAdm": is_adm, - "isChrome": is_chrome + "isChrome": is_chrome, } def check_type(self, variable, variable_string, type): class_of_variable = str(type().__class__)[8:-2] if variable is not None: - assert isinstance(variable, type), \ - "'{}' has to be a {}".format( - variable_string, class_of_variable) + assert isinstance(variable, type), "'{}' has to be a {}".format( + variable_string, class_of_variable + ) def get_common_data(self): return { @@ -337,5 +341,25 @@ def get_common_data(self): **self.appearance_data, **self.delivery_data, **self.grouping_and_collapsing_data, - **self.platform_to_deliver_to_data + **self.platform_to_deliver_to_data, + } + + def get_instance_data(self): + raise NotImplementedError( + "Method 'get_instance_data' of '{}' needs to be implemented".format( + self.__class__.__name__ + ) + ) + + def get_data(self): + data = dict( + chain({**self.get_instance_data()}.items(), self.get_common_data().items()) + ) + + data = { + key: value + for key, value in data.items() + if value is not None or key not in fields_to_ignore_if_no_set } + + return data diff --git a/onesignal/segment_notification.py b/onesignal/segment_notification.py index a46a830..87d41e6 100644 --- a/onesignal/segment_notification.py +++ b/onesignal/segment_notification.py @@ -24,8 +24,8 @@ def __init__(self, self.included_segments = included_segments self.excluded_segments = excluded_segments - def get_data(self): - return dict(chain({ + def get_instance_data(self): + return { "included_segments": self.included_segments, "excluded_segments": self.excluded_segments - }.items(), self.get_common_data().items())) + } diff --git a/tests/test_core.py b/tests/test_core.py index ed1f8e2..d1512a1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -32,7 +32,7 @@ def test_cancel(client): def test_details(client): notification = onesignal.SegmentNotification( contents={"en": "Hello World"}, - included_segments=onesignal.SegmentNotification.ALL + included_segments=[onesignal.SegmentNotification.ALL] ) client.send(notification) details = client.details(notification)