cloudfloordns.models.domain
1# from dataclasses import dataclass, field 2import datetime 3import logging 4from collections import ChainMap 5from typing import Any, List, Literal, Optional 6 7from pydantic import AliasChoices, BaseModel, ConfigDict, Field, field_validator 8 9from cloudfloordns.constants import NS1, NS2, NS3, NS4 10 11 12class DomainDescription(BaseModel): 13 """ 14 Pydantic model 15 """ 16 17 status: Optional[str] 18 status_extended: Optional[str] 19 20 class Config: 21 extra = "allow" 22 23 24class Contact(BaseModel): 25 """ 26 Pydantic model 27 """ 28 29 firstname: Optional[str] = Field(alias="FirstName", default=None) 30 lastname: Optional[str] = Field(alias="LastName", default=None) 31 companyname: Optional[str] = Field(alias="Organization", default=None) 32 streetaddress: Optional[str] = Field(alias="Address", default=None) 33 city: Optional[str] = Field(alias="City", default=None) 34 state: Optional[str] = Field(alias="State", default=None) 35 postalcode: Optional[str] = Field(alias="PostalCode", default=None) 36 country: Optional[str] = Field(alias="Country", default=None) 37 phone: Optional[str] = Field(alias="Phone", default=None) 38 fax: Optional[str] = Field(alias="Fax", default=None) 39 email: Optional[str] = Field(alias="Email", default=None) 40 41 class Config: 42 populate_by_name = True 43 extra = "ignore" 44 45 def dump_as(self, prefix, by_alias=False): 46 prefix = prefix.title() if by_alias else prefix.lower() 47 return { 48 f"{prefix}{k}": v for k, v in self.model_dump(by_alias=by_alias).items() 49 } 50 51 def as_owner(self, by_alias=False): 52 return self.dump_as("owner", by_alias=by_alias) 53 54 def as_admin(self, by_alias=False): 55 return self.dump_as("admin", by_alias=by_alias) 56 57 def as_tech(self, by_alias=False): 58 return self.dump_as("tech", by_alias=by_alias) 59 60 def as_bill(self, by_alias=False): 61 return self.dump_as("bill", by_alias=by_alias) 62 63 64class DomainPayload(BaseModel): 65 """ 66 Pydantic model 67 """ 68 69 domainname: str 70 organisation: Optional[str] = Field(default=None, alias="DomainOrganization") 71 72 # Owner informations 73 ownerfirstname: str = Field(alias="OwnerFirstName") 74 ownerlastname: str = Field(alias="OwnerLastName") 75 ownercompanyname: str = Field(alias="OwnerOrganization") 76 ownerstreetaddress: str = Field(alias="OwnerAddress") 77 ownercity: str = Field(alias="OwnerCity") 78 ownerstate: str = Field(alias="OwnerState") 79 ownerpostalcode: str = Field(alias="OwnerPostalCode") 80 ownercountry: str = Field(alias="OwnerCountry") 81 ownerphone: str = Field(alias="OwnerPhone") 82 ownerfax: str = Field(alias="OwnerFax") 83 owneremail: str = Field(alias="OwnerEmail") 84 85 # Admin informations 86 adminfirstname: str = Field(alias="AdminFirstName") 87 adminlastname: str = Field(alias="AdminLastName") 88 admincompanyname: str = Field(alias="AdminOrganization") 89 adminstreetaddress: str = Field(alias="AdminAddress") 90 admincity: str = Field(alias="AdminCity") 91 adminstate: str = Field(alias="AdminState") 92 adminpostalcode: str = Field(alias="AdminPostalCode") 93 admincountry: str = Field(alias="AdminCountry") 94 adminphone: str = Field(alias="AdminPhone") 95 adminfax: str = Field(alias="AdminFax") 96 adminemail: str = Field(alias="AdminEmail") 97 98 # Billing Contact informations 99 billfirstname: str = Field(alias="BillFirstName") 100 billlastname: str = Field(alias="BillLastName") 101 billcompanyname: str = Field(alias="BillOrganization") 102 billstreetaddress: str = Field(alias="BillAddress") 103 billcity: str = Field(alias="BillCity") 104 billstate: str = Field(alias="BillState") 105 billpostalcode: str = Field(alias="BillPostalCode") 106 billcountry: str = Field(alias="BillCountry") 107 billphone: str = Field(alias="BillPhone") 108 billfax: str = Field(alias="BillFax") 109 billemail: str = Field(alias="BillEmail") 110 111 # Technical Contact informations 112 techfirstname: str = Field(alias="TechFirstName") 113 techlastname: str = Field(alias="TechLastName") 114 techcompanyname: str = Field(alias="TechOrganization") 115 techstreetaddress: str = Field(alias="TechAddress") 116 techcity: str = Field(alias="TechCity") 117 techstate: str = Field(alias="TechState") 118 techcountry: str = Field(alias="TechCountry") 119 techpostalcode: str = Field(alias="TechPostalCode") 120 techphone: str = Field(alias="TechPhone") 121 techfax: str = Field(alias="TechFax") 122 techemail: str = Field(alias="TechEmail") 123 124 # Other informations 125 groups_ids: List[str] = Field(default_factory=list) 126 assign_default_groups_nameserver: Literal[0, 1] = 1 127 autorenew: Optional[str] = Field( 128 validation_alias=AliasChoices("auto_renew", "autorenew"), default=None 129 ) 130 reg_opt_out: Optional[str] = None 131 nom_type: Optional[str] = None 132 lock: Optional[str] = Field( 133 validation_alias=AliasChoices("lock", "locked"), default=None 134 ) 135 136 domain_ns1: Optional[str] = Field(alias="DomainNS1", default=NS1) 137 domain_ns2: Optional[str] = Field(alias="DomainNS2", default=NS2) 138 domain_ns3: Optional[str] = Field(alias="DomainNS3", default=NS3) 139 domain_ns4: Optional[str] = Field(alias="DomainNS4", default=NS4) 140 domain_ns1_ip: Optional[str] = Field(alias="DomainNS1IP", default=None) 141 domain_ns2_ip: Optional[str] = Field(alias="DomainNS2IP", default=None) 142 domain_ns3_ip: Optional[str] = Field(alias="DomainNS3IP", default=None) 143 domain_ns4_ip: Optional[str] = Field(alias="DomainNS4IP", default=None) 144 145 @field_validator("lock", mode="before") 146 @classmethod 147 def ensure_locked_as_string(cls, v: Any): 148 if not isinstance(v, str): 149 return str(v) 150 return v 151 152 def _setcontact(self, prefix, info: Contact): 153 data = info.dump_as(prefix) 154 for k, v in data.items(): 155 setattr(self, k, v) 156 # Domain.model_validate(self) 157 return self 158 159 def set_owner(self, info: Contact): 160 return self._setcontact("Owner", info) 161 162 def set_admin(self, info: Contact): 163 return self._setcontact("Admin", info) 164 165 def set_bill(self, info: Contact): 166 return self._setcontact("Bill", info) 167 168 def set_tech(self, info: Contact): 169 return self._setcontact("Tech", info) 170 171 @classmethod 172 def prepare( 173 cls, 174 domainname: str, 175 owner: Contact, 176 admin: Contact, 177 bill: Contact, 178 tech: Contact, 179 ) -> "DomainPayload": 180 contact_data = { 181 k: v 182 for prefix, c in ( 183 ("Owner", owner), 184 ("Admin", admin), 185 ("Bill", bill), 186 ("Tech", tech), 187 ) 188 for k, v in c.dump_as(prefix).items() 189 } 190 return cls.model_validate({"domainname": domainname, **contact_data}) 191 192 def dump_for_update(self): 193 payload = self.model_dump( 194 by_alias=True, 195 exclude=[ 196 "domainname", 197 # "domain_ns1", 198 # "domain_ns2", 199 ], 200 exclude_none=True, 201 exclude_unset=True, 202 ) 203 payload = { 204 k: v for k, v in payload.items() if not k.lower().startswith("owner") 205 } 206 payload = {k: v for k, v in payload.items() if not k.lower().endswith("fax")} 207 return payload 208 209 # https://docs.pydantic.dev/latest/concepts/config/ 210 # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.validate_assignment 211 model_config = ConfigDict( 212 populate_by_name=True, 213 extra="ignore", 214 validate_assignment=True, 215 ) 216 217 218CLOUDFLOORDNS_NAMESERVERS = ( 219 "dns1.name-s.net.", 220 "dns2.name-s.net.", 221 "dns0.mtgsy.com.", 222 "dns3.mtgsy.com.", 223 "dns4.mtgsy.com.", 224 "ns1.g02.cfdns.net.", 225 "ns1.g02.cfdns.net.", 226 "ns2.g02.cfdns.biz.", 227 "ns3.g02.cfdns.info.", 228 "ns4.g02.cfdns.co.uk.", 229) 230CLOUDFLOORDNS_NAMESERVERS_DOMAINS = ( 231 "name-s.net.", 232 "mtgsy.com.", 233 "cfdns.net.", 234 "cfdns.net.", 235 "cfdns.biz.", 236 "cfdns.info.", 237 "cfdns.co.uk.", 238) 239 240 241def is_cloudlfoordns_ns(ns): 242 # Ensure the nameserver ends with a single dot. 243 ns = ns.strip().rstrip(".").lower() + "." 244 return ns.endswith(CLOUDFLOORDNS_NAMESERVERS_DOMAINS) 245 246 247class Domain(BaseModel): 248 """ 249 Pydantic model 250 """ 251 252 model_config = ConfigDict( 253 populate_by_name=True, 254 extra="allow", 255 # https://docs.pydantic.dev/latest/concepts/pydantic_settings/#case-sensitivity 256 # case_sensitive = True 257 # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.validate_assignment 258 validate_assignment=True, 259 ) 260 261 domainname: str = Field(validation_alias=AliasChoices("domainname", "domain")) 262 263 id: Optional[str] = None 264 epp: Optional[str] = Field(default=None, alias="EPP") 265 organisation: Optional[str] = None 266 267 # Owner informations 268 ownerfirstname: Optional[str] = Field(default=None, alias="OwnerFirstName") 269 ownerlastname: Optional[str] = Field(default=None, alias="OwnerLastName") 270 ownercompanyname: Optional[str] = Field(default=None, alias="OwnerOrganization") 271 ownerstreetaddress: Optional[str] = Field(default=None, alias="OwnerAddress") 272 ownercity: Optional[str] = Field(default=None, alias="OwnerCity") 273 ownerstate: Optional[str] = Field(default=None, alias="OwnerState") 274 ownerpostalcode: Optional[str] = Field(default=None, alias="OwnerPostalCode") 275 ownercountry: Optional[str] = Field(default=None, alias="OwnerCountry") 276 ownerphone: Optional[str] = Field(default=None, alias="OwnerPhone") 277 ownerfax: Optional[str] = Field(default=None, alias="OwnerFax") 278 owneremail: Optional[str] = Field(default=None, alias="OwnerEmail") 279 280 # Admin informations 281 adminfirstname: Optional[str] = Field(default=None, alias="AdminFirstName") 282 adminlastname: Optional[str] = Field(default=None, alias="AdminLastName") 283 admincompanyname: Optional[str] = Field(default=None, alias="AdminOrganization") 284 adminstreetaddress: Optional[str] = Field(default=None, alias="AdminAddress") 285 admincity: Optional[str] = Field(default=None, alias="AdminCity") 286 adminstate: Optional[str] = Field(default=None, alias="AdminState") 287 adminpostalcode: Optional[str] = Field(default=None, alias="AdminPostalCode") 288 admincountry: Optional[str] = Field(default=None, alias="AdminCountry") 289 adminphone: Optional[str] = Field(default=None, alias="AdminPhone") 290 adminfax: Optional[str] = Field(default=None, alias="AdminFax") 291 adminemail: Optional[str] = Field(default=None, alias="AdminEmail") 292 293 # Billing Contact informations 294 billfirstname: Optional[str] = Field(default=None, alias="BillFirstName") 295 billlastname: Optional[str] = Field(default=None, alias="BillLastName") 296 billcompanyname: Optional[str] = Field(default=None, alias="BillOrganization") 297 billstreetaddress: Optional[str] = Field(default=None, alias="BillAddress") 298 billcity: Optional[str] = Field(default=None, alias="BillCity") 299 # There is a typo in the returned value. 300 billstate: Optional[str] = Field( 301 default=None, 302 alias="BillState", 303 validation_alias=AliasChoices("billState", "BillState"), 304 ) 305 billpostalcode: Optional[str] = Field(default=None, alias="BillPostalCode") 306 billcountry: Optional[str] = Field(default=None, alias="BillCountry") 307 billphone: Optional[str] = Field(default=None, alias="BillPhone") 308 billfax: Optional[str] = Field(default=None, alias="BillFax") 309 billemail: Optional[str] = Field(default=None, alias="BillEmail") 310 311 # Technical Contact informations 312 techfirstname: Optional[str] = Field(default=None, alias="TechFirstName") 313 techlastname: Optional[str] = Field(default=None, alias="TechLastName") 314 techcompanyname: Optional[str] = Field(default=None, alias="TechOrganization") 315 techstreetaddress: Optional[str] = Field(default=None, alias="TechAddress") 316 techcity: Optional[str] = Field(default=None, alias="TechCity") 317 techstate: Optional[str] = Field(default=None, alias="TechState") 318 techcountry: Optional[str] = Field(default=None, alias="TechCountry") 319 techpostalcode: Optional[str] = Field(default=None, alias="TechPostalCode") 320 techphone: Optional[str] = Field(default=None, alias="TechPhone") 321 techfax: Optional[str] = Field(default=None, alias="TechFax") 322 techemail: Optional[str] = Field(default=None, alias="TechEmail") 323 324 # Other informations 325 auto_renew: Optional[str] = None 326 reg_opt_out: Optional[str] = None 327 username: Optional[str] = None 328 status: Optional[str] = None 329 use_trustee: Optional[str] = None 330 locked: Optional[str] = None 331 editzone: Optional[str] = None 332 expires: Optional[datetime.date] = None 333 deleteonexpiry: Optional[str] = None 334 companyregno: Optional[str] = None 335 client_delete_prohibited_lock: Optional[str] = None 336 client_update_prohibited_lock: Optional[str] = None 337 client_transfer_prohibited_lock: Optional[str] = None 338 registeredhere: Optional[str] = None 339 nameserver: List[str] = Field(default_factory=list) 340 domain_description: Optional[DomainDescription] = None 341 342 @property 343 def is_externally_managed(self): 344 is_cfdns_ns = [is_cloudlfoordns_ns(ns) for ns in self.nameserver] 345 if all(is_cfdns_ns): 346 return False 347 if not any(is_cfdns_ns): 348 return True 349 # Part of the nameserver are owned by CFDns, the rest is not 350 # => The nameserver configuration is wrong 351 ns_txt = ", ".join(self.nameserver) 352 logging.warning( 353 f"Domain {self.domainname} has inconsistant nameservers: {ns_txt}" 354 ) 355 return False 356 357 @field_validator("locked", mode="before") 358 @classmethod 359 def ensure_locked_as_string(cls, v: Any): 360 if not isinstance(v, str): 361 return str(v) 362 return v 363 364 # @model_validator(mode='before') 365 # @classmethod 366 # def check_card_number_omitted(cls, data: Any) -> Any: 367 # if isinstance(data, dict): 368 # assert ( 369 # 'card_number' not in data 370 # ), 'card_number should not be included' 371 # return data 372 373 def _setcontact(self, prefix, info: Contact): 374 data = info.dump_as(prefix) 375 for k, v in data.items(): 376 setattr(self, k, v) 377 # Domain.model_validate(self) 378 return self 379 380 def _getcontact(self, prefix: str) -> Contact: 381 data = {k.removeprefix(prefix): v for k, v in self.model_dump().items()} 382 return Contact.model_validate(data) 383 384 def set_owner(self, info: Contact): 385 return self._setcontact("owner", info) 386 387 def set_admin(self, info: Contact): 388 return self._setcontact("admin", info) 389 390 def set_bill(self, info: Contact): 391 return self._setcontact("bill", info) 392 393 def set_tech(self, info: Contact): 394 return self._setcontact("tech", info) 395 396 def update_contact( 397 self, 398 owner: Optional[Contact] = None, 399 admin: Optional[Contact] = None, 400 tech: Optional[Contact] = None, 401 bill: Optional[Contact] = None, 402 timeout=None, 403 ): 404 converted = ( 405 d 406 for d in ( 407 owner and owner.as_owner(), 408 admin and admin.as_admin(), 409 tech and tech.as_tech(), 410 bill and bill.as_bill(), 411 ) 412 if d 413 ) 414 data = dict(ChainMap(*converted)) 415 for k, v in data.items(): 416 setattr(self, k, v) 417 # Domain.model_validate(self) 418 return self 419 420 def register_payload(self, use_default_ns: bool = True) -> DomainPayload: 421 data = self.model_dump(by_alias=True) 422 data.update( 423 { 424 # "groups_ids": ..., 425 "assign_default_groups_nameserver": 1 if use_default_ns else 0, 426 } 427 ) 428 429 return DomainPayload.model_validate(data) 430 431 def dump_for_update(self): 432 return self.model_dump( 433 by_alias=True, 434 exclude=[ 435 "epp", 436 "status", 437 "use_trustee", 438 "locked", 439 "domain_description", 440 ], 441 )
class
DomainDescription(pydantic.main.BaseModel):
13class DomainDescription(BaseModel): 14 """ 15 Pydantic model 16 """ 17 18 status: Optional[str] 19 status_extended: Optional[str] 20 21 class Config: 22 extra = "allow"
Pydantic model
class
DomainDescription.Config:
class
Contact(pydantic.main.BaseModel):
25class Contact(BaseModel): 26 """ 27 Pydantic model 28 """ 29 30 firstname: Optional[str] = Field(alias="FirstName", default=None) 31 lastname: Optional[str] = Field(alias="LastName", default=None) 32 companyname: Optional[str] = Field(alias="Organization", default=None) 33 streetaddress: Optional[str] = Field(alias="Address", default=None) 34 city: Optional[str] = Field(alias="City", default=None) 35 state: Optional[str] = Field(alias="State", default=None) 36 postalcode: Optional[str] = Field(alias="PostalCode", default=None) 37 country: Optional[str] = Field(alias="Country", default=None) 38 phone: Optional[str] = Field(alias="Phone", default=None) 39 fax: Optional[str] = Field(alias="Fax", default=None) 40 email: Optional[str] = Field(alias="Email", default=None) 41 42 class Config: 43 populate_by_name = True 44 extra = "ignore" 45 46 def dump_as(self, prefix, by_alias=False): 47 prefix = prefix.title() if by_alias else prefix.lower() 48 return { 49 f"{prefix}{k}": v for k, v in self.model_dump(by_alias=by_alias).items() 50 } 51 52 def as_owner(self, by_alias=False): 53 return self.dump_as("owner", by_alias=by_alias) 54 55 def as_admin(self, by_alias=False): 56 return self.dump_as("admin", by_alias=by_alias) 57 58 def as_tech(self, by_alias=False): 59 return self.dump_as("tech", by_alias=by_alias) 60 61 def as_bill(self, by_alias=False): 62 return self.dump_as("bill", by_alias=by_alias)
Pydantic model
class
Contact.Config:
class
DomainPayload(pydantic.main.BaseModel):
65class DomainPayload(BaseModel): 66 """ 67 Pydantic model 68 """ 69 70 domainname: str 71 organisation: Optional[str] = Field(default=None, alias="DomainOrganization") 72 73 # Owner informations 74 ownerfirstname: str = Field(alias="OwnerFirstName") 75 ownerlastname: str = Field(alias="OwnerLastName") 76 ownercompanyname: str = Field(alias="OwnerOrganization") 77 ownerstreetaddress: str = Field(alias="OwnerAddress") 78 ownercity: str = Field(alias="OwnerCity") 79 ownerstate: str = Field(alias="OwnerState") 80 ownerpostalcode: str = Field(alias="OwnerPostalCode") 81 ownercountry: str = Field(alias="OwnerCountry") 82 ownerphone: str = Field(alias="OwnerPhone") 83 ownerfax: str = Field(alias="OwnerFax") 84 owneremail: str = Field(alias="OwnerEmail") 85 86 # Admin informations 87 adminfirstname: str = Field(alias="AdminFirstName") 88 adminlastname: str = Field(alias="AdminLastName") 89 admincompanyname: str = Field(alias="AdminOrganization") 90 adminstreetaddress: str = Field(alias="AdminAddress") 91 admincity: str = Field(alias="AdminCity") 92 adminstate: str = Field(alias="AdminState") 93 adminpostalcode: str = Field(alias="AdminPostalCode") 94 admincountry: str = Field(alias="AdminCountry") 95 adminphone: str = Field(alias="AdminPhone") 96 adminfax: str = Field(alias="AdminFax") 97 adminemail: str = Field(alias="AdminEmail") 98 99 # Billing Contact informations 100 billfirstname: str = Field(alias="BillFirstName") 101 billlastname: str = Field(alias="BillLastName") 102 billcompanyname: str = Field(alias="BillOrganization") 103 billstreetaddress: str = Field(alias="BillAddress") 104 billcity: str = Field(alias="BillCity") 105 billstate: str = Field(alias="BillState") 106 billpostalcode: str = Field(alias="BillPostalCode") 107 billcountry: str = Field(alias="BillCountry") 108 billphone: str = Field(alias="BillPhone") 109 billfax: str = Field(alias="BillFax") 110 billemail: str = Field(alias="BillEmail") 111 112 # Technical Contact informations 113 techfirstname: str = Field(alias="TechFirstName") 114 techlastname: str = Field(alias="TechLastName") 115 techcompanyname: str = Field(alias="TechOrganization") 116 techstreetaddress: str = Field(alias="TechAddress") 117 techcity: str = Field(alias="TechCity") 118 techstate: str = Field(alias="TechState") 119 techcountry: str = Field(alias="TechCountry") 120 techpostalcode: str = Field(alias="TechPostalCode") 121 techphone: str = Field(alias="TechPhone") 122 techfax: str = Field(alias="TechFax") 123 techemail: str = Field(alias="TechEmail") 124 125 # Other informations 126 groups_ids: List[str] = Field(default_factory=list) 127 assign_default_groups_nameserver: Literal[0, 1] = 1 128 autorenew: Optional[str] = Field( 129 validation_alias=AliasChoices("auto_renew", "autorenew"), default=None 130 ) 131 reg_opt_out: Optional[str] = None 132 nom_type: Optional[str] = None 133 lock: Optional[str] = Field( 134 validation_alias=AliasChoices("lock", "locked"), default=None 135 ) 136 137 domain_ns1: Optional[str] = Field(alias="DomainNS1", default=NS1) 138 domain_ns2: Optional[str] = Field(alias="DomainNS2", default=NS2) 139 domain_ns3: Optional[str] = Field(alias="DomainNS3", default=NS3) 140 domain_ns4: Optional[str] = Field(alias="DomainNS4", default=NS4) 141 domain_ns1_ip: Optional[str] = Field(alias="DomainNS1IP", default=None) 142 domain_ns2_ip: Optional[str] = Field(alias="DomainNS2IP", default=None) 143 domain_ns3_ip: Optional[str] = Field(alias="DomainNS3IP", default=None) 144 domain_ns4_ip: Optional[str] = Field(alias="DomainNS4IP", default=None) 145 146 @field_validator("lock", mode="before") 147 @classmethod 148 def ensure_locked_as_string(cls, v: Any): 149 if not isinstance(v, str): 150 return str(v) 151 return v 152 153 def _setcontact(self, prefix, info: Contact): 154 data = info.dump_as(prefix) 155 for k, v in data.items(): 156 setattr(self, k, v) 157 # Domain.model_validate(self) 158 return self 159 160 def set_owner(self, info: Contact): 161 return self._setcontact("Owner", info) 162 163 def set_admin(self, info: Contact): 164 return self._setcontact("Admin", info) 165 166 def set_bill(self, info: Contact): 167 return self._setcontact("Bill", info) 168 169 def set_tech(self, info: Contact): 170 return self._setcontact("Tech", info) 171 172 @classmethod 173 def prepare( 174 cls, 175 domainname: str, 176 owner: Contact, 177 admin: Contact, 178 bill: Contact, 179 tech: Contact, 180 ) -> "DomainPayload": 181 contact_data = { 182 k: v 183 for prefix, c in ( 184 ("Owner", owner), 185 ("Admin", admin), 186 ("Bill", bill), 187 ("Tech", tech), 188 ) 189 for k, v in c.dump_as(prefix).items() 190 } 191 return cls.model_validate({"domainname": domainname, **contact_data}) 192 193 def dump_for_update(self): 194 payload = self.model_dump( 195 by_alias=True, 196 exclude=[ 197 "domainname", 198 # "domain_ns1", 199 # "domain_ns2", 200 ], 201 exclude_none=True, 202 exclude_unset=True, 203 ) 204 payload = { 205 k: v for k, v in payload.items() if not k.lower().startswith("owner") 206 } 207 payload = {k: v for k, v in payload.items() if not k.lower().endswith("fax")} 208 return payload 209 210 # https://docs.pydantic.dev/latest/concepts/config/ 211 # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.validate_assignment 212 model_config = ConfigDict( 213 populate_by_name=True, 214 extra="ignore", 215 validate_assignment=True, 216 )
Pydantic model
@classmethod
def
prepare( cls, domainname: str, owner: Contact, admin: Contact, bill: Contact, tech: Contact) -> DomainPayload:
172 @classmethod 173 def prepare( 174 cls, 175 domainname: str, 176 owner: Contact, 177 admin: Contact, 178 bill: Contact, 179 tech: Contact, 180 ) -> "DomainPayload": 181 contact_data = { 182 k: v 183 for prefix, c in ( 184 ("Owner", owner), 185 ("Admin", admin), 186 ("Bill", bill), 187 ("Tech", tech), 188 ) 189 for k, v in c.dump_as(prefix).items() 190 } 191 return cls.model_validate({"domainname": domainname, **contact_data})
def
dump_for_update(self):
193 def dump_for_update(self): 194 payload = self.model_dump( 195 by_alias=True, 196 exclude=[ 197 "domainname", 198 # "domain_ns1", 199 # "domain_ns2", 200 ], 201 exclude_none=True, 202 exclude_unset=True, 203 ) 204 payload = { 205 k: v for k, v in payload.items() if not k.lower().startswith("owner") 206 } 207 payload = {k: v for k, v in payload.items() if not k.lower().endswith("fax")} 208 return payload
CLOUDFLOORDNS_NAMESERVERS =
('dns1.name-s.net.', 'dns2.name-s.net.', 'dns0.mtgsy.com.', 'dns3.mtgsy.com.', 'dns4.mtgsy.com.', 'ns1.g02.cfdns.net.', 'ns1.g02.cfdns.net.', 'ns2.g02.cfdns.biz.', 'ns3.g02.cfdns.info.', 'ns4.g02.cfdns.co.uk.')
CLOUDFLOORDNS_NAMESERVERS_DOMAINS =
('name-s.net.', 'mtgsy.com.', 'cfdns.net.', 'cfdns.net.', 'cfdns.biz.', 'cfdns.info.', 'cfdns.co.uk.')
def
is_cloudlfoordns_ns(ns):
class
Domain(pydantic.main.BaseModel):
248class Domain(BaseModel): 249 """ 250 Pydantic model 251 """ 252 253 model_config = ConfigDict( 254 populate_by_name=True, 255 extra="allow", 256 # https://docs.pydantic.dev/latest/concepts/pydantic_settings/#case-sensitivity 257 # case_sensitive = True 258 # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.validate_assignment 259 validate_assignment=True, 260 ) 261 262 domainname: str = Field(validation_alias=AliasChoices("domainname", "domain")) 263 264 id: Optional[str] = None 265 epp: Optional[str] = Field(default=None, alias="EPP") 266 organisation: Optional[str] = None 267 268 # Owner informations 269 ownerfirstname: Optional[str] = Field(default=None, alias="OwnerFirstName") 270 ownerlastname: Optional[str] = Field(default=None, alias="OwnerLastName") 271 ownercompanyname: Optional[str] = Field(default=None, alias="OwnerOrganization") 272 ownerstreetaddress: Optional[str] = Field(default=None, alias="OwnerAddress") 273 ownercity: Optional[str] = Field(default=None, alias="OwnerCity") 274 ownerstate: Optional[str] = Field(default=None, alias="OwnerState") 275 ownerpostalcode: Optional[str] = Field(default=None, alias="OwnerPostalCode") 276 ownercountry: Optional[str] = Field(default=None, alias="OwnerCountry") 277 ownerphone: Optional[str] = Field(default=None, alias="OwnerPhone") 278 ownerfax: Optional[str] = Field(default=None, alias="OwnerFax") 279 owneremail: Optional[str] = Field(default=None, alias="OwnerEmail") 280 281 # Admin informations 282 adminfirstname: Optional[str] = Field(default=None, alias="AdminFirstName") 283 adminlastname: Optional[str] = Field(default=None, alias="AdminLastName") 284 admincompanyname: Optional[str] = Field(default=None, alias="AdminOrganization") 285 adminstreetaddress: Optional[str] = Field(default=None, alias="AdminAddress") 286 admincity: Optional[str] = Field(default=None, alias="AdminCity") 287 adminstate: Optional[str] = Field(default=None, alias="AdminState") 288 adminpostalcode: Optional[str] = Field(default=None, alias="AdminPostalCode") 289 admincountry: Optional[str] = Field(default=None, alias="AdminCountry") 290 adminphone: Optional[str] = Field(default=None, alias="AdminPhone") 291 adminfax: Optional[str] = Field(default=None, alias="AdminFax") 292 adminemail: Optional[str] = Field(default=None, alias="AdminEmail") 293 294 # Billing Contact informations 295 billfirstname: Optional[str] = Field(default=None, alias="BillFirstName") 296 billlastname: Optional[str] = Field(default=None, alias="BillLastName") 297 billcompanyname: Optional[str] = Field(default=None, alias="BillOrganization") 298 billstreetaddress: Optional[str] = Field(default=None, alias="BillAddress") 299 billcity: Optional[str] = Field(default=None, alias="BillCity") 300 # There is a typo in the returned value. 301 billstate: Optional[str] = Field( 302 default=None, 303 alias="BillState", 304 validation_alias=AliasChoices("billState", "BillState"), 305 ) 306 billpostalcode: Optional[str] = Field(default=None, alias="BillPostalCode") 307 billcountry: Optional[str] = Field(default=None, alias="BillCountry") 308 billphone: Optional[str] = Field(default=None, alias="BillPhone") 309 billfax: Optional[str] = Field(default=None, alias="BillFax") 310 billemail: Optional[str] = Field(default=None, alias="BillEmail") 311 312 # Technical Contact informations 313 techfirstname: Optional[str] = Field(default=None, alias="TechFirstName") 314 techlastname: Optional[str] = Field(default=None, alias="TechLastName") 315 techcompanyname: Optional[str] = Field(default=None, alias="TechOrganization") 316 techstreetaddress: Optional[str] = Field(default=None, alias="TechAddress") 317 techcity: Optional[str] = Field(default=None, alias="TechCity") 318 techstate: Optional[str] = Field(default=None, alias="TechState") 319 techcountry: Optional[str] = Field(default=None, alias="TechCountry") 320 techpostalcode: Optional[str] = Field(default=None, alias="TechPostalCode") 321 techphone: Optional[str] = Field(default=None, alias="TechPhone") 322 techfax: Optional[str] = Field(default=None, alias="TechFax") 323 techemail: Optional[str] = Field(default=None, alias="TechEmail") 324 325 # Other informations 326 auto_renew: Optional[str] = None 327 reg_opt_out: Optional[str] = None 328 username: Optional[str] = None 329 status: Optional[str] = None 330 use_trustee: Optional[str] = None 331 locked: Optional[str] = None 332 editzone: Optional[str] = None 333 expires: Optional[datetime.date] = None 334 deleteonexpiry: Optional[str] = None 335 companyregno: Optional[str] = None 336 client_delete_prohibited_lock: Optional[str] = None 337 client_update_prohibited_lock: Optional[str] = None 338 client_transfer_prohibited_lock: Optional[str] = None 339 registeredhere: Optional[str] = None 340 nameserver: List[str] = Field(default_factory=list) 341 domain_description: Optional[DomainDescription] = None 342 343 @property 344 def is_externally_managed(self): 345 is_cfdns_ns = [is_cloudlfoordns_ns(ns) for ns in self.nameserver] 346 if all(is_cfdns_ns): 347 return False 348 if not any(is_cfdns_ns): 349 return True 350 # Part of the nameserver are owned by CFDns, the rest is not 351 # => The nameserver configuration is wrong 352 ns_txt = ", ".join(self.nameserver) 353 logging.warning( 354 f"Domain {self.domainname} has inconsistant nameservers: {ns_txt}" 355 ) 356 return False 357 358 @field_validator("locked", mode="before") 359 @classmethod 360 def ensure_locked_as_string(cls, v: Any): 361 if not isinstance(v, str): 362 return str(v) 363 return v 364 365 # @model_validator(mode='before') 366 # @classmethod 367 # def check_card_number_omitted(cls, data: Any) -> Any: 368 # if isinstance(data, dict): 369 # assert ( 370 # 'card_number' not in data 371 # ), 'card_number should not be included' 372 # return data 373 374 def _setcontact(self, prefix, info: Contact): 375 data = info.dump_as(prefix) 376 for k, v in data.items(): 377 setattr(self, k, v) 378 # Domain.model_validate(self) 379 return self 380 381 def _getcontact(self, prefix: str) -> Contact: 382 data = {k.removeprefix(prefix): v for k, v in self.model_dump().items()} 383 return Contact.model_validate(data) 384 385 def set_owner(self, info: Contact): 386 return self._setcontact("owner", info) 387 388 def set_admin(self, info: Contact): 389 return self._setcontact("admin", info) 390 391 def set_bill(self, info: Contact): 392 return self._setcontact("bill", info) 393 394 def set_tech(self, info: Contact): 395 return self._setcontact("tech", info) 396 397 def update_contact( 398 self, 399 owner: Optional[Contact] = None, 400 admin: Optional[Contact] = None, 401 tech: Optional[Contact] = None, 402 bill: Optional[Contact] = None, 403 timeout=None, 404 ): 405 converted = ( 406 d 407 for d in ( 408 owner and owner.as_owner(), 409 admin and admin.as_admin(), 410 tech and tech.as_tech(), 411 bill and bill.as_bill(), 412 ) 413 if d 414 ) 415 data = dict(ChainMap(*converted)) 416 for k, v in data.items(): 417 setattr(self, k, v) 418 # Domain.model_validate(self) 419 return self 420 421 def register_payload(self, use_default_ns: bool = True) -> DomainPayload: 422 data = self.model_dump(by_alias=True) 423 data.update( 424 { 425 # "groups_ids": ..., 426 "assign_default_groups_nameserver": 1 if use_default_ns else 0, 427 } 428 ) 429 430 return DomainPayload.model_validate(data) 431 432 def dump_for_update(self): 433 return self.model_dump( 434 by_alias=True, 435 exclude=[ 436 "epp", 437 "status", 438 "use_trustee", 439 "locked", 440 "domain_description", 441 ], 442 )
Pydantic model
model_config =
{'populate_by_name': True, 'extra': 'allow', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
domain_description: Optional[DomainDescription]
is_externally_managed
343 @property 344 def is_externally_managed(self): 345 is_cfdns_ns = [is_cloudlfoordns_ns(ns) for ns in self.nameserver] 346 if all(is_cfdns_ns): 347 return False 348 if not any(is_cfdns_ns): 349 return True 350 # Part of the nameserver are owned by CFDns, the rest is not 351 # => The nameserver configuration is wrong 352 ns_txt = ", ".join(self.nameserver) 353 logging.warning( 354 f"Domain {self.domainname} has inconsistant nameservers: {ns_txt}" 355 ) 356 return False
def
update_contact( self, owner: Optional[Contact] = None, admin: Optional[Contact] = None, tech: Optional[Contact] = None, bill: Optional[Contact] = None, timeout=None):
397 def update_contact( 398 self, 399 owner: Optional[Contact] = None, 400 admin: Optional[Contact] = None, 401 tech: Optional[Contact] = None, 402 bill: Optional[Contact] = None, 403 timeout=None, 404 ): 405 converted = ( 406 d 407 for d in ( 408 owner and owner.as_owner(), 409 admin and admin.as_admin(), 410 tech and tech.as_tech(), 411 bill and bill.as_bill(), 412 ) 413 if d 414 ) 415 data = dict(ChainMap(*converted)) 416 for k, v in data.items(): 417 setattr(self, k, v) 418 # Domain.model_validate(self) 419 return self