Skip to content

major/oinker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

73 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐷 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_..."

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

About

🐽 Async Python client for the Porkbun API - oink your way to DNS management

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors