Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add aioapns version of APNS #696

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open

Add aioapns version of APNS #696

wants to merge 17 commits into from

Conversation

pomali
Copy link

@pomali pomali commented Nov 2, 2023

Since version python 3.10+ is not supported by apns2 (because of dependency on hyper) we add aioapns version of sending APNs notifications.

We add installation extra [APNS_ASYNC] that installs aioapns and use new version of service if aioapns is installed. Tests are also conditional on installed modules/version of python.

Related to

Since version python 3.10+ is not supported by `apns2` (because of dependency on `hyper`) we add `aioapns` version of sending APNs notifications.

We add installation extra `[APNS_ASYNC]` that installs aioapns and use new version of service if `aioapns` is installed. Tests are also conditional on installed modules/version of python.
@pomali
Copy link
Author

pomali commented Nov 2, 2023

There are some (maybe) controversial decisions, feel free to discuss them

  • choice of aioapns - it looks like only library with active development
  • supporting both apns2 and aioapns - because we don't want to break older code
  • deciding on which version to use in models.py - sure we can move it to apns.py and move apns2 code to apns_legacy.py

@sivasankarankb
Copy link

@pomali You have left print() statements in the code. See apns_async.py.

Copy link

codecov bot commented Dec 8, 2023

Codecov Report

Attention: Patch coverage is 11.20690% with 103 lines in your changes are missing coverage. Please review.

Project coverage is 64.66%. Comparing base (0f79181) to head (7500caa).

Current head 7500caa differs from pull request most recent head f8c192a

Please upload reports for the commit f8c192a to get more accurate results.

Files Patch % Lines
push_notifications/apns_async.py 4.62% 103 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #696      +/-   ##
==========================================
- Coverage   70.39%   64.66%   -5.73%     
==========================================
  Files          27       28       +1     
  Lines        1101     1214     +113     
  Branches      180      199      +19     
==========================================
+ Hits          775      785      +10     
- Misses        288      391     +103     
  Partials       38       38              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@itayAmza
Copy link

@pomali, any progress for this one?
I had the apns2 0.7.2 requires PyJWT<2.0.0,>=1.4.0, but you have pyjwt 2.1.0 which is incompatible issue, and pointing to your branch (as a temporary solution) completely solves it.

@pomali
Copy link
Author

pomali commented Jan 25, 2024

I'm not sure what's missing, maybe someone should review it? And then there is code coverage check failing, but most of failing lines are comments, so I am not sure how important it is.

Is there anyone who could point me in right direction?

@itayAmza glad that it helped :)

@itayAmza
Copy link

@jamaalscarlett following this one can you help us figure out who can help review this PR?

@50-Course
Copy link
Member

hopefully, I get to have a look into both PRs, tonight. Thank you for the notification @itayAmza

@jamaalscarlett
Copy link
Member

  1. I can review the PR, but I have never used APNS. Has someone actually run this with an ios/macos app?
  2. Regarding the support for both apns2 and aioapns, I think we should add a deprecation warning with this release then completely remove apns2 with the next release.

@pomali
Copy link
Author

pomali commented Mar 11, 2024

I ran it with an iOS simulator, but a second pair of eyes with a real device would be good idea

@ssyberg
Copy link

ssyberg commented Mar 13, 2024

I ran it with an iOS simulator, but a second pair of eyes with a real device would be good idea

I just tried this and got this error when trying to send a test message via apns:

ModuleNotFoundError at /admin/push_notifications/apnsdevice/
No module named 'apns2'
Request Method:	POST
Request URL:	https://local.a24-vod:8060/admin/push_notifications/apnsdevice/
Django Version:	4.2
Exception Type:	ModuleNotFoundError
Exception Value:	
No module named 'apns2'

collapse_id: str = None,
aps_kwargs: dict = {},
message_kwargs: dict = {},
notification_request_kwargs: dict = {},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since notification_request_kwargs argument has a default value and it's content is changed in the function logic, the default state can change during execution. If any previous call has set values expiration, priority or collapse_id, these are persisted to any next call and if the next call does not have these values or has the value set to None, the values are still passed to sent NotificationRequest. A simple test case to demonstrate this is to send first message with expiration and second without expiration set. The value from first call is passed to second call NotificationRequest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved it by creating copy of notification_request_kwargs, but if you consider it code smell I could rework it to having default argument None and replacing them in function with {}. Added your test (test_apns_send_messages_different_priority)

