SDK-2614: Python - Support configuration for IDV shortened flow - python#454
Conversation
|
🤖 Claude Code ReviewCode Review FindingsSummaryThis branch adds three feature-aligned capabilities for the IDV shortened-flow:
The code cleanly follows the existing builder/serializer idioms in the SDK, but there are several correctness, API design, and test-coverage gaps worth addressing before merging. CriticalNone. Major
Minor
Nit
|
There was a problem hiding this comment.
Pull request overview
Adds support for the IDV shortened flow in the Python Doc Scan SDK by allowing SDK config to suppress specific client screens, and by introducing new request/response types for a Watchlist Advanced CA check and Face Capture task.
Changes:
- Add
suppressed_screenstoSdkConfig(builder + JSON serialization) and screen identifier constants. - Add builders for
WATCHLIST_ADVANCED_CA(check) andFACE_CAPTURE(task), plus retrieval response types and parsing/type registration. - Extend
GetSessionResultwith filtered accessors for watchlist screening vs. advanced CA checks; add SDK config tests for suppressed screens.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| yoti_python_sdk/doc_scan/constants.py | Adds new constants for watchlist advanced CA, face capture, and suppressable screen identifiers. |
| yoti_python_sdk/doc_scan/session/create/sdk_config.py | Adds suppressed_screens to config/builder and serializes it in to_json(). |
| yoti_python_sdk/doc_scan/session/create/check/watchlist_advanced_ca.py | New builder/config for Watchlist Advanced CA Profiles check. |
| yoti_python_sdk/doc_scan/session/create/check/init.py | Re-exports the new Watchlist Advanced CA builder. |
| yoti_python_sdk/doc_scan/session/create/task/face_capture.py | New builder/config for Face Capture task (empty config). |
| yoti_python_sdk/doc_scan/session/create/task/init.py | Re-exports the new Face Capture task builder. |
| yoti_python_sdk/doc_scan/session/retrieve/check_response.py | Adds WatchlistAdvancedCaProfilesCheckResponse. |
| yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py | Registers parsing for WATCHLIST_ADVANCED_CA and adds filtered accessors for watchlist checks. |
| yoti_python_sdk/doc_scan/session/retrieve/task_response.py | Adds FaceCaptureTaskResponse. |
| yoti_python_sdk/doc_scan/session/retrieve/resource_response.py | Registers parsing for FACE_CAPTURE tasks. |
| yoti_python_sdk/doc_scan/init.py | Re-exports the new builders/config types at the package level. |
| yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py | Adds coverage for setting/omitting/serializing suppressed_screens. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -230,6 +232,26 @@ def id_document_comparison_checks(self): | |||
| """ | |||
| return self.__checks_of_type((IDDocumentComparisonCheckResponse,)) | |||
|
|
|||
| @property | |||
| def watchlist_screening_checks(self): | |||
| """ | |||
| A filtered list of checks, returning only Watchlist Screening checks | |||
|
|
|||
| :return: the Watchlist Screening checks | |||
| :rtype: list[WatchlistScreeningCheckResponse] | |||
| """ | |||
| return self.__checks_of_type((WatchlistScreeningCheckResponse,)) | |||
|
|
|||
| @property | |||
| def watchlist_advanced_ca_checks(self): | |||
| """ | |||
| A filtered list of checks, returning only Watchlist Advanced CA Profiles checks | |||
|
|
|||
| :return: the Watchlist Advanced CA Profiles checks | |||
| :rtype: list[WatchlistAdvancedCaProfilesCheckResponse] | |||
| """ | |||
| return self.__checks_of_type((WatchlistAdvancedCaProfilesCheckResponse,)) | |||
There was a problem hiding this comment.
WATCHLIST_ADVANCED_CA parsing and the new watchlist_screening_checks / watchlist_advanced_ca_checks accessors are not covered by the existing GetSessionResult unit tests. Please extend tests/doc_scan/session/retrieve/test_get_session_result.py to include a WATCHLIST_SCREENING and WATCHLIST_ADVANCED_CA check and assert the new filtered properties return the correct instances.
| types = { | ||
| constants.ID_DOCUMENT_TEXT_DATA_EXTRACTION: TextExtractionTaskResponse, | ||
| constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION: SupplementaryDocumentTextExtractionTaskResponse, | ||
| constants.FACE_CAPTURE: FaceCaptureTaskResponse, | ||
| } |
There was a problem hiding this comment.
The new FACE_CAPTURE task type mapping isn’t covered by existing ResourceResponse tests. Please update tests/doc_scan/session/retrieve/test_resource_response.py to include a task with type FACE_CAPTURE and assert it is parsed as FaceCaptureTaskResponse (and that unknown types still fall back to TaskResponse).
| assert result.success_url is self.SOME_SUCCESS_URL | ||
| assert result.error_url is self.SOME_ERROR_URL | ||
| assert result.privacy_policy_url is self.SOME_PRIVACY_POLICY_URL | ||
| assert result.allow_handoff is True | ||
| assert result.suppressed_screens is self.SOME_SUPPRESSED_SCREENS |
There was a problem hiding this comment.
These assertions use is for string/list comparisons (e.g. success_url, error_url, privacy_policy_url, suppressed_screens). is checks object identity and can be flaky (string interning, list copies); use == for value equality and reserve is for None/True/False comparisons.
| :param privacy_policy_url: the privacy policy url | ||
| :type privacy_policy_url: str | ||
| :param allow_handoff: boolean flag for allow_handoff | ||
| :type allow_handoff: bool | ||
| :param suppressed_screens: the screens to suppress in the IDV flow | ||
| :type suppressed_screens: list[str] |
There was a problem hiding this comment.
The constructor docstring lists privacy_policy_url before allow_handoff, but the actual parameter order is allow_handoff then privacy_policy_url. Please align the docstring with the signature (or reorder the arguments) to avoid misleading callers reading the docs.
| class RequestedFaceCaptureTask(RequestedTask): | ||
| """ | ||
| Requests creation of a Face Capture Task | ||
| """ | ||
|
|
||
| def __init__(self, config): | ||
| """ | ||
| :param config: the face capture task configuration | ||
| :type config: RequestedFaceCaptureTaskConfig | ||
| """ | ||
| self.__config = config | ||
|
|
||
| @property | ||
| def type(self): | ||
| return constants.FACE_CAPTURE | ||
|
|
||
| @property | ||
| def config(self): | ||
| return self.__config | ||
|
|
||
|
|
||
| class RequestedFaceCaptureTaskBuilder(object): | ||
| """ | ||
| Builder to assist creation of :class:`RequestedFaceCaptureTask` | ||
| """ | ||
|
|
||
| @staticmethod | ||
| def build(): | ||
| config = RequestedFaceCaptureTaskConfig() | ||
| return RequestedFaceCaptureTask(config) |
There was a problem hiding this comment.
This PR adds a new task builder/config (RequestedFaceCaptureTask*), but there is no corresponding unit test in tests/doc_scan/session/create/task/ (similar tasks like text extraction have coverage). Please add tests covering type, config.to_json(), and serialization via YotiEncoder to prevent regressions.
| class WatchlistAdvancedCaProfilesCheckConfig(YotiSerializable): | ||
| """ | ||
| The configuration applied when creating a Watchlist Advanced CA Profiles check. | ||
| """ | ||
|
|
||
| def __init__(self, remove_deceased=None, share_url=None, sources=None): | ||
| """ | ||
| :param remove_deceased: whether to remove deceased individuals from results | ||
| :type remove_deceased: bool or None | ||
| :param share_url: whether to share the URL in results | ||
| :type share_url: bool or None | ||
| :param sources: the sources configuration | ||
| :type sources: WatchlistAdvancedCaSourcesConfig or None | ||
| """ | ||
| self.__remove_deceased = remove_deceased | ||
| self.__share_url = share_url | ||
| self.__sources = sources | ||
|
|
||
| @property | ||
| def remove_deceased(self): | ||
| """ | ||
| Whether deceased individuals are removed from results | ||
|
|
||
| :return: remove deceased flag | ||
| :rtype: bool or None | ||
| """ | ||
| return self.__remove_deceased | ||
|
|
||
| @property | ||
| def share_url(self): | ||
| """ | ||
| Whether the URL is shared in results | ||
|
|
||
| :return: share URL flag | ||
| :rtype: bool or None | ||
| """ | ||
| return self.__share_url | ||
|
|
||
| @property | ||
| def sources(self): | ||
| """ | ||
| The sources configuration for the advanced CA profiles check | ||
|
|
||
| :return: the sources configuration | ||
| :rtype: WatchlistAdvancedCaSourcesConfig or None | ||
| """ | ||
| return self.__sources | ||
|
|
||
| def to_json(self): | ||
| return remove_null_values( | ||
| { | ||
| "remove_deceased": self.__remove_deceased, | ||
| "share_url": self.__share_url, | ||
| "sources": self.__sources, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| class WatchlistAdvancedCaSourcesConfig(YotiSerializable): | ||
| """ | ||
| Configures the sources for a Watchlist Advanced CA Profiles check. | ||
| """ | ||
|
|
||
| def __init__(self, types=None): | ||
| """ | ||
| :param types: the list of source types to check against | ||
| :type types: list[str] or None | ||
| """ | ||
| self.__types = types or [] | ||
|
|
||
| @property | ||
| def types(self): | ||
| """ | ||
| The list of source types | ||
|
|
||
| :return: the source types | ||
| :rtype: list[str] | ||
| """ | ||
| return self.__types | ||
|
|
||
| def to_json(self): | ||
| return remove_null_values({"types": self.__types}) | ||
|
|
||
|
|
||
| class WatchlistAdvancedCaProfilesCheck(RequestedCheck): | ||
| """ | ||
| Requests creation of a Watchlist Advanced CA Profiles check | ||
| """ | ||
|
|
||
| def __init__(self, config): | ||
| """ | ||
| :param config: the Watchlist Advanced CA Profiles check configuration | ||
| :type config: WatchlistAdvancedCaProfilesCheckConfig | ||
| """ | ||
| self.__config = config | ||
|
|
||
| @property | ||
| def type(self): | ||
| return constants.WATCHLIST_ADVANCED_CA_CHECK_TYPE | ||
|
|
||
| @property | ||
| def config(self): | ||
| return self.__config | ||
|
|
||
|
|
||
| class WatchlistAdvancedCaProfilesCheckBuilder(object): | ||
| """ | ||
| Builder to assist creation of :class:`WatchlistAdvancedCaProfilesCheck` | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| self.__remove_deceased = None | ||
| self.__share_url = None | ||
| self.__sources = None | ||
|
|
||
| def with_remove_deceased(self, remove_deceased): | ||
| """ | ||
| Sets whether deceased individuals should be removed from results | ||
|
|
||
| :param remove_deceased: the remove deceased flag | ||
| :type remove_deceased: bool | ||
| :return: the builder | ||
| :rtype: WatchlistAdvancedCaProfilesCheckBuilder | ||
| """ | ||
| self.__remove_deceased = remove_deceased | ||
| return self | ||
|
|
||
| def with_share_url(self, share_url): | ||
| """ | ||
| Sets whether the URL should be shared in results | ||
|
|
||
| :param share_url: the share URL flag | ||
| :type share_url: bool | ||
| :return: the builder | ||
| :rtype: WatchlistAdvancedCaProfilesCheckBuilder | ||
| """ | ||
| self.__share_url = share_url | ||
| return self | ||
|
|
||
| def with_sources(self, sources): | ||
| """ | ||
| Sets the sources configuration for the check | ||
|
|
||
| :param sources: the sources configuration | ||
| :type sources: WatchlistAdvancedCaSourcesConfig | ||
| :return: the builder | ||
| :rtype: WatchlistAdvancedCaProfilesCheckBuilder | ||
| """ | ||
| self.__sources = sources | ||
| return self | ||
|
|
||
| def build(self): | ||
| config = WatchlistAdvancedCaProfilesCheckConfig( | ||
| self.__remove_deceased, self.__share_url, self.__sources | ||
| ) | ||
| return WatchlistAdvancedCaProfilesCheck(config) |
There was a problem hiding this comment.
This PR introduces a new check builder/config (WatchlistAdvancedCaProfilesCheck*), but there are no unit tests alongside existing check builder tests (e.g. in tests/doc_scan/session/create/check/). Please add coverage for builder defaults vs. set fields and the expected to_json() output, including how sources/types serializes.



