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 support for get_app() with App authentication #2549

Merged
merged 5 commits into from Jun 16, 2023

Conversation

chantra
Copy link
Contributor

@chantra chantra commented Jun 9, 2023

An authenticated app can call /app to get details about itself.

Authentication must be done using jwt for authentication or it will fail with an error like https://gist.github.com/chantra/209819389b2dec6cc6e03a13d301f5e5

@chantra
Copy link
Contributor Author

chantra commented Jun 9, 2023

@EnricoMi , I am not super satisfied of the current solution. Let me know how you would see this better integrated.

It seems to me that wether through GithubIntegration, or Github main object, we need to detect that the authentication will be done using JWT. In MainClass.py, I use the token_type as a proxy for this.
I don't think GithubIntegration is exclusively JWT auth based, so the same should probably apply.
In any cases, the current solution is wonky. Happy to iterate on it.

@codecov-commenter
Copy link

codecov-commenter commented Jun 9, 2023

Codecov Report

Patch coverage: 92.85% and no project coverage change.

Comparison is base (6a21761) 98.50% compared to head (88df8f4) 98.50%.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2549   +/-   ##
=======================================
  Coverage   98.50%   98.50%           
=======================================
  Files         128      128           
  Lines       12753    12757    +4     
=======================================
+ Hits        12562    12566    +4     
  Misses        191      191           
Impacted Files Coverage Δ
github/Auth.py 93.27% <66.66%> (ø)
github/AccessToken.py 100.00% <100.00%> (ø)
github/AuthenticatedUser.py 99.04% <100.00%> (ø)
github/GithubIntegration.py 96.61% <100.00%> (+0.24%) ⬆️
github/MainClass.py 99.27% <100.00%> (ø)
github/Repository.py 97.13% <100.00%> (ø)

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

github/MainClass.py Show resolved Hide resolved
tests/GithubApp.py Outdated Show resolved Hide resolved
@EnricoMi EnricoMi changed the title support get_app() for App authentication mode Add support for get_app() with App authentication Jun 9, 2023
github/MainClass.py Outdated Show resolved Hide resolved
tests/GithubApp.py Outdated Show resolved Hide resolved
github/MainClass.py Show resolved Hide resolved
github/GithubIntegration.py Show resolved Hide resolved
@chantra
Copy link
Contributor Author

chantra commented Jun 9, 2023

Thanks @EnricoMi

Applied your recommendations in the second commit. If that works out, feel free to squash it into the first one.

@chantra
Copy link
Contributor Author

chantra commented Jun 12, 2023

running manually, this is failing due to utcnow deprecation:

tests/GithubApp.py .F                                                                                                                                                                                                                                                                                            [100%]

======================================================================================================================================================= FAILURES =======================================================================================================================================================
________________________________________________________________________________________________________________________________________ GithubAppAuth.testGetAuthenticatedApp _________________________________________________________________________________________________________________________________________

self = <tests.GithubApp.GithubAppAuth testMethod=testGetAuthenticatedApp>

    def testGetAuthenticatedApp(self):
        with self.assertWarns(DeprecationWarning) as warning:
            app = self.g.get_app()

>           self.assertWarning(
                warning,
                "Argument slug is mandatory, calling this method without the slug argument is deprecated, please use github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
            )

tests/GithubApp.py:111:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/Framework.py:334: in assertWarning
    self.assertWarnings(warning, expected)
tests/Framework.py:337: in assertWarnings
    self.assertEqual(len(warning.warnings), len(expecteds))
E   AssertionError: 2 != 1
=================================================================================================================================================== warnings summary ===================================================================================================================================================
github/Repository.py:3476
  /Users/chantra/devel/PyGithub/github/Repository.py:3476: DeprecationWarning: datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.now(datetime.UTC).
    def mark_notifications_as_read(self, last_read_at=datetime.datetime.utcnow()):

github/AuthenticatedUser.py:1232
  /Users/chantra/devel/PyGithub/github/AuthenticatedUser.py:1232: DeprecationWarning: datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.now(datetime.UTC).
    def mark_notifications_as_read(self, last_read_at=datetime.datetime.utcnow()):

tests/GithubApp.py::GithubApp::testGetPublicApp
  /Users/chantra/devel/PyGithub/.py312/lib/python3.12/site-packages/httpretty/core.py:1077: DeprecationWarning: datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.now(datetime.UTC).
    now = datetime.utcnow()

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=============================================================================================================================================== short test summary info ================================================================================================================================================
FAILED tests/GithubApp.py::GithubAppAuth::testGetAuthenticatedApp - AssertionError: 2 != 1

While cI can fix the deprecation for pygithub, httpretty will still generate a warning.

@chantra
Copy link
Contributor Author

chantra commented Jun 12, 2023

I am introducing assertWarningIn to be able to check for a specific warning in the list of warnings.

@chantra chantra requested a review from EnricoMi June 12, 2023 18:52
@EnricoMi
Copy link
Collaborator

running manually, this is failing due to utcnow deprecation:

Then use the recommended datetime.now(datetime.UTC).

Copy link
Collaborator

@EnricoMi EnricoMi left a comment

Choose a reason for hiding this comment

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

Almost there!

tests/Framework.py Show resolved Hide resolved

class GithubAppAuth(Framework.TestCase):
def setUp(self):
self.appAuthMode = True
Copy link
Collaborator

Choose a reason for hiding this comment

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

This sets auth mode for all subsequent tests, this should only be used by tests.conftest.pytest_configure via command line arguments like --record.

Please use the same way of authentication as used in tests/GithubIntegration.py:

        auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
        g = github.Github(auth=auth)
        app = g.get_app()

You can import APP_ID and PRIVATE_KEY from tests/GithubIntegration.py.

So this GithubAppAuth class and def setUp can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, I removed this code from the PR then as it is not required. This feature would then be orthogonal to this PR.

@@ -410,6 +437,10 @@ def activateJWTAuthMode(): # pragma no cover (Function useful only when recordi
BasicTestCase.jwtAuthMode = True


def activateAppAuthMode(): # pragma no cover (Function useful only when recording new tests, not used during automated tests)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this needs to be added to tests.conftest.pytest_configure to be useful

github/GithubIntegration.py Show resolved Hide resolved
"github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
category=DeprecationWarning,
)
return GithubIntegration(auth=self.__requester._Requester__auth).get_app()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please rebase and access Requester.auth:

Suggested change
return GithubIntegration(auth=self.__requester._Requester__auth).get_app()
return GithubIntegration(auth=self.__requester.auth).get_app()

@chantra
Copy link
Contributor Author

chantra commented Jun 13, 2023

running manually, this is failing due to utcnow deprecation:

Then use the recommended datetime.now(datetime.UTC).

Per my commit message in c3a2d35 , datetime.UTC was introduced in 3.11, datetime.timezone.utc works across the board of python 3.X tested in this project.

@chantra
Copy link
Contributor Author

chantra commented Jun 13, 2023

ok, I force-pushed an update, but TL;DR:

  • removed appAuthMode from Framework as it is not actively used after I followed your suggestion
  • access .auth directly through requester
  • test that we assert when provided a non AppAuth to GithubIntegration

@chantra chantra force-pushed the ghapp_getapp branch 3 times, most recently from bf72672 to f09c6b8 Compare June 14, 2023 04:29
@chantra chantra requested a review from EnricoMi June 14, 2023 05:01
Comment on lines 111 to 120
self.assertWarningIn(
warning,
"Argument slug is mandatory, calling this method without the slug argument is deprecated, please use github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
self.assertWarningIn(
warning,
"Argument slug is mandatory, calling this method without the slug argument is deprecated, please use github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
)
self.assertWarningIn(
warning,
"Argument slug is mandatory, calling this method without the slug argument is deprecated, "
"please use github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
)

Comment on lines 62 to 66
with self.assertRaisesRegex(
AssertionError,
"GithubIntegration requires github.Auth.AppAuth authentication, not .*",
):
github.GithubIntegration(auth=self.oauth_token)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
with self.assertRaisesRegex(
AssertionError,
"GithubIntegration requires github.Auth.AppAuth authentication, not .*",
):
github.GithubIntegration(auth=self.oauth_token)
with self.assertRaises(
AssertionError,
f"GithubIntegration requires github.Auth.AppAuth authentication, not {type(auth)}",
):
github.GithubIntegration(auth=auth)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch on the auth vs self.oauth_token :)

)
if GithubCredentials.app_id and GithubCredentials.app_private_key
else None
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to remove this existing code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had added this, do you want me to re-add it even if this is not used here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This diff tells me, this was there before and your pull-request removes it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the last bit left. I'd say there is no need to remove this code by this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hum, I was sure I had added that when I was creating a new test class.... I may have it mixed up with something else. Will re-introduce.

