diff --git a/examples/yoti_example_django/requirements.txt b/examples/yoti_example_django/requirements.txt
index 9e07af80..cd45786c 100644
--- a/examples/yoti_example_django/requirements.txt
+++ b/examples/yoti_example_django/requirements.txt
@@ -9,7 +9,7 @@ asn1==2.2.0 # via yoti
certifi==2018.4.16 # via requests
cffi==1.14.0 # via cryptography
chardet==3.0.4 # via requests
-cryptography==2.9.2 # via pyopenssl, yoti
+cryptography==3.2 # via pyopenssl, yoti
deprecated==1.2.10 # via yoti
django-sslserver==0.22 # via -r requirements.in
django==3.0.7 # via -r requirements.in, django-sslserver
diff --git a/examples/yoti_example_flask/requirements.txt b/examples/yoti_example_flask/requirements.txt
index 5acd8600..a5a293a8 100644
--- a/examples/yoti_example_flask/requirements.txt
+++ b/examples/yoti_example_flask/requirements.txt
@@ -9,7 +9,7 @@ certifi==2018.4.16 # via requests
cffi==1.14.0 # via -r requirements.in, cryptography
chardet==3.0.4 # via requests
click==6.7 # via flask
-cryptography==2.9.2 # via pyopenssl, yoti
+cryptography==3.2 # via pyopenssl, yoti
deprecated==1.2.10 # via yoti
flask==1.1.1 # via -r requirements.in
future==0.16.0 # via yoti
diff --git a/requirements.in b/requirements.in
index 14edca50..a229f183 100644
--- a/requirements.in
+++ b/requirements.in
@@ -7,7 +7,7 @@ pbr==1.10.0
protobuf==3.13.0
pyopenssl==19.1.0
PyYAML==5.2 # PyYAML 5.3 does not support Python 3.4
-pytz==2020.1
+pytz==2020.4
requests>=2.20.0
urllib3>=1.24.3
deprecated==1.2.10
diff --git a/requirements.txt b/requirements.txt
index 4a90f75a..b8c0490b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,7 +18,7 @@ pbr==1.10.0 # via -r requirements.in
protobuf==3.13.0 # via -r requirements.in
pycparser==2.18 # via cffi
pyopenssl==19.1.0 # via -r requirements.in
-pytz==2020.1 # via -r requirements.in
+pytz==2020.4 # via -r requirements.in
pyyaml==5.2 # via -r requirements.in
requests==2.21.0 # via -r requirements.in
six==1.10.0 # via cryptography, protobuf, pyopenssl
diff --git a/setup.py b/setup.py
index 6ea005f6..4f3033a6 100644
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,7 @@
"asn1==2.2.0",
"pyopenssl>=18.0.0",
"iso8601==0.1.13",
+ "pytz==2020.4",
],
extras_require={
"examples": [
@@ -44,7 +45,7 @@
"python-coveralls==2.9.3",
"coverage==4.5.4",
"mock==2.0.0",
- "virtualenv==20.0.33",
+ "virtualenv==20.1.0",
],
},
classifiers=[
diff --git a/sonar-project.properties b/sonar-project.properties
index 28191bca..b8f41b51 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -2,8 +2,9 @@ sonar.host.url = https://sonarcloud.io
sonar.organization = getyoti
sonar.projectKey = getyoti:python
sonar.projectName = Python SDK
-sonar.projectVersion = 2.13.0
+sonar.projectVersion = 2.14.0
sonar.exclusions = yoti_python_sdk/tests/**,examples/**,yoti_python_sdk/protobuf/**/*
sonar.python.pylint.reportPath = coverage.out
sonar.verbose = true
+sonar.coverage.exclusions = yoti_python_sdk/version.py
diff --git a/yoti_python_sdk/doc_scan/__init__.py b/yoti_python_sdk/doc_scan/__init__.py
index fc9d5733..b9068ee9 100644
--- a/yoti_python_sdk/doc_scan/__init__.py
+++ b/yoti_python_sdk/doc_scan/__init__.py
@@ -7,6 +7,9 @@
from .session.create.check.face_match import RequestedFaceMatchCheckBuilder
from .session.create.check.liveness import RequestedLivenessCheckBuilder
from .session.create.task.text_extraction import RequestedTextExtractionTaskBuilder
+from .session.create.task.supplementary_doc_text_extraction import (
+ RequestedSupplementaryDocTextExtractionTaskBuilder,
+)
from .session.create.notification_config import NotificationConfigBuilder
from .session.create.sdk_config import SdkConfigBuilder
from .session.create.session_spec import SessionSpecBuilder
@@ -18,6 +21,7 @@
"RequestedFaceMatchCheckBuilder",
"RequestedIDDocumentComparisonCheckBuilder",
"RequestedTextExtractionTaskBuilder",
+ "RequestedSupplementaryDocTextExtractionTaskBuilder",
"SessionSpecBuilder",
"NotificationConfigBuilder",
"SdkConfigBuilder",
diff --git a/yoti_python_sdk/doc_scan/client.py b/yoti_python_sdk/doc_scan/client.py
index e7d406a4..b7a276f4 100644
--- a/yoti_python_sdk/doc_scan/client.py
+++ b/yoti_python_sdk/doc_scan/client.py
@@ -138,6 +138,9 @@ def get_media_content(self, session_id, media_id):
)
response = request.execute()
+ if response.status_code == 204:
+ return None
+
if response.status_code != 200:
raise DocScanException("Failed to retrieve media content", response)
diff --git a/yoti_python_sdk/doc_scan/constants.py b/yoti_python_sdk/doc_scan/constants.py
index b0f724c3..6aa18d1d 100644
--- a/yoti_python_sdk/doc_scan/constants.py
+++ b/yoti_python_sdk/doc_scan/constants.py
@@ -8,6 +8,10 @@
ID_DOCUMENT_FACE_MATCH = "ID_DOCUMENT_FACE_MATCH"
LIVENESS = "LIVENESS"
ZOOM = "ZOOM"
+SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK = "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK"
+SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION = (
+ "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION"
+)
CAMERA = "CAMERA"
CAMERA_AND_UPLOAD = "CAMERA_AND_UPLOAD"
@@ -18,6 +22,7 @@
SESSION_COMPLETION = "SESSION_COMPLETION"
ID_DOCUMENT = "ID_DOCUMENT"
+SUPPLEMENTARY_DOCUMENT = "SUPPLEMENTARY_DOCUMENT"
ORTHOGONAL_RESTRICTIONS = "ORTHOGONAL_RESTRICTIONS"
DOCUMENT_RESTRICTIONS = "DOCUMENT_RESTRICTIONS"
INCLUSION_WHITELIST = "WHITELIST"
@@ -29,3 +34,5 @@
DESIRED = "DESIRED"
IGNORE = "IGNORE"
+
+PROOF_OF_ADDRESS = "PROOF_OF_ADDRESS"
diff --git a/yoti_python_sdk/doc_scan/session/create/filter/__init__.py b/yoti_python_sdk/doc_scan/session/create/filter/__init__.py
index e1a088e6..aa9998d6 100644
--- a/yoti_python_sdk/doc_scan/session/create/filter/__init__.py
+++ b/yoti_python_sdk/doc_scan/session/create/filter/__init__.py
@@ -4,10 +4,12 @@
)
from .orthogonal_restrictions_filter import OrthogonalRestrictionsFilterBuilder
from .required_id_document import RequiredIdDocumentBuilder
+from .required_supplementary_document import RequiredSupplementaryDocumentBuilder
__all__ = [
"DocumentRestrictionsFilterBuilder",
"DocumentRestrictionBuilder",
"OrthogonalRestrictionsFilterBuilder",
"RequiredIdDocumentBuilder",
+ "RequiredSupplementaryDocumentBuilder",
]
diff --git a/yoti_python_sdk/doc_scan/session/create/filter/required_supplementary_document.py b/yoti_python_sdk/doc_scan/session/create/filter/required_supplementary_document.py
new file mode 100644
index 00000000..f543b800
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/create/filter/required_supplementary_document.py
@@ -0,0 +1,90 @@
+from yoti_python_sdk.doc_scan.constants import SUPPLEMENTARY_DOCUMENT
+from yoti_python_sdk.utils import remove_null_values
+from .required_document import RequiredDocument
+
+
+class RequiredSupplementaryDocument(RequiredDocument):
+ def __init__(self, objective, document_types=None, country_codes=None):
+ """
+ :param objective: the objective for the document
+ :type objective: Objective
+ :param document_types: the document types
+ :type document_types: list[str]
+ :param country_codes: the country codes
+ :type country_codes: list[str]
+ """
+ self.__objective = objective
+ self.__document_types = document_types
+ self.__country_codes = country_codes
+
+ @property
+ def type(self):
+ return SUPPLEMENTARY_DOCUMENT
+
+ def to_json(self):
+ return remove_null_values(
+ {
+ "type": self.type,
+ "objective": self.__objective,
+ "document_types": self.__document_types,
+ "country_codes": self.__country_codes,
+ }
+ )
+
+
+class RequiredSupplementaryDocumentBuilder(object):
+ """
+ Builder used to assist the creation of a required supplementary document.
+ """
+
+ def __init__(self):
+ self.__objective = None
+ self.__document_types = None
+ self.__country_codes = None
+
+ def with_objective(self, objective):
+ """
+ Sets the supplementary document objective
+
+ :param objective: the objective
+ :type objective: Objective
+ :return: the builder
+ :rtype: RequiredSupplementaryDocumentBuilder
+ """
+ self.__objective = objective
+ return self
+
+ def with_document_types(self, document_types):
+ """
+ Sets the supplementary document types
+
+ :param document_types: the document types
+ :type document_types: list[str]
+ :return: the builder
+ :rtype: RequiredSupplementaryDocumentBuilder
+ """
+ self.__document_types = document_types
+ return self
+
+ def with_country_codes(self, country_codes):
+ """
+ Sets the supplementary document country codes
+
+ :param country_codes: the country codes
+ :type country_codes: list[str]
+ :return: the builder
+ :rtype: RequiredSupplementaryDocumentBuilder
+ """
+ self.__country_codes = country_codes
+ return self
+
+ def build(self):
+ """
+ Builds a required supplementary document, using the values supplied to the builder
+
+ :return: the required supplementary document
+ :rtype: RequiredSupplementaryDocument
+ """
+ return RequiredSupplementaryDocument(
+ self.__objective, self.__document_types, self.__country_codes
+ )
diff --git a/yoti_python_sdk/doc_scan/session/create/objective/__init__.py b/yoti_python_sdk/doc_scan/session/create/objective/__init__.py
new file mode 100644
index 00000000..5b3abd10
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/create/objective/__init__.py
@@ -0,0 +1,5 @@
+from .proof_of_address_objective import ProofOfAddressObjectiveBuilder
+
+__all__ = [
+ "ProofOfAddressObjectiveBuilder",
+]
diff --git a/yoti_python_sdk/doc_scan/session/create/objective/objective.py b/yoti_python_sdk/doc_scan/session/create/objective/objective.py
new file mode 100644
index 00000000..0f468451
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/create/objective/objective.py
@@ -0,0 +1,26 @@
+from abc import ABCMeta
+from abc import abstractmethod
+
+from yoti_python_sdk.utils import YotiSerializable, remove_null_values
+
+
+class Objective(YotiSerializable):
+ """
+ The objective of the document
+ """
+
+ __metaclass__ = ABCMeta
+
+ @property
+ @abstractmethod
+ def type(self):
+ """
+ Return the type of the objective
+
+ :return: the type
+ :rtype: str
+ """
+ raise NotImplementedError
+
+ def to_json(self):
+ return remove_null_values({"type": self.type})
diff --git a/yoti_python_sdk/doc_scan/session/create/objective/proof_of_address_objective.py b/yoti_python_sdk/doc_scan/session/create/objective/proof_of_address_objective.py
new file mode 100644
index 00000000..6607c2fc
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/create/objective/proof_of_address_objective.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from yoti_python_sdk.doc_scan.constants import PROOF_OF_ADDRESS
+from .objective import Objective
+
+
+class ProofOfAddressObjective(Objective):
+ """
+ Proof of address document objective
+ """
+
+ @property
+ def type(self):
+ return PROOF_OF_ADDRESS
+
+
+class ProofOfAddressObjectiveBuilder(object):
+ """
+ Builder to assist creation of :class:`ProofOfAddressObjective`
+ """
+
+ def build(self):
+ """
+ :return: the proof of address objective
+ :rtype: ProofOfAddressObjective
+ """
+ return ProofOfAddressObjective()
diff --git a/yoti_python_sdk/doc_scan/session/create/task/__init__.py b/yoti_python_sdk/doc_scan/session/create/task/__init__.py
index 6ad73b42..ab8cec54 100644
--- a/yoti_python_sdk/doc_scan/session/create/task/__init__.py
+++ b/yoti_python_sdk/doc_scan/session/create/task/__init__.py
@@ -1,3 +1,9 @@
from .text_extraction import RequestedTextExtractionTaskBuilder
+from .supplementary_doc_text_extraction import (
+ RequestedSupplementaryDocTextExtractionTaskBuilder,
+)
-__all__ = ["RequestedTextExtractionTaskBuilder"]
+__all__ = [
+ "RequestedTextExtractionTaskBuilder",
+ "RequestedSupplementaryDocTextExtractionTaskBuilder",
+]
diff --git a/yoti_python_sdk/doc_scan/session/create/task/supplementary_doc_text_extraction.py b/yoti_python_sdk/doc_scan/session/create/task/supplementary_doc_text_extraction.py
new file mode 100644
index 00000000..b3ebea43
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/create/task/supplementary_doc_text_extraction.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from yoti_python_sdk.doc_scan import constants
+from yoti_python_sdk.utils import YotiSerializable, remove_null_values
+from .requested_task import RequestedTask
+
+
+class RequestedSupplementaryDocTextExtractionTaskConfig(YotiSerializable):
+ def __init__(self, manual_check):
+ """
+ :param manual_check: the manual check value
+ :type manual_check: str
+ """
+ self.__manual_check = manual_check
+
+ @property
+ def manual_check(self):
+ """
+ Describes the manual fallback behaviour applied to each Task
+
+ :return: the manual check value
+ """
+ return self.__manual_check
+
+ def to_json(self):
+ return remove_null_values({"manual_check": self.manual_check})
+
+
+class RequestedSupplementaryDocTextExtractionTask(RequestedTask):
+ """
+ Builder to assist creation of :class:`RequestedSupplementaryDocTextExtractionTask`
+ """
+
+ def __init__(self, config):
+ """
+ :param config: the text extraction task configuration
+ :type config: RequestedSupplementaryDocTextExtractionTaskConfig
+ """
+ self.__config = config
+
+ @property
+ def type(self):
+ return constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION
+
+ @property
+ def config(self):
+ return self.__config
+
+
+class RequestedSupplementaryDocTextExtractionTaskBuilder(object):
+ """
+ Builder to assist creation of :class:`RequestedSupplementaryDocTextExtractionTask`
+ """
+
+ def __init__(self):
+ self.__manual_check = None
+
+ def with_manual_check_always(self):
+ """
+ Sets the manual check value to be "ALWAYS"
+
+ :return: the builder
+ :rtype: RequestedSupplementaryDocTextExtractionTaskBuilder
+ """
+ self.__manual_check = constants.ALWAYS
+ return self
+
+ def with_manual_check_fallback(self):
+ """
+ Sets the manual check value to be "FALLBACK"
+
+ :return: the builder
+ :rtype: RequestedSupplementaryDocTextExtractionTaskBuilder
+ """
+ self.__manual_check = constants.FALLBACK
+ return self
+
+ def with_manual_check_never(self):
+ """
+ Sets the manual check value to be "NEVER"
+
+ :return: the builder
+ :rtype: RequestedSupplementaryDocTextExtractionTaskBuilder
+ """
+ self.__manual_check = constants.NEVER
+ return self
+
+ def build(self):
+ config = RequestedSupplementaryDocTextExtractionTaskConfig(self.__manual_check)
+ return RequestedSupplementaryDocTextExtractionTask(config)
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/check_response.py b/yoti_python_sdk/doc_scan/session/retrieve/check_response.py
index 52f78388..f1cb515d 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/check_response.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/check_response.py
@@ -176,3 +176,11 @@ class IDDocumentComparisonCheckResponse(CheckResponse):
"""
pass
+
+
+class SupplementaryDocumentTextDataCheckResponse(CheckResponse):
+ """
+ Represents a Supplementary Document Text Data check for a given session
+ """
+
+ pass
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/file_response.py b/yoti_python_sdk/doc_scan/session/retrieve/file_response.py
new file mode 100644
index 00000000..3b3ab59c
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/retrieve/file_response.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from yoti_python_sdk.doc_scan.session.retrieve.media_response import MediaResponse
+
+
+class FileResponse(object):
+ """
+ Represents a file
+ """
+
+ def __init__(self, data=None):
+ """
+ :param data: the data to parse
+ :type data: dict or None
+ """
+ if data is None:
+ data = dict()
+
+ self.__media = MediaResponse(data["media"]) if "media" in data.keys() else None
+
+ @property
+ def media(self):
+ """
+ Returns the media associated with the file
+
+ :return: the media
+ :rtype: MediaResponse or None
+ """
+ return self.__media
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/generated_check_response.py b/yoti_python_sdk/doc_scan/session/retrieve/generated_check_response.py
index eddcb508..37d60482 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/generated_check_response.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/generated_check_response.py
@@ -46,3 +46,11 @@ class GeneratedTextDataCheckResponse(GeneratedCheckResponse):
"""
pass
+
+
+class GeneratedSupplementaryDocumentTextDataCheckResponse(GeneratedCheckResponse):
+ """
+ Represents a generated Supplementary Document Text Data check response
+ """
+
+ pass
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py b/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py
index 71a69a34..f39eed98 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+from deprecated import deprecated
from iso8601 import (
ParseError,
@@ -7,12 +8,15 @@
)
from yoti_python_sdk.doc_scan import constants
-from .check_response import AuthenticityCheckResponse
-from .check_response import CheckResponse
-from .check_response import FaceMatchCheckResponse
-from .check_response import IDDocumentComparisonCheckResponse
-from .check_response import LivenessCheckResponse
-from .check_response import TextDataCheckResponse
+from .check_response import (
+ AuthenticityCheckResponse,
+ CheckResponse,
+ FaceMatchCheckResponse,
+ IDDocumentComparisonCheckResponse,
+ LivenessCheckResponse,
+ TextDataCheckResponse,
+ SupplementaryDocumentTextDataCheckResponse,
+)
from .resource_container import ResourceContainer
@@ -77,6 +81,7 @@ def __parse_check(check):
constants.ID_DOCUMENT_TEXT_DATA_CHECK: TextDataCheckResponse,
constants.LIVENESS: LivenessCheckResponse,
constants.ID_DOCUMENT_COMPARISON: IDDocumentComparisonCheckResponse,
+ constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK: SupplementaryDocumentTextDataCheckResponse,
}
clazz = types.get(check.get("type", None), CheckResponse)
return clazz(check)
@@ -173,15 +178,36 @@ def face_match_checks(self):
return self.__checks_of_type((FaceMatchCheckResponse,))
@property
+ @deprecated("replaced by id_document_text_data_checks")
def text_data_checks(self):
"""
A filtered list of checks, returning only Text Data checks
+ :return: the Text Data checks
+ :rtype: list[TextDataCheckResponse]
+ """
+ return self.id_document_text_data_checks
+
+ @property
+ def id_document_text_data_checks(self):
+ """
+ A filtered list of checks, returning only ID Document Text Data checks
+
:return: the Text Data checks
:rtype: list[TextDataCheckResponse]
"""
return self.__checks_of_type((TextDataCheckResponse,))
+ @property
+ def supplementary_document_text_data_checks(self):
+ """
+ A filtered list of checks, returning only Supplementary Document Text Data checks
+
+ :return: the Text Data checks
+ :rtype: list[SupplementaryDocumentTextDataCheckResponse]
+ """
+ return self.__checks_of_type((SupplementaryDocumentTextDataCheckResponse,))
+
@property
def liveness_checks(self):
"""
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py b/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py
index c2efbfe1..8295e503 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py
@@ -4,6 +4,9 @@
from yoti_python_sdk.doc_scan.session.retrieve.id_document_resource_response import (
IdDocumentResourceResponse,
)
+from yoti_python_sdk.doc_scan.session.retrieve.supplementary_document_resource_response import (
+ SupplementaryDocumentResourceResponse,
+)
from yoti_python_sdk.doc_scan.session.retrieve.liveness_resource_response import (
LivenessResourceResponse,
ZoomLivenessResourceResponse,
@@ -29,6 +32,11 @@ def __init__(self, data=None):
for document in data.get("id_documents", [])
]
+ self.__supplementary_documents = [
+ SupplementaryDocumentResourceResponse(document)
+ for document in data.get("supplementary_documents", [])
+ ]
+
self.__liveness_capture = [
self.__parse_liveness_capture(liveness)
for liveness in data.get("liveness_capture", [])
@@ -64,6 +72,16 @@ def id_documents(self):
"""
return self.__id_documents
+ @property
+ def supplementary_documents(self):
+ """
+ Return a list of supplementary document resources
+
+ :return: list of supplementary documents
+ :rtype: list[SupplementaryDocumentResourceResponse]
+ """
+ return self.__supplementary_documents
+
@property
def liveness_capture(self):
"""
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py b/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py
index 7a54b0bc..bd5c8087 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py
@@ -1,6 +1,9 @@
from yoti_python_sdk.doc_scan import constants
-from .task_response import TaskResponse
-from .task_response import TextExtractionTaskResponse
+from .task_response import (
+ TaskResponse,
+ TextExtractionTaskResponse,
+ SupplementaryDocumentTextExtractionTaskResponse,
+)
class ResourceResponse(object):
@@ -29,7 +32,10 @@ def __parse_task(task):
:return: the parsed task
:rtype: TaskResponse
"""
- types = {constants.ID_DOCUMENT_TEXT_DATA_EXTRACTION: TextExtractionTaskResponse}
+ types = {
+ constants.ID_DOCUMENT_TEXT_DATA_EXTRACTION: TextExtractionTaskResponse,
+ constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION: SupplementaryDocumentTextExtractionTaskResponse,
+ }
clazz = types.get(
task.get("type", None), TaskResponse # Default fallback for task type
)
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/supplementary_document_resource_response.py b/yoti_python_sdk/doc_scan/session/retrieve/supplementary_document_resource_response.py
new file mode 100644
index 00000000..6ec4deb0
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/retrieve/supplementary_document_resource_response.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from yoti_python_sdk.doc_scan.session.retrieve.document_fields_response import (
+ DocumentFieldsResponse,
+)
+from yoti_python_sdk.doc_scan.session.retrieve.file_response import (
+ FileResponse,
+)
+from yoti_python_sdk.doc_scan.session.retrieve.page_response import PageResponse
+from yoti_python_sdk.doc_scan.session.retrieve.resource_response import ResourceResponse
+from yoti_python_sdk.doc_scan.session.retrieve.task_response import (
+ SupplementaryDocumentTextExtractionTaskResponse,
+)
+
+
+class SupplementaryDocumentResourceResponse(ResourceResponse):
+ """
+ Represents an Supplementary Document resource for a given session
+ """
+
+ def __init__(self, data=None):
+ """
+ :param data: the data to parse
+ :type data: dict or None
+ """
+ if data is None:
+ data = dict()
+
+ ResourceResponse.__init__(self, data)
+
+ self.__document_type = data.get("document_type", None)
+ self.__issuing_country = data.get("issuing_country", None)
+ self.__pages = [PageResponse(page) for page in data.get("pages", [])]
+ self.__document_fields = (
+ DocumentFieldsResponse(data["document_fields"])
+ if "document_fields" in data.keys()
+ else None
+ )
+ self.__document_file = (
+ FileResponse(data["file"]) if "file" in data.keys() else None
+ )
+
+ @property
+ def document_type(self):
+ """
+ Returns the supplementary document type, e.g. "UTILITY_BILL"
+
+ :return: the document type
+ :rtype: str or None
+ """
+ return self.__document_type
+
+ @property
+ def issuing_country(self):
+ """
+ Returns the issuing country of the supplementary document
+
+ :return: the issuing country
+ :rtype: str or None
+ """
+ return self.__issuing_country
+
+ @property
+ def pages(self):
+ """
+ Returns the individual pages of the supplementary document
+
+ :return: the pages
+ :rtype: list[PageResponse]
+ """
+ return self.__pages
+
+ @property
+ def document_fields(self):
+ """
+ Returns the associated document fields
+
+ :return: the document fields
+ :rtype: DocumentFieldsResponse
+ """
+ return self.__document_fields
+
+ @property
+ def document_file(self):
+ """
+ Returns the associated document file
+
+ :return: the document file
+ :rtype: FileResponse
+ """
+ return self.__document_file
+
+ @property
+ def text_extraction_tasks(self):
+ """
+ Returns a list of text extraction tasks associated
+ with the supplementary document
+
+ :return: list of text extraction tasks
+ :rtype: list[TextExtractionTaskResponse]
+ """
+ return [
+ task
+ for task in self.tasks
+ if isinstance(task, SupplementaryDocumentTextExtractionTaskResponse)
+ ]
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/task_response.py b/yoti_python_sdk/doc_scan/session/retrieve/task_response.py
index 43dc5bb6..899e140b 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/task_response.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/task_response.py
@@ -7,9 +7,8 @@
from yoti_python_sdk.doc_scan import constants
from yoti_python_sdk.doc_scan.session.retrieve.generated_check_response import (
GeneratedCheckResponse,
-)
-from yoti_python_sdk.doc_scan.session.retrieve.generated_check_response import (
GeneratedTextDataCheckResponse,
+ GeneratedSupplementaryDocumentTextDataCheckResponse,
)
from yoti_python_sdk.doc_scan.session.retrieve.generated_media import GeneratedMedia
@@ -51,7 +50,10 @@ def __parse_generated_check(generated_check):
:return: the parse generated check
:rtype: GeneratedCheckResponse
"""
- types = {constants.ID_DOCUMENT_TEXT_DATA_CHECK: GeneratedTextDataCheckResponse}
+ types = {
+ constants.ID_DOCUMENT_TEXT_DATA_CHECK: GeneratedTextDataCheckResponse,
+ constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK: GeneratedSupplementaryDocumentTextDataCheckResponse,
+ }
clazz = types.get(
generated_check.get("type", None),
@@ -153,7 +155,7 @@ def generated_media(self):
class TextExtractionTaskResponse(TaskResponse):
"""
- Represents a Text Extraction task response
+ Represents an ID Document Text Extraction task response
"""
@property
@@ -163,3 +165,17 @@ def generated_text_data_checks(self):
for check in self.generated_checks
if isinstance(check, GeneratedTextDataCheckResponse)
]
+
+
+class SupplementaryDocumentTextExtractionTaskResponse(TaskResponse):
+ """
+ Represents a Supplementary Document Text Extraction task response
+ """
+
+ @property
+ def generated_text_data_checks(self):
+ return [
+ check
+ for check in self.generated_checks
+ if isinstance(check, GeneratedSupplementaryDocumentTextDataCheckResponse)
+ ]
diff --git a/yoti_python_sdk/dynamic_sharing_service/extension/__init__.py b/yoti_python_sdk/dynamic_sharing_service/extension/__init__.py
index 927ddf45..0e1d40cb 100644
--- a/yoti_python_sdk/dynamic_sharing_service/extension/__init__.py
+++ b/yoti_python_sdk/dynamic_sharing_service/extension/__init__.py
@@ -1,9 +1,11 @@
from .extension_builder import ExtensionBuilder
from .location_constraint_extension_builder import LocationConstraintExtensionBuilder
from .transactional_flow_extension_builder import TransactionalFlowExtensionBuilder
+from .third_party_attribute_extension import ThirdPartyAttributeExtension
__all__ = [
"ExtensionBuilder",
"LocationConstraintExtensionBuilder",
"TransactionalFlowExtensionBuilder",
+ "ThirdPartyAttributeExtension",
]
diff --git a/yoti_python_sdk/dynamic_sharing_service/policy/dynamic_policy_builder.py b/yoti_python_sdk/dynamic_sharing_service/policy/dynamic_policy_builder.py
index bf03ad8a..b30bd617 100644
--- a/yoti_python_sdk/dynamic_sharing_service/policy/dynamic_policy_builder.py
+++ b/yoti_python_sdk/dynamic_sharing_service/policy/dynamic_policy_builder.py
@@ -38,6 +38,10 @@ def __attribute_keyword_parser(self, attributeBuilder, **kwargs):
if constraints:
attributeBuilder.with_constraint(constraints)
+ accept_self_asserted = kwargs.get("accept_self_asserted", None)
+ if accept_self_asserted is not None:
+ attributeBuilder.with_accept_self_asserted(accept_self_asserted)
+
def with_wanted_attribute_by_name(self, wanted_name, **kwargs):
"""
@param wanted_name The name of the attribute to include
@@ -122,6 +126,11 @@ def with_document_details(self, **kwargs):
config.ATTRIBUTE_DOCUMENT_DETAILS, **kwargs
)
+ def with_document_images(self, **kwargs):
+ return self.with_wanted_attribute_by_name(
+ config.ATTRIBUTE_DOCUMENT_IMAGES, **kwargs
+ )
+
"""
@param wanted_auth_type
"""
diff --git a/yoti_python_sdk/tests/doc_scan/mocks.py b/yoti_python_sdk/tests/doc_scan/mocks.py
index b5e2a358..458cd376 100644
--- a/yoti_python_sdk/tests/doc_scan/mocks.py
+++ b/yoti_python_sdk/tests/doc_scan/mocks.py
@@ -38,7 +38,11 @@ def mocked_request_media_content():
)
-def mocked_request_missing_content():
+def mocked_request_no_content():
+ return MockResponse(status_code=204, text="")
+
+
+def mocked_request_not_found():
return MockResponse(status_code=404, text="")
diff --git a/yoti_python_sdk/tests/doc_scan/session/create/filter/test_required_supplementary_document.py b/yoti_python_sdk/tests/doc_scan/session/create/filter/test_required_supplementary_document.py
new file mode 100644
index 00000000..edbd7446
--- /dev/null
+++ b/yoti_python_sdk/tests/doc_scan/session/create/filter/test_required_supplementary_document.py
@@ -0,0 +1,97 @@
+import json
+import unittest
+
+from mock import (
+ Mock,
+ MagicMock,
+)
+
+from yoti_python_sdk.doc_scan.session.create.objective.objective import Objective
+from yoti_python_sdk.doc_scan.session.create.objective import (
+ ProofOfAddressObjectiveBuilder,
+)
+from yoti_python_sdk.doc_scan.session.create.filter.required_document import (
+ RequiredDocument,
+)
+from yoti_python_sdk.doc_scan.session.create.filter.required_supplementary_document import (
+ RequiredSupplementaryDocument,
+ RequiredSupplementaryDocumentBuilder,
+)
+from yoti_python_sdk.utils import YotiEncoder
+
+
+class RequestedSupplementaryDocTextExtractionTaskTest(unittest.TestCase):
+ def test_builds_required_supplementary_document(self):
+ objective_mock = Mock(spec=Objective)
+
+ result = (
+ RequiredSupplementaryDocumentBuilder()
+ .with_objective(objective_mock)
+ .build()
+ )
+
+ assert isinstance(result, RequiredDocument)
+ assert isinstance(result, RequiredSupplementaryDocument)
+ assert result.type == "SUPPLEMENTARY_DOCUMENT"
+
+ def test_to_json_contains_proof_of_address_objective(self):
+ proof_of_address_objective = ProofOfAddressObjectiveBuilder().build()
+
+ result = (
+ RequiredSupplementaryDocumentBuilder()
+ .with_objective(proof_of_address_objective)
+ .build()
+ )
+
+ assert result.to_json().get("objective") == proof_of_address_objective
+
+ def test_to_json_contains_country_codes(self):
+ objective_mock = Mock(spec=Objective)
+ some_country_codes = ["GBR", "USA"]
+
+ result = (
+ RequiredSupplementaryDocumentBuilder()
+ .with_objective(objective_mock)
+ .with_country_codes(some_country_codes)
+ .build()
+ )
+
+ assert result.to_json().get("country_codes") == some_country_codes
+
+ def test_to_json_contains_document_types(self):
+ objective_mock = Mock(spec=Objective)
+ some_document_types = ["UTILITY_BILL"]
+
+ result = (
+ RequiredSupplementaryDocumentBuilder()
+ .with_objective(objective_mock)
+ .with_document_types(some_document_types)
+ .build()
+ )
+
+ assert result.to_json().get("document_types") == some_document_types
+
+ def test_to_json_excludes_none_values(self):
+ objective_mock = Mock(spec=Objective)
+
+ result = (
+ RequiredSupplementaryDocumentBuilder()
+ .with_objective(objective_mock)
+ .build()
+ )
+
+ assert "country_codes" not in result.to_json().keys()
+ assert "document_types" not in result.to_json().keys()
+
+ def test_should_serialize_to_json_without_error(self):
+ objective_mock = Mock(spec=Objective)
+ objective_mock.to_json = MagicMock(return_value="")
+
+ result = (
+ RequiredSupplementaryDocumentBuilder()
+ .with_objective(objective_mock)
+ .build()
+ )
+
+ s = json.dumps(result, cls=YotiEncoder)
+ assert s is not None and s != ""
diff --git a/yoti_python_sdk/tests/doc_scan/session/create/objective/__init__.py b/yoti_python_sdk/tests/doc_scan/session/create/objective/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/yoti_python_sdk/tests/doc_scan/session/create/objective/test_proof_of_address_objective.py b/yoti_python_sdk/tests/doc_scan/session/create/objective/test_proof_of_address_objective.py
new file mode 100644
index 00000000..ed82dd53
--- /dev/null
+++ b/yoti_python_sdk/tests/doc_scan/session/create/objective/test_proof_of_address_objective.py
@@ -0,0 +1,31 @@
+import json
+import unittest
+
+from yoti_python_sdk.doc_scan.session.create.objective.objective import Objective
+from yoti_python_sdk.doc_scan.session.create.objective.proof_of_address_objective import (
+ ProofOfAddressObjective,
+)
+from yoti_python_sdk.doc_scan.session.create.objective import (
+ ProofOfAddressObjectiveBuilder,
+)
+from yoti_python_sdk.utils import YotiEncoder
+
+
+class ProofOfAddressObjectiveTest(unittest.TestCase):
+ def test_builder_builds_proof_of_address_objective(self):
+ result = ProofOfAddressObjectiveBuilder().build()
+
+ assert isinstance(result, Objective)
+ assert isinstance(result, ProofOfAddressObjective)
+ assert result.type == "PROOF_OF_ADDRESS"
+
+ def test_to_json_contains_type(self):
+ result = ProofOfAddressObjectiveBuilder().build()
+
+ assert result.to_json().get("type") == "PROOF_OF_ADDRESS"
+
+ def test_should_serialize_to_json_without_error(self):
+ result = ProofOfAddressObjectiveBuilder().build()
+
+ s = json.dumps(result, cls=YotiEncoder)
+ assert s is not None and s != ""
diff --git a/yoti_python_sdk/tests/doc_scan/session/create/task/test_supplementary_doc_text_extraction_task.py b/yoti_python_sdk/tests/doc_scan/session/create/task/test_supplementary_doc_text_extraction_task.py
new file mode 100644
index 00000000..2f66948a
--- /dev/null
+++ b/yoti_python_sdk/tests/doc_scan/session/create/task/test_supplementary_doc_text_extraction_task.py
@@ -0,0 +1,89 @@
+import json
+import unittest
+
+from yoti_python_sdk.doc_scan.session.create.task import (
+ RequestedSupplementaryDocTextExtractionTaskBuilder,
+)
+from yoti_python_sdk.doc_scan.session.create.task.requested_task import RequestedTask
+from yoti_python_sdk.doc_scan.session.create.task.supplementary_doc_text_extraction import (
+ RequestedSupplementaryDocTextExtractionTask,
+ RequestedSupplementaryDocTextExtractionTaskConfig,
+)
+
+from yoti_python_sdk.utils import YotiEncoder
+
+
+class RequestedSupplementaryDocTextExtractionTaskTest(unittest.TestCase):
+ def test_should_build_text_data_extraction_task(self):
+ result = RequestedSupplementaryDocTextExtractionTaskBuilder().build()
+
+ assert isinstance(result, RequestedTask)
+ assert isinstance(result, RequestedSupplementaryDocTextExtractionTask)
+ assert isinstance(
+ result.config, RequestedSupplementaryDocTextExtractionTaskConfig
+ )
+
+ assert result.type == "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION"
+
+ def test_should_build_with_manual_check_always(self):
+ result = (
+ RequestedSupplementaryDocTextExtractionTaskBuilder()
+ .with_manual_check_always()
+ .build()
+ )
+
+ assert result.config.manual_check == "ALWAYS"
+
+ def test_should_build_with_manual_check_fallback(self):
+ result = (
+ RequestedSupplementaryDocTextExtractionTaskBuilder()
+ .with_manual_check_fallback()
+ .build()
+ )
+
+ assert result.config.manual_check == "FALLBACK"
+
+ def test_should_build_with_manual_check_never(self):
+ result = (
+ RequestedSupplementaryDocTextExtractionTaskBuilder()
+ .with_manual_check_never()
+ .build()
+ )
+
+ assert result.config.manual_check == "NEVER"
+
+ def test_should_serialize_to_json_without_error(self):
+ result = (
+ RequestedSupplementaryDocTextExtractionTaskBuilder()
+ .with_manual_check_never()
+ .build()
+ )
+
+ s = json.dumps(result, cls=YotiEncoder)
+ assert s is not None and s != ""
+
+ def test_to_json_should_return_correct_properties(self):
+ result = (
+ RequestedSupplementaryDocTextExtractionTaskBuilder()
+ .with_manual_check_always()
+ .build()
+ )
+
+ json = result.to_json()
+ assert json is not None
+
+ json_config = json.get("config").to_json()
+ assert json_config.get("manual_check") == "ALWAYS"
+
+ def test_to_json_should_not_include_null_config_values(self):
+ result = RequestedSupplementaryDocTextExtractionTaskBuilder().build()
+
+ json = result.to_json()
+ assert json is not None
+
+ json_config = json.get("config").to_json()
+ assert json_config == {}
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_file_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_file_response.py
new file mode 100644
index 00000000..3a96734b
--- /dev/null
+++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_file_response.py
@@ -0,0 +1,21 @@
+import unittest
+
+from yoti_python_sdk.doc_scan.session.retrieve.file_response import FileResponse
+from yoti_python_sdk.doc_scan.session.retrieve.media_response import MediaResponse
+
+
+class FileResponseTest(unittest.TestCase):
+ def test_should_parse_correctly(self):
+ data = {"media": {}}
+
+ result = FileResponse(data)
+ assert isinstance(result.media, MediaResponse)
+
+ def test_should_parse_when_none(self):
+ result = FileResponse(None)
+ assert isinstance(result, FileResponse)
+ assert result.media is None
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_get_session_result.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_get_session_result.py
index b1634876..04456ae3 100644
--- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_get_session_result.py
+++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_get_session_result.py
@@ -6,18 +6,11 @@
from yoti_python_sdk.doc_scan.session.retrieve.check_response import (
AuthenticityCheckResponse,
-)
-from yoti_python_sdk.doc_scan.session.retrieve.check_response import (
FaceMatchCheckResponse,
-)
-from yoti_python_sdk.doc_scan.session.retrieve.check_response import (
LivenessCheckResponse,
-)
-from yoti_python_sdk.doc_scan.session.retrieve.check_response import (
IDDocumentComparisonCheckResponse,
-)
-from yoti_python_sdk.doc_scan.session.retrieve.check_response import (
TextDataCheckResponse,
+ SupplementaryDocumentTextDataCheckResponse,
)
from yoti_python_sdk.doc_scan.session.retrieve.get_session_result import (
GetSessionResult,
@@ -40,6 +33,7 @@ class GetSessionResultTest(unittest.TestCase):
{"type": "ID_DOCUMENT_FACE_MATCH"},
{"type": "LIVENESS"},
{"type": "ID_DOCUMENT_COMPARISON"},
+ {"type": "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK"},
]
EXPECTED_BIOMETRIC_CONSENT_DATETIME = datetime(
@@ -73,12 +67,13 @@ def test_should_parse_different_checks(self):
assert result.state is self.SOME_STATE
assert result.user_tracking_id is self.SOME_USER_TRACKING_ID
- assert len(result.checks) == 5
+ assert len(result.checks) == 6
assert isinstance(result.checks[0], AuthenticityCheckResponse)
assert isinstance(result.checks[1], TextDataCheckResponse)
assert isinstance(result.checks[2], FaceMatchCheckResponse)
assert isinstance(result.checks[3], LivenessCheckResponse)
assert isinstance(result.checks[4], IDDocumentComparisonCheckResponse)
+ assert isinstance(result.checks[5], SupplementaryDocumentTextDataCheckResponse)
assert isinstance(result.resources, ResourceContainer)
@@ -93,12 +88,33 @@ def test_should_filter_checks(self):
result = GetSessionResult(data)
- assert len(result.checks) == 5
+ assert len(result.checks) == 6
+
assert len(result.authenticity_checks) == 1
+ assert isinstance(result.authenticity_checks[0], AuthenticityCheckResponse)
+
assert len(result.face_match_checks) == 1
+ assert isinstance(result.face_match_checks[0], FaceMatchCheckResponse)
+
assert len(result.liveness_checks) == 1
+ assert isinstance(result.liveness_checks[0], LivenessCheckResponse)
+
assert len(result.text_data_checks) == 1
+ assert isinstance(result.text_data_checks[0], TextDataCheckResponse)
+
+ assert len(result.id_document_text_data_checks) == 1
+ assert isinstance(result.id_document_text_data_checks[0], TextDataCheckResponse)
+
assert len(result.id_document_comparison_checks) == 1
+ assert isinstance(
+ result.id_document_comparison_checks[0], IDDocumentComparisonCheckResponse
+ )
+
+ assert len(result.supplementary_document_text_data_checks) == 1
+ assert isinstance(
+ result.supplementary_document_text_data_checks[0],
+ SupplementaryDocumentTextDataCheckResponse,
+ )
if __name__ == "__main__":
diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py
index d397d8c5..5a08c5a0 100644
--- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py
+++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py
@@ -15,6 +15,7 @@ class ResourceContainerTest(unittest.TestCase):
def test_should_parse_correctly(self):
data = {
"id_documents": [{"first": "id_document"}, {"second": "id_document"}],
+ "supplementary_documents": [{"first": "document"}, {"second": "document"}],
"liveness_capture": [
{"liveness_type": "ZOOM"},
{"liveness_type": "someUnknown"},
@@ -24,6 +25,7 @@ def test_should_parse_correctly(self):
result = ResourceContainer(data)
assert len(result.id_documents) == 2
+ assert len(result.supplementary_documents) == 2
assert len(result.liveness_capture) == 2
assert isinstance(result.liveness_capture[0], ZoomLivenessResourceResponse)
assert isinstance(result.liveness_capture[1], LivenessResourceResponse)
diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_supplementary_document_resource_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_supplementary_document_resource_response.py
new file mode 100644
index 00000000..06c72d7a
--- /dev/null
+++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_supplementary_document_resource_response.py
@@ -0,0 +1,90 @@
+import unittest
+
+from yoti_python_sdk.doc_scan.session.retrieve.document_fields_response import (
+ DocumentFieldsResponse,
+)
+from yoti_python_sdk.doc_scan.session.retrieve.file_response import (
+ FileResponse,
+)
+from yoti_python_sdk.doc_scan.session.retrieve.supplementary_document_resource_response import (
+ SupplementaryDocumentResourceResponse,
+)
+from yoti_python_sdk.doc_scan.session.retrieve.task_response import (
+ SupplementaryDocumentTextExtractionTaskResponse,
+ TaskResponse,
+)
+
+
+class SupplementaryDocumentResourceResponseTest(unittest.TestCase):
+ SOME_ID = "someId"
+ SOME_DOCUMENT_TYPE = "someDocumentType"
+ SOME_ISSUING_COUNTRY = "someIssuingCountry"
+ SOME_TASKS = [
+ {"first": "task", "type": "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION"},
+ {"second": "task"},
+ ]
+ SOME_PAGES = [{"first": "page"}, {"second": "page"}]
+ SOME_DOCUMENT_FIELDS = {"media": {}}
+ SOME_DOCUMENT_FILE = {"media": {}}
+
+ def test_should_parse_correctly(self):
+ data = {
+ "id": self.SOME_ID,
+ "document_type": self.SOME_DOCUMENT_TYPE,
+ "issuing_country": self.SOME_ISSUING_COUNTRY,
+ "tasks": self.SOME_TASKS,
+ "pages": self.SOME_PAGES,
+ "document_fields": self.SOME_DOCUMENT_FIELDS,
+ "file": self.SOME_DOCUMENT_FILE,
+ }
+
+ result = SupplementaryDocumentResourceResponse(data)
+
+ assert result.id == self.SOME_ID
+ assert result.document_type == self.SOME_DOCUMENT_TYPE
+ assert result.issuing_country == self.SOME_ISSUING_COUNTRY
+ assert len(result.tasks) == 2
+ assert len(result.pages) == 2
+ assert isinstance(result.document_fields, DocumentFieldsResponse)
+ assert isinstance(result.document_file, FileResponse)
+
+ def test_should_parse_when_none(self):
+ result = SupplementaryDocumentResourceResponse(None)
+
+ assert result.id is None
+ assert result.document_type is None
+ assert result.issuing_country is None
+ assert len(result.tasks) == 0
+ assert len(result.pages) == 0
+ assert result.document_fields is None
+ assert result.document_file is None
+
+ def test_should_parse_tasks_with_type(self):
+ data = {
+ "id": self.SOME_ID,
+ "document_type": self.SOME_DOCUMENT_TYPE,
+ "issuing_country": self.SOME_ISSUING_COUNTRY,
+ "tasks": self.SOME_TASKS,
+ "pages": self.SOME_PAGES,
+ "document_fields": self.SOME_DOCUMENT_FIELDS,
+ }
+
+ result = SupplementaryDocumentResourceResponse(data)
+
+ assert len(result.tasks) == 2
+ assert isinstance(
+ result.tasks[0], SupplementaryDocumentTextExtractionTaskResponse
+ )
+ assert isinstance(result.tasks[1], TaskResponse)
+
+ def test_should_filter_text_extraction_tasks(self):
+ data = {"tasks": self.SOME_TASKS}
+
+ result = SupplementaryDocumentResourceResponse(data)
+
+ assert len(result.tasks) == 2
+ assert len(result.text_extraction_tasks) == 1
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_task_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_task_response.py
index 7d91b20d..1653dde3 100644
--- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_task_response.py
+++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_task_response.py
@@ -5,6 +5,7 @@
from yoti_python_sdk.doc_scan.session.retrieve.generated_check_response import (
GeneratedCheckResponse,
+ GeneratedSupplementaryDocumentTextDataCheckResponse,
)
from yoti_python_sdk.doc_scan.session.retrieve.generated_check_response import (
GeneratedTextDataCheckResponse,
@@ -12,6 +13,7 @@
from yoti_python_sdk.doc_scan.session.retrieve.task_response import (
TaskResponse,
TextExtractionTaskResponse,
+ SupplementaryDocumentTextExtractionTaskResponse,
)
@@ -22,6 +24,7 @@ class TaskResponseTest(unittest.TestCase):
SOME_GENERATED_CHECKS = [
{"type": "ID_DOCUMENT_TEXT_DATA_CHECK"},
+ {"type": "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK"},
{"type": "someUnknownType"},
]
@@ -60,9 +63,13 @@ def test_should_parse_correctly(self):
assert result.created == self.EXPECTED_DATETIME
assert result.last_updated == self.EXPECTED_DATETIME
- assert len(result.generated_checks) == 2
+ assert len(result.generated_checks) == 3
assert isinstance(result.generated_checks[0], GeneratedTextDataCheckResponse)
- assert isinstance(result.generated_checks[1], GeneratedCheckResponse)
+ assert isinstance(
+ result.generated_checks[1],
+ GeneratedSupplementaryDocumentTextDataCheckResponse,
+ )
+ assert isinstance(result.generated_checks[2], GeneratedCheckResponse)
assert len(result.generated_media) == 2
@@ -82,8 +89,23 @@ def test_should_filter_generated_text_data_checks(self):
result = TextExtractionTaskResponse(data)
- assert len(result.generated_checks) == 2
+ assert len(result.generated_checks) == 3
assert len(result.generated_text_data_checks) == 1
+ assert isinstance(
+ result.generated_text_data_checks[0], GeneratedTextDataCheckResponse
+ )
+
+ def test_supplementary_task_should_filter_generated_text_data_checks(self):
+ data = {"generated_checks": self.SOME_GENERATED_CHECKS}
+
+ result = SupplementaryDocumentTextExtractionTaskResponse(data)
+
+ assert len(result.generated_checks) == 3
+ assert len(result.generated_text_data_checks) == 1
+ assert isinstance(
+ result.generated_text_data_checks[0],
+ GeneratedSupplementaryDocumentTextDataCheckResponse,
+ )
if __name__ == "__main__":
diff --git a/yoti_python_sdk/tests/doc_scan/test_doc_scan_client.py b/yoti_python_sdk/tests/doc_scan/test_doc_scan_client.py
index 11eefc9c..4ec7dc2a 100644
--- a/yoti_python_sdk/tests/doc_scan/test_doc_scan_client.py
+++ b/yoti_python_sdk/tests/doc_scan/test_doc_scan_client.py
@@ -13,7 +13,8 @@
mocked_request_failed_session_creation,
mocked_request_failed_session_retrieval,
mocked_request_media_content,
- mocked_request_missing_content,
+ mocked_request_no_content,
+ mocked_request_not_found,
mocked_request_server_error,
mocked_request_successful_session_creation,
mocked_request_successful_session_retrieval,
@@ -111,7 +112,7 @@ def test_should_raise_exception_for_delete_session(_, doc_scan_client):
@mock.patch(
"yoti_python_sdk.http.SignedRequest.execute",
- side_effect=mocked_request_missing_content,
+ side_effect=mocked_request_not_found,
)
def test_should_raise_exception_for_invalid_content(_, doc_scan_client):
"""
@@ -141,7 +142,20 @@ def test_should_return_media_value(_, doc_scan_client):
@mock.patch(
"yoti_python_sdk.http.SignedRequest.execute",
- side_effect=mocked_request_missing_content,
+ side_effect=mocked_request_no_content,
+)
+def test_should_return_none_for_media_no_content(_, doc_scan_client):
+ """
+ :type doc_scan_client: DocScanClient
+ """
+ media = doc_scan_client.get_media_content(SOME_SESSION_ID, SOME_MEDIA_ID)
+
+ assert media is None
+
+
+@mock.patch(
+ "yoti_python_sdk.http.SignedRequest.execute",
+ side_effect=mocked_request_not_found,
)
def test_should_throw_exception_for_delete_media(_, doc_scan_client):
"""
@@ -170,7 +184,7 @@ def test_should_return_supported_documents_response(_, doc_scan_client):
@mock.patch(
"yoti_python_sdk.http.SignedRequest.execute",
- side_effect=mocked_request_missing_content,
+ side_effect=mocked_request_not_found,
)
def test_should_throw_exception_for_supported_documents(_, doc_scan_client):
"""
diff --git a/yoti_python_sdk/tests/dynamic_sharing_service/policy/test_dynamic_policy_builder.py b/yoti_python_sdk/tests/dynamic_sharing_service/policy/test_dynamic_policy_builder.py
index d383962d..6d36d1a9 100644
--- a/yoti_python_sdk/tests/dynamic_sharing_service/policy/test_dynamic_policy_builder.py
+++ b/yoti_python_sdk/tests/dynamic_sharing_service/policy/test_dynamic_policy_builder.py
@@ -47,10 +47,11 @@ def test_build_with_simple_attributes():
builder.with_selfie()
builder.with_email()
builder.with_document_details()
+ builder.with_document_images()
policy = builder.build()
attr_names = [attr["name"] for attr in policy["wanted"]]
- assert len(policy["wanted"]) == 12
+ assert len(policy["wanted"]) == 13
assert config.ATTRIBUTE_FAMILY_NAME in attr_names
assert config.ATTRIBUTE_GIVEN_NAMES in attr_names
assert config.ATTRIBUTE_FULL_NAME in attr_names
@@ -63,6 +64,7 @@ def test_build_with_simple_attributes():
assert config.ATTRIBUTE_SELFIE in attr_names
assert config.ATTRIBUTE_EMAIL_ADDRESS in attr_names
assert config.ATTRIBUTE_DOCUMENT_DETAILS in attr_names
+ assert config.ATTRIBUTE_DOCUMENT_IMAGES in attr_names
def test_build_with_age_derived_attributes():
@@ -128,3 +130,18 @@ def test_attributes_with_constraints():
constraint = SourceConstraintBuilder().with_national_id().build()
policy = DynamicPolicyBuilder().with_nationality(constraints=constraint).build()
assert len(policy["wanted"][0]["constraints"]) == 1
+
+
+def test_attributes_with_accept_self_asserted_true():
+ policy = DynamicPolicyBuilder().with_nationality(accept_self_asserted=True).build()
+ assert policy["wanted"][0]["accept_self_asserted"] is True
+
+
+def test_attributes_with_accept_self_asserted_false():
+ policy = DynamicPolicyBuilder().with_nationality(accept_self_asserted=False).build()
+ assert policy["wanted"][0]["accept_self_asserted"] is False
+
+
+def test_attributes_without_accept_self_asserted():
+ policy = DynamicPolicyBuilder().with_nationality().build()
+ assert not hasattr(policy["wanted"][0], "accept_self_asserted")
diff --git a/yoti_python_sdk/version.py b/yoti_python_sdk/version.py
index 1947b0b3..18b49b72 100644
--- a/yoti_python_sdk/version.py
+++ b/yoti_python_sdk/version.py
@@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
-__version__ = "2.13.0"
+__version__ = "2.14.0"