Skip to content

pypia_ctl

Typed mini-SDK around the official piactl CLI (Private Internet Access).
Use it to inspect status, connect/disconnect via strategies, stream monitor events, manage .env defaults, and wire the PIA proxy into httpx, Playwright, and Selenium β€” with a tiny plugin system on top.

Docs CI Docs License: MIT


Why pypia_ctl?

  • πŸ”’ Safe subprocess wrapper over piactl with typed exceptions
  • βš™οΈ Pydantic v2 settings (env/.env/defaults) β€” OS env > .env > defaults
  • 🧠 Strategy connect (preferred β†’ random β†’ exact) with retries
  • πŸ“‘ Live monitor: async stream of piactl monitor <key> events
  • 🧰 Adapters for httpx, Playwright, Selenium proxying
  • πŸ”Œ Plugins: load custom hooks via simple module:Class paths
  • 🧾 CLI (Typer): env-init, env-print, status, connect, disconnect, monitor

Requirements

  • Python 3.13
  • PIA desktop/daemon + piactl available on PATH
  • macOS/Linux (Windows WSL works if piactl is available)

Tip

Not sure if piactl is on PATH?
bash which piactl && piactl --version


Install

Choose your tool:

=== "PDM"

pdm add pypia-ctl

=== "pip"

python -m pip install pypia-ctl

For docs/development:

pdm add -G docs mkdocs mkdocs-material 'mkdocstrings[python]' \
  mkdocs-gen-files mkdocs-literate-nav mkdocs-section-index mkdocs-include-markdown-plugin

Quick start

1) Create defaults:

pdm run pypia env-init
# writes/merges a .env (non-destructive)

2) Inspect current status:

pdm run pypia status

3) Connect using the preferred strategy (tries your preferred list first, then random):

pdm run pypia connect --strategy preferred

4) Stream live monitor updates:

pdm run pypia monitor --key connectionstate

See full command docs in CLI.


Minimal Python usage

from pypia_ctl import init_settings, fetch_status, connect_with_strategy

# Load settings: OS env > .env > defaults (no writes)
settings = init_settings(create_env=False)

status = fetch_status()
print(status.connection_state)

connect_with_strategy(strategy="preferred", max_retries=2)

More walkthroughs in Usage.


Settings overview

Settings are read from: 1. OS environment (highest precedence) 2. .env (middle) 3. Built-in defaults (fallback)

Key environment variables (lists are JSON arrays):

Key Example
PIA_PROTOCOL wireguard
PIA_DEFAULT_REGION auto
PIA_PREFERRED_REGIONS ["us-new-york","ca-ontario"]
PIA_RANDOMIZE_REGION true
PIA_PROXY__KIND socks5
PIA_PROXY__HOST localhost
PIA_PROXY__PORT 1080
PIA_PROXY__USERNAME / PIA_PROXY__PASSWORD ""
PIA_PLUGINS ["pkg.mod:Hook"]
PIA_REGION_FILTERS__include_countries ["us-","ca-"]
PIA_REGION_FILTERS__exclude_countries ["cn-","ru-"]

.env template

```env PIA_PROTOCOL=wireguard PIA_DEFAULT_REGION=auto PIA_PREFERRED_REGIONS=["us-new-york","ca-ontario"] PIA_RANDOMIZE_REGION=true

PIA_PROXY__KIND=socks5 PIA_PROXY__HOST=localhost PIA_PROXY__PORT=1080 PIA_PROXY__USERNAME= PIA_PROXY__PASSWORD=

PIA_PLUGINS=[] PIA_REGION_FILTERS__include_countries=["us-","ca-"] PIA_REGION_FILTERS__exclude_countries=[] ```

Full schema in API.


Adapters (proxy wiring)

Wire PIA’s proxy into common clients:

=== "httpx"

from pypia_ctl.adapters import httpx_proxy
import httpx

proxies = httpx_proxy()  # respects PIA_PROXY__* settings
with httpx.Client(proxies=proxies, timeout=10) as client:
    r = client.get("https://ipinfo.io/ip")
    print(r.text)

=== "Playwright"

from pypia_ctl.adapters import playwright_proxy
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=playwright_proxy())
    page = browser.new_page()
    page.goto("https://ipinfo.io/ip")
    print(page.text_content("body"))

=== "Selenium"

from pypia_ctl.adapters import selenium_proxy
from selenium import webdriver

options = webdriver.ChromeOptions()
selenium_proxy(options)  # mutates options to add proxy
driver = webdriver.Chrome(options=options)
driver.get("https://ipinfo.io/ip")
print(driver.page_source)
driver.quit()

Details & caveats in Adapters.


Strategy connect

  • preferred: Try PIA_PREFERRED_REGIONS in order. If none available, optionally fall back to random (based on settings).
  • random: Pick a random eligible region (respecting include/exclude filters).
  • exact: Connect to exactly the given region slug.
# exact requires a region slug
pdm run pypia connect --strategy exact --exact-region ca-ontario

Plugins

Load custom hooks with a minimal protocol (e.g., observability, extra validation, toggling app behavior).

  • Configure as JSON list in .env: env PIA_PLUGINS=["pkg.mod:MyPlugin"]
  • The loader imports and instantiates each class at runtime.

See examples in API and test fixtures for reference.


Troubleshooting

mkdocs can't import my package

Add to mkdocs.yml: yaml plugins: - mkdocstrings: handlers: python: paths: [src] Or run with PYTHONPATH=src.

piactl not found

Ensure PIA is installed and piactl is on PATH: bash which piactl piactl --version

JSON lists in .env

Use JSON arrays, not CSV. Example: PIA_PREFERRED_REGIONS=["us-new-york","ca-ontario"]

More errors and fixes in Errors.


Architecture (high-level)

+-------------------+       +--------------------+       +--------------------+
|  Typer CLI (app)  | --->  |   Core (runner)    | --->  |  piactl (daemon)   |
+-------------------+       +--------------------+       +--------------------+
          |                          |
          v                          v
+-------------------+       +--------------------+
|  Env/Settings     |       |  Adapters (proxy)  |
|  (Pydantic v2)    |       |  httpx/PLW/Sel     |
+-------------------+       +--------------------+
          |
          v
+-------------------+
|  Plugins (loader) |
+-------------------+

Roadmap

  • Optional telemetry hooks via plugin
  • Better cross-platform region selectors
  • Rich TUI monitor

Contributions welcome β€” see CONTRIBUTING.md.


Publishing to PyPI (quick checklist)

  1. Update pyproject.toml (name, version, description, classifiers).
  2. Build: bash pdm build
  3. Upload (use TestPyPI first): bash pdm publish --repository testpypi # then: pdm publish
  4. Tag & release on GitHub; CI can build docs from main.