with self.assertWarns(DeprecationWarning) as warning:
app = g.get_app()

self.assertWarningIn(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
self.assertWarningIn(
self.assertWarning(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem is that httpretty/core generates another warning, see #2549 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Which Python version are you using?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 108 to 111
with self.assertWarns(DeprecationWarning) as warning:
app = g.get_app()

self.assertWarningIn(
Copy link
Collaborator

Choose a reason for hiding this comment

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

To be honest, I don't like assertWarningIn, because it may hide unexpected warnings that we want to fail on.

Having httpretty warn here is annoying, but we can disable that and everything works as expected:

Suggested change
with self.assertWarns(DeprecationWarning) as warning:
app = g.get_app()
self.assertWarningIn(
with self.assertWarns(DeprecationWarning) as warning:
# we ignore warnings from httpretty dependency
import warnings
warnings.filterwarnings("ignore", module="httpretty")
app = g.get_app()
self.assertWarning(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh great, I did not know we could filter the warnings. Will change. Thanks

Comment on lines 339 to 344
def assertWarnings(self, warning, *expecteds):
self.assertEqual(len(warning.warnings), len(expecteds))
for message, expected in zip(warning.warnings, expecteds):
self.assertIsInstance(message, warnings.WarningMessage)
self.assertIsInstance(message.message, DeprecationWarning)
self.assertEqual(message.message.args, (expected,))
Copy link
Collaborator

Choose a reason for hiding this comment

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

An error like

 E   AssertionError: 2 != 1

is not really helpful, lets improve this:

Suggested change
def assertWarnings(self, warning, *expecteds):
self.assertEqual(len(warning.warnings), len(expecteds))
for message, expected in zip(warning.warnings, expecteds):
self.assertIsInstance(message, warnings.WarningMessage)
self.assertIsInstance(message.message, DeprecationWarning)
self.assertEqual(message.message.args, (expected,))
def assertWarnings(self, warning, *expecteds):
actual = [(type(message), type(message.message), message.message.args) for message in warning.warnings]
expected = [(warnings.WarningMessage, DeprecationWarning, (expected,)) for expected in expecteds]
self.assertSequenceEqual(actual, expected)

An authenticated app can call `/app` to get details about itself.

Authentication must be done using jwt for authentication or it will fail with
an error like https://gist.github.com/chantra/209819389b2dec6cc6e03a13d301f5e5
@chantra chantra force-pushed the ghapp_getapp branch 2 times, most recently from 6d5e5eb to 8b1df3f Compare June 16, 2023 05:11
@chantra
Copy link
Contributor Author

chantra commented Jun 16, 2023

rebased, re-ported utcnow change and got rid of assertWarningIn.

@chantra chantra requested a review from EnricoMi June 16, 2023 05:16
…e can re-use it across tests.

* assert deprecation warning and transparently call GithubIntegration.get_app() when no slug is provided
* check that deprecation warning happens in test case
* assert that auth is an AppAuth in GithubIntegration and test it
https://docs.python.org/dev/whatsnew/3.12.html

> datetime.datetime’s utcnow() and utcfromtimestamp() are deprecated and will be removed in a future version. Instead, use timezone-aware objects to represent datetimes in UTC: respectively, call now() and fromtimestamp() with the tz parameter set to datetime.UTC. (Contributed by Paul Ganssle in gh-103857.)

`datetime.UTC is new in 3.11, but is essentially an alias to datetime.timezone.utc

This change replaces the occurences of utcnow() with datetime.timezone.utc so it works on older python releases.
In some cases, some deprecation messages may be generated by third-party libraries depedending
on the python version we are using.

This is a problem when we want to insure that a specific message is raised.

This change introduce `assertWarningIn`, which will check if a specific deprecation warning
is in the list of warnings.
@chantra
Copy link
Contributor Author

chantra commented Jun 16, 2023

ok, if I did not mess up, the CI should be green in a few and all feedback addressed :D

Copy link
Collaborator

@EnricoMi EnricoMi left a comment

Choose a reason for hiding this comment

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

LGTM

@EnricoMi
Copy link
Collaborator

Thank you for your patience!

@EnricoMi EnricoMi merged commit 6d4b6d1 into PyGithub:master Jun 16, 2023
10 checks passed
@chantra
Copy link
Contributor Author

chantra commented Jun 16, 2023

Thanks to you too!

@chantra chantra deleted the ghapp_getapp branch June 16, 2023 19:03
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

3 participants