whodap


Namewhodap JSON
Version 0.1.11 PyPI version JSON
download
home_pagehttps://github.com/pogzyb/whodap
SummarySimple RDAP Utility for Python
upload_time2023-11-04 14:08:36
maintainer
docs_urlNone
authorJoseph Obarzanek
requires_python>=3.6, <4
license
keywords security whois rdap research
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ## whodap

[![PyPI version](https://badge.fury.io/py/whodap.svg)](https://badge.fury.io/py/whodap)
![example workflow](https://github.com/pogzyb/whodap/actions/workflows/run-build-and-test.yml/badge.svg)
[![codecov](https://codecov.io/gh/pogzyb/whodap/branch/main/graph/badge.svg?token=NCfdf6ftb9)](https://codecov.io/gh/pogzyb/whodap)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

`whodap` | Simple RDAP Utility for Python

- Support for asyncio HTTP requests ([`httpx`](https://www.python-httpx.org/))
- Leverages the [`SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) type for cleaner RDAP Response traversal
- Keeps the familiar look of WHOIS via the `to_whois_dict` method for DNS lookups

#### Quickstart

```python
import asyncio
from pprint import pprint

import whodap

# Looking up a domain name
response = whodap.lookup_domain(domain='bitcoin', tld='org') 
# Equivalent asyncio call
loop = asyncio.get_event_loop()
response = loop.run_until_complete(whodap.aio_lookup_domain(domain='bitcoin', tld='org'))
# "response" is a DomainResponse object. It contains the output from the RDAP lookup.
print(response)
# Traverse the DomainResponse via "dot" notation
print(response.events)
"""
[{
  "eventAction": "last update of RDAP database",
  "eventDate": "2021-04-23T21:50:03"
},
 {
  "eventAction": "registration",
  "eventDate": "2008-08-18T13:19:55"
}, ... ]
"""
# Retrieving the registration date from above:
print(response.events[1].eventDate)
"""
2008-08-18 13:19:55
"""
# Don't want "dot" notation? Use `to_dict` to get the RDAP response as a dictionary
pprint(response.to_dict())
# Use `to_whois_dict` for the familiar look of WHOIS output
pprint(response.to_whois_dict())
"""
{abuse_email: 'abuse@namecheap.com',
 abuse_phone: 'tel:+1.6613102107',
 admin_address: 'P.O. Box 0823-03411, Panama, Panama, PA',
 admin_email: '2603423f6ed44178a3b9d728827aa19a.protect@whoisguard.com',
 admin_fax: 'fax:+51.17057182',
 admin_name: 'WhoisGuard Protected',
 admin_organization: 'WhoisGuard, Inc.',
 admin_phone: 'tel:+507.8365503',
 billing_address: None,
 billing_email: None,
 billing_fax: None,
 billing_name: None,
 billing_organization: None,
 billing_phone: None,
 created_date: datetime.datetime(2008, 8, 18, 13, 19, 55),
 domain_name: 'bitcoin.org',
 expires_date: datetime.datetime(2029, 8, 18, 13, 19, 55),
 nameservers: ['dns1.registrar-servers.com', 'dns2.registrar-servers.com'],
 registrant_address: 'P.O. Box 0823-03411, Panama, Panama, PA',
 registrant_email: '2603423f6ed44178a3b9d728827aa19a.protect@whoisguard.com',
 registrant_fax: 'fax:+51.17057182',
 registrant_name: 'WhoisGuard Protected',
 registrant_organization: None,
 registrant_phone: 'tel:+507.8365503',
 registrar_address: '4600 E Washington St #305, Phoenix, Arizona, 85034',
 registrar_email: 'support@namecheap.com',
 registrar_fax: None,
 registrar_name: 'NAMECHEAP INC',
 registrar_phone: 'tel:+1.6613102107',
 status: ['client transfer prohibited'],
 technical_address: 'P.O. Box 0823-03411, Panama, Panama, PA',
 technical_email: '2603423f6ed44178a3b9d728827aa19a.protect@whoisguard.com',
 technical_fax: 'fax:+51.17057182',
 technical_name: 'WhoisGuard Protected',
 technical_organization: 'WhoisGuard, Inc.',
 technical_phone: 'tel:+507.8365503',
 updated_date: datetime.datetime(2019, 11, 24, 13, 58, 35)}
"""
```

#### Exported Functions and Classes

| Object      | Description |
| ----------- | ----------- |
|  `lookup_domain`      | Performs an RDAP query for the given Domain and TLD                     |
|  `lookup_ipv4`        | Performs an RDAP query for the given IPv4 address                       |
|  `lookup_ipv6`        | Performs an RDAP query for the given IPv6 address                       |
|  `lookup_asn`         | Performs an RDAP query for the Autonomous System with the given Number  |
|  `aio_lookup_domain`  | async counterpart to `lookup_domain`  |
|  `aio_lookup_ipv4`    | async counterpart to `lookup_ipv4`    |
|  `aio_lookup_ipv6`    | async counterpart to `lookup_ipv6`    |
|  `aio_lookup_asn`     | async counterpart to `lookup_asn`     |
|  `DNSClient`     | Reusable client for RDAP DNS queries    |
|  `IPv4Client`     | Reusable client for RDAP IPv4 queries     |
|  `IPv6Client`     | Reusable client for RDAP IPv6 queries     |
|  `ASNClient`     | Reusable client for RDAP ASN queries     |


#### Common Usage Patterns

- Using the DNSClient:
```python
import whodap

# Initialize an instance of DNSClient using classmethods: `new_client` or `new_aio_client`
dns_client = whodap.DNSClient.new_client()
for domain, tld in [('google', 'com'), ('google', 'buzz')]:
    response = dns_client.lookup(domain, tld)
    
# Equivalent asyncio call
dns_client = await whodap.DNSClient.new_aio_client()
for domain, tld in [('google', 'com'), ('google', 'buzz')]:
    response = await dns_client.aio_lookup(domain, tld)
    
# Use the DNSClient contextmanagers: `new_client_context` or `new_aio_client_context`
with whodap.DNSClient.new_client_context() as dns_client:
    for domain, tld in [('google', 'com'), ('google', 'buzz')]:
        response = dns_client.lookup(domain, tld)

# Equivalent asyncio call
async with whodap.DNSClient.new_aio_client_context() as dns_client:
    for domain, tld in [('google', 'com'), ('google', 'buzz')]:
        response = await dns_client.aio_lookup(domain, tld)
```

- Configurable `httpx` client:

```python
import asyncio

import httpx
import whodap

# Initialize a custom, pre-configured httpx client ...
httpx_client = httpx.Client(proxies=httpx.Proxy('https://user:pw@proxy_url.net'))
# ... or an async client
aio_httpx_client = httpx.AsyncClient(proxies=httpx.Proxy('http://user:pw@proxy_url.net'))

# Three common methods for leveraging httpx clients are outlined below:

# 1) Pass the httpx client directly into the convenience functions: `lookup_domain` or `aio_lookup_domain`
# Important: In this scenario, you are responsible for closing the httpx client.
# In this example, the given httpx client is used as a contextmanager; ensuring it is "closed" when finished.
async with aio_httpx_client:
    futures = []
    for domain, tld in [('google', 'com'), ('google', 'buzz')]:
        task = whodap.aio_lookup_domain(domain, tld, httpx_client=aio_httpx_client)
        futures.append(task)
    await asyncio.gather(*futures)

# 2) Pass the httpx_client into the DNSClient classmethod: `new_client` or `new_aio_client`
aio_dns_client = await whodap.DNSClient.new_aio_client(aio_httpx_client)
result = await aio_dns_client.aio_lookup('google', 'buzz')
await aio_httpx_client.aclose()

# 3) Pass the httpx_client into the DNSClient contextmanagers: `new_client_context` or `new_aio_client_context`
# This method ensures the underlying httpx_client is closed when exiting the "with" block.
async with whodap.DNSClient.new_aio_client_context(aio_httpx_client) as dns_client:
    for domain, tld in [('google', 'com'), ('google', 'buzz')]:
        response = await dns_client.aio_lookup(domain, tld)
```

- Using the `to_whois_dict` method and `RDAPConformanceException`
```python
import logging

from whodap import lookup_domain
from whodap.errors import RDAPConformanceException

logger = logging.getLogger(__name__)

# strict = False (default)
rdap_response = lookup_domain("example", "com")
whois_format = rdap_response.to_whois_dict()
logger.info(f"whois={whois_format}")
# Given a valid RDAP response, the `to_whois_dict` method will attempt to
# convert the RDAP format into a flattened dictionary of WHOIS key/values

# strict = True
try:
    # Unfortunately, there are instances in which the RDAP protocol is not
    # properly implemented by the registrar. By default, the `to_whois_dict`
    # will still attempt to parse the into the WHOIS dictionary. However,
    # there is no guarantee that the information will be correct or non-null. 
    # If your applications rely on accurate information, the `strict=True`
    # parameter will raise an `RDAPConformanceException` when encountering
    # invalid or incorrectly formatted RDAP responses.
    rdap_response = lookup_domain("example", "com")
    whois_format = rdap_response.to_whois_dict(strict=True)
except RDAPConformanceException:
    logger.exception("RDAP response is incorrectly formatted.")
```

#### Contributions
- Interested in contributing? 
- Have any questions or comments? 
- Anything that you'd like to see?
- Anything that doesn't look right?

Please post a question or comment.

#### Roadmap

[alpha] 0.1.X Release:
- ~~Support for RDAP "domain" queries~~
- ~~Support for RDAP "ipv4" and "ipv6" queries~~
- ~~Support for RDAP ASN queries~~
- Abstract the HTTP Client (`httpx` is the defacto client for now)
- Add parser utils/helpers for IPv4, IPv6, and ASN Responses (if someone shows interest)
- Add RDAP response validation support leveraging [ICANN's tool](https://github.com/icann/rdap-conformance-tool/)

#### RDAP Resources:
- [rdap.org](https://rdap.org/)
- [RFC 9082](https://datatracker.ietf.org/doc/html/rfc9082) 

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/pogzyb/whodap",
    "name": "whodap",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6, <4",
    "maintainer_email": "",
    "keywords": "security,whois,rdap,research",
    "author": "Joseph Obarzanek",
    "author_email": "pogzyb@umich.edu",
    "download_url": "https://files.pythonhosted.org/packages/fd/df/f9ca5c8a8aba7702f1b0b3051b17cdbbf714903a30b9e4576ab9bd1d92ac/whodap-0.1.11.tar.gz",
    "platform": null,
    "description": "## whodap\n\n[![PyPI version](https://badge.fury.io/py/whodap.svg)](https://badge.fury.io/py/whodap)\n![example workflow](https://github.com/pogzyb/whodap/actions/workflows/run-build-and-test.yml/badge.svg)\n[![codecov](https://codecov.io/gh/pogzyb/whodap/branch/main/graph/badge.svg?token=NCfdf6ftb9)](https://codecov.io/gh/pogzyb/whodap)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\n`whodap` | Simple RDAP Utility for Python\n\n- Support for asyncio HTTP requests ([`httpx`](https://www.python-httpx.org/))\n- Leverages the [`SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) type for cleaner RDAP Response traversal\n- Keeps the familiar look of WHOIS via the `to_whois_dict` method for DNS lookups\n\n#### Quickstart\n\n```python\nimport asyncio\nfrom pprint import pprint\n\nimport whodap\n\n# Looking up a domain name\nresponse = whodap.lookup_domain(domain='bitcoin', tld='org') \n# Equivalent asyncio call\nloop = asyncio.get_event_loop()\nresponse = loop.run_until_complete(whodap.aio_lookup_domain(domain='bitcoin', tld='org'))\n# \"response\" is a DomainResponse object. It contains the output from the RDAP lookup.\nprint(response)\n# Traverse the DomainResponse via \"dot\" notation\nprint(response.events)\n\"\"\"\n[{\n  \"eventAction\": \"last update of RDAP database\",\n  \"eventDate\": \"2021-04-23T21:50:03\"\n},\n {\n  \"eventAction\": \"registration\",\n  \"eventDate\": \"2008-08-18T13:19:55\"\n}, ... ]\n\"\"\"\n# Retrieving the registration date from above:\nprint(response.events[1].eventDate)\n\"\"\"\n2008-08-18 13:19:55\n\"\"\"\n# Don't want \"dot\" notation? Use `to_dict` to get the RDAP response as a dictionary\npprint(response.to_dict())\n# Use `to_whois_dict` for the familiar look of WHOIS output\npprint(response.to_whois_dict())\n\"\"\"\n{abuse_email: 'abuse@namecheap.com',\n abuse_phone: 'tel:+1.6613102107',\n admin_address: 'P.O. Box 0823-03411, Panama, Panama, PA',\n admin_email: '2603423f6ed44178a3b9d728827aa19a.protect@whoisguard.com',\n admin_fax: 'fax:+51.17057182',\n admin_name: 'WhoisGuard Protected',\n admin_organization: 'WhoisGuard, Inc.',\n admin_phone: 'tel:+507.8365503',\n billing_address: None,\n billing_email: None,\n billing_fax: None,\n billing_name: None,\n billing_organization: None,\n billing_phone: None,\n created_date: datetime.datetime(2008, 8, 18, 13, 19, 55),\n domain_name: 'bitcoin.org',\n expires_date: datetime.datetime(2029, 8, 18, 13, 19, 55),\n nameservers: ['dns1.registrar-servers.com', 'dns2.registrar-servers.com'],\n registrant_address: 'P.O. Box 0823-03411, Panama, Panama, PA',\n registrant_email: '2603423f6ed44178a3b9d728827aa19a.protect@whoisguard.com',\n registrant_fax: 'fax:+51.17057182',\n registrant_name: 'WhoisGuard Protected',\n registrant_organization: None,\n registrant_phone: 'tel:+507.8365503',\n registrar_address: '4600 E Washington St #305, Phoenix, Arizona, 85034',\n registrar_email: 'support@namecheap.com',\n registrar_fax: None,\n registrar_name: 'NAMECHEAP INC',\n registrar_phone: 'tel:+1.6613102107',\n status: ['client transfer prohibited'],\n technical_address: 'P.O. Box 0823-03411, Panama, Panama, PA',\n technical_email: '2603423f6ed44178a3b9d728827aa19a.protect@whoisguard.com',\n technical_fax: 'fax:+51.17057182',\n technical_name: 'WhoisGuard Protected',\n technical_organization: 'WhoisGuard, Inc.',\n technical_phone: 'tel:+507.8365503',\n updated_date: datetime.datetime(2019, 11, 24, 13, 58, 35)}\n\"\"\"\n```\n\n#### Exported Functions and Classes\n\n| Object      | Description |\n| ----------- | ----------- |\n|  `lookup_domain`      | Performs an RDAP query for the given Domain and TLD                     |\n|  `lookup_ipv4`        | Performs an RDAP query for the given IPv4 address                       |\n|  `lookup_ipv6`        | Performs an RDAP query for the given IPv6 address                       |\n|  `lookup_asn`         | Performs an RDAP query for the Autonomous System with the given Number  |\n|  `aio_lookup_domain`  | async counterpart to `lookup_domain`  |\n|  `aio_lookup_ipv4`    | async counterpart to `lookup_ipv4`    |\n|  `aio_lookup_ipv6`    | async counterpart to `lookup_ipv6`    |\n|  `aio_lookup_asn`     | async counterpart to `lookup_asn`     |\n|  `DNSClient`     | Reusable client for RDAP DNS queries    |\n|  `IPv4Client`     | Reusable client for RDAP IPv4 queries     |\n|  `IPv6Client`     | Reusable client for RDAP IPv6 queries     |\n|  `ASNClient`     | Reusable client for RDAP ASN queries     |\n\n\n#### Common Usage Patterns\n\n- Using the DNSClient:\n```python\nimport whodap\n\n# Initialize an instance of DNSClient using classmethods: `new_client` or `new_aio_client`\ndns_client = whodap.DNSClient.new_client()\nfor domain, tld in [('google', 'com'), ('google', 'buzz')]:\n    response = dns_client.lookup(domain, tld)\n    \n# Equivalent asyncio call\ndns_client = await whodap.DNSClient.new_aio_client()\nfor domain, tld in [('google', 'com'), ('google', 'buzz')]:\n    response = await dns_client.aio_lookup(domain, tld)\n    \n# Use the DNSClient contextmanagers: `new_client_context` or `new_aio_client_context`\nwith whodap.DNSClient.new_client_context() as dns_client:\n    for domain, tld in [('google', 'com'), ('google', 'buzz')]:\n        response = dns_client.lookup(domain, tld)\n\n# Equivalent asyncio call\nasync with whodap.DNSClient.new_aio_client_context() as dns_client:\n    for domain, tld in [('google', 'com'), ('google', 'buzz')]:\n        response = await dns_client.aio_lookup(domain, tld)\n```\n\n- Configurable `httpx` client:\n\n```python\nimport asyncio\n\nimport httpx\nimport whodap\n\n# Initialize a custom, pre-configured httpx client ...\nhttpx_client = httpx.Client(proxies=httpx.Proxy('https://user:pw@proxy_url.net'))\n# ... or an async client\naio_httpx_client = httpx.AsyncClient(proxies=httpx.Proxy('http://user:pw@proxy_url.net'))\n\n# Three common methods for leveraging httpx clients are outlined below:\n\n# 1) Pass the httpx client directly into the convenience functions: `lookup_domain` or `aio_lookup_domain`\n# Important: In this scenario, you are responsible for closing the httpx client.\n# In this example, the given httpx client is used as a contextmanager; ensuring it is \"closed\" when finished.\nasync with aio_httpx_client:\n    futures = []\n    for domain, tld in [('google', 'com'), ('google', 'buzz')]:\n        task = whodap.aio_lookup_domain(domain, tld, httpx_client=aio_httpx_client)\n        futures.append(task)\n    await asyncio.gather(*futures)\n\n# 2) Pass the httpx_client into the DNSClient classmethod: `new_client` or `new_aio_client`\naio_dns_client = await whodap.DNSClient.new_aio_client(aio_httpx_client)\nresult = await aio_dns_client.aio_lookup('google', 'buzz')\nawait aio_httpx_client.aclose()\n\n# 3) Pass the httpx_client into the DNSClient contextmanagers: `new_client_context` or `new_aio_client_context`\n# This method ensures the underlying httpx_client is closed when exiting the \"with\" block.\nasync with whodap.DNSClient.new_aio_client_context(aio_httpx_client) as dns_client:\n    for domain, tld in [('google', 'com'), ('google', 'buzz')]:\n        response = await dns_client.aio_lookup(domain, tld)\n```\n\n- Using the `to_whois_dict` method and `RDAPConformanceException`\n```python\nimport logging\n\nfrom whodap import lookup_domain\nfrom whodap.errors import RDAPConformanceException\n\nlogger = logging.getLogger(__name__)\n\n# strict = False (default)\nrdap_response = lookup_domain(\"example\", \"com\")\nwhois_format = rdap_response.to_whois_dict()\nlogger.info(f\"whois={whois_format}\")\n# Given a valid RDAP response, the `to_whois_dict` method will attempt to\n# convert the RDAP format into a flattened dictionary of WHOIS key/values\n\n# strict = True\ntry:\n    # Unfortunately, there are instances in which the RDAP protocol is not\n    # properly implemented by the registrar. By default, the `to_whois_dict`\n    # will still attempt to parse the into the WHOIS dictionary. However,\n    # there is no guarantee that the information will be correct or non-null. \n    # If your applications rely on accurate information, the `strict=True`\n    # parameter will raise an `RDAPConformanceException` when encountering\n    # invalid or incorrectly formatted RDAP responses.\n    rdap_response = lookup_domain(\"example\", \"com\")\n    whois_format = rdap_response.to_whois_dict(strict=True)\nexcept RDAPConformanceException:\n    logger.exception(\"RDAP response is incorrectly formatted.\")\n```\n\n#### Contributions\n- Interested in contributing? \n- Have any questions or comments? \n- Anything that you'd like to see?\n- Anything that doesn't look right?\n\nPlease post a question or comment.\n\n#### Roadmap\n\n[alpha] 0.1.X Release:\n- ~~Support for RDAP \"domain\" queries~~\n- ~~Support for RDAP \"ipv4\" and \"ipv6\" queries~~\n- ~~Support for RDAP ASN queries~~\n- Abstract the HTTP Client (`httpx` is the defacto client for now)\n- Add parser utils/helpers for IPv4, IPv6, and ASN Responses (if someone shows interest)\n- Add RDAP response validation support leveraging [ICANN's tool](https://github.com/icann/rdap-conformance-tool/)\n\n#### RDAP Resources:\n- [rdap.org](https://rdap.org/)\n- [RFC 9082](https://datatracker.ietf.org/doc/html/rfc9082) \n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Simple RDAP Utility for Python",
    "version": "0.1.11",
    "project_urls": {
        "Bug Tracker": "https://github.com/pogzyb/whodap/issues",
        "Homepage": "https://github.com/pogzyb/whodap"
    },
    "split_keywords": [
        "security",
        "whois",
        "rdap",
        "research"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "050fa8a2cd0ff12c7d77176ab68e823e40e9f3e6dda5a3ebd02cd06641b2b455",
                "md5": "8ed8772a8cd00d7794d0f1d87ff12eb8",
                "sha256": "19c313af2b3c6bc06204b4a448e889a9260f9bd6e98b6dfbf78fc72973cabfaa"
            },
            "downloads": -1,
            "filename": "whodap-0.1.11-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "8ed8772a8cd00d7794d0f1d87ff12eb8",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6, <4",
            "size": 16168,
            "upload_time": "2023-11-04T14:08:34",
            "upload_time_iso_8601": "2023-11-04T14:08:34.579899Z",
            "url": "https://files.pythonhosted.org/packages/05/0f/a8a2cd0ff12c7d77176ab68e823e40e9f3e6dda5a3ebd02cd06641b2b455/whodap-0.1.11-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fddff9ca5c8a8aba7702f1b0b3051b17cdbbf714903a30b9e4576ab9bd1d92ac",
                "md5": "e832d721233c783149d5b92f0f2ce11d",
                "sha256": "87766c8f4339aec9838beb637499efa9e89894db5deaafe3de32e8f3b683916c"
            },
            "downloads": -1,
            "filename": "whodap-0.1.11.tar.gz",
            "has_sig": false,
            "md5_digest": "e832d721233c783149d5b92f0f2ce11d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6, <4",
            "size": 21692,
            "upload_time": "2023-11-04T14:08:36",
            "upload_time_iso_8601": "2023-11-04T14:08:36.104828Z",
            "url": "https://files.pythonhosted.org/packages/fd/df/f9ca5c8a8aba7702f1b0b3051b17cdbbf714903a30b9e4576ab9bd1d92ac/whodap-0.1.11.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-11-04 14:08:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "pogzyb",
    "github_project": "whodap",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "whodap"
}
        
Elapsed time: 2.68543s