Source code for pyepp.contact
"""
Contact Mapping Module. This module is used to manage contact objects in Registry.
"""
from typing import Optional
from dataclasses import dataclass, asdict
from bs4 import BeautifulSoup
from pyepp import helper
from pyepp.base_command import BaseCommand
from pyepp.command_templates import (
CONTACT_CHECK_XML,
CONTACT_INFO_XML,
CONTACT_CREATE_XML,
CONTACT_DELETE_XML,
CONTACT_UPDATE_XML,
)
from pyepp.epp import EppResultCode, EppResultData
[docs]
@dataclass
class AddressData:
"""Contact address data class."""
street_1: Optional[str] = ""
city: Optional[str] = ""
country_code: Optional[str] = ""
street_2: Optional[str] = ""
street_3: Optional[str] = ""
province: Optional[str] = ""
postal_code: Optional[str] = ""
[docs]
@dataclass
class PostalInfoData:
"""Contact postal info data class."""
name: Optional[str]
organization: Optional[str] = ""
address: Optional[AddressData] = None
[docs]
@dataclass
class ContactData:
"""Contact data class. Contains the properties of the contacts associated with the domain name."""
# pylint: disable=invalid-name,too-many-instance-attributes
id: str
email: Optional[str] = ""
postal_info: Optional[PostalInfoData] = None
status: Optional[list[str]] = None
phone: Optional[str] = ""
fax: Optional[str] = ""
password: Optional[str] = ""
create_date: Optional[str] = ""
creat_client_id: Optional[str] = ""
sponsoring_client_id: Optional[str] = ""
update_client_id: Optional[str] = ""
update_date: Optional[str] = ""
[docs]
class Contact(BaseCommand):
"""Epp Contact object class. This class is used to create and manage the contacts within the Registry.
Contacts are individuals or organizations that are associated with domain names. There are different types of
contacts including:
* Registrant - The entity that has the authority to use and manage the domain name.
* Administrative Contact - Either the Registrant or someone authorized to act on behalf of the Registrant.
* Technical Contact - A technical contact is an individual identified as a contact for technical information-related
administration of a registered domain name.
* Billing Contact - Also known as the Finance Contact, this is the individual or organization responsible for
payment of fees related to the domain name and will monitor period activity, account balances, and account status.
"""
def _data_to_dict(self, data: ContactData) -> dict:
"""Convert a contact dataclass to a dictionary.
:param data: Contact details
:return: Contact details
:rtype: dict
"""
data_dict = asdict(data)
postal_info = data_dict.pop("postal_info", {})
address = postal_info.pop("address", {}) if postal_info is not None else {}
if address:
data_dict.update(address)
if postal_info:
data_dict.update(postal_info)
return data_dict
[docs]
def check(
self, contact_ids: list[str], client_transaction_id: Optional[str] = None
) -> EppResultData:
"""A successful Contact Check request determines whether a Contact ID is available for use and whether a contact
can be created in the Registry. When creating a new contact, the Registrar must generate a Registry-unique
contact ID. A Registry Contact Check request can determine whether an ID is already in use.
:param contact_ids: List of contact ids
:param client_transaction_id: Client transaction id
:return: contact check result
:rtype: EppResultData
"""
result = self.execute(
CONTACT_CHECK_XML,
ids=contact_ids,
client_transaction_id=client_transaction_id,
)
if result.code != int(EppResultCode.SUCCESS.value):
return result
raw_response = BeautifulSoup(result.raw_response, "xml")
contacts_check_data = raw_response.find_all("cd")
result_data = {}
for contact_cd in contacts_check_data:
contact = contact_cd.find("id")
available = contact.get("avail") in ("true", "1")
reason = contact_cd.find("reason").text if not available else None
result_data[contact.text] = {
"avail": available,
"reason": reason,
}
result.result_data = result_data
return result
[docs]
def info(
self, contact_id: str, client_transaction_id: Optional[str] = None
) -> EppResultData:
"""
A successful Contact Info request retrieves information associated with an existing contact.
All available information is returned if the querying Registrar is the contact’s sponsor. For a non-sponsoring
Registrar, all contact information is returned if the correct authorization code is entered. As well, if the
Authorization Code Expiry has been configured, the authorization code must not be expired. Otherwise,
the <contact:info> will fail.
:param contact_id: Contact ID
:param client_transaction_id: Client transaction id
:return: Contact details
:rtype: EppResultData
"""
result = self.execute(
CONTACT_INFO_XML, id=contact_id, client_transaction_id=client_transaction_id
)
if result.code != int(EppResultCode.SUCCESS.value):
return result
raw_response = BeautifulSoup(result.raw_response, "xml")
result_data = {
"id": raw_response.find("id").text,
"status": [status.text for status in raw_response.find_all("status")],
"create_date": raw_response.find("crDate").text,
"creat_client_id": raw_response.find("crID").text,
"sponsoring_client_id": raw_response.find("clID").text,
"update_client_id": (
raw_response.find("upID").text if raw_response.find("upID") else None
),
"update_date": (
raw_response.find("upDate").text
if raw_response.find("upDate")
else None
),
"postal_info": PostalInfoData(
**{
"name": raw_response.find("name").text,
"organization": (
raw_response.find("org").text
if raw_response.find("org")
else None
),
"address": AddressData(
**{
"street_1": (
raw_response.find_all("street")[0].text
if len(raw_response.find_all("street")) >= 1
else None
),
"street_2": (
raw_response.find_all("street")[1].text
if len(raw_response.find_all("street")) >= 2
else None
),
"street_3": (
raw_response.find_all("street")[2].text
if len(raw_response.find_all("street")) >= 3
else None
),
"city": raw_response.find("city").text,
"province": (
raw_response.find("sp").text
if raw_response.find("sp")
else None
),
"postal_code": (
raw_response.find("pc").text
if raw_response.find("pc")
else None
),
"country_code": raw_response.find("cc").text,
}
),
}
),
"phone": (
raw_response.find("voice").text if raw_response.find("voice") else None
),
"fax": raw_response.find("fax").text if raw_response.find("fax") else None,
"email": raw_response.find("email").text,
}
if raw_response.find("pw"):
result_data["password"] = raw_response.find("pw").text
result.result_data = ContactData(**result_data)
return result
[docs]
def create(
self, contact: ContactData, client_transaction_id: Optional[str] = None
) -> EppResultData:
"""A successful Contact Create request creates a contact object in the Registry. To create a domain name
successfully, a Registrar does not need to be the sponsor of the related hosts but must be the sponsor of all
assigned contacts.
:param contact: Contact
:param client_transaction_id: Client transaction id
:return: Result object
:rtype: EppResultData
"""
params = self._data_to_dict(contact)
params["client_transaction_id"] = client_transaction_id
if not params.get("password"):
params["password"] = helper.generate_password(16)
result = self.execute(CONTACT_CREATE_XML, **params)
return result
[docs]
def delete(
self, contact_id: str, client_transaction_id: Optional[str] = None
) -> EppResultData:
"""A successful Contact Delete request deletes a contact object from the Registry
:param contact_id: Contact ID
:param client_transaction_id: Client transaction id
:return: Result object
"""
result = self.execute(
CONTACT_DELETE_XML,
id=contact_id,
client_transaction_id=client_transaction_id,
)
return result
[docs]
def update(
self,
contact: ContactData,
add_status: Optional[str] = "",
remove_status: Optional[str] = "",
client_transaction_id: Optional[str] = None,
) -> EppResultData:
"""A successful Contact Update request modifies a contact object in the Registry. Updates to Registrant contacts
must be valid and must be complete.
:param contact: Contact details to be updated
:param add_status: Status to be added
:param remove_status: Status to be removed
:param client_transaction_id: Client transaction id
:return: Result object
:rtype: EppResultData
"""
params = self._data_to_dict(contact)
params["add_status"] = add_status
params["remove_status"] = remove_status
params["postalinfo_change"] = bool(contact.postal_info)
params["address_change"] = bool(
contact.postal_info and contact.postal_info.address
)
params["client_transaction_id"] = client_transaction_id
result = self.execute(CONTACT_UPDATE_XML, **params)
return result