Skip to main content

🐷 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

CI codecov PyPI version Python 3.13+ License: MIT

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 Piglet when 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_..."
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)

Source distribution for oinker 0.1.2
File Size Uploaded
oinker-0.1.2.tar.gz 28.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for oinker 0.1.2
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page