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