-
- {% if liveness.frames|length > 0 %}
+ {% for liveness in session_result.resources.zoom_liveness_resources %}
+ {% set liveness_num = loop.index %}
+
+
+
+
+
+ | ID |
+ {{ liveness.id }} |
+
+
+
+
+
+
+ {% if liveness.frames|length > 0 %}
+
+
+
+
+ {% for frame in liveness.frames %}
+ {% if frame.media is not none %}
-
+
+
+ {% endif %}
+
+
+
+ {% endfor %}
+ {% endwith %}
+
+ {% if session_result.resources.static_liveness_resources|length > 0 %}
+
+
+
Static Liveness Resources
+
+
+ {% endif %}
+
+ {% with liveness_num=0 %}
+ {% for liveness in session_result.resources.static_liveness_resources %}
+ {% set liveness_num = loop.index %}
+
+
+
+
+
+ | ID |
+ {{ liveness.id }} |
+
+
+ | Liveness Type |
+ {{ liveness.liveness_type }} |
+
+
+
+
+
+ {% if liveness.image is not none and liveness.image.media is not none %}
+
+
+
+
+
+

+
+
Static Liveness Check
+
Media ID: {{ liveness.image.media.id }}
- {% endif %}
+
+ {% endif %}
- {% endfor %}
+
+
+ {% endfor %}
{% endwith %}
{% include "layout/footer.html" %}
diff --git a/yoti_python_sdk/doc_scan/constants.py b/yoti_python_sdk/doc_scan/constants.py
index 279d4e5e..ec2ea3a3 100644
--- a/yoti_python_sdk/doc_scan/constants.py
+++ b/yoti_python_sdk/doc_scan/constants.py
@@ -8,6 +8,7 @@
ID_DOCUMENT_FACE_MATCH = "ID_DOCUMENT_FACE_MATCH"
LIVENESS = "LIVENESS"
ZOOM = "ZOOM"
+STATIC = "STATIC"
SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK = "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK"
SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION = (
"SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION"
diff --git a/yoti_python_sdk/doc_scan/session/create/check/liveness.py b/yoti_python_sdk/doc_scan/session/create/check/liveness.py
index f9dc1075..57ae8e4f 100644
--- a/yoti_python_sdk/doc_scan/session/create/check/liveness.py
+++ b/yoti_python_sdk/doc_scan/session/create/check/liveness.py
@@ -11,15 +11,18 @@ class RequestedLivenessCheckConfig(YotiSerializable):
The configuration applied when creating a Liveness Check
"""
- def __init__(self, liveness_type, max_retries):
+ def __init__(self, liveness_type, max_retries, manual_check=None):
"""
:param liveness_type: the liveness type
:type liveness_type: str
:param max_retries: the maximum number of retries
:type max_retries: int
+ :param manual_check: the manual check value
+ :type manual_check: str or None
"""
self.__liveness_type = liveness_type
self.__max_retries = max_retries
+ self.__manual_check = manual_check
@property
def liveness_type(self):
@@ -39,9 +42,23 @@ def max_retries(self):
"""
return self.__max_retries
+ @property
+ def manual_check(self):
+ """
+ The manual check value for the liveness check
+
+ :return: the manual check value
+ :rtype: str or None
+ """
+ return self.__manual_check
+
def to_json(self):
return remove_null_values(
- {"liveness_type": self.liveness_type, "max_retries": self.max_retries}
+ {
+ "liveness_type": self.liveness_type,
+ "max_retries": self.max_retries,
+ "manual_check": self.manual_check,
+ }
)
@@ -74,6 +91,7 @@ class RequestedLivenessCheckBuilder(object):
def __init__(self):
self.__liveness_type = None
self.__max_retries = None
+ self.__manual_check = None
def for_zoom_liveness(self):
"""
@@ -84,6 +102,15 @@ def for_zoom_liveness(self):
"""
return self.with_liveness_type(constants.ZOOM)
+ def for_static_liveness(self):
+ """
+ Sets the liveness type to "STATIC"
+
+ :return: the builder
+ :rtype: RequestedLivenessCheckBuilder
+ """
+ return self.with_liveness_type(constants.STATIC)
+
def with_liveness_type(self, liveness_type):
"""
Sets the liveness type on the builder
@@ -109,6 +136,18 @@ def with_max_retries(self, max_retries):
self.__max_retries = max_retries
return self
+ def with_manual_check_never(self):
+ """
+ Sets the manual check value to "NEVER"
+
+ :return: the builder
+ :rtype: RequestedLivenessCheckBuilder
+ """
+ self.__manual_check = constants.NEVER
+ return self
+
def build(self):
- config = RequestedLivenessCheckConfig(self.__liveness_type, self.__max_retries)
+ config = RequestedLivenessCheckConfig(
+ self.__liveness_type, self.__max_retries, self.__manual_check
+ )
return RequestedLivenessCheck(config)
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/image_response.py b/yoti_python_sdk/doc_scan/session/retrieve/image_response.py
new file mode 100644
index 00000000..f983e550
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/retrieve/image_response.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from .media_response import MediaResponse
+
+
+class ImageResponse(object):
+ """
+ Represents an image resource within a static liveness check
+ """
+
+ 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 information for the image
+
+ :return: the media
+ :rtype: MediaResponse or None
+ """
+ return self.__media
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 8295e503..3aaf0ecd 100644
--- a/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py
+++ b/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py
@@ -11,6 +11,9 @@
LivenessResourceResponse,
ZoomLivenessResourceResponse,
)
+from yoti_python_sdk.doc_scan.session.retrieve.static_liveness_resource_response import (
+ StaticLivenessResourceResponse,
+)
class ResourceContainer(object):
@@ -54,7 +57,7 @@ def __parse_liveness_capture(liveness_capture):
:return: the parsed liveness capture
:rtype: LivenessResourceResponse
"""
- types = {"ZOOM": ZoomLivenessResourceResponse}
+ types = {"ZOOM": ZoomLivenessResourceResponse, "STATIC": StaticLivenessResourceResponse}
clazz = types.get(
liveness_capture.get("liveness_type", None),
@@ -105,3 +108,17 @@ def zoom_liveness_resources(self):
for liveness in self.__liveness_capture
if isinstance(liveness, ZoomLivenessResourceResponse)
]
+
+ @property
+ def static_liveness_resources(self):
+ """
+ Returns a filtered list of static liveness capture resources
+
+ :return: list of static liveness captures
+ :rtype: list[StaticLivenessResourceResponse]
+ """
+ return [
+ liveness
+ for liveness in self.__liveness_capture
+ if isinstance(liveness, StaticLivenessResourceResponse)
+ ]
diff --git a/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py b/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py
new file mode 100644
index 00000000..970258e1
--- /dev/null
+++ b/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from .liveness_resource_response import LivenessResourceResponse
+from .image_response import ImageResponse
+
+
+class StaticLivenessResourceResponse(LivenessResourceResponse):
+ """
+ Represents a Static Liveness 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()
+
+ LivenessResourceResponse.__init__(self, data)
+
+ self.__image = (
+ ImageResponse(data["image"]) if "image" in data.keys() else None
+ )
+
+ @property
+ def image(self):
+ """
+ Returns the associated image for the static liveness resource
+
+ :return: the image
+ :rtype: ImageResponse or None
+ """
+ return self.__image
diff --git a/yoti_python_sdk/tests/doc_scan/session/create/check/test_liveness_check.py b/yoti_python_sdk/tests/doc_scan/session/create/check/test_liveness_check.py
index 130deb69..6b14f934 100644
--- a/yoti_python_sdk/tests/doc_scan/session/create/check/test_liveness_check.py
+++ b/yoti_python_sdk/tests/doc_scan/session/create/check/test_liveness_check.py
@@ -52,6 +52,67 @@ def test_should_serialize_to_json_without_error(self):
s = json.dumps(result, cls=YotiEncoder)
assert s is not None and s != ""
+ def test_should_build_with_static_liveness_type(self):
+ result = (
+ RequestedLivenessCheckBuilder()
+ .for_static_liveness()
+ .with_max_retries(3)
+ .build()
+ )
+
+ assert result.type == "LIVENESS"
+ assert result.config.liveness_type == "STATIC"
+ assert result.config.max_retries == 3
+
+ def test_should_build_with_manual_check_never(self):
+ result = (
+ RequestedLivenessCheckBuilder()
+ .for_static_liveness()
+ .with_max_retries(3)
+ .with_manual_check_never()
+ .build()
+ )
+
+ assert result.config.liveness_type == "STATIC"
+ assert result.config.manual_check == "NEVER"
+
+ def test_should_serialize_static_liveness_to_json(self):
+ result = (
+ RequestedLivenessCheckBuilder()
+ .for_static_liveness()
+ .with_max_retries(3)
+ .with_manual_check_never()
+ .build()
+ )
+
+ json_str = json.dumps(result, cls=YotiEncoder)
+ assert json_str is not None
+
+ # Verify the JSON contains the expected fields
+ json_data = json.loads(json_str)
+ assert json_data["type"] == "LIVENESS"
+ assert json_data["config"]["liveness_type"] == "STATIC"
+ assert json_data["config"]["manual_check"] == "NEVER"
+ assert json_data["config"]["max_retries"] == 3
+
+ def test_should_omit_manual_check_when_not_set(self):
+ result = (
+ RequestedLivenessCheckBuilder()
+ .for_static_liveness()
+ .with_max_retries(3)
+ .build()
+ )
+
+ json_str = json.dumps(result, cls=YotiEncoder)
+ assert json_str is not None
+
+ # Verify the JSON does not contain the manual_check field
+ json_data = json.loads(json_str)
+ assert json_data["type"] == "LIVENESS"
+ assert json_data["config"]["liveness_type"] == "STATIC"
+ assert "manual_check" not in json_data["config"]
+ assert json_data["config"]["max_retries"] == 3
+
if __name__ == "__main__":
unittest.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 5a08c5a0..6607d2d9 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
@@ -49,6 +49,19 @@ def test_should_filter_zoom_liveness_resources(self):
assert len(result.liveness_capture) == 2
assert len(result.zoom_liveness_resources) == 1
+ def test_should_filter_static_liveness_resources(self):
+ data = {
+ "liveness_capture": [
+ {"liveness_type": "STATIC"},
+ {"liveness_type": "someUnknown"},
+ ]
+ }
+
+ result = ResourceContainer(data)
+
+ assert len(result.liveness_capture) == 2
+ assert len(result.static_liveness_resources) == 1
+
if __name__ == "__main__":
unittest.main()
diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py
new file mode 100644
index 00000000..0037810c
--- /dev/null
+++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py
@@ -0,0 +1,72 @@
+import unittest
+from yoti_python_sdk.doc_scan.session.retrieve.static_liveness_resource_response import (
+ StaticLivenessResourceResponse,
+)
+from yoti_python_sdk.doc_scan.session.retrieve.image_response import ImageResponse
+from yoti_python_sdk.doc_scan.session.retrieve.media_response import MediaResponse
+
+
+class StaticLivenessResourceResponseTest(unittest.TestCase):
+ def test_should_parse_static_liveness_resource(self):
+ data = {
+ "id": "bbbbbbb-5717-4562-b3fc-2c963f66afa6",
+ "source": {"type": "END_USER"},
+ "liveness_type": "STATIC",
+ "image": {
+ "media": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "type": "IMAGE",
+ "created": "2021-06-11T11:39:24Z",
+ "last_updated": "2021-06-11T11:39:24Z",
+ }
+ },
+ "tasks": [],
+ }
+
+ result = StaticLivenessResourceResponse(data)
+
+ assert result.id == "bbbbbbb-5717-4562-b3fc-2c963f66afa6"
+ assert result.liveness_type == "STATIC"
+ assert isinstance(result.image, ImageResponse)
+ assert isinstance(result.image.media, MediaResponse)
+ assert result.image.media.id == "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ assert result.image.media.type == "IMAGE"
+
+ def test_should_handle_missing_image(self):
+ data = {
+ "id": "test-id",
+ "liveness_type": "STATIC",
+ "tasks": [],
+ }
+
+ result = StaticLivenessResourceResponse(data)
+
+ assert result.id == "test-id"
+ assert result.liveness_type == "STATIC"
+ assert result.image is None
+
+ def test_should_parse_media_id_for_retrieval(self):
+ data = {
+ "id": "resource-id",
+ "liveness_type": "STATIC",
+ "image": {
+ "media": {
+ "id": "media-id-123",
+ "type": "IMAGE",
+ "created": "2021-06-11T11:39:24Z",
+ "last_updated": "2021-06-11T11:39:24Z",
+ }
+ },
+ "tasks": [],
+ }
+
+ result = StaticLivenessResourceResponse(data)
+
+ # Verify we can access the media ID for content retrieval
+ assert result.image is not None
+ assert result.image.media is not None
+ assert result.image.media.id == "media-id-123"
+
+
+if __name__ == "__main__":
+ unittest.main()