Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ def example_main():
storage_provider=DemoStorageProvider(),
)

# In contrast to an in-memory only application that must initialize a login every
# time, an app with persistent storage can skip this when it is not needed.
if not plsdk_auth.is_initialized():
plsdk_auth.user_login(allow_open_browser=True)
# In contrast to in-memory applications that must initialize a login every
# time, an app with persistent storage can skip user prompts when they
# are not needed.
# This helper will prompt the user only when it is necessary.
plsdk_auth.ensure_initialized(allow_open_browser=True,
allow_tty_prompt=True)

# Create a Planet SDK object that uses the loaded auth session.
sess = planet.Session(plsdk_auth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ def example_main():
save_state_to_storage=True,
)

# In contrast to an in-memory only application that must initialize a login every
# time, an app with persistent storage can skip this when it is not needed.
if not plsdk_auth.is_initialized():
plsdk_auth.user_login(allow_open_browser=True)
# In contrast to in-memory applications that must initialize a login every
# time, an app with persistent storage can skip user prompts when they
# are not needed.
# This helper will prompt the user only when it is necessary.
plsdk_auth.ensure_initialized(allow_open_browser=True,
allow_tty_prompt=True)

# Create a Planet SDK object that uses the loaded auth session.
sess = planet.Session(plsdk_auth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def example_main():
# specified file that was created with older versions of the SDK
plsdk_auth = planet.Auth.from_file("legacy_api_key_file.json")

# Explicit login is not required for API key use. The above sufficient.
# Explicit login is not required for API key use. The above is sufficient.
# plsdk_auth.user_login()

# Create a Planet SDK object that uses the loaded auth session
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ def example_main():
# is false, the state will only be persistent in memory and the
# user will need to login each time the application is run.
plsdk_auth = planet.Auth.from_profile("planet-user",
save_state_to_storage=False)
save_state_to_storage=True)

if not plsdk_auth.is_initialized():
plsdk_auth.user_login(allow_open_browser=True, allow_tty_prompt=True)
plsdk_auth.ensure_initialized(allow_open_browser=True,
allow_tty_prompt=True)

# Create a Planet SDK object that uses the loaded auth session.
sess = planet.Session(plsdk_auth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def example_main():
)
sys.exit(99)

# Alternatively, an application can call this, which will attempt to
# initialize the session if it is not already initialized.
# plsdk_auth.ensure_initialized(allow_open_browser=True, allow_tty_prompt=True)

# Create a Planet SDK object that uses the loaded auth session.
sess = planet.Session(plsdk_auth)
pl = planet.Planet(sess)
Expand Down
76 changes: 71 additions & 5 deletions planet/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import typing
import warnings
import httpx
from typing import List

from .auth_builtins import _ProductionEnv, _OIDC_AUTH_CLIENT_CONFIG__USER_SKEL, _OIDC_AUTH_CLIENT_CONFIG__M2M_SKEL
import planet_auth
Expand Down Expand Up @@ -132,7 +131,7 @@ def from_profile(
def from_oauth_user_auth_code(
client_id: str,
callback_url: str,
requested_scopes: typing.Optional[List[str]] = None,
requested_scopes: typing.Optional[typing.List[str]] = None,
save_state_to_storage: bool = True,
profile_name: typing.Optional[str] = None,
storage_provider: typing.Optional[
Expand Down Expand Up @@ -193,7 +192,7 @@ def from_oauth_user_auth_code(
@staticmethod
def from_oauth_user_device_code(
client_id: str,
requested_scopes: typing.Optional[List[str]] = None,
requested_scopes: typing.Optional[typing.List[str]] = None,
save_state_to_storage: bool = True,
profile_name: typing.Optional[str] = None,
storage_provider: typing.Optional[
Expand Down Expand Up @@ -255,7 +254,7 @@ def from_oauth_user_device_code(
def from_oauth_m2m(
client_id: str,
client_secret: str,
requested_scopes: typing.Optional[List[str]] = None,
requested_scopes: typing.Optional[typing.List[str]] = None,
save_state_to_storage: bool = True,
profile_name: typing.Optional[str] = None,
storage_provider: typing.Optional[
Expand Down Expand Up @@ -456,7 +455,64 @@ def is_initialized(self) -> bool:
user based sessions, this means that a login has been performed
or saved login session data has been located. For M2M and API Key
sessions, this should be true if keys or secrets have been
properly configured.
properly configured. The network will not be probed, and the user
will not be prompted by this method.

Expired sessions or invalid credentials will not be detected.
See `ensure_initialized()` for a method that will check the validity
of sessions.
"""

@abc.abstractmethod
def ensure_initialized(
self,
allow_open_browser: typing.Optional[bool] = False,
allow_tty_prompt: typing.Optional[bool] = False,
) -> None:
"""
Do everything necessary to ensure the auth context is ready for use,
while still biasing towards just-in-time operations and not making
unnecessary network requests or prompts for user interaction.

This can be more complex than it sounds given the variations in
possible session state. Clients may be initialized with active
sessions, initialized with stale but still valid sessions,
initialized with invalid or expired sessions, or completely
uninitialized. The process taken to ensure client readiness with
as little user disruption as possible is as follows:

1. If the client has been logged in and has a non-expired
session, the client will be considered ready without prompting
the user or probing the network. This will not require user
interaction.
2. If the client has not been logged in and is an M2M client,
the client will be considered ready without prompting
the user or probing the network. This will not require
user interaction. Login will be delayed until it is required.
3. If the client has been logged in and has an expired access token,
the network will be probed to attempt a refresh of the session.
This should not require user interaction. If refresh fails,
the user will be prompted to perform a fresh login, requiring
user interaction.
4. If the client has never been logged in and is user interactive
client (verses an M2M client), a user interactive login will be
initiated.

There still may be conditions where we believe we are
ready, but requests will still ultimately fail. Saved secrets for M2M
clients could be wrong, or the user could be denied by the API.

When a user interactive login is required, the client must specify
whether a local web browser may be opened and/or whether the TTY
may be used to prompt the user.

If the auth context cannot be made ready, an exception will be raised.

Parameters:
allow_open_browser: specify whether login is permitted to open
a local browser window.
allow_tty_prompt: specify whether login is permitted to request
input from the terminal.
"""


Expand Down Expand Up @@ -496,5 +552,15 @@ def device_user_login_complete(self, login_initialization_info: dict):
def is_initialized(self) -> bool:
return self._plauth.request_authenticator_is_ready()

def ensure_initialized(
self,
allow_open_browser: typing.Optional[bool] = False,
allow_tty_prompt: typing.Optional[bool] = False,
) -> None:
return self._plauth.ensure_request_authenticator_is_ready(
allow_open_browser=allow_open_browser,
allow_tty_prompt=allow_tty_prompt,
)


AuthType = Auth
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies = [
"pyjwt>=2.1",
"tqdm>=4.56",
"typing-extensions",
"planet-auth>=2.1.0",
"planet-auth>=2.3.0a1759946524",
]
readme = "README.md"
requires-python = ">=3.10"
Expand Down