Python SDK for Piper Agent Credential Management. Secure, flexible, and simple.
Project description
🛡️ Pyper SDK: Simple, Secure, & Flexible Access to User-Approved Secrets
Empower your Python applications to securely use your users' API keys and credentials — with user consent at the core, and minimal hassle for you. Pyper SDK intelligently handles secret retrieval from multiple sources, so you can focus on building your app.
✨ Why Pyper SDK?
-
🔐 Offload Secret Management: Let the Piper system (when enabled by the user) securely store and manage user secrets like OpenAI keys, Notion tokens, and database credentials.
-
🛡️ Enhance Trust & Security: Users explicitly grant your app permission via their Piper dashboard for specific secrets. The SDK primarily aims to vend short-lived tokens for services that support them, with an option for securely exchanging for raw secrets when necessary. The agent's own credentials (like its client secret) are managed on the Piper backend, never needing to be handled or stored by the client-side SDK.
-
💡 Simplified Developer Experience: A clean API (
PiperClientandget_secret()) with intelligent, configurable, multi-tiered secret acquisition makes integration straightforward. -
🔄 Flexible Context Handling: Supports automatic local context detection via the Piper Link desktop app (for users interacting directly) and explicit context IDs for remote or non-interactive services.
-
🧩 Graceful Fallbacks & User Guidance (Enhanced in v0.7.1):
- Built-in, configurable fallbacks to environment variables and local agent configuration files ensure your application remains functional even if Piper is not used or encounters an issue.
- New in v0.7.1: The SDK now enables more resilient applications by allowing non-raising secret acquisition and provides methods to generate user-friendly, actionable advice to help users resolve configuration problems (like missing grants or disconnected Piper Link).
🔁 Core Flow (with Pyper SDK v0.7.1+)
-
User Setup (Handled by Piper System, if used):
-
Users store their secrets (e.g., API keys) in their secure Piper account.
-
Through the Piper interface (e.g., at
https://agentpiper.com/secrets), users grant your application ("Agent") permission to access specific secrets, which your application will request by a logical variable name (e.g., "NOTION_API_KEY"). -
For applications running on the user's desktop, the Piper Link app can provide the necessary local context to the SDK automatically.
-
-
Your Application Flow (Using Pyper SDK):
- Initialize
PiperClient: Configure it once with your agent'sclient_idand your preferred secret acquisition strategy. You can also check if the client initialized correctly.
from piper_sdk import PiperClient, PiperSecretAcquisitionError, PiperConfigError import os # For environment variable example try: piper = PiperClient( client_id="YOUR_AGENT_CLIENT_ID", # Only client_id is needed now! # Configure your preferred acquisition strategy: use_piper=True, # Attempt to use Piper system (default: True) attempt_local_discovery=True, # If use_piper=True, try to find Piper Link GUI (default: True) # piper_link_instance_id=os.environ.get("PIPER_INSTANCE_ID_HOST_PROVIDED"), # Optional explicit instance_id fallback_to_env=True, # Fallback to OS environment variables (default: True) # env_variable_prefix="MY_AGENT_", # Optional prefix for env vars # env_variable_map={"NOTION_API_KEY": "AGENT_NOTION_KEY"}, # Optional custom mapping fallback_to_local_config=False, # Fallback to a local JSON config file (default: False) # local_config_file_path="~/.my_agent_secrets.json" # Path if local config fallback is enabled # piper_ui_grant_page_url="https://your.piper-ui.com/grants" # Optional: Override default grant page URL ) if piper.client_initialization_ok: print("PiperClient ready.") else: print("ERROR: PiperClient did not initialize correctly. Cannot fetch secrets reliably.") # Get advice for the initialization error (v0.7.1+) init_advice = piper.get_resolution_advice("") # Empty var_name for init errors if init_advice: print("\nClient Initialization Problem:\n", init_advice) # Agent might decide to exit or operate in a limited mode. except Exception as e: # Catch any other unexpected error during init (though most config errors are now stored) print(f"Unexpected critical error initializing PiperClient: {e}") # This is for truly unexpected issues, not regular config validation handled by client_initialization_ok
-
Request Secrets: Call
piper.get_secret("YOUR_VARIABLE_NAME"). The SDK intelligently tries the configured sources in order:-
Piper System (if
use_piper=True) -
Environment Variables (if
fallback_to_env=Trueand Piper skipped or failed) -
Local Agent Configuration File (if
fallback_to_local_config=Trueand previous tiers skipped or failed)
-
-
Handle Secret Info or Failures (v0.7.1+):
# Assuming piper client was initialized successfully (piper.client_initialization_ok was True) if 'piper' in locals() and piper and piper.client_initialization_ok: # --- Option 1: Default raising behavior (good for critical secrets at startup) --- try: api_key_info = piper.get_secret("CRITICAL_API_KEY") api_key = api_key_info['value'] print(f"Got CRITICAL_API_KEY from: {api_key_info['source']}") # Use the api_key except PiperSecretAcquisitionError as e: print(f"CRITICAL ERROR: Could not acquire secret for '{e.variable_name}'. Agent might need to exit or limit functionality.") # str(e) is very informative for logs: print("Detailed Error:\n", e) # For more user-friendly advice (v0.7.1+): advice = piper.get_resolution_advice(e.variable_name, error_object=e) if advice: print("\nUser Advice to resolve CRITICAL_API_KEY issue:\n", advice) # You might present this advice to the user or log it for support. except PiperConfigError as e: print(f"CRITICAL SDK CONFIG ERROR during get_secret: {e}") # --- Option 2: Non-raising behavior (good for non-critical secrets or deferring errors - v0.7.1+) --- db_pass_info = piper.get_secret("DATABASE_PASSWORD", fetch_raw_secret=True, raise_on_failure=False) if db_pass_info and db_pass_info.get("value"): db_password = db_pass_info['value'] print(f"Got DB Password from: {db_pass_info['source']}") # Use db_password else: # Secret not found, or another error occurred. db_pass_info contains details. print(f"WARNING: Could not acquire DATABASE_PASSWORD (non-critical).") if db_pass_info and db_pass_info.get("error_object"): # The error_object is the actual PiperError instance error_for_db_pass = db_pass_info['error_object'] # print(f" Error details for dev: {error_for_db_pass}") # For logging # Generate user-friendly advice. This is typically done when the user # attempts an action that *requires* DATABASE_PASSWORD. advice = piper.get_resolution_advice("DATABASE_PASSWORD", error_object=error_for_db_pass) if advice: print("\nGuidance for DATABASE_PASSWORD issue (if user tries to use related feature):\n", advice) # In a real app, an LLM or UI would present this advice at the point of need. else: print(" Could not determine specific reason for DATABASE_PASSWORD failure from SDK (no error object found).")
- Initialize
✨ Graceful Startup & User Guidance (New in v0.7.1)
Pyper SDK v0.7.1 introduces powerful features to help your application start up smoothly even if secrets aren't immediately available, and to provide clear, actionable guidance to your users:
-
✅ Non-Raising Secret Acquisition: The
piper.get_secret()method now accepts araise_on_failure=Falseparameter. When set, instead of raising an exception, it returns a detailed dictionary upon failure, allowing your application to continue running and handle the missing secret gracefully (e.g., at the point a feature requiring it is used). -
🗣️ Actionable User Advice: A new method,
piper.get_resolution_advice(variable_name, error_object?), intelligently inspects acquisition failures (or client initialization errors) and generates user-friendly, multi-line advice. This helps users understand why a secret is missing (e.g., "Grant needed," "Piper Link not connected," "Environment variable not set") and how to fix it, often providing direct links to the Piper UI. -
🔍 Inspectable Initialization:
PiperClientnow has aclient_initialization_ok(bool) attribute. After creating the client, check this attribute. IfFalse, critical configuration errors occurred (e.g., missingclient_id). You can usepiper.get_resolution_advice("")(with an empty variable name) for guidance on client setup issues.
This enables you to build a more resilient and user-friendly experience, deferring error handling until a secret is actually needed and then guiding the user effectively.
🚀 Installation
pip install pyper-sdk
Requires Python 3.7+
🛠️ Complete PiperClient Configuration
The PiperClient can be initialized with the following parameters to fine-tune its behavior. Most parameters have sensible defaults.
-
client_id: str: (Required) Your agent's client ID obtained from the Piper Console. This is the primary identifier for your agent. -
use_piper: bool(default:True): Whether to attempt secret retrieval via the Piper system. IfFalse, the SDK will only try configured fallback methods. -
piper_link_instance_id: Optional[str](default:None): An explicit Piper LinkinstanceIdprovided by the host environment. If you provide this, the SDK will use it for Piper operations and skip local discovery for this specific ID. -
attempt_local_discovery: bool(default:True): Ifuse_piperisTrueand no explicitpiper_link_instance_idis active for a call (either from constructor orget_secret), the SDK will attempt to discover a running Piper Link application on the local machine (typically athttp://localhost:31477) to get aninstanceId. Set toFalseto disable this automatic discovery. -
fallback_to_env: bool(default:True): If the Piper tier is skipped or fails, setting this toTrueenables the SDK to attempt to retrieve the secret from OS environment variables. -
env_variable_prefix: str(default:""): An optional prefix for environment variable names. For example, ifvariable_nameis "API_KEY" andenv_variable_prefixis "MYAPP_", the SDK will look for "MYAPP_API_KEY". -
env_variable_map: Optional[Dict[str, str]](default:None): For more control over environment variable names, provide a dictionary mapping your logicalvariable_name(e.g., "DATABASE_USER") to the exact environment variable name you want the SDK to check (e.g.,{"DATABASE_USER": "CUSTOM_APP_DB_USER_ENV"}). This map takes precedence over theenv_variable_prefixand default normalization for the mapped names. -
fallback_to_local_config: bool(default:False): If prior tiers (Piper, Environment Variables) are skipped or fail, setting this toTrueenables the SDK to attempt to retrieve the secret from a local JSON configuration file. -
local_config_file_path: Optional[str](default:None): Required iffallback_to_local_configisTrue. Specifies the absolute or user-expanded path (e.g.,"~/.my_agent/secrets.json") to the local secrets file. The file should be a flat JSON object where keys are the logical variable names. -
piper_ui_grant_page_url: Optional[str](default:"https://agentpiper.com/secrets"): The base URL of your Piper system's UI page where users manage grants. The SDK uses this to construct helpful deep links (e.g., inPiperGrantNeededErrorandget_resolution_advice) to guide users if a grant is missing. -
requests_session: Optional[requests.Session](default:None): Allows advanced users to provide a customrequests.Sessionobject. This can be useful for custom SSL configurations, proxies, or default headers for all SDK's HTTP requests. Most users will not need this.
(Note: For developers needing to point the SDK at alternative backend service URLs for testing or specialized deployments, additional override parameters are available in the PiperClient constructor. These are not typically needed for general use and can be found by inspecting the PiperClient.__init__ signature in the source code.)
Key PiperClient Attributes & Methods (v0.7.1+):
-
piper.client_initialization_ok: bool: (Read-only attribute) AfterPiperClient(...), check this.Trueif critical configurations were met,Falseotherwise (e.g., missingclient_id). -
piper.get_secret(variable_name: str, piper_link_instance_id_for_call: Optional[str] = None, fetch_raw_secret: bool = False, raise_on_failure: bool = True) -> Optional[Dict[str, Any]]: The primary method to retrieve secrets. See examples above. -
piper.get_last_error_for_variable(variable_name: str) -> Optional[PiperError]: Ifget_secretwas called withraise_on_failure=Falseand an error occurred forvariable_name, this method retrieves that storedPiperErrorobject. ReturnsNoneif no error is stored. -
piper.get_resolution_advice(variable_name: str, error_object: Optional[PiperError] = None) -> Optional[str]: Generates a user-friendly, multi-line string with actionable advice for resolving secret acquisition or client initialization issues.- If
error_objectis provided, it generates advice for that error. - Else, if
variable_nameis provided, it usesget_last_error_for_variable(variable_name). - Else (e.g., if
variable_nameis""), it uses any stored client initialization error. ReturnsNoneif no relevant error is found.
- If
Advanced Usage: Dynamic Grant & Context Handling (v0.7.1+)
For applications requiring more dynamic responses to changes in user grants or Piper Link context without restarting, the following methods can be utilized:
-
piper.is_grant_still_active(variable_name: str, ...) -> bool: Proactively checks if a specific grant forvariable_nameis still active in the Piper system. This involves a lightweight call to the backend. It's useful for validating a previously fetched secret before use, especially if external grant revocations are possible.- Returns
Trueif the grant is active,Falseotherwise. - If the grant is found to be inactive and
store_error_if_inactive=True(default), it updates the internal error state for that variable, allowingget_resolution_advice()to provide accurate, current guidance.
- Returns
-
piper.clear_last_error_for_variable(variable_name: str) -> None: Manually clears any stored error for avariable_namethat might have been cached from a previousget_secret(..., raise_on_failure=False)call oris_grant_still_active(). This is useful before a deliberate re-attempt to fetch a secret, ensuring that any subsequent advice is based on the very latest attempt. -
piper.clear_cached_instance_id() -> None: Clears theinstanceIdcached by the SDK from a previous local Piper Link discovery. If you anticipate the Piper Link application might have been restarted or the user's session within it changed, call this method to force the SDK to re-discover theinstanceIdon the next operation that requires it (and hasattempt_local_discovery=True).
Using these methods, an application can build more sophisticated logic to handle scenarios like:
- Checking if a grant was revoked before using a cached secret, and then guiding the user to re-grant.
- Allowing a user to explicitly trigger a "refresh secrets" or "retry connection" action that clears stale state and attempts a fresh acquisition.
🌐 User Context (instanceId) for Piper Tier
When using the Piper system, a user context (instanceId) is needed:
-
Explicitly Provided: Pass
piper_link_instance_idin thePiperClientconstructor (highest precedence for client-wide default) or to a specificget_secret(..., piper_link_instance_id_for_call=...)call (overrides client default for that call). This is useful for services or when the context is known externally. -
Automatic Local Discovery: If no explicit
instanceIdis active for a call, andattempt_local_discovery=True(default), the SDK queries the Piper Link GUI's local endpoint (typicallyhttp://localhost:31477/piper-link-context) to get the currentinstanceId. This is ideal for desktop applications where the user interacts with Piper Link.
🧯 Error Handling (Enhanced in v0.7.1)
The SDK provides robust error information:
-
get_secret(..., raise_on_failure=True)(Default Behavior):-
All secret-retrieval failures from any tier raise
PiperSecretAcquisitionError. This error includes:variable_name: The name of the variable that failed.attempted_sources_summary: A dictionary mapping source names (e.g., "Piper", "EnvironmentVariable", "LocalConfigFile") to specific errors or failure messages (e.g., aPiperGrantNeededErrorobject, a string like "Environment variable 'X' not set", or aFileNotFoundError). Its__str__method provides a comprehensive summary ideal for logging.
-
Other errors like
PiperConfigError(for SDK setup issues found during a call) or more specificPiperAuthErrorsubtypes might be raised directly from the Piper tier if they occur before all tiers are exhausted.
-
-
get_secret(..., raise_on_failure=False)(New in v0.7.1):-
If an error occurs (client initialization issue passed down, configuration problem during the call, or secret acquisition failure across tiers), this mode does not raise an exception.
-
Instead, it returns a dictionary containing details about the failure, including an
error_objectkey holding the actualPiperErrorinstance (e.g.,PiperSecretAcquisitionError,PiperConfigError). The dictionary also includesvalue: None,source(e.g., "client_initialization_failure", "piper_grant_needed"), andvariable_name. -
The error is also stored internally and can be retrieved using
piper.get_last_error_for_variable(variable_name).
-
-
get_resolution_advice(variable_name, error_object?)(New in v0.7.1):- Use this method to convert an error object into a user-friendly, actionable string. This is the recommended way to generate messages for your users when
get_secret(non-raising) indicates a failure, or ifpiper.client_initialization_okisFalse.
- Use this method to convert an error object into a user-friendly, actionable string. This is the recommended way to generate messages for your users when
Key Error Types you might interact with:
-
PiperConfigError: Issues with howPiperClientis configured or invalid inputs to its methods. -
PiperLinkNeededError: Piper Link context (instanceId) is required for a Piper tier operation but is missing. -
PiperGrantNeededError: User needs to grant permission in the Piper UI for the requested variable. Contains aconstructed_grant_urlattribute with a direct link for the user (e.g.,https://agentpiper.com/secrets?response_type=code&scope=manage_grants&client=YOUR_AGENT_ID&variable=REQUESTED_VAR_NAME). -
PiperAuthError/PiperForbiddenError: Authentication or permission issues with the Piper backend services. -
PiperRawSecretExchangeError: Errors specific to the raw secret exchange step iffetch_raw_secret=True. -
PiperSecretAcquisitionError: The umbrella error whenget_secret(in raising mode) fails after trying all configured tiers, or theerror_objectreturned by non-raisingget_secretif all tiers fail.
🤝 Contributing
Please open an issue or PR on GitHub → https://github.com/greylab0/piper-python-sdk.
License: MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyper_sdk-0.7.1.tar.gz.
File metadata
- Download URL: pyper_sdk-0.7.1.tar.gz
- Upload date:
- Size: 33.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
489757d8108bece63e87fcc8c9fe9110c20a805d4022273d6d635b2af6762f07
|
|
| MD5 |
44edf81930bb9836032796be1bbfbb1d
|
|
| BLAKE2b-256 |
c450ba6021028b5652f9174d43dfa45bdee28f0875c6b7e42a4b15c29de2e981
|
File details
Details for the file pyper_sdk-0.7.1-py3-none-any.whl.
File metadata
- Download URL: pyper_sdk-0.7.1-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a6c75839c8955e6bf33e2c91fa44beed863dde7b9f6108968ec36722e913040
|
|
| MD5 |
78fc72733687c5b319b2986da7d4075b
|
|
| BLAKE2b-256 |
a5c38d812342a3475f578e0cb42d43beb276f235866da62071ad7cb843432165
|