Summary
Adds support for the IDV shortened flow in the Python SDK by introducing configurable
suppressed_screensonSdkConfig, plus new builders for aWATCHLIST_ADVANCED_CAcheck and aFACE_CAPTUREtask, along with the matching response types for retrieval. This brings the Python SDK in line with other Yoti SDKs for SDK-2614.Changes
yoti_python_sdk/doc_scan/constants.py: AddsWATCHLIST_ADVANCED_CA_CHECK_TYPE,FACE_CAPTURE, and suppressable screen identifiers (ID_DOCUMENT_EDUCATION,ID_DOCUMENT_REQUIREMENTS,SUPPLEMENTARY_DOCUMENT_EDUCATION,ZOOM_LIVENESS_EDUCATION,STATIC_LIVENESS_EDUCATION,FACE_CAPTURE_EDUCATION,FLOW_COMPLETION).session/create/sdk_config.py: Addssuppressed_screensfield,with_suppressed_screens(...)builder method, property accessor, and JSON serialisation (omitted when unset viaremove_null_values).session/create/check/watchlist_advanced_ca.py(new):WatchlistAdvancedCaProfilesCheck,WatchlistAdvancedCaProfilesCheckConfig,WatchlistAdvancedCaSourcesConfig, andWatchlistAdvancedCaProfilesCheckBuilderwithwith_remove_deceased,with_share_url,with_sources.session/create/task/face_capture.py(new):RequestedFaceCaptureTask,RequestedFaceCaptureTaskConfig,RequestedFaceCaptureTaskBuilder(empty config, serialises to{}).session/retrieve/check_response.py+get_session_result.py: AddsWatchlistAdvancedCaProfilesCheckResponse, wires it into the type map, and exposeswatchlist_screening_checksandwatchlist_advanced_ca_checksfiltered accessors.session/retrieve/task_response.py+resource_response.py: AddsFaceCaptureTaskResponseand registers it against theFACE_CAPTUREtask type.doc_scan/__init__.pyand the createcheck/taskpackage__init__s: Re-export the new builders (WatchlistScreeningCheckBuilder,WatchlistAdvancedCaProfilesCheckBuilder,WatchlistAdvancedCaSourcesConfig,RequestedFaceCaptureTaskBuilder).tests/doc_scan/session/create/test_sdk_config.py: Adds coverage for setting/omitting/emptysuppressed_screensand its JSON serialisation behaviour.QA Test Steps
pip install -e .from the repo root.pytest yoti_python_sdk/tests/doc_scan -q.pytest yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py -qand confirmtest_should_build_correctly,test_not_passing_suppressed_screens,test_passing_empty_suppressed_screens,test_suppressed_screens_serializes_to_json, andtest_unset_suppressed_screens_not_in_jsonall pass."suppressed_screens": ["ID_DOCUMENT_EDUCATION", "FLOW_COMPLETION"].SdkConfigwithout callingwith_suppressed_screens(...)and confirm the key is absent fromto_json()output.with_suppressed_screens([])and confirmto_json()["suppressed_screens"] == [](empty list survivesremove_null_values).remove_deceased,share_url, and asourcesobject withtypes: ["pep", "sanction"]; unset fields should be omitted."type": "WATCHLIST_ADVANCED_CA"intoGetSessionResult(seeget_session_result.py:82-86) and assert:result.watchlist_advanced_ca_checksreturns the check;result.watchlist_screening_checksis unaffected for aWATCHLIST_SCREENINGcheck."type": "FACE_CAPTURE"throughResourceResponse(seeresource_response.py:36-41) and confirm the task is instantiated asFaceCaptureTaskResponse.SessionSpecBuilderwithSdkConfigBuilder().with_suppressed_screens([...]), aRequestedFaceCaptureTaskBuilder.build()task, and aWatchlistAdvancedCaProfilesCheckBuildercheck. Confirm the Yoti API accepts the request and the IDV web flow skips the suppressed screens.RequestedLivenessCheckBuilder,RequestedFaceMatchCheckBuilder,WatchlistScreeningCheckBuilder,RequestedTextExtractionTaskBuilder,NotificationConfigBuilder) still import fromyoti_python_sdk.doc_scanwithout errors.Notes
suppressed_screensis passed through to the API as-is; the SDK does not validate the screen identifiers beyond exposing the constants defined inconstants.py— unknown values will be forwarded to the backend.WatchlistAdvancedCaSourcesConfig.typesdefaults to[]whenNoneis supplied, and always serialises thetypeskey (even when empty). Confirm this matches the backend contract if strict omission is expected.RequestedFaceCaptureTaskConfigcurrently has no configurable fields and serialises to{}; extend it when the API adds options (e.g. manual capture toggles).WatchlistAdvancedCaProfilesCheckBuilder,RequestedFaceCaptureTaskBuilder, and the new retrieval response classes — this PR only extends the existingtest_sdk_config.py.Related Jira: SDK-2614
Auto-generated by n8n + Claude CLI