Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/refactor dto #16

Merged
merged 14 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ repos:
"--disable=C",
"--disable=C0415",
"--disable=W",
"--disable=R0903",
"--disable=E1101",
"--msg-template={path}||{msg_id}||{symbol}||{category}||{line}||{column}||{msg}",
]
- repo: https://github.com/pre-commit/mirrors-mypy
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension-pkg-allow-list=
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
extension-pkg-whitelist=ErrorWrapper

# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
Expand Down
7 changes: 2 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ build-backend = "poetry.core.masonry.api"
[tool.mypy]
python_version = "3.9"
plugins = ["pydantic.mypy"]
# See: https://blog.wolt.com/engineering/2021/09/30/professional-grade-mypy-configuration/
disallow_untyped_defs = true
check_untyped_defs = true
disallow_any_unimported = true
ignore_missing_imports = false
no_implicit_optional = true
warn_return_any = true
show_error_codes = true
warn_unused_ignores = true

# See: https://pydantic-docs.helpmanual.io/mypy_plugin/
# mypy per-module options:
Expand Down
93 changes: 91 additions & 2 deletions registrations/domain/dto.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"""Data transfer object for domain registration."""
"""Data transfer object for hospital domain registration."""
from __future__ import annotations

from typing import Optional

import pydantic

from registrations.domain.hospital import registration
from registrations.domain.location.location import Address
from registrations.utils import enum_utils
from registrations.utils.errors import InvalidRegistrationEntryError


class RegisterKeyContact(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected keyword: Unexpected keyword argument extra to call object.__init_subclass__.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

pydantic.BaseModel,
extra=pydantic.Extra.forbid,
allow_mutation=False,
validate_assignment=True,
arbitrary_types_allowed=False,
Expand All @@ -21,7 +25,7 @@ class RegisterKeyContact(
email: Optional[pydantic.EmailStr]


class HospitalRegistrationEntry(
class ToHospitalRegistrationEntry(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uninitialized attribute: Attribute hospital_contact_number is declared in class ToHospitalRegistrationEntry to have type str but is never initialized.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uninitialized attribute: Attribute name is declared in class ToHospitalRegistrationEntry to have type str but is never initialized.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uninitialized attribute: Attribute verified_status is declared in class ToHospitalRegistrationEntry to have type Optional[str] but is never initialized.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uninitialized attribute: Attribute address is declared in class ToHospitalRegistrationEntry to have type Address but is never initialized.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uninitialized attribute: Attribute key_contact is declared in class ToHospitalRegistrationEntry to have type Optional[RegisterKeyContact] but is never initialized.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uninitialized attribute: Attribute ownership_type is declared in class ToHospitalRegistrationEntry to have type str but is never initialized.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

pydantic.BaseModel,
allow_mutation=False,
validate_assignment=True,
Expand All @@ -35,3 +39,88 @@ class HospitalRegistrationEntry(
key_contact: Optional[RegisterKeyContact]
verified_status: Optional[str]
address: Address

@pydantic.validator("ownership_type", pre=True)
@classmethod
def validate_ownership_type(cls, ownership_type: str) -> str:
"""Validate ownership is limited to OwnershipType enum."""
if ownership_type not in registration.OwnershipType.values():
raise ValueError(f"Invalid ownership type: {ownership_type}")
return ownership_type

@pydantic.validator("name", pre=True)
@classmethod
def validate_name(cls, name: str) -> str:
"""Validate name has atleast one word with 3 characters."""
split_names = name.strip().split()
valid_conditions = split_names and (
any(len(word) >= 2 for word in split_names)
or (len(split_names) == 1 and len(name.strip().split()[0]) >= 3)
)
if not valid_conditions:
raise ValueError(f"Invalid name: {name}. Not enough characters.")
return name

@pydantic.root_validator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid decoration: While applying decorator pydantic.root_validator

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

@classmethod
def validate_hospital_entity(cls, values: dict) -> dict:
"""Validate hospital entity.

We either get a verification status or an unverified
manual registration entry. We cannot get both.
Either attribute should be accessible via registration_entry
as verified_status or key_contact.
"""
verified_status = values.get("verified_status")
key_contact: Optional[RegisterKeyContact] = values.get("key_contact")
wrong_invariant = not key_contact and not verified_status
wrong_invariant = wrong_invariant or bool(
verified_status
and (
(
verified_status
== enum_utils.enum_value_of(
registration.VerificationStatus.Unverified
)
and not key_contact
)
or (
verified_status
!= enum_utils.enum_value_of(
registration.VerificationStatus.Unverified
)
and key_contact
)
)
)
if wrong_invariant:
error_msg = (
f"Cannot have {verified_status} verification status with "
f"{(key_contact and key_contact.name) or key_contact} key contact."
)
raise InvalidRegistrationEntryError(error_msg)
return values

def build_hospital_entity_dict(self) -> dict:
"""Build hospital entity.

:return: dict, the hospital entity dict to register.
"""
builder_dict = self.dict()
if verified_status := builder_dict.get("verified_status"):
builder_dict["verified_status"] = registration.VerificationStatus(
verified_status
)
if key_contact := builder_dict.pop("key_contact", None):
builder_dict["key_contact_registrar"] = registration.ContactPerson(
name=key_contact.get("name"),
mobile_number=registration.PhoneNumber(
number=key_contact.get("mobile")
),
email=key_contact.get("email"),
)
builder_dict["phone_number"] = registration.PhoneNumber(
number=builder_dict.pop("hospital_contact_number")
)
builder_dict["hospital_name"] = builder_dict.pop("name")
return builder_dict
95 changes: 51 additions & 44 deletions registrations/domain/hospital/registration.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
from __future__ import annotations

import uuid
from typing import Any
from typing import Dict
from typing import Optional
from typing import Type
from typing import TypeVar
from typing import Union

import email_validator
import phonenumbers
import pydantic
from email_validator import EmailNotValidError
from phonenumbers import parse as parse_number

from registrations.domain.location.location import Address
from registrations.utils import enum_utils


class MissingRegistrationFieldError(pydantic.ValidationError):
def __init__(
self,
error_msg: str,
model: Type[pydantic.BaseModel],
exc_tb: Optional[str] = None,
):
error_wrapper = pydantic.error_wrappers.ErrorWrapper(
Exception(error_msg), exc_tb or error_msg
)
super().__init__(errors=[error_wrapper], model=model)

from registrations.utils.errors import MissingRegistrationFieldError

# ************************************************* #
# These are the domain entities of registration..
Expand All @@ -39,6 +27,7 @@ class OwnershipType(enum_utils.EnumWithItems):
Public = "public"
Private = "private"
Pub_Pvt = "public_private"
Charitable = "charitable"


# Value Object
Expand All @@ -55,7 +44,7 @@ class PhoneNumber(pydantic.BaseModel, allow_mutation=False, validate_assignment=
@pydantic.validator("number", pre=True)
@classmethod
def _validate_number(cls, phone_number: str) -> str:
phonenum_obj: phonenumbers.PhoneNumber = phonenumbers.parse(phone_number)
phonenum_obj: phonenumbers.PhoneNumber = parse_number(phone_number) # type: ignore[no-any-unimported]
if not (
phonenumbers.is_possible_number(phonenum_obj)
and phonenumbers.is_valid_number(phonenum_obj)
Expand All @@ -65,7 +54,12 @@ def _validate_number(cls, phone_number: str) -> str:


# Value Object
class ContactPerson(pydantic.BaseModel, allow_mutation=False, validate_assignment=True):
class ContactPerson(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected keyword: Unexpected keyword argument extra to call object.__init_subclass__.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

pydantic.BaseModel,
extra=pydantic.Extra.forbid,
allow_mutation=False,
validate_assignment=True,
):
"""Key contact person registering the hospital."""

name: str
Expand All @@ -82,6 +76,24 @@ def _validate_email(
return email


# ================================================== #
# Fix for incompatible type.
# See: https://github.com/python/mypy/issues/5382
# Fixes error of type:
# Argument 1 to <Some Caller> has incompatible type "**Dict[str, <Data types>]";
# expected...
# TypeDict and Final does not fix this.
# ================================================== #
HospitalEntryDictType = Union[
pydantic.UUID1,
str,
Optional[OwnershipType],
Address,
PhoneNumber,
Optional[VerificationStatus],
]


class HospitalEntryAggregate(pydantic.BaseModel, validate_assignment=True):
"""Aggregate hospital entity. Not to be directly used.

Expand All @@ -98,47 +110,45 @@ class HospitalEntryAggregate(pydantic.BaseModel, validate_assignment=True):
phone_number: PhoneNumber

@classmethod
def build_factory(cls, **kwargs) -> HospitalEntityType:
cls._validate_attributes(**kwargs)
def build_factory(cls, **kwargs: HospitalEntryDictType) -> HospitalEntityType:
return (
cls._build_unclaimed_hospital_factory(**kwargs)
if cls._can_be_verified(**kwargs)
else cls._build_unverified_hospital_factory(**kwargs)
)

@classmethod
def register_unverified_hospital_factory(
cls, **kwargs
) -> UnverifiedRegisteredHospital:
return UnverifiedRegisteredHospital(**kwargs)

@classmethod
def register_unclaimed_hospital_factory(cls, **kwargs) -> UnclaimedHospital:
return UnclaimedHospital(**kwargs)

@classmethod
def _build_unclaimed_hospital_factory(cls, **kwargs) -> UnclaimedHospital:
return cls.register_unclaimed_hospital_factory(**kwargs)
def _build_unclaimed_hospital_factory(
cls, **kwargs: HospitalEntryDictType
) -> UnclaimedHospital:
# Required to typecase the expectation
# of kwargs to have any type.
values_dict: Dict[str, Any] = kwargs
return UnclaimedHospital(**values_dict)

@classmethod
def _build_unverified_hospital_factory(
cls, **kwargs
cls, **kwargs: HospitalEntryDictType
) -> UnverifiedRegisteredHospital:
if not kwargs.get("key_contact_registrar"):
raise MissingRegistrationFieldError(
"Field missing. key_contact_registrar is required.",
UnverifiedRegisteredHospital,
)
return cls.register_unverified_hospital_factory(**kwargs)
# Required to typecase the expectation
# of kwargs to have any type.
values_dict: Dict[str, Any] = kwargs
return UnverifiedRegisteredHospital(**values_dict)

@pydantic.root_validator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid decoration: While applying decorator pydantic.root_validator

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

@classmethod
def _validate_attributes(cls, **kwargs) -> bool:
if missing_attrs := cls._check_missing_attributes(**kwargs):
def _validate_attributes(cls, values: dict) -> dict:
if missing_attrs := cls._check_missing_attributes(**values):
raise AttributeError(f"Missing Attributes: {','.join(missing_attrs)}")
return True
return values

@classmethod
def _check_missing_attributes(cls, **kwargs) -> Optional[list]:
def _check_missing_attributes(cls, **kwargs: str) -> Optional[list]:
"""Check entity attributes if absent in input."""
field_attrs = cls.__fields__.keys()
kwarg_keys = kwargs.keys()
Expand All @@ -147,7 +157,7 @@ def _check_missing_attributes(cls, **kwargs) -> Optional[list]:
@classmethod
def _can_be_verified(
cls,
**kwargs,
**kwargs: HospitalEntryDictType,
) -> bool:
"""Checks if the hospital entry can be verified.

Expand All @@ -171,14 +181,14 @@ class UnclaimedHospital(HospitalEntryAggregate):
verified_status: VerificationStatus

@classmethod
def hospital_is_verified(cls, **kwargs) -> bool:
def hospital_is_verified(cls, **kwargs: HospitalEntryDictType) -> bool:
return (
bool(kwargs.get("verified_status"))
and kwargs["verified_status"] == VerificationStatus.Verified
)

@classmethod
def hospital_verification_pending(cls, **kwargs) -> bool:
def hospital_verification_pending(cls, **kwargs: HospitalEntryDictType) -> bool:
return (
bool(kwargs.get("verified_status"))
and kwargs["verified_status"] == VerificationStatus.Pending
Expand All @@ -190,7 +200,4 @@ def hospital_verification_pending(cls, **kwargs) -> bool:
# Incompatible return type:
# Expected UnverifiedRegisteredHospital but got typing.Union[UnclaimedHospital, UnverifiedRegisteredHospital].
# ************************************************* #
UnionRegisteredTypes = TypeVar(
"UnionRegisteredTypes", UnverifiedRegisteredHospital, UnclaimedHospital
)
HospitalEntityType = Union[UnclaimedHospital, UnverifiedRegisteredHospital]
2 changes: 1 addition & 1 deletion registrations/domain/location/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Address(pydantic.BaseModel, extra=pydantic.Extra.forbid):

@pydantic.root_validator(pre=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid decoration: While applying decorator pydantic.root_validator(...)

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request. If you change your mind, just comment @sonatype-lift unignore.

@classmethod
def _validate_address(cls, values):
def _validate_address(cls, values: dict) -> dict:
# TODO: use internal library to validate abbreviation ISO codes for:
# 1. country
# 2. state
Expand Down
Loading