🐷 Oinker
Domain management that doesn't stink! 🐽
A delightfully Pythonic library for managing domains at Porkbun. DNS records, DNSSEC, SSL certificates, URL forwarding, and more. Async-first with sync wrappers, type-safe, and thoroughly tested.
📚 Full Documentation | 🐽 Not affiliated with Porkbun
Features
- 🚀 Async-first design - Built on httpx for modern async/await support
- 🔒 Type-safe records - Dataclasses with validation for all DNS record types
- 🔄 Sync wrappers - Use
Pigletwhen you don't need async - 💻 CLI included - Manage DNS from the command line
- 🔁 Auto-retry - Exponential backoff for transient failures
- 🐍 Python 3.13+ - Modern Python with full type annotations
📦 Installation
pip install oinker
For CLI support:
pip install "oinker[cli]"
🚀 Quick Start
Set your Porkbun API credentials:
export PORKBUN_API_KEY="pk1_..."
export PORKBUN_SECRET_KEY="sk1_..."
Async (Recommended)
from oinker import AsyncPiglet, ARecord
async with AsyncPiglet() as piglet:
# Test connection
pong = await piglet.ping()
print(f"Your IP: {pong.your_ip}")
# List DNS records
records = await piglet.dns.list("example.com")
for record in records:
print(f"{record.record_type} {record.name} -> {record.content}")
# Create an A record
record_id = await piglet.dns.create(
"example.com",
ARecord(content="1.2.3.4", name="www")
)
# Delete by ID
await piglet.dns.delete("example.com", record_id=record_id)
Sync
from oinker import Piglet, ARecord
with Piglet() as piglet:
pong = piglet.ping()
print(f"Your IP: {pong.your_ip}")
records = piglet.dns.list("example.com")
CLI
# Test connection
$ oinker ping
🐷 Oink! Connected successfully.
Your IP: 203.0.113.42
# List DNS records
$ oinker dns list example.com
┌────────┬─────────────────┬──────┬───────────┬─────┐
│ ID │ Name │ Type │ Content │ TTL │
├────────┼─────────────────┼──────┼───────────┼─────┤
│ 123456 │ example.com │ A │ 1.2.3.4 │ 600 │
│ 123457 │ www.example.com │ A │ 1.2.3.4 │ 600 │
└────────┴─────────────────┴──────┴───────────┴─────┘
# Create an A record
$ oinker dns create example.com A www 1.2.3.4
🐷 Squeee! Created record 123458
# Delete a record
$ oinker dns delete example.com --id 123458
🐷 Gobbled up record 123458
📝 DNS Record Types
Oinker provides type-safe dataclasses for all Porkbun-supported record types:
from oinker import (
ARecord, # IPv4 address
AAAARecord, # IPv6 address
MXRecord, # Mail server
TXTRecord, # Text record
CNAMERecord, # Canonical name
ALIASRecord, # ALIAS/ANAME
NSRecord, # Name server
SRVRecord, # Service record
TLSARecord, # DANE/TLSA
CAARecord, # CA Authorization
HTTPSRecord, # HTTPS binding
SVCBRecord, # Service binding
SSHFPRecord, # SSH fingerprint
)
Records validate their content on construction:
from oinker import ARecord, ValidationError
try:
ARecord(content="not-an-ip")
except ValidationError as e:
print(e) # Invalid IPv4 address: not-an-ip
🌐 Domain Operations
async with AsyncPiglet() as piglet:
# List all domains
domains = await piglet.domains.list()
# Get/update nameservers
ns = await piglet.domains.get_nameservers("example.com")
await piglet.domains.update_nameservers("example.com", [
"ns1.example.com",
"ns2.example.com",
])
# URL forwarding
forwards = await piglet.domains.get_url_forwards("example.com")
# Check domain availability
availability = await piglet.domains.check("example.com")
🔐 DNSSEC
from oinker import DNSSECRecordCreate
async with AsyncPiglet() as piglet:
# List DNSSEC records
records = await piglet.dnssec.list("example.com")
# Create DNSSEC record
await piglet.dnssec.create("example.com", DNSSECRecordCreate(
key_tag="64087",
algorithm="13",
digest_type="2",
digest="15E445BD...",
))
🔒 SSL Certificates
async with AsyncPiglet() as piglet:
bundle = await piglet.ssl.retrieve("example.com")
print(bundle.certificate_chain)
print(bundle.private_key)
⚠️ Error Handling
from oinker import (
AsyncPiglet,
OinkerError,
AuthenticationError,
RateLimitError,
NotFoundError,
)
async with AsyncPiglet() as piglet:
try:
await piglet.dns.list("example.com")
except AuthenticationError:
print("Check your API credentials")
except NotFoundError:
print("Domain not found or API access not enabled")
except RateLimitError as e:
print(f"Slow down! Retry after {e.retry_after}s")
except OinkerError as e:
print(f"API error: {e}")
📚 Documentation
For more examples and detailed API reference, check out the full documentation.
🐷 Disclaimer
This project is not affiliated with Porkbun in any way. It's just a passion project by someone who really, really loves Porkbun. They're amazing! 🐽
📄 License
MIT
Metadata
Release files for oinker 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| oinker-0.1.2.tar.gz | 28.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| oinker-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.5 kB
Release files / oinker-0.1.2.tar.gz
| Download URL | oinker-0.1.2.tar.gz |
|---|---|
| Size | 28.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8dd03a11316ceb5e0ce95b838e4f942a28b5e369350f89908866028d21ca4d47
|
|
BLAKE2b-256 checksum How to use checksums |
6f9e42cd284bf5580530960b817b5e3f6acea7b1501c96ba7530667a3d6156f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 23, 2026.
Transparency logRelease files / oinker-0.1.2-py3-none-any.whl
| Download URL | oinker-0.1.2-py3-none-any.whl |
|---|---|
| Size | 44.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c7e3aa0bfcb76b928da12776aa36d5beb18c2a8e997cf58da7625d4dc3c503ab
|
|
BLAKE2b-256 checksum How to use checksums |
408b3db1ae3f4cee3a3afecd20afb565663ce595839d6e903816bf2ba0bf7593
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 23, 2026.
Transparency log