pltr-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pltr/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
pltr/auth/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Authentication module for pltr."""
pltr/auth/base.py ADDED
@@ -0,0 +1,49 @@
1
+ """
2
+ Base authentication classes for Palantir Foundry.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Dict, Any
7
+
8
+
9
+ class AuthProvider(ABC):
10
+ """Abstract base class for authentication providers."""
11
+
12
+ @abstractmethod
13
+ def get_client(self) -> Any:
14
+ """Return an authenticated Foundry client."""
15
+ pass
16
+
17
+ @abstractmethod
18
+ def validate(self) -> bool:
19
+ """Validate authentication credentials."""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def get_config(self) -> Dict[str, Any]:
24
+ """Return authentication configuration."""
25
+ pass
26
+
27
+
28
+ class AuthError(Exception):
29
+ """Base exception for authentication errors."""
30
+
31
+ pass
32
+
33
+
34
+ class InvalidCredentialsError(AuthError):
35
+ """Raised when credentials are invalid."""
36
+
37
+ pass
38
+
39
+
40
+ class MissingCredentialsError(AuthError):
41
+ """Raised when required credentials are missing."""
42
+
43
+ pass
44
+
45
+
46
+ class ProfileNotFoundError(AuthError):
47
+ """Raised when a profile cannot be found."""
48
+
49
+ pass
pltr/auth/manager.py ADDED
@@ -0,0 +1,129 @@
1
+ """
2
+ Authentication manager for getting configured Foundry clients.
3
+ """
4
+
5
+ from typing import Optional, Any
6
+
7
+ from .base import AuthProvider, ProfileNotFoundError, MissingCredentialsError
8
+ from .storage import CredentialStorage
9
+ from .token import TokenAuthProvider
10
+ from .oauth import OAuthClientProvider
11
+ from ..config.profiles import ProfileManager
12
+
13
+
14
+ class AuthManager:
15
+ """Manages authentication and provides configured Foundry clients."""
16
+
17
+ def __init__(self):
18
+ """Initialize authentication manager."""
19
+ self.storage = CredentialStorage()
20
+ self.profile_manager = ProfileManager()
21
+
22
+ def get_client(self, profile: Optional[str] = None) -> Any:
23
+ """
24
+ Get an authenticated Foundry client for a profile.
25
+
26
+ Args:
27
+ profile: Profile name (uses active profile if not specified)
28
+
29
+ Returns:
30
+ Configured FoundryClient instance
31
+
32
+ Raises:
33
+ ProfileNotFoundError: If profile doesn't exist
34
+ MissingCredentialsError: If credentials are incomplete
35
+ """
36
+ # Determine which profile to use
37
+ if not profile:
38
+ profile = self.profile_manager.get_active_profile()
39
+ if not profile:
40
+ raise ProfileNotFoundError(
41
+ "No profile specified and no default profile configured. "
42
+ "Run 'pltr configure configure' to set up authentication."
43
+ )
44
+
45
+ # Get credentials for the profile
46
+ try:
47
+ credentials = self.storage.get_profile(profile)
48
+ except ProfileNotFoundError:
49
+ raise ProfileNotFoundError(
50
+ f"Profile '{profile}' not found. "
51
+ f"Run 'pltr configure configure --profile {profile}' to set it up."
52
+ )
53
+
54
+ # Create appropriate auth provider
55
+ provider = self._create_provider(credentials)
56
+
57
+ # Return authenticated client
58
+ return provider.get_client()
59
+
60
+ def _create_provider(self, credentials: dict) -> AuthProvider:
61
+ """
62
+ Create an auth provider from credentials.
63
+
64
+ Args:
65
+ credentials: Dictionary containing auth configuration
66
+
67
+ Returns:
68
+ Configured AuthProvider instance
69
+
70
+ Raises:
71
+ MissingCredentialsError: If auth type is unknown or credentials incomplete
72
+ """
73
+ auth_type = credentials.get("auth_type")
74
+ host = credentials.get("host")
75
+
76
+ if not auth_type:
77
+ raise MissingCredentialsError(
78
+ "Authentication type not specified in credentials"
79
+ )
80
+ if not host:
81
+ raise MissingCredentialsError("Host URL not specified in credentials")
82
+
83
+ if auth_type == "token":
84
+ token = credentials.get("token")
85
+ if not token:
86
+ raise MissingCredentialsError("Token not found in credentials")
87
+ return TokenAuthProvider(token=token, host=host)
88
+
89
+ elif auth_type == "oauth":
90
+ client_id = credentials.get("client_id")
91
+ client_secret = credentials.get("client_secret")
92
+ if not client_id or not client_secret:
93
+ raise MissingCredentialsError("OAuth client credentials incomplete")
94
+ return OAuthClientProvider(
95
+ client_id=client_id,
96
+ client_secret=client_secret,
97
+ host=host,
98
+ scopes=credentials.get("scopes", []),
99
+ )
100
+
101
+ else:
102
+ raise MissingCredentialsError(f"Unknown authentication type: {auth_type}")
103
+
104
+ def get_current_profile(self) -> Optional[str]:
105
+ """
106
+ Get the name of the currently active profile.
107
+
108
+ Returns:
109
+ Profile name or None if no profile is configured
110
+ """
111
+ return self.profile_manager.get_active_profile()
112
+
113
+ def validate_profile(self, profile: Optional[str] = None) -> bool:
114
+ """
115
+ Validate that a profile's credentials work.
116
+
117
+ Args:
118
+ profile: Profile name (uses active profile if not specified)
119
+
120
+ Returns:
121
+ True if credentials are valid
122
+
123
+ Raises:
124
+ Exception if validation fails
125
+ """
126
+ self.get_client(profile)
127
+ # The actual validation will happen when we try to use the client
128
+ # in the verify command
129
+ return True
pltr/auth/oauth.py ADDED
@@ -0,0 +1,83 @@
1
+ """
2
+ OAuth2 client authentication for Palantir Foundry.
3
+ """
4
+
5
+ import os
6
+ from typing import Dict, Optional, Any
7
+
8
+ from .base import AuthProvider, InvalidCredentialsError, MissingCredentialsError
9
+
10
+
11
+ class OAuthClientProvider(AuthProvider):
12
+ """Authentication provider using OAuth2 client credentials."""
13
+
14
+ def __init__(
15
+ self,
16
+ client_id: Optional[str] = None,
17
+ client_secret: Optional[str] = None,
18
+ host: Optional[str] = None,
19
+ scopes: Optional[list] = None,
20
+ ):
21
+ """
22
+ Initialize OAuth2 client authentication.
23
+
24
+ Args:
25
+ client_id: OAuth2 client ID
26
+ client_secret: OAuth2 client secret
27
+ host: Foundry host URL (e.g., 'https://your-stack.palantirfoundry.com')
28
+ scopes: List of scopes to request
29
+ """
30
+ self.client_id = client_id or os.environ.get("FOUNDRY_CLIENT_ID")
31
+ self.client_secret = client_secret or os.environ.get("FOUNDRY_CLIENT_SECRET")
32
+ self.host = host or os.environ.get("FOUNDRY_HOST")
33
+ self.scopes = scopes or []
34
+
35
+ if not self.client_id:
36
+ raise MissingCredentialsError(
37
+ "Client ID is required for OAuth authentication"
38
+ )
39
+ if not self.client_secret:
40
+ raise MissingCredentialsError(
41
+ "Client secret is required for OAuth authentication"
42
+ )
43
+ if not self.host:
44
+ raise MissingCredentialsError(
45
+ "Host URL is required for OAuth authentication"
46
+ )
47
+
48
+ def get_client(self) -> Any:
49
+ """Return an authenticated Foundry client."""
50
+ from foundry_sdk import FoundryClient, ConfidentialClientAuth
51
+
52
+ auth = ConfidentialClientAuth(
53
+ client_id=self.client_id, # type: ignore
54
+ client_secret=self.client_secret, # type: ignore
55
+ scopes=self.scopes,
56
+ )
57
+ return FoundryClient(auth=auth, hostname=self.host)
58
+
59
+ def validate(self) -> bool:
60
+ """Validate authentication credentials."""
61
+ try:
62
+ self.get_client()
63
+ # Try to make a simple API call to validate the credentials
64
+ # This would need to be replaced with an actual validation call
65
+ # when the SDK provides one
66
+ return True
67
+ except Exception as e:
68
+ raise InvalidCredentialsError(f"OAuth validation failed: {e}")
69
+
70
+ def get_config(self) -> Dict[str, Any]:
71
+ """Return authentication configuration."""
72
+ return {
73
+ "type": "oauth",
74
+ "host": self.host,
75
+ "client_id": self.client_id,
76
+ "client_secret": "***"
77
+ + (
78
+ self.client_secret[-4:]
79
+ if self.client_secret and len(self.client_secret) > 4
80
+ else ""
81
+ ),
82
+ "scopes": self.scopes,
83
+ }
pltr/auth/storage.py ADDED
@@ -0,0 +1,87 @@
1
+ """
2
+ Secure credential storage using keyring.
3
+ """
4
+
5
+ import json
6
+ import keyring
7
+ from typing import Dict, Any
8
+
9
+ from .base import ProfileNotFoundError
10
+
11
+
12
+ class CredentialStorage:
13
+ """Manages secure storage of authentication credentials."""
14
+
15
+ SERVICE_NAME = "pltr-cli"
16
+
17
+ def __init__(self):
18
+ """Initialize credential storage."""
19
+ self.keyring = keyring
20
+
21
+ def save_profile(self, profile: str, credentials: Dict[str, Any]) -> None:
22
+ """
23
+ Save credentials for a profile.
24
+
25
+ Args:
26
+ profile: Profile name
27
+ credentials: Dictionary of credentials to store
28
+ """
29
+ # Convert credentials to JSON string for storage
30
+ credentials_json = json.dumps(credentials)
31
+ self.keyring.set_password(self.SERVICE_NAME, profile, credentials_json)
32
+
33
+ def get_profile(self, profile: str) -> Dict[str, Any]:
34
+ """
35
+ Retrieve credentials for a profile.
36
+
37
+ Args:
38
+ profile: Profile name
39
+
40
+ Returns:
41
+ Dictionary of stored credentials
42
+
43
+ Raises:
44
+ ProfileNotFoundError: If profile doesn't exist
45
+ """
46
+ credentials_json = self.keyring.get_password(self.SERVICE_NAME, profile)
47
+ if not credentials_json:
48
+ raise ProfileNotFoundError(f"Profile '{profile}' not found")
49
+
50
+ return json.loads(credentials_json)
51
+
52
+ def delete_profile(self, profile: str) -> None:
53
+ """
54
+ Delete a profile's credentials.
55
+
56
+ Args:
57
+ profile: Profile name
58
+ """
59
+ try:
60
+ self.keyring.delete_password(self.SERVICE_NAME, profile)
61
+ except keyring.errors.PasswordDeleteError:
62
+ raise ProfileNotFoundError(f"Profile '{profile}' not found")
63
+
64
+ def list_profiles(self) -> list:
65
+ """
66
+ List all available profiles.
67
+
68
+ Returns:
69
+ List of profile names
70
+ """
71
+ # Note: keyring doesn't provide a direct way to list all usernames
72
+ # for a service, so we'll need to store this separately in config
73
+ # This is a placeholder that will be integrated with config/profiles.py
74
+ return []
75
+
76
+ def profile_exists(self, profile: str) -> bool:
77
+ """
78
+ Check if a profile exists.
79
+
80
+ Args:
81
+ profile: Profile name
82
+
83
+ Returns:
84
+ True if profile exists, False otherwise
85
+ """
86
+ credentials = self.keyring.get_password(self.SERVICE_NAME, profile)
87
+ return credentials is not None
pltr/auth/token.py ADDED
@@ -0,0 +1,55 @@
1
+ """
2
+ Token-based authentication for Palantir Foundry.
3
+ """
4
+
5
+ import os
6
+ from typing import Dict, Optional, Any
7
+
8
+ from .base import AuthProvider, InvalidCredentialsError, MissingCredentialsError
9
+
10
+
11
+ class TokenAuthProvider(AuthProvider):
12
+ """Authentication provider using bearer tokens."""
13
+
14
+ def __init__(self, token: Optional[str] = None, host: Optional[str] = None):
15
+ """
16
+ Initialize token authentication.
17
+
18
+ Args:
19
+ token: Bearer token for authentication
20
+ host: Foundry host URL (e.g., 'https://your-stack.palantirfoundry.com')
21
+ """
22
+ self.token = token or os.environ.get("FOUNDRY_TOKEN")
23
+ self.host = host or os.environ.get("FOUNDRY_HOST")
24
+
25
+ if not self.token:
26
+ raise MissingCredentialsError("Token is required for authentication")
27
+ if not self.host:
28
+ raise MissingCredentialsError("Host URL is required for authentication")
29
+
30
+ def get_client(self) -> Any:
31
+ """Return an authenticated Foundry client."""
32
+ from foundry_sdk import FoundryClient, UserTokenAuth
33
+
34
+ auth = UserTokenAuth(token=self.token) # type: ignore
35
+ return FoundryClient(auth=auth, hostname=self.host)
36
+
37
+ def validate(self) -> bool:
38
+ """Validate authentication credentials."""
39
+ try:
40
+ self.get_client()
41
+ # Try to make a simple API call to validate the token
42
+ # This would need to be replaced with an actual validation call
43
+ # when the SDK provides one
44
+ return True
45
+ except Exception as e:
46
+ raise InvalidCredentialsError(f"Token validation failed: {e}")
47
+
48
+ def get_config(self) -> Dict[str, Any]:
49
+ """Return authentication configuration."""
50
+ return {
51
+ "type": "token",
52
+ "host": self.host,
53
+ "token": "***"
54
+ + (self.token[-4:] if self.token and len(self.token) > 4 else ""),
55
+ }
pltr/cli.py ADDED
@@ -0,0 +1,54 @@
1
+ """
2
+ Main CLI entry point for pltr.
3
+ """
4
+
5
+ import typer
6
+ from typing_extensions import Annotated
7
+
8
+ from pltr import __version__
9
+ from pltr.commands import configure, verify, dataset
10
+
11
+ app = typer.Typer(
12
+ name="pltr",
13
+ help="Command-line interface for Palantir Foundry APIs",
14
+ no_args_is_help=True,
15
+ )
16
+
17
+ # Add subcommands
18
+ app.add_typer(configure.app, name="configure", help="Manage authentication profiles")
19
+ app.add_typer(verify.app, name="verify", help="Verify authentication")
20
+ app.add_typer(dataset.app, name="dataset", help="Manage datasets")
21
+
22
+
23
+ def version_callback(value: bool):
24
+ """Show version and exit."""
25
+ if value:
26
+ typer.echo(f"pltr {__version__}")
27
+ raise typer.Exit()
28
+
29
+
30
+ @app.callback()
31
+ def main(
32
+ version: Annotated[
33
+ bool, typer.Option("--version", callback=version_callback, help="Show version")
34
+ ] = False,
35
+ ):
36
+ """
37
+ Command-line interface for Palantir Foundry APIs.
38
+
39
+ Built on top of the official foundry-platform-sdk, pltr provides
40
+ intuitive commands for dataset management, ontology operations,
41
+ SQL queries, and more.
42
+ """
43
+ pass
44
+
45
+
46
+ @app.command()
47
+ def hello():
48
+ """Test command to verify CLI is working."""
49
+ typer.echo("Hello from pltr! 🚀")
50
+ typer.echo("CLI is working correctly.")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ app()
@@ -0,0 +1 @@
1
+ """CLI commands for pltr."""
@@ -0,0 +1,151 @@
1
+ """
2
+ Configuration commands for pltr CLI.
3
+ """
4
+
5
+ import typer
6
+ from typing import Optional
7
+ from rich.console import Console
8
+ from rich.prompt import Prompt, Confirm
9
+
10
+ from ..auth.storage import CredentialStorage
11
+ from ..auth.base import ProfileNotFoundError
12
+ from ..config.profiles import ProfileManager
13
+
14
+ app = typer.Typer()
15
+ console = Console()
16
+
17
+
18
+ @app.command()
19
+ def configure(
20
+ profile: Optional[str] = typer.Option(
21
+ "default", "--profile", "-p", help="Profile name"
22
+ ),
23
+ auth_type: Optional[str] = typer.Option(
24
+ None, "--auth-type", help="Authentication type (token or oauth)"
25
+ ),
26
+ host: Optional[str] = typer.Option(None, "--host", help="Foundry host URL"),
27
+ token: Optional[str] = typer.Option(
28
+ None, "--token", help="Bearer token (for token auth)"
29
+ ),
30
+ client_id: Optional[str] = typer.Option(
31
+ None, "--client-id", help="OAuth client ID"
32
+ ),
33
+ client_secret: Optional[str] = typer.Option(
34
+ None, "--client-secret", help="OAuth client secret"
35
+ ),
36
+ ):
37
+ """Configure authentication for Palantir Foundry."""
38
+ storage = CredentialStorage()
39
+ profile_manager = ProfileManager()
40
+
41
+ # Ensure profile is not None
42
+ if not profile:
43
+ profile = "default"
44
+
45
+ # Check if profile already exists
46
+ if storage.profile_exists(profile):
47
+ if not Confirm.ask(f"Profile '{profile}' already exists. Overwrite?"):
48
+ console.print("[yellow]Configuration cancelled.[/yellow]")
49
+ raise typer.Exit()
50
+
51
+ # Interactive mode if no auth type specified
52
+ if not auth_type:
53
+ auth_type = Prompt.ask(
54
+ "Authentication type", choices=["token", "oauth"], default="token"
55
+ )
56
+
57
+ # Get host URL
58
+ if not host:
59
+ host = Prompt.ask(
60
+ "Foundry host URL", default="https://your-stack.palantirfoundry.com"
61
+ )
62
+
63
+ credentials = {"auth_type": auth_type, "host": host}
64
+
65
+ if auth_type == "token":
66
+ # Token authentication
67
+ if not token:
68
+ token = Prompt.ask("Bearer token", password=True)
69
+ credentials["token"] = token
70
+
71
+ elif auth_type == "oauth":
72
+ # OAuth authentication
73
+ if not client_id:
74
+ client_id = Prompt.ask("OAuth client ID")
75
+ if not client_secret:
76
+ client_secret = Prompt.ask("OAuth client secret", password=True)
77
+
78
+ credentials["client_id"] = client_id
79
+ credentials["client_secret"] = client_secret
80
+
81
+ # Save credentials
82
+ storage.save_profile(profile, credentials)
83
+ profile_manager.add_profile(profile)
84
+
85
+ # Set as default if it's the first profile
86
+ if len(profile_manager.list_profiles()) == 1:
87
+ profile_manager.set_default(profile)
88
+
89
+ console.print(f"[green]✓[/green] Profile '{profile}' configured successfully")
90
+
91
+
92
+ @app.command()
93
+ def list_profiles():
94
+ """List all configured profiles."""
95
+ profile_manager = ProfileManager()
96
+ profiles = profile_manager.list_profiles()
97
+ default = profile_manager.get_default()
98
+
99
+ if not profiles:
100
+ console.print("[yellow]No profiles configured.[/yellow]")
101
+ console.print("Run 'pltr configure' to set up your first profile.")
102
+ return
103
+
104
+ console.print("[bold]Configured profiles:[/bold]")
105
+ for profile in profiles:
106
+ if profile == default:
107
+ console.print(f" * {profile} [green](default)[/green]")
108
+ else:
109
+ console.print(f" {profile}")
110
+
111
+
112
+ @app.command()
113
+ def set_default(
114
+ profile: str = typer.Argument(..., help="Profile name to set as default"),
115
+ ):
116
+ """Set a profile as the default."""
117
+ profile_manager = ProfileManager()
118
+
119
+ if profile not in profile_manager.list_profiles():
120
+ console.print(f"[red]Error:[/red] Profile '{profile}' not found")
121
+ raise typer.Exit(1)
122
+
123
+ profile_manager.set_default(profile)
124
+ console.print(f"[green]✓[/green] Profile '{profile}' set as default")
125
+
126
+
127
+ @app.command()
128
+ def delete(
129
+ profile: str = typer.Argument(..., help="Profile name to delete"),
130
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
131
+ ):
132
+ """Delete a profile."""
133
+ storage = CredentialStorage()
134
+ profile_manager = ProfileManager()
135
+
136
+ if profile not in profile_manager.list_profiles():
137
+ console.print(f"[red]Error:[/red] Profile '{profile}' not found")
138
+ raise typer.Exit(1)
139
+
140
+ if not force:
141
+ if not Confirm.ask(f"Delete profile '{profile}'?"):
142
+ console.print("[yellow]Deletion cancelled.[/yellow]")
143
+ raise typer.Exit()
144
+
145
+ try:
146
+ storage.delete_profile(profile)
147
+ profile_manager.remove_profile(profile)
148
+ console.print(f"[green]✓[/green] Profile '{profile}' deleted")
149
+ except ProfileNotFoundError:
150
+ console.print(f"[red]Error:[/red] Could not delete profile '{profile}'")
151
+ raise typer.Exit(1)