@kupsum
Copy link

kupsum commented Mar 17, 2024

There is a possible bug with default argument pollution. Below test case can demonstrate the issue. I've also added a review comment about this.

	@mock.patch("push_notifications.apns_async.APNs", autospec=True)
	def test_apns_send_messages_different_priority(self, mock_apns):
		self._create_devices(["abc", "def"])
		device_1 = APNSDevice.objects.get(registration_id="abc")
		device_2 = APNSDevice.objects.get(registration_id="def")

		device_1.send_message("Hello world 1", expiration=time.time() + 1, priority=5, collapse_id="1")
		args_1, _ = mock_apns.return_value.send_notification.call_args

		device_2.send_message("Hello world 2")
		args_2, _ = mock_apns.return_value.send_notification.call_args

		req = args_1[0]
		self.assertEqual(req.device_token, "abc")
		self.assertEqual(req.message["aps"]["alert"], "Hello world 1")
		self.assertAlmostEqual(req.time_to_live, 1, places=-1)
		self.assertEqual(req.priority, 5)
		self.assertEqual(req.collapse_key, "1")

		reg_2 = args_2[0]
		self.assertEqual(reg_2.device_token, "def")
		self.assertEqual(reg_2.message["aps"]["alert"], "Hello world 2")
		self.assertIsNone(reg_2.time_to_live, "No time to live should be specified")
		self.assertIsNone(reg_2.priority, "No priority should be specified")
		self.assertIsNone(reg_2.collapse_key, "No collapse key should be specified")

@pomali
Copy link
Author

pomali commented Mar 18, 2024

I ran it with an iOS simulator, but a second pair of eyes with a real device would be good idea

I just tried this and got this error when trying to send a test message via apns:

ModuleNotFoundError at /admin/push_notifications/apnsdevice/
No module named 'apns2'
Request Method:	POST
Request URL:	https://local.a24-vod:8060/admin/push_notifications/apnsdevice/
Django Version:	4.2
Exception Type:	ModuleNotFoundError
Exception Value:	
No module named 'apns2'

do you have aioapns installed? because that is condition on which we use the new code

added mention to README

pip install django-push-notifications[APNS_ASYNC]

@pomali
Copy link
Author

pomali commented Mar 18, 2024

There is a possible bug with default argument pollution. Below test case can demonstrate the issue. I've also added a review comment about this.

Thanks for catching this, I added your test and fixed the issue.

@kmasuhr
Copy link

kmasuhr commented Mar 18, 2024

I think it is worth to add python 3.10 as testing target in CI, since it should enable support for version 3.10

python-version: ['3.6', '3.7', '3.8', '3.9']

@ssyberg
Copy link

ssyberg commented Mar 21, 2024

I ran it with an iOS simulator, but a second pair of eyes with a real device would be good idea

I just tried this and got this error when trying to send a test message via apns:

ModuleNotFoundError at /admin/push_notifications/apnsdevice/
No module named 'apns2'
Request Method:	POST
Request URL:	https://local.a24-vod:8060/admin/push_notifications/apnsdevice/
Django Version:	4.2
Exception Type:	ModuleNotFoundError
Exception Value:	
No module named 'apns2'

do you have aioapns installed? because that is condition on which we use the new code

added mention to README

pip install django-push-notifications[APNS_ASYNC]

I'm pretty sure I did when I tested it, but wouldn't that be handled by pip/piptools?

@50-Course
Copy link
Member

Hello @kmasuhr,

I have just updated this PR to bump the version of python being used in CI.
However, it would seem one of its transitive dependencies is not compatible with Python 3.10 - precisely, the hyper==0.7.0
HTTP Client.

Given that the hyper library is not actively maintained, I would like to propose that we replace it with a maintained alternative.
I am open to suggestions, but I would recommend using httpx as a good replacement.

@pomali
Copy link
Author

pomali commented Mar 26, 2024

I'm pretty sure I did when I tested it, but wouldn't that be handled by pip/piptools?

@ssyberg it should be handled by pip, but it's optional dependency, so maybe it didn't get installed?

@rib3
Copy link

rib3 commented Mar 29, 2024

I have just updated this PR to bump the version of python being used in CI. However, it would seem one of its transitive dependencies is not compatible with Python 3.10 - precisely, the hyper==0.7.0 HTTP Client.

Given that the hyper library is not actively maintained, I would like to propose that we replace it with a maintained alternative. I am open to suggestions, but I would recommend using httpx as a good replacement.

hyper is a dependency of apns2.
This PR adds support for aioapns as an alternative apple push notification library.
(because of the problems with apns2/hyper)

There are some github issues with additional details / background linked in the PR description.

@ssyberg
Copy link

ssyberg commented Apr 8, 2024

I have just updated this PR to bump the version of python being used in CI. However, it would seem one of its transitive dependencies is not compatible with Python 3.10 - precisely, the hyper==0.7.0 HTTP Client.
Given that the hyper library is not actively maintained, I would like to propose that we replace it with a maintained alternative. I am open to suggestions, but I would recommend using httpx as a good replacement.

hyper is a dependency of apns2. This PR adds support for aioapns as an alternative apple push notification library. (because of the problems with apns2/hyper)

There are some github issues with additional details / background linked in the PR description.

If I'm understanding correctly, the inclusion of hyper in this PR is only to stay backwards compatible with apns2 for people who don't want to upgrade to using aioapns in which case they need to stay <3.10 (which if they are using apns2 I think is implied). IMO this could be noted in the docs but shouldn't block merge, a lot of people are waiting for this to get merged and it's blocking dev for anyone on later version setups.

Copy link

@kupsum kupsum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments when trying to install this in Python 3.11 system

@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you consider adding 3.11 too?

Suggested change
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepted change

@@ -22,6 +22,7 @@ classifiers =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you consider adding Python 3.11? Either way, Python 3.10 was missing

Suggested change
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepted change

tox.ini Outdated
@@ -41,6 +45,8 @@ deps =
dj32: Django>=3.2,<3.3
dj40: Django>=4.0,<4.0.5
dj405: Django>=4.0.5,<4.1
dj42: Django>=4.2,<4.3
dj42: aioapns>=3.1,<3.2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would make send to have apns2 for Python 3.9 or less and aioapns Python 3.10+

Suggested change
dj42: aioapns>=3.1,<3.2
py{36,37,38,39}: apns2
py{310,311}: aioapns>=3.1,<3.2

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated deps, removed apns2 from default leaving it only in py{36,37,38,39}

from apns2.client import NotificationPriority
from push_notifications.apns import _apns_send
from push_notifications.exceptions import APNSUnsupportedPriority
except AttributeError:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apns2 and aioapns are not supported together with conflict

The conflict is caused by:
    aioapns 3.1 depends on pyjwt>=2.0.0
    apns2 0.7.2 depends on PyJWT<2.0.0 and >=1.4.0

If apns2 is not installed, ModuleNotFoundError is raised.

Suggested change
except AttributeError:
expect (AttributeError, ModuleNotFoundError):

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepted change

class APNSModelTestCase(TestCase):
from push_notifications.exceptions import APNSError
from push_notifications.models import APNSDevice
except AttributeError:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apns2 and aioapns are not supported together with conflict

The conflict is caused by:
    aioapns 3.1 depends on pyjwt>=2.0.0
    apns2 0.7.2 depends on PyJWT<2.0.0 and >=1.4.0

If apns2 is not installed, ModuleNotFoundError is raised.

Suggested change
except AttributeError:
expect (AttributeError, ModuleNotFoundError):

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepted change

tox.ini Outdated
Comment on lines 6 to 7
py{38,39,310}-dj{40,405}
py311-dj42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Django 4.2 supports still Python 3.8

Suggested change
py{38,39,310}-dj{40,405}
py311-dj42
py{38,39,310,311}-dj{40,405,42}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepted change


from . import models
from .conf import get_manager
from .exceptions import APNSServerError

ErrFunc = Optional[Callable[[NotificationRequest, NotificationResult], Awaitable[None]]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of office right now, Awaitable is that some form of Future result?

Copy link
Member

@50-Course 50-Course May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#Update: Interesting,

https://docs.python.org/3/library/typing.html#annotating-callable-objects

I have always took a different approach to this!

Comment on lines +172 to +174
notification_request_kwargs_out["time_to_live"] = expiration - int(
time.time()
)
Copy link
Member

@50-Course 50-Course May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ruff botched this formatting in here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 looks good to me, can we have a test for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants