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.
Why pypia_ctl?¶
- π Safe subprocess wrapper over
piactlwith 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:Classpaths - π§Ύ CLI (Typer):
env-init,env-print,status,connect,disconnect,monitor
Requirements¶
- Python 3.13
- PIA desktop/daemon +
piactlavailable on PATH - macOS/Linux (Windows WSL works if
piactlis 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_REGIONSin 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)¶
- Update
pyproject.toml(name, version, description, classifiers). - Build:
bash pdm build - Upload (use TestPyPI first):
bash pdm publish --repository testpypi # then: pdm publish - Tag & release on GitHub; CI can build docs from
main.
Links¶
- Usage: usage.md
- CLI: cli.md
- Adapters: adapters.md
- Errors: errors.md
- API: api.md