From f6e1d28fd70ca19e9358b4d439fe23d7cf64e87a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 05:28:53 +0000 Subject: [PATCH 01/17] Bump virtualenv from 20.0.33 to 20.0.34 Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.0.33 to 20.0.34. - [Release notes](https://github.com/pypa/virtualenv/releases) - [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/virtualenv/compare/20.0.33...20.0.34) Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6ea005f6..5c1c5767 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ "python-coveralls==2.9.3", "coverage==4.5.4", "mock==2.0.0", - "virtualenv==20.0.33", + "virtualenv==20.0.34", ], }, classifiers=[ From 20dd070f861639b7eba039d375500d81ceb37a98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 05:35:15 +0000 Subject: [PATCH 02/17] Bump virtualenv from 20.0.34 to 20.0.35 Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.0.34 to 20.0.35. - [Release notes](https://github.com/pypa/virtualenv/releases) - [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/virtualenv/compare/20.0.34...20.0.35) Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5c1c5767..9168c67f 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ "python-coveralls==2.9.3", "coverage==4.5.4", "mock==2.0.0", - "virtualenv==20.0.34", + "virtualenv==20.0.35", ], }, classifiers=[ From 81223f676072dc44f7d044962c2111fdde0ac3d7 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Wed, 21 Oct 2020 15:32:20 +0100 Subject: [PATCH 03/17] Bump minor version --- sonar-project.properties | 2 +- yoti_python_sdk/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index 28191bca..ff0ea2a3 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ 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 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" From d1f3cca6403c617dcca9d14ee6b6dfb2345b9f40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2020 05:58:06 +0000 Subject: [PATCH 04/17] Bump virtualenv from 20.0.35 to 20.1.0 Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.0.35 to 20.1.0. - [Release notes](https://github.com/pypa/virtualenv/releases) - [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/virtualenv/compare/20.0.35...20.1.0) Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9168c67f..ca8205dc 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ "python-coveralls==2.9.3", "coverage==4.5.4", "mock==2.0.0", - "virtualenv==20.0.35", + "virtualenv==20.1.0", ], }, classifiers=[ From 91778a35a9c831f965b45143173842a9b4a57067 Mon Sep 17 00:00:00 2001 From: Ed Harrod Date: Thu, 29 Oct 2020 14:05:50 +0000 Subject: [PATCH 05/17] NA: Update dependabot.yml schedule --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 466e6d29..01d0134a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: pip directory: "/" schedule: - interval: daily + interval: monthly open-pull-requests-limit: 3 target-branch: development reviewers: From b6422e08e07b9af6edfc22be58213fc5b3197ccf Mon Sep 17 00:00:00 2001 From: David Grayston Date: Fri, 30 Oct 2020 16:13:59 +0000 Subject: [PATCH 06/17] SDK-1809: Support media responses with no content --- examples/doc_scan/app.py | 7 +++--- yoti_python_sdk/doc_scan/client.py | 3 +++ yoti_python_sdk/tests/doc_scan/mocks.py | 6 ++++- .../tests/doc_scan/test_doc_scan_client.py | 22 +++++++++++++++---- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/examples/doc_scan/app.py b/examples/doc_scan/app.py index 0a862861..46c864cd 100644 --- a/examples/doc_scan/app.py +++ b/examples/doc_scan/app.py @@ -73,9 +73,7 @@ def create_session(): ) .with_sdk_config(sdk_config) .with_required_document(build_required_id_document_restriction("PASSPORT")) - .with_required_document( - build_required_id_document_restriction("DRIVING_LICENCE") - ) + .with_required_document(RequiredIdDocumentBuilder().build()) .build() ) @@ -152,6 +150,9 @@ def media(): except DocScanException as e: return render_template("error.html", error=e.text) + if retrieved_media is None: + return Response("", status=204) + return Response( retrieved_media.content, content_type=retrieved_media.mime_type, status=200 ) 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/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/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): """ From 19b827c520ea94ca3352686dca8093ff6827bd0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 05:48:59 +0000 Subject: [PATCH 07/17] Bump pytz from 2020.1 to 2020.4 Bumps [pytz](https://github.com/stub42/pytz) from 2020.1 to 2020.4. - [Release notes](https://github.com/stub42/pytz/releases) - [Commits](https://github.com/stub42/pytz/compare/release_2020.1...release_2020.4) Signed-off-by: dependabot[bot] --- requirements.in | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From fba217b1b5cf87d3d7a97a45566e66afa3e6de66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2020 22:36:00 +0000 Subject: [PATCH 08/17] Bump cryptography from 2.9.2 to 3.2 in /examples/doc_scan Bumps [cryptography](https://github.com/pyca/cryptography) from 2.9.2 to 3.2. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/2.9.2...3.2) Signed-off-by: dependabot[bot] --- examples/doc_scan/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/doc_scan/requirements.txt b/examples/doc_scan/requirements.txt index a0479040..38b136a4 100644 --- a/examples/doc_scan/requirements.txt +++ b/examples/doc_scan/requirements.txt @@ -9,7 +9,7 @@ certifi==2020.4.5.1 # via requests cffi==1.14.0 # via cryptography chardet==3.0.4 # via requests click==7.1.2 # via flask -cryptography==2.9.2 # via pyopenssl, yoti +cryptography==3.2 # via pyopenssl, yoti deprecated==1.2.10 # via yoti filetype==1.0.7 # via -r requirements.in flask==1.1.2 # via -r requirements.in From ec7666f14a657d861334e34e2704afbd4f4667f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2020 21:22:06 +0000 Subject: [PATCH 09/17] Bump cryptography from 2.9.2 to 3.2 in /examples/yoti_example_django Bumps [cryptography](https://github.com/pyca/cryptography) from 2.9.2 to 3.2. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/2.9.2...3.2) Signed-off-by: dependabot[bot] --- examples/yoti_example_django/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2b329828eb4c5d3d885b7f46c836c7e8201f8729 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2020 21:23:40 +0000 Subject: [PATCH 10/17] Bump cryptography from 2.9.2 to 3.2 in /examples/yoti_example_flask Bumps [cryptography](https://github.com/pyca/cryptography) from 2.9.2 to 3.2. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/2.9.2...3.2) Signed-off-by: dependabot[bot] --- examples/yoti_example_flask/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/yoti_example_flask/requirements.txt b/examples/yoti_example_flask/requirements.txt index 5acd8600..30675bdf 100644 --- a/examples/yoti_example_flask/requirements.txt +++ b/examples/yoti_example_flask/requirements.txt @@ -9,11 +9,13 @@ 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 +enum34==1.1.10 # via cryptography flask==1.1.1 # via -r requirements.in future==0.16.0 # via yoti idna==2.7 # via requests +ipaddress==1.0.23 # via cryptography iso8601==0.1.13 # via yoti itsdangerous==0.24 # via flask jinja2==2.10.1 # via -r requirements.in, flask From 08180b9729c7b948fd482e5f7cef2b3def600075 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Tue, 3 Nov 2020 15:33:50 +0000 Subject: [PATCH 11/17] NA: Regenerate flask demo requirements.txt --- examples/yoti_example_flask/requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/yoti_example_flask/requirements.txt b/examples/yoti_example_flask/requirements.txt index 30675bdf..a5a293a8 100644 --- a/examples/yoti_example_flask/requirements.txt +++ b/examples/yoti_example_flask/requirements.txt @@ -11,11 +11,9 @@ chardet==3.0.4 # via requests click==6.7 # via flask cryptography==3.2 # via pyopenssl, yoti deprecated==1.2.10 # via yoti -enum34==1.1.10 # via cryptography flask==1.1.1 # via -r requirements.in future==0.16.0 # via yoti idna==2.7 # via requests -ipaddress==1.0.23 # via cryptography iso8601==0.1.13 # via yoti itsdangerous==0.24 # via flask jinja2==2.10.1 # via -r requirements.in, flask From aec3df7adf55867ff0fca6f35d31d0cade9c370f Mon Sep 17 00:00:00 2001 From: David Grayston Date: Wed, 4 Nov 2020 12:38:36 +0000 Subject: [PATCH 12/17] SDK-1714: Add support for supplementary documents --- examples/doc_scan/app.py | 27 ++- examples/doc_scan/templates/success.html | 216 +++++++++++++++++- yoti_python_sdk/doc_scan/__init__.py | 4 + yoti_python_sdk/doc_scan/constants.py | 7 + .../session/create/filter/__init__.py | 2 + .../filter/required_supplementary_document.py | 90 ++++++++ .../session/create/objective/__init__.py | 5 + .../session/create/objective/objective.py | 26 +++ .../objective/proof_of_address_objective.py | 28 +++ .../doc_scan/session/create/task/__init__.py | 8 +- .../task/supplementary_doc_text_extraction.py | 91 ++++++++ .../session/retrieve/check_response.py | 8 + .../session/retrieve/file_response.py | 30 +++ .../retrieve/generated_check_response.py | 8 + .../session/retrieve/get_session_result.py | 38 ++- .../session/retrieve/resource_container.py | 18 ++ .../session/retrieve/resource_response.py | 12 +- ...upplementary_document_resource_response.py | 107 +++++++++ .../session/retrieve/task_response.py | 24 +- .../test_required_supplementary_document.py | 97 ++++++++ .../session/create/objective/__init__.py | 0 .../test_proof_of_address_objective.py | 31 +++ ..._supplementary_doc_text_extraction_task.py | 89 ++++++++ .../session/retrieve/test_file_response.py | 21 ++ .../retrieve/test_get_session_result.py | 36 ++- .../retrieve/test_resource_container.py | 2 + ...upplementary_document_resource_response.py | 90 ++++++++ .../session/retrieve/test_task_response.py | 28 ++- 28 files changed, 1107 insertions(+), 36 deletions(-) create mode 100644 yoti_python_sdk/doc_scan/session/create/filter/required_supplementary_document.py create mode 100644 yoti_python_sdk/doc_scan/session/create/objective/__init__.py create mode 100644 yoti_python_sdk/doc_scan/session/create/objective/objective.py create mode 100644 yoti_python_sdk/doc_scan/session/create/objective/proof_of_address_objective.py create mode 100644 yoti_python_sdk/doc_scan/session/create/task/supplementary_doc_text_extraction.py create mode 100644 yoti_python_sdk/doc_scan/session/retrieve/file_response.py create mode 100644 yoti_python_sdk/doc_scan/session/retrieve/supplementary_document_resource_response.py create mode 100644 yoti_python_sdk/tests/doc_scan/session/create/filter/test_required_supplementary_document.py create mode 100644 yoti_python_sdk/tests/doc_scan/session/create/objective/__init__.py create mode 100644 yoti_python_sdk/tests/doc_scan/session/create/objective/test_proof_of_address_objective.py create mode 100644 yoti_python_sdk/tests/doc_scan/session/create/task/test_supplementary_doc_text_extraction_task.py create mode 100644 yoti_python_sdk/tests/doc_scan/session/retrieve/test_file_response.py create mode 100644 yoti_python_sdk/tests/doc_scan/session/retrieve/test_supplementary_document_resource_response.py diff --git a/examples/doc_scan/app.py b/examples/doc_scan/app.py index 46c864cd..3de18b4d 100644 --- a/examples/doc_scan/app.py +++ b/examples/doc_scan/app.py @@ -7,6 +7,7 @@ RequestedIDDocumentComparisonCheckBuilder, RequestedLivenessCheckBuilder, RequestedTextExtractionTaskBuilder, + RequestedSupplementaryDocTextExtractionTaskBuilder, SdkConfigBuilder, SessionSpecBuilder, ) @@ -15,6 +16,10 @@ RequiredIdDocumentBuilder, DocumentRestrictionBuilder, DocumentRestrictionsFilterBuilder, + RequiredSupplementaryDocumentBuilder, +) +from yoti_python_sdk.doc_scan.session.create.objective import ( + ProofOfAddressObjectiveBuilder, ) from .settings import YOTI_APP_BASE_URL, YOTI_CLIENT_SDK_ID, YOTI_KEY_FILE_PATH @@ -52,7 +57,7 @@ def create_session(): .with_user_tracking_id("some-user-tracking-id") .with_requested_check( RequestedDocumentAuthenticityCheckBuilder() - .with_manual_check_never() + .with_manual_check_always() .build() ) .with_requested_check( @@ -62,18 +67,28 @@ def create_session(): .build() ) .with_requested_check( - RequestedFaceMatchCheckBuilder().with_manual_check_never().build() + RequestedFaceMatchCheckBuilder().with_manual_check_always().build() ) .with_requested_check(RequestedIDDocumentComparisonCheckBuilder().build()) .with_requested_task( RequestedTextExtractionTaskBuilder() - .with_manual_check_never() + .with_manual_check_always() .with_chip_data_desired() .build() ) + .with_requested_task( + RequestedSupplementaryDocTextExtractionTaskBuilder() + .with_manual_check_always() + .build() + ) .with_sdk_config(sdk_config) .with_required_document(build_required_id_document_restriction("PASSPORT")) .with_required_document(RequiredIdDocumentBuilder().build()) + .with_required_document( + RequiredSupplementaryDocumentBuilder() + .with_objective(ProofOfAddressObjectiveBuilder().build()) + .build() + ) .build() ) @@ -96,7 +111,7 @@ def index(): try: result = create_session() except DocScanException as e: - return render_template("error.html", error=e.text) + return render_template("error.html", error=e.message) session["doc_scan_session_id"] = result.session_id @@ -118,7 +133,7 @@ def success(): try: session_result = doc_scan_client.get_session(session_id) except DocScanException as e: - return render_template("error.html", error=e.text) + return render_template("error.html", error=e.message) return render_template("success.html", session_result=session_result) @@ -148,7 +163,7 @@ def media(): try: retrieved_media = doc_scan_client.get_media_content(session_id, media_id) except DocScanException as e: - return render_template("error.html", error=e.text) + return render_template("error.html", error=e.message) if retrieved_media is None: return Response("", status=204) diff --git a/examples/doc_scan/templates/success.html b/examples/doc_scan/templates/success.html index 646e3c5f..19615dba 100644 --- a/examples/doc_scan/templates/success.html +++ b/examples/doc_scan/templates/success.html @@ -73,20 +73,20 @@

{% endif %} - {% if session_result.text_data_checks|length > 0 %} + {% if session_result.id_document_text_data_checks|length > 0 %}

- {% for check in session_result.text_data_checks %} + {% for check in session_result.id_document_text_data_checks %} {% with check=check %} {% include "partials/check.html" %} {% endwith %} @@ -163,6 +163,29 @@

{% endif %} + + {% if session_result.supplementary_document_text_data_checks|length > 0 %} +
+
+

+ +

+
+
+
+ {% for check in session_result.supplementary_document_text_data_checks %} + {% with check=check %} + {% include "partials/check.html" %} + {% endwith %} + {% endfor %} +
+
+
+ {% endif %}
@@ -360,6 +383,193 @@

Frame
{% endfor %} {% endwith %} + {% if session_result.resources.supplementary_documents|length > 0 %} +
+
+

Supplementary Documents

+
+
+ {% endif %} + + {% with doc_num=0 %} + {% for document in session_result.resources.supplementary_documents %} + {% set doc_num = loop.index %} +
+
+ +

+ {{ document.document_type }} {{ document.issuing_country }} +

+ +
+ {% if document.document_fields is not none %} +
+
+

+ +

+
+
+
+ {% if document.document_fields.media is not none %} +
Media
+ + + + + + + +
ID + + {{ document.document_fields.media.id }} + +
+ {% endif %} +
+
+
+ {% endif %} + + {% if document.document_file is not none %} + {% if document.document_file.media is not none %} + + {% endif %} + {% endif %} + + {% if document.text_extraction_tasks|length > 0 %} +
+
+

+ +

+
+
+
+ {% for task in document.text_extraction_tasks %} + + {% with task=task %} + {% include "partials/task.html" %} + {% endwith %} + + {% if task.generated_text_data_checks|length > 0 %} +
Generated Text Data Checks
+ + {% for generated_check in task.generated_text_data_checks %} + + + + + + + +
ID{{ generated_check.id }}
+ {% endfor %} + {% endif %} + + {% if task.generated_media|length > 0 %} +
Generated Media
+ + {% for generated_media in task.generated_media %} + + + + + + + + + + + +
ID + {{ generated_media.id }} +
Type{{ generated_media.type }}
+ {% endfor %} + {% endif %} + {% endfor %} +
+
+
+ {% endif %} + + {% if document.pages|length > 0 %} + {% with page_num=0 %} + {% for page in document.pages %} + {% set page_num = loop.index %} +
+
+

+ +

+
+
+ +
+ {% if page.media is not none %} +
+ +
+

Method: {{ page.capture_method }}

+
+
+ {% endif %} +
+ + {% if page.frames|length > 0 %} +
+ {% for frame in page.frames %} + {% if frame.media is not none %} +
+ +
+
Frame
+
+
+ {% endif %} + {% endfor %} +
+ {% endif %} + +
+
+ {% endfor %} + {% endwith %} + {% endif %} +
+
+
+ {% endfor %} + {% endwith %} + {% if session_result.resources.zoom_liveness_resources|length > 0 %}
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/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/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__": From 0f47ea1c7d20a26be6a1c5556585ecad35cbf7ae Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 9 Nov 2020 11:58:52 +0000 Subject: [PATCH 13/17] SDK-1615: Add document images dynamic policy builder method --- .../dynamic_sharing_service/policy/dynamic_policy_builder.py | 5 +++++ .../policy/test_dynamic_policy_builder.py | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) 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..987be053 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 @@ -122,6 +122,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/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..65ac58b2 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(): From 3b85e7004c073a16bdd96fef2f65d1dccaa51407 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 9 Nov 2020 12:14:51 +0000 Subject: [PATCH 14/17] SDK-1615: Support accept self asserted keyword argument --- .../policy/dynamic_policy_builder.py | 4 ++++ .../policy/test_dynamic_policy_builder.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) 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 987be053..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 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 65ac58b2..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 @@ -130,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") From 39cbe54608cc415cf81a965f2781504091438c5f Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 9 Nov 2020 13:43:39 +0000 Subject: [PATCH 15/17] SDK-1615: Export ThirdPartyAttributeExtension --- yoti_python_sdk/dynamic_sharing_service/extension/__init__.py | 2 ++ 1 file changed, 2 insertions(+) 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", ] From e2a7e71e815a7f38ebcf4921e1248b7d1691d69e Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 9 Nov 2020 16:09:38 +0000 Subject: [PATCH 16/17] SDK-1615: Add pytz to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ca8205dc..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": [ From 1ceff9f4ca46a69e58c6abff04d5b48b4cc904b4 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 9 Nov 2020 17:28:25 +0000 Subject: [PATCH 17/17] NA: Exclude version from coverage --- sonar-project.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/sonar-project.properties b/sonar-project.properties index ff0ea2a3..b8f41b51 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,3 +7,4 @@ 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