Skip to content

Skip Iris results without a risk score instead of raising TypeError - #204

Open
eastagiletracker wants to merge 1 commit into
DomainTools:mainfrom
eastagiletracker:agile-board/iris-filter-missing-risk-score
Open

Skip Iris results without a risk score instead of raising TypeError#204
eastagiletracker wants to merge 1 commit into
DomainTools:mainfrom
eastagiletracker:agile-board/iris-filter-missing-risk-score

Conversation

@eastagiletracker

@eastagiletracker eastagiletracker commented Aug 7, 2026

Copy link
Copy Markdown

This PR proposes a fix for iris_investigate() and iris_enrich() raising TypeError: '>' not supported between instances of 'NoneType' and 'int' when a risk-score threshold is applied and one of the returned domains carries no risk score. We include this PR work along with a full history of your repo at https://eastagiletracker.com/projects/175. You can sign in with your GitHub ID to claim ownership of the project.

What goes wrong

filter_by_riskscore.__call__ (domaintools/filters.py:145) reads result.get("domain_risk", {}).get("risk_score") and compares the value to the threshold without checking that a score actually came back. Both parts of that lookup are optional in the Iris spec bundled with this package: in domaintools/specs/iris-openapi.yaml, DomainProfileTemplate declares no required list, and DomainRisk declares risk_score as an ordinary optional property. So a spec-valid result with no domain_risk block — or with a domain_risk that only carries components — evaluates None > 70 and aborts the whole iris_investigate() / iris_enrich() call rather than skipping that one row. The recorded Iris payload in tests/fixtures/vcr/test_iris.yaml is an example of a real response whose result object has no domain_risk key at all.

The other three filters in that module already guard this case explicitly — filter_by_expire_date and filter_by_date_updated_after both continue past rows they cannot compare, with a # skip uncomparable date comment. filter_by_riskscore is the one that does not.

Reproduced on main at cb2248d, with a stubbed transport so no API key is needed:

from unittest.mock import MagicMock, patch
from domaintools import API

api = API("notauser", "notakey", rate_limit=False)
payload = {"response": {"results": [{"domain": "example.com"}], "results_count": 1}}
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = payload

with patch("domaintools.base_results.Client") as mock_client:
    mock_client.return_value.__enter__.return_value.post.return_value = mock_response
    api.iris_investigate(domains=["example.com"], risk_score_threshold=70)
  File "domaintools/api.py", line 788, in iris_investigate
    filtered_results = DTResultFilter(result_set=results).by(
  File "domaintools/filters.py", line 14, in by
    self._result_set = _dt_filter(self._result_set)
  File "domaintools/filters.py", line 146, in __call__
    if domain_risk_score > self._threshold:
TypeError: '>' not supported between instances of 'NoneType' and 'int'

The change

filter_by_riskscore now skips a result whose risk score is missing or None, matching the convention the neighbouring filters already use. Four shapes are covered: no domain_risk key, domain_risk: null, a domain_risk without risk_score, and risk_score: null. Domains that do carry a score are filtered exactly as before — the comparison, the strict >, and the ordering of the filter chain are all untouched — so the existing test_filter_by_riskscore is unmodified and still passes as the control. The change is six lines in domaintools/filters.py; there is no public signature change and no behaviour change for any result that already had a score.

Verification

tox's test command on a clean checkout of main at cb2248d gives 121 passed, 2 skipped; with this branch it gives 123 passed, 2 skipped — the two added tests, no new failures and none of the existing tests altered:

$ python -m pytest tests --ignore=tests/e2e -q
123 passed, 2 skipped in 54.21s

Both new tests fail without the fix, which you can confirm directly:

$ git checkout main -- domaintools/filters.py   # drop the fix, keep the tests
$ python -m pytest tests -q -k "riskscore or risk_score"
FAILED tests/test_filters.py::DTFiltersTest::test_filter_by_riskscore_skips_results_without_risk_score
FAILED tests/test_api.py::test_iris_investigate_risk_score_threshold_with_scoreless_result
2 failed, 1 passed

The one that keeps passing there is the pre-existing test_filter_by_riskscore, which is the control for "scored domains still filter the same way". tests/test_api.py::test_iris_investigate_risk_score_threshold_with_scoreless_result drives the whole public path — API.iris_investigate(..., risk_score_threshold=70) over a stubbed transport, asserting the score-less domain is dropped and the scored one is returned — rather than only the filter class, so the fix is covered end to end. Re-running the reproduction above against this branch returns results_count = 0 instead of raising, and swapping api.iris_investigate(domains=["example.com"], risk_score_threshold=70) for api.iris_enrich("example.com", risk_score=70) in that same snippet shows the identical crash on main and the identical fix here, since both methods share the filter chain.

How this was managed

The board below was built by importing this repository's own issues, pull requests and milestones, and it is where this piece of work was tracked from investigation through to this PR: https://eastagiletracker.com/projects/175 — the story for this change is https://eastagiletracker.com/projects/175/stories/47466

board

If you'd rather not receive contributions like this, reply no-more-prs on this pull request and we won't open any further ones on your repositories.


Lawrence W. Sinclair
CEO / East Agile
linkedin.com/in/lwsinclair/
eastagile.com

filter_by_riskscore compared `domain_risk.risk_score` to the threshold
without checking that a score was present, so any Iris result missing the
`domain_risk` block raised `TypeError: '>' not supported between instances
of 'NoneType' and 'int'` and took down the whole
`iris_investigate`/`iris_enrich` call. `domain_risk` and its `risk_score`
are both optional in the bundled Iris spec.

Uncomparable results are now skipped, matching what filter_by_expire_date
and filter_by_date_updated_after already do; add test cases for the
missing, null and score-less `domain_risk` shapes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant