Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
publish-pypi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Install dependencies
Expand Down
40 changes: 21 additions & 19 deletions mati/resources/verifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,7 @@ def govt_id_document(self) -> Optional[VerificationDocument]:

@property
def proof_of_residency_validation(self) -> Optional[DocumentScore]:
por = self.proof_of_residency_document
if not por:
return None
return DocumentScore(
all([step.status == 200 and not step.error for step in por.steps])
and not (
self.computed
and self.computed['is_document_expired']['data'][
'proof_of_residency'
]
),
sum([step.status for step in por.steps if not step.error]),
[step.error['code'] for step in por.steps if step.error],
)
return self.get_document_validation(self.proof_of_residency_document)

@property
def proof_of_life_validation(self) -> Optional[DocumentScore]:
Expand All @@ -139,13 +126,28 @@ def proof_of_life_validation(self) -> Optional[DocumentScore]:

@property
def govt_id_validation(self) -> Optional[DocumentScore]:
govt = self.govt_id_document
if not govt:
return self.get_document_validation(self.govt_id_document)

def get_document_validation(
self, document: Optional[VerificationDocument]
) -> Optional[DocumentScore]:
if not document:
return None
document_type = document.type.replace("-", "_")
is_expired = (
self.computed['is_document_expired']['data'][document_type]
if self.computed
else False
)
if is_expired:
document.add_expired_step()
return DocumentScore(
all(
[step.status == 200 and not step.error for step in govt.steps]
[
step.status == 200 and not step.error
for step in document.steps
]
),
sum([step.status for step in govt.steps if not step.error]),
[step.error['code'] for step in govt.steps if step.error],
sum([step.status for step in document.steps if not step.error]),
[step.error['code'] for step in document.steps if step.error],
)
22 changes: 22 additions & 0 deletions mati/types/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,28 @@ def ocr_number(self) -> str:
return self.fields['ocr_number']['value']
return ''

def add_expired_step(self) -> None:
'''
Appends an expired error step to the document if missing.
The steps list is used by the errors property.
This ensures document expiration is reflected
in the reported errors.
Required because Metamap does not add this error.
'''
step_id = f"{self.type}-document-expired"
if not any(step.id == step_id for step in self.steps):
self.steps.append(
VerificationDocumentStep(
id=step_id,
status=200,
error={
'type': 'StepError',
'code': 'document.expired',
'message': f'Document {self.document_type} expired',
},
)
)


@dataclass
class LivenessMedia:
Expand Down
2 changes: 1 addition & 1 deletion mati/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.0.6'
__version__ = '2.0.7'
25 changes: 24 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

from mati import Client
from mati.resources.verifications import Verification
from mati.types import VerificationDocument, VerificationDocumentStep

VERIFICATION_RESP = {
Expand Down Expand Up @@ -222,12 +223,34 @@ def verification(client: Client) -> Generator:


@pytest.fixture
def verification_without_pol(client: Client):
def verification_without_pol(
client: Client,
) -> Generator[Verification, None, None]:
verification = client.verifications.retrieve('634870763768f1001cac7591')
verification.steps = []
yield verification


@pytest.fixture
def verification_with_govt_expired(
client: Client,
) -> Generator[Verification, None, None]:
verification = client.verifications.retrieve('686c77811ee936aece7016ac')
verification.computed["is_document_expired"]["data"]["national_id"] = True
yield verification


@pytest.fixture
def verification_with_poa_expired(
client: Client,
) -> Generator[Verification, None, None]:
verification = client.verifications.retrieve('686c77811ee936aece7016ac')
verification.computed["is_document_expired"]["data"][
"proof_of_residency"
] = True
yield verification


@pytest.fixture
def verification_document_national_id() -> VerificationDocument:
return VerificationDocument(
Expand Down
165 changes: 165 additions & 0 deletions tests/resources/cassettes/test_retrieve_verification_invalid_govt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
interactions:
- request:
body: grant_type=client_credentials
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '29'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- mati-python/2.0.7.dev1
method: POST
uri: https://api.getmati.com/oauth
response:
body:
string: '{"access_token": "ACCESS_TOKEN", "expiresIn": 3600, "payload": {"user":
{"_id": "ID", "firstName": "FIRST_NAME", "lastName": "LAST_NAME"}}}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 08 Jul 2025 23:18:57 GMT
Transfer-Encoding:
- chunked
cache-control:
- no-store, no-cache, must-revalidate, proxy-revalidate
content-security-policy:
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
expect-ct:
- max-age=0
expires:
- '0'
pragma:
- no-cache
referrer-policy:
- no-referrer
strict-transport-security:
- max-age=15552000; includeSubDomains; preload
surrogate-control:
- no-store
x-content-type-options:
- nosniff
x-dns-prefetch-control:
- 'off'
x-download-options:
- noopen
x-envoy-upstream-service-time:
- '141'
x-frame-options:
- DENY
x-permitted-cross-domain-policies:
- none
x-request-id:
- add8dd4e-74a3-414d-83aa-f8e1dd998672
x-xss-protection:
- '1'
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- mati-python/2.0.7.dev1
method: GET
uri: https://api.getmati.com/v2/verifications/686c77811ee936aece7016ac
response:
body:
string: '{"expired": false, "identity": {"status": "verified"}, "flow": {"id":
"some_flow", "name": "Default flow"}, "documents": [{"country": "MX", "region":
"", "type": "national-id", "photos": ["https://media.getmati.com/media/xxx",
"https://media.getmati.com/media/yyy"], "steps": [{"error": null, "status":
200, "id": "template-matching"}, {"error": null, "status": 200, "id": "mexican-curp-validation",
"data": {"curp": "CURP", "fullName": "LAST FIRST", "birthDate": "01/01/1980",
"gender": "HOMBRE", "nationality": "MEXICO", "surname": "LAST", "secondSurname":
"", "name": "FIRST"}}, {"error": null, "status": 200, "id": "document-reading",
"data": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive":
true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth":
{"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate":
{"value": "2030-12-31", "label": "Date of Expiration", "format": "date"},
"curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia
36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01",
"label": "Emission Date", "format": "date"}}}, {"error": null, "status": 200,
"id": "alteration-detection"}, {"error": null, "status": 200, "id": "watchlists"}],
"fields": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive":
true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth":
{"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate":
{"value": "2030-12-31", "label": "Date of Expiration", "format": "date"},
"curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia
36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01",
"label": "Emission Date", "format": "date"}}}, {"country": "MX", "region":
null, "type": "proof-of-residency", "steps": [{"status": 200, "id": "document-reading",
"data": {"fullName": {"required": true, "label": "Name", "value": "FIRST NAME"},
"address": {"label": "Address", "value": "Varsovia 36, 06600 CDMX"}, "emissionDate":
{"format": "date", "label": "Emission Date", "value": "1880-01-01"}}, "error":
null}, {"status": 200, "id": "watchlists", "error": null}], "fields": {"address":
{"value": "Varsovia 36, 06600 CDMX"}, "emissionDate": {"value": "1880-01-01"},
"fullName": {"value": "FIRST LASTNAME"}}, "photos": ["https://media.getmati.com/file?location=xyc"]}],
"steps": [{"status": 200, "id": "liveness", "data": {"videoUrl": "https://media.getmati.com/file?location=abc",
"spriteUrl": "https://media.getmati.com/file?location=def", "selfieUrl": "https://media.getmati.com/file?location=hij"},
"error": null}], "hasProblem": false, "computed": {"age": {"data": 100}, "isDocumentExpired":
{"data": {"national-id": false, "proof-of-residency": false}}}, "id": "5d9fb1f5bfbfac001a349bfb",
"metadata": {"name": "First Last", "dob": "1980-01-01"}}'
headers:
Connection:
- keep-alive
Content-Length:
- '7918'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 08 Jul 2025 23:18:57 GMT
cache-control:
- no-store, no-cache, must-revalidate, proxy-revalidate
content-security-policy:
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
expect-ct:
- max-age=0
expires:
- '0'
pragma:
- no-cache
referrer-policy:
- no-referrer
strict-transport-security:
- max-age=15552000; includeSubDomains; preload
surrogate-control:
- no-store
x-content-type-options:
- nosniff
x-dns-prefetch-control:
- 'off'
x-download-options:
- noopen
x-envoy-upstream-service-time:
- '258'
x-frame-options:
- DENY
x-permitted-cross-domain-policies:
- none
x-request-id:
- 98bc4ee3-a97d-442f-bfbc-5fdd49f302a7
x-xss-protection:
- '1'
status:
code: 200
message: OK
version: 1
Loading
Loading