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

Verification: elegant methods for callouts #26

Merged
merged 12 commits into from
Apr 30, 2024
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- uses: actions/setup-node@v3
- name: Install wait-port
run: |
npm install -g wait-port
sudo npm install -g wait-port

- name: Test E2E with Pytest and Wiremock
run: |
Expand Down
76 changes: 66 additions & 10 deletions sinch/domains/verification/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,93 @@
GetVerificationStatusByReferenceResponse
)
from sinch.domains.verification.models.requests import (
StartVerificationRequest,
StartSMSVerificationRequest,
StartFlashCallVerificationRequest,
StartCalloutVerificationRequest,
StartSeamlessVerificationRequest,
ReportVerificationByIdentityRequest,
ReportVerificationByIdRequest,
GetVerificationStatusByIdRequest,
GetVerificationStatusByIdentityRequest,
GetVerificationStatusByReferenceRequest
)

from sinch.domains.verification.enums import VerificationMethod
from sinch.domains.verification.models import VerificationIdentity


class Verifications:
def __init__(self, sinch):
self._sinch = sinch

def start(
def start_sms(
self,
identity: VerificationIdentity,
reference: str = None,
custom: str = None,
expiry: str = None,
code_type: str = None,
template: str = None
) -> StartVerificationResponse:
return self._sinch.configuration.transport.request(
StartVerificationEndpoint(
request_data=StartSMSVerificationRequest(
identity=identity,
reference=reference,
custom=custom,
expiry=expiry,
code_type=code_type,
template=template
)
)
)

def start_flash_call(
self,
identity: VerificationIdentity,
reference: str = None,
custom: str = None,
dial_timeout: int = None
) -> StartVerificationResponse:
return self._sinch.configuration.transport.request(
StartVerificationEndpoint(
request_data=StartFlashCallVerificationRequest(
identity=identity,
reference=reference,
custom=custom,
dial_timeout=dial_timeout
)
)
)

def start_callout(
self,
identity: dict,
method: VerificationMethod,
identity: VerificationIdentity,
reference: str = None,
custom: str = None,
flash_call_options: dict = None
speech_locale: str = None
) -> StartVerificationResponse:
return self._sinch.configuration.transport.request(
StartVerificationEndpoint(
request_data=StartVerificationRequest(
request_data=StartCalloutVerificationRequest(
identity=identity,
method=method,
reference=reference,
custom=custom,
flash_call_options=flash_call_options
speech_locale=speech_locale
)
)
)

def start_seamless(
self,
identity: VerificationIdentity,
reference: str = None,
custom: str = None
) -> StartVerificationResponse:
return self._sinch.configuration.transport.request(
StartVerificationEndpoint(
request_data=StartSeamlessVerificationRequest(
identity=identity,
reference=reference,
custom=custom
)
)
)
Expand Down
34 changes: 29 additions & 5 deletions sinch/domains/verification/endpoints/start_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from sinch.domains.verification.enums import VerificationMethod
from sinch.domains.verification.models.requests import StartVerificationRequest
from sinch.domains.verification.models.responses import (
FlashCallResponse,
SMSResponse,
DataResponse,
StartVerificationResponse,
StartSMSInitiateVerificationResponse,
StartDataInitiateVerificationResponse,
Expand Down Expand Up @@ -31,21 +34,42 @@ def request_body(self):
def handle_response(self, response: HTTPResponse) -> StartVerificationResponse:
super().handle_response(response)
if self.request_data.method == VerificationMethod.SMS.value:
sms_response = response.body.get("sms")
return StartSMSInitiateVerificationResponse(
**response.body
id=response.body.get("id"),
method=response.body.get("method"),
_links=response.body.get("_links"),
sms=SMSResponse(
interception_timeout=response.body["sms"].get("interceptionTimeout"),

Choose a reason for hiding this comment

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

We can expect the server to always return this field, but it's noted as optional in the specification. Wouldn't it be safer to check for the existence of the sms property before reading sub-properties?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added some additional logic.

template=response.body["sms"].get("template")
) if sms_response else None
)
elif self.request_data.method == VerificationMethod.FLASHCALL.value:
elif self.request_data.method == VerificationMethod.FLASH_CALL.value:
flash_call_response = response.body.get("flashCall")
return StartFlashCallInitiateVerificationResponse(
id=response.body.get("id"),
method=response.body.get("method"),
_links=response.body.get("_links"),
flashcall=response.body.get("flashCall")
flash_call=FlashCallResponse(
cli_filter=response.body["flashCall"].get("cliFilter"),
interception_timeout=response.body["flashCall"].get("interceptionTimeout"),
report_timeout=response.body["flashCall"].get("reportTimeout"),
deny_call_after=response.body["flashCall"].get("denyCallAfter")
) if flash_call_response else None
)
elif self.request_data.method == VerificationMethod.CALLOUT.value:
return StartCalloutInitiateVerificationResponse(
**response.body
id=response.body.get("id"),
method=response.body.get("method"),
_links=response.body.get("_links")
)
elif self.request_data.method == VerificationMethod.SEAMLESS.value:
seamless_response = response.body.get("seamless")
return StartDataInitiateVerificationResponse(
**response.body
id=response.body.get("id"),
method=response.body.get("method"),
_links=response.body.get("_links"),
seamless=DataResponse(
target_uri=response.body["seamless"].get("targetUri")
) if seamless_response else None
)
2 changes: 1 addition & 1 deletion sinch/domains/verification/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

class VerificationMethod(Enum):
SMS = "sms"
FLASHCALL = "flashcall"
FLASH_CALL = "flashCall"
CALLOUT = "callout"
SEAMLESS = "seamless"

Expand Down
6 changes: 6 additions & 0 deletions sinch/domains/verification/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import TypedDict, Literal


class VerificationIdentity(TypedDict):
type: Literal["number"]
endpoint: str
66 changes: 63 additions & 3 deletions sinch/domains/verification/models/requests.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,75 @@
from dataclasses import dataclass
from sinch.core.models.base_model import SinchRequestBaseModel
from sinch.domains.verification.enums import VerificationMethod
from sinch.domains.verification.models import VerificationIdentity


@dataclass
class StartVerificationRequest(SinchRequestBaseModel):
identity: dict
method: VerificationMethod
identity: VerificationIdentity
reference: str
custom: str
flash_call_options: dict


@dataclass
class StartSMSVerificationRequest(StartVerificationRequest):
expiry: str
code_type: str
template: str
method: str = VerificationMethod.SMS.value

def as_dict(self):
payload = super().as_dict()
payload["smsOptions"] = {}

if payload.get("code_type"):
payload["smsOptions"].update({
"codeType": payload.pop("code_type")
})
elif payload.get("expiry"):
payload["smsOptions"].update({
"expiry": payload.pop("expiry")
})
elif payload.get("template"):
payload["smsOptions"].update({
"template": payload.pop("template")
})
return payload


@dataclass
class StartFlashCallVerificationRequest(StartVerificationRequest):
dial_timeout: int
method: str = VerificationMethod.FLASH_CALL.value

def as_dict(self):
payload = super().as_dict()
if payload.get("dial_timeout"):
payload["flashCallOptions"] = {
"dialTimeout": payload.pop("dial_timeout")
}
return payload


@dataclass
class StartCalloutVerificationRequest(StartVerificationRequest):
speech_locale: str
method: str = VerificationMethod.CALLOUT.value

def as_dict(self):
payload = super().as_dict()
if payload.get("speech_locale"):
payload["calloutOptions"] = {
"speech": {
"locale": payload.pop("speech_locale")
}
}
return payload


@dataclass
class StartSeamlessVerificationRequest(StartVerificationRequest):
method: str = VerificationMethod.SEAMLESS.value


@dataclass
Expand Down
31 changes: 25 additions & 6 deletions sinch/domains/verification/models/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
from sinch.domains.verification.enums import VerificationMethod, VerificationStatus


@dataclass
class FlashCallResponse:
cli_filter: str
interception_timeout: int
report_timeout: int
deny_call_after: int


@dataclass
class SMSResponse:
template: str
interception_timeout: str


@dataclass
class DataResponse:
target_uri: str


@dataclass
class StartVerificationResponse(SinchBaseModel):
id: str
Expand All @@ -12,22 +31,22 @@ class StartVerificationResponse(SinchBaseModel):

@dataclass
class StartSMSInitiateVerificationResponse(StartVerificationResponse):
sms: dict
sms: SMSResponse


@dataclass
class StartFlashCallInitiateVerificationResponse(StartVerificationResponse):
flashcall: dict
flash_call: FlashCallResponse


@dataclass
class StartCalloutInitiateVerificationResponse(StartVerificationResponse):
callout: dict
class StartDataInitiateVerificationResponse(StartVerificationResponse):
seamless: DataResponse


@dataclass
class StartDataInitiateVerificationResponse(StartVerificationResponse):
seamless: dict
class StartCalloutInitiateVerificationResponse(StartVerificationResponse):
pass


@dataclass
Expand Down
Loading
Loading