Skip to main content

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 (PiperClient and get_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+)

  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.

  2. Your Application Flow (Using Pyper SDK):

    • Initialize PiperClient: Configure it once with your agent's client_id and 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:

      1. Piper System (if use_piper=True)

      2. Environment Variables (if fallback_to_env=True and Piper skipped or failed)

      3. Local Agent Configuration File (if fallback_to_local_config=True and 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).")
      

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 a raise_on_failure=False parameter. 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: PiperClient now has a client_initialization_ok (bool) attribute. After creating the client, check this attribute. If False, critical configuration errors occurred (e.g., missing client_id). You can use piper.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. If False, the SDK will only try configured fallback methods.

  • piper_link_instance_id: Optional[str] (default: None): An explicit Piper Link instanceId provided 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): If use_piper is True and no explicit piper_link_instance_id is active for a call (either from constructor or get_secret), the SDK will attempt to discover a running Piper Link application on the local machine (typically at http://localhost:31477) to get an instanceId. Set to False to disable this automatic discovery.

  • fallback_to_env: bool (default: True): If the Piper tier is skipped or fails, setting this to True enables 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, if variable_name is "API_KEY" and env_variable_prefix is "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 logical variable_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 the env_variable_prefix and 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 to True enables the SDK to attempt to retrieve the secret from a local JSON configuration file.

  • local_config_file_path: Optional[str] (default: None): Required if fallback_to_local_config is True. 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., in PiperGrantNeededError and get_resolution_advice) to guide users if a grant is missing.

  • requests_session: Optional[requests.Session] (default: None): Allows advanced users to provide a custom requests.Session object. 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) After PiperClient(...), check this. True if critical configurations were met, False otherwise (e.g., missing client_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]: If get_secret was called with raise_on_failure=False and an error occurred for variable_name, this method retrieves that stored PiperError object. Returns None if 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_object is provided, it generates advice for that error.
    • Else, if variable_name is provided, it uses get_last_error_for_variable(variable_name).
    • Else (e.g., if variable_name is ""), it uses any stored client initialization error. Returns None if no relevant error is found.

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 for variable_name is 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 True if the grant is active, False otherwise.
    • If the grant is found to be inactive and store_error_if_inactive=True (default), it updates the internal error state for that variable, allowing get_resolution_advice() to provide accurate, current guidance.
  • piper.clear_last_error_for_variable(variable_name: str) -> None: Manually clears any stored error for a variable_name that might have been cached from a previous get_secret(..., raise_on_failure=False) call or is_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 the instanceId cached 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 the instanceId on the next operation that requires it (and has attempt_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_id in the PiperClient constructor (highest precedence for client-wide default) or to a specific get_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 instanceId is active for a call, and attempt_local_discovery=True (default), the SDK queries the Piper Link GUI's local endpoint (typically http://localhost:31477/piper-link-context) to get the current instanceId. 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., a PiperGrantNeededError object, a string like "Environment variable 'X' not set", or a FileNotFoundError). Its __str__ method provides a comprehensive summary ideal for logging.
    • Other errors like PiperConfigError (for SDK setup issues found during a call) or more specific PiperAuthError subtypes 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_object key holding the actual PiperError instance (e.g., PiperSecretAcquisitionError, PiperConfigError). The dictionary also includes value: None, source (e.g., "client_initialization_failure", "piper_grant_needed"), and variable_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 if piper.client_initialization_ok is False.

Key Error Types you might interact with:

  • PiperConfigError: Issues with how PiperClient is 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 a constructed_grant_url attribute 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 if fetch_raw_secret=True.

  • PiperSecretAcquisitionError: The umbrella error when get_secret (in raising mode) fails after trying all configured tiers, or the error_object returned by non-raising get_secret if all tiers fail.

🤝 Contributing

Please open an issue or PR on GitHub → https://github.com/greylab0/piper-python-sdk.

License: MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyper_sdk-0.7.1.tar.gz (33.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyper_sdk-0.7.1-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

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

Hashes for pyper_sdk-0.7.1.tar.gz
Algorithm Hash digest
SHA256 489757d8108bece63e87fcc8c9fe9110c20a805d4022273d6d635b2af6762f07
MD5 44edf81930bb9836032796be1bbfbb1d
BLAKE2b-256 c450ba6021028b5652f9174d43dfa45bdee28f0875c6b7e42a4b15c29de2e981

See more details on using hashes here.

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

Hashes for pyper_sdk-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8a6c75839c8955e6bf33e2c91fa44beed863dde7b9f6108968ec36722e913040
MD5 78fc72733687c5b319b2986da7d4075b
BLAKE2b-256 a5c38d812342a3475f578e0cb42d43beb276f235866da62071ad7cb843432165

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page