From fcb024c548e716dad2161754fe00c4614ee7e57e Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 4 Jun 2026 19:54:32 +0000 Subject: [PATCH 1/4] Add lychee link checker config and CI workflow Part of STF-557. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/links.yml | 32 +++++++++++++++++++ .gitignore | 2 ++ lychee.toml | 64 +++++++++++++++++++++++++++++++++++++ mise.lock | 29 +++++++++++++++++ mise.toml | 9 ++++++ 5 files changed, 136 insertions(+) create mode 100644 .github/workflows/links.yml create mode 100644 lychee.toml create mode 100644 mise.lock create mode 100644 mise.toml diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 0000000..5e3255b --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,32 @@ +name: Links + +on: + push: + pull_request: + schedule: + - cron: "0 13 * * 1" # weekly, to catch external link rot without a commit + workflow_dispatch: + +permissions: + contents: read + +jobs: + linkChecker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup mise + uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + with: + install: false + + # Install only lychee (not the repo's full toolchain) and run the check. + - name: Check links + env: + MISE_AUTO_INSTALL: "false" + run: | + mise install lychee + mise run check-links diff --git a/.gitignore b/.gitignore index 2041228..06daeaf 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,5 @@ MANIFEST # Claude Code .claude + +.lycheecache diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 0000000..9ca4438 --- /dev/null +++ b/lychee.toml @@ -0,0 +1,64 @@ +# Lychee link checker configuration +# https://lychee.cli.rs/#/usage/config +# +# Run locally with: +# lychee './**/*.md' './**/*.rst' './src/minfraud/**/*.py' './pyproject.toml' + +# Include URL fragments in checks +include_fragments = true + +# Don't allow any redirects, so links that have moved are surfaced and can be +# updated to their canonical destination. +max_redirects = 0 + +# Accept these HTTP status codes +# 100-103: Informational responses +# 200-299: Success responses +# 403: Forbidden (some sites use this for rate limiting) +# 429: Too Many Requests +# 500-599: Server errors (temporary issues shouldn't fail CI) +# 999: LinkedIn's custom status code +accept = ["100..=103", "200..=299", "403", "429", "500..=599", "999"] + +# Exclude URL patterns from checking (treated as regular expressions) +exclude = [ + # GitHub blob URLs with line-number fragments (not parseable as page anchors) + '^https://github\.com/[^/]+/[^/]+/blob/[0-9a-fA-F]+/.+#L\d+$', + # Live / auth-gated MaxMind endpoints: appear as code string literals or + # require login, so they can't be verified by an anonymous request. + '^https://geoip\.maxmind\.com', + '^https://geolite\.info', + '^https://minfraud\.maxmind\.com', + '^https://sandbox\.maxmind\.com', + '^https://updates\.maxmind\.com', + '^https://www\.maxmind\.com/en/accounts/', + '^https://www\.maxmind\.com/en/account/login', + # Local / placeholder URLs (e.g. the proxy example in docstrings) + '^file://', + '^https?://example\.(com|org|net)', + '^http://localhost', + '127\.0\.0\.1', +] + +# Exclude file paths from getting checked (regular expressions, matched against +# the path relative to the working directory). Patterns are segment-anchored +# with (^|/) so short names like "build" don't match unintended paths. +exclude_path = [ + '(^|/)node_modules/', + '(^|/)\.venv/', + '(^|/)venv/', + '(^|/)build/', + '(^|/)dist/', + '(^|/)\.eggs/', + '(^|/)[^/]*\.egg-info/', + '(^|/)docs/_build/', + # Changelog: historical entries are preserved as-is, not rewritten + '(^|/)HISTORY\.rst$', +] + +# Cache results for 1 day to speed up repeated checks +cache = true +max_cache_age = "1d" + +# Skip missing input files instead of erroring +skip_missing = true diff --git a/mise.lock b/mise.lock new file mode 100644 index 0000000..156e912 --- /dev/null +++ b/mise.lock @@ -0,0 +1,29 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + +[[tools.lychee]] +version = "0.23.0" +backend = "aqua:lycheeverse/lychee" + +[tools.lychee."platforms.linux-arm64"] +checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz" + +[tools.lychee."platforms.linux-arm64-musl"] +checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz" + +[tools.lychee."platforms.linux-x64"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.linux-x64-musl"] +checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz" + +[tools.lychee."platforms.macos-arm64"] +checksum = "sha256:4c8034900e11083b68ac6f6582c377ff1f704e268991999e09d717973e493e7f" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-arm64-macos.dmg" + +[tools.lychee."platforms.windows-x64"] +checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe" diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..fff9190 --- /dev/null +++ b/mise.toml @@ -0,0 +1,9 @@ +[settings] +lockfile = true + +[tools] +lychee = "latest" + +[tasks.check-links] +description = "Check links with lychee" +run = "lychee --no-progress './**/*.md' './**/*.rst' './src/minfraud/**/*.py' './pyproject.toml'" From 7c41d7588af513150167174dbfec8b1f725081aa Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 4 Jun 2026 19:59:17 +0000 Subject: [PATCH 2/4] Update stale and redirecting links - pyproject.toml: https://www.maxmind.com/ -> https://www.maxmind.com/en/home - pyproject.toml: https://minfraud.readthedocs.org/ -> https://minfraud.readthedocs.io/en/latest/ - CLAUDE.md: https://minfraud.readthedocs.io/ -> https://minfraud.readthedocs.io/en/latest/ - CLAUDE.md: dev.maxmind.com/minfraud/report-a-transaction -> .../report-a-transaction/ - README.dev.rst: https://pypi.python.org/pypi/minfraud -> https://pypi.org/project/minfraud/ - README.rst: dev.maxmind.com/minfraud/report-a-transaction?lang=en -> .../report-a-transaction/?lang=en - README.rst: dev.maxmind.com/minfraud/api-documentation/requests?lang=en -> .../requests/?lang=en - README.rst: https://minfraud.readthedocs.io/ -> https://minfraud.readthedocs.io/en/latest/ - README.rst: https://www.maxmind.com/en/account -> https://www.maxmind.com/en/accounts/current/people/current - README.rst: https://www.maxmind.com/en/support -> https://support.maxmind.com/knowledge-base - models.py: https://tools.ietf.org/html/rfc3339 -> https://datatracker.ietf.org/doc/html/rfc3339 - models.py: dev.maxmind.com/minfraud/track-devices?lang=en -> .../track-devices/?lang=en - models.py: dev.maxmind.com/minfraud/api-documentation/responses?lang=en#... -> .../responses/?lang=en#... - webservice.py: dev.maxmind.com/minfraud/api-documentation/requests?lang=en -> .../requests/?lang=en - webservice.py: dev.maxmind.com/minfraud/report-a-transaction?lang=en -> .../report-a-transaction/?lang=en - webservice.py: dev.maxmind.com/minfraud/report-transaction/#Request_Body (404) -> .../report-a-transaction/?lang=en Also excluded the amazon.com referrer_uri example value and the JS-rendered dev.maxmind.com responses-schema fragments (verified at the page level) from link checking. Part of STF-557. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 4 ++-- README.dev.rst | 2 +- README.rst | 12 ++++++------ lychee.toml | 6 ++++++ pyproject.toml | 4 ++-- src/minfraud/models.py | 6 +++--- src/minfraud/webservice.py | 16 ++++++++-------- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fe5d153..6d04a7b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -429,7 +429,7 @@ uv run tox ## Additional Resources -- [API Documentation](https://minfraud.readthedocs.io/) +- [API Documentation](https://minfraud.readthedocs.io/en/latest/) - [minFraud Web Services Docs](https://dev.maxmind.com/minfraud/) -- [Report Transaction API](https://dev.maxmind.com/minfraud/report-a-transaction) +- [Report Transaction API](https://dev.maxmind.com/minfraud/report-a-transaction/) - GitHub Issues: https://github.com/maxmind/minfraud-api-python/issues diff --git a/README.dev.rst b/README.dev.rst index 395b4da..3d7586d 100644 --- a/README.dev.rst +++ b/README.dev.rst @@ -11,4 +11,4 @@ Steps for doing a release: release script. 7. Run ``dev-bin/release.sh`` and follow the prompts. 8. Verify the release on `GitHub `_ - and `PyPI `_. + and `PyPI `_. diff --git a/README.rst b/README.rst index e90e4bd..aa53c80 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ Description This package provides an API for the `MaxMind minFraud Score, Insights, and Factors web services `_ as well as the `Report Transaction web service -`_. +`_. Installation ------------ @@ -30,7 +30,7 @@ Documentation ------------- Complete API documentation is available on `Read the Docs -`_. +`_. Usage ----- @@ -79,7 +79,7 @@ The Score web service is called with the ``score()`` method: Each of these methods takes a dictionary representing the transaction to be sent to the web service. The structure of this dictionary should be in `the format specified in the REST API documentation -`__. +`__. All fields are optional. Report Transactions Usage @@ -97,7 +97,7 @@ Report Transaction web service is called with the ``report()`` method: The method takes a dictionary representing the report to be sent to the web service. The structure of this dictionary should be in `the format specified in the REST API documentation -`__. The +`__. The required fields are ``tag`` and one or more of the following: ``ip_address``, ``maxmind_id``, ``minfraud_id``, ``transaction_id``. @@ -134,7 +134,7 @@ The possible errors are: Additionally, ``score``, ``insights`` and ``factors`` may also raise: * ``minfraud.InsufficientFundsError`` - This will be raised when `your - account `_ is out of funds. + account `_ is out of funds. Examples -------- @@ -322,7 +322,7 @@ Please report all issues with this code using the `GitHub issue tracker `_. If you are having an issue with a MaxMind service that is not specific to the -client API, please contact `MaxMind support `_ +client API, please contact `MaxMind support `_ for assistance. Copyright and License diff --git a/lychee.toml b/lychee.toml index 9ca4438..efa1bfe 100644 --- a/lychee.toml +++ b/lychee.toml @@ -24,6 +24,12 @@ accept = ["100..=103", "200..=299", "403", "429", "500..=599", "999"] exclude = [ # GitHub blob URLs with line-number fragments (not parseable as page anchors) '^https://github\.com/[^/]+/[^/]+/blob/[0-9a-fA-F]+/.+#L\d+$', + # dev.maxmind.com API docs are a JS-rendered single-page app, so its in-page + # anchors (e.g. response-schema fragments) aren't present in the static HTML + # lychee fetches. The pages themselves are verified without the fragment. + '^https://dev\.maxmind\.com/minfraud/api-documentation/responses/.*#', + # referrer_uri example value in a docstring/doctest (not a documentation link) + '^http://www\.amazon\.com/$', # Live / auth-gated MaxMind endpoints: appear as code string literals or # require login, so they can't be verified by an anonymous request. '^https://geoip\.maxmind\.com', diff --git a/pyproject.toml b/pyproject.toml index b22c8f3..bb2eb62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,8 +45,8 @@ lint = [ ] [project.urls] -Homepage = "https://www.maxmind.com/" -Documentation = "https://minfraud.readthedocs.org/" +Homepage = "https://www.maxmind.com/en/home" +Documentation = "https://minfraud.readthedocs.io/en/latest/" "Source Code" = "https://github.com/maxmind/minfraud-api-python" "Issue Tracker" = "https://github.com/maxmind/minfraud-api-python/issues" diff --git a/src/minfraud/models.py b/src/minfraud/models.py index d320dbf..d86e2ba 100644 --- a/src/minfraud/models.py +++ b/src/minfraud/models.py @@ -97,7 +97,7 @@ class GeoIP2Location(geoip2.records.Location): local_time: str | None """The date and time of the transaction in the time zone associated with the IP address. The value is formatted according to - `RFC 3339 `_. For instance, the + `RFC 3339 `_. For instance, the local time in Boston might be returned as 2015-04-27T19:17:24-04:00.""" def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -211,7 +211,7 @@ class Device(_Serializable): In order to receive device output from minFraud Insights or minFraud Factors, you must be using the `Device Tracking Add-on - `_. + `_. """ confidence: float | None @@ -620,7 +620,7 @@ class ServiceWarning(_Serializable): code: str | None """This value is a machine-readable code identifying the warning. See the `response warnings documentation - `_ + `_ for the current list of of warning codes.""" warning: str | None diff --git a/src/minfraud/webservice.py b/src/minfraud/webservice.py index 69eb601..8bc83a9 100644 --- a/src/minfraud/webservice.py +++ b/src/minfraud/webservice.py @@ -274,7 +274,7 @@ async def factors( :param transaction: A dictionary containing the transaction to be sent to the minFraud Factors web service as specified in the `REST API documentation - `_. + `_. :type transaction: dict :param validate: If set to false, validation of the transaction dictionary will be disabled. This validation helps ensure that your @@ -313,7 +313,7 @@ async def insights( :param transaction: A dictionary containing the transaction to be sent to the minFraud Insights web service as specified in the `REST API documentation - `_. + `_. :type transaction: dict :param validate: If set to false, validation of the transaction dictionary will be disabled. This validation helps ensure that your @@ -352,7 +352,7 @@ async def score( :param transaction: A dictionary containing the transaction to be sent to the minFraud Score web service as specified in the `REST API documentation - `_. + `_. :type transaction: dict :param validate: If set to false, validation of the transaction dictionary will be disabled. This validation helps ensure that your @@ -390,7 +390,7 @@ async def report( :param report: A dictionary containing the transaction report to be sent to the Report Transations web service as specified in the `REST API` documentation - _. + _. :type report: dict :param validate: If set to false, validation of the report dictionary will be disabled. This validation helps ensure that your request is @@ -538,7 +538,7 @@ def factors( :param transaction: A dictionary containing the transaction to be sent to the minFraud Factors web service as specified in the `REST API documentation - `_. + `_. :type transaction: dict :param validate: If set to false, validation of the transaction dictionary will be disabled. This validation helps ensure that your @@ -577,7 +577,7 @@ def insights( :param transaction: A dictionary containing the transaction to be sent to the minFraud Insights web service as specified in the `REST API documentation - `_. + `_. :type transaction: dict :param validate: If set to false, validation of the transaction dictionary will be disabled. This validation helps ensure that your @@ -616,7 +616,7 @@ def score( :param transaction: A dictionary containing the transaction to be sent to the minFraud Score web service as specified in the `REST API documentation - `_. + `_. :type transaction: dict :param validate: If set to false, validation of the transaction dictionary will be disabled. This validation helps ensure that your @@ -654,7 +654,7 @@ def report( :param report: A dictionary containing the transaction report to be sent to the Report Transations web service as specified in the `REST API` documentation - _. + _. :type report: dict :param validate: If set to false, validation of the report dictionary will be disabled. This validation helps ensure that your request is From 27a7fa0fc1e7b7bda194f05a9abd70d09a61e0ef Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 4 Jun 2026 21:12:13 +0000 Subject: [PATCH 3/4] Resolve zizmor findings in CI workflows - Add missing 'v' prefix to action pin version comments (ref-version-mismatch). - Disable uv caching in the release workflow (cache-poisoning); dependency caching is unnecessary for one-off release builds. Part of STF-557. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 7 +++++-- .github/workflows/test.yml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4af9f1a..3d2b9f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,10 @@ jobs: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # 8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + # Disable caching in the release workflow (zizmor cache-poisoning). + enable-cache: false - name: Build run: uv build @@ -47,4 +50,4 @@ jobs: name: artifact path: dist - - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # 1.14.0 + - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce4cbab..1ea93f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: submodules: true persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # 8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Install tox run: uv tool install --python-preference only-managed --python 3.13 tox --with tox-uv --with tox-gh - name: Install Python From c1a9cc95b7714c23a87b17f5767839f7be294383 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 4 Jun 2026 21:41:31 +0000 Subject: [PATCH 4/4] Fix broken RST hyperlink in report-transaction docstrings The link text backtick was closed early and the closing `_ was missing, so the report-a-transaction URL rendered as plain text. Pre-existing; fixed here while validating links. Part of STF-557. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/minfraud/webservice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/minfraud/webservice.py b/src/minfraud/webservice.py index 8bc83a9..85abd02 100644 --- a/src/minfraud/webservice.py +++ b/src/minfraud/webservice.py @@ -388,9 +388,9 @@ async def report( """Send a transaction report to the Report Transaction endpoint. :param report: A dictionary containing the transaction report to be sent - to the Report Transations web service as specified in the `REST API` + to the Report Transations web service as specified in the `REST API documentation - _. + `_. :type report: dict :param validate: If set to false, validation of the report dictionary will be disabled. This validation helps ensure that your request is @@ -652,9 +652,9 @@ def report( """Send a transaction report to the Report Transaction endpoint. :param report: A dictionary containing the transaction report to be sent - to the Report Transations web service as specified in the `REST API` + to the Report Transations web service as specified in the `REST API documentation - _. + `_. :type report: dict :param validate: If set to false, validation of the report dictionary will be disabled. This validation helps ensure that your request is