-
Notifications
You must be signed in to change notification settings - Fork 4
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
feat: Add viewer api key provider to integrations module(s) #372
Conversation
☂️ Python Coverage
Overall Coverage
New FilesNo new covered files... Modified Files
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, this looks great! I have a few minor questions.
```python | ||
from shiny import App, ui | ||
from posit.connect import Client | ||
from posit.connect.external.connect_api import ConnectAPIKeyProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you make the import from posit.connect.external import ConnectAPIKeyProvider
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the pattern that databricks and snowflake currently use. Their classes are not necessarily namespaced either. Would you prefer me to add those to the module level in __init__.py
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that would be great! Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess my point was more that this could be confusing since for example the databricks module provides: PositLocalContentCredentialsProvider
which does not denote anything related to databricks so if we throw this along with the snowflake one and this new one, the user wouldnt know what to import.
Can rename them upon import in init but then we have two names for the same thing.
client = Client() | ||
user_session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token") | ||
provider = ConnectAPIKeyProvider(client, user_session_token) | ||
viewer_client = Client(api_key=provider.viewer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should reduce the abstractions further, in a follow up change.
session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
client = Client(access_token=session_token)
Another option is extending the requests
AuthBase
: https://github.com/psf/requests/blob/main/src/requests/auth.py#L69
We currently leverage this here:
class Auth(AuthBase): |
The end result could look like this (with better variable names)...
from posit import connect
auth = connect.auth.Session()
client = connect.Client(auth=auth)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats interesting! The problem I see with your suggestion though is that an exchange needs to happen to retrieve an API key. Client(access_token=session_token)
implies that a user session token can be used to authenticate with the API which it cannot. If this means we just always do the exchange with the token provided in that argument then, I think there could be trouble down the road if we ever wanted to support JWTs to authenticate with the API.
Also, I have been treating Connect API as an oauth integration since that is where that lives in the backend, would it make sense to treat it as a provider like the rest of the integrations or do something special?
Happy to set up that call to discuss this specifically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
connect.auth.Session()
So this would be the way to get the header value of the user session token? I briefly discussed this with Barret as well. I like the idea but this couples the SDK to the various frontends that may want to access this since the session conn's headers is how we get this in shiny apps, but the value lives elsewhere for streamlit apps. Definitely open to ideas on how to tackle this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On your first comment, what if we do something like this to be more explicit:
session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
client = Client.from_user_session_token(session_token)
or
user_session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
viewer_client = ConnectAPIKeyProvider(user_session_token).viewer_client
and the provider ctor could have optional param to pass in client in otherwise I create one using Client() internally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea but this couples the SDK to the various frontends
Good point. I should have said...
token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
auth = connect.auth.Session(token=token)
client = connect.Client(auth=auth)
I also like the factory pattern you suggested.
I suppose the crux of the problem is determining what the correct approach is for our ideal future state. What does it look like to construct a connect.Client
instance when using an OAuth2 integration instead of sniffing the Posit-Connect-User-Session-Token
header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this particular "integration" is an exception to the rule in terms of how it is defined and used compared to the others. This should be the only integration in the near future that would result in an Client() being created since the other integrations are there to use external systems. This one is very particular in the sense that our API only supports API Keys so we need to specify that the user session token is only necessary for the exchange and not used to authenticate with the api directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like the argument made here though that the result the user wants is a client that is scoped to the viewer to do some sort of action on their behalf so this could be simplified slightly to fully abstract away the exchange itself and make it a ConnectAPIViewerClientProvider or something like that. Will give us more room to make big changes in future if need be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pinging me on this @mconflitti-pbc. Reading the Python implementation is helpful; it'd also be helpful for me to chat briefly to make sure I understand the requirements correctly. I wrote up an issue to track this here: posit-dev/connectapi#362
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
First pass at getting the sdk updated to provide an abstracted mechanism to retrieve a viewer api key in a similar way as our other external integrations.
Tests updated to match other providers.
This has been tested with my viewer api key test shiny app successfully.
App code: