Skip to content

Send a roboflow-python User-Agent on every SDK request - #559

Merged
iurisilvio merged 2 commits into
mainfrom
iuri/sdk-user-agent
Oct 19, 2026
Merged

iurisilvio merged 2 commits into
mainfrom
iuri/sdk-user-agent

Conversation

@iurisilvio

Copy link
Copy Markdown
Contributor

What does this PR do?

Every HTTP request the SDK and the CLI make now sends User-Agent: roboflow-python/<version> (today roboflow-python/1.7.1). On main they send requests' default, python-requests/<version>, which any other Python client sends too, so SDK traffic can't be told apart in the API's request logs, which already record the User-Agent.

  • roboflow/util/http.py (new): get, post, put, patch, delete, head and request wrap the requests functions of the same name and add the header. Headers the caller passes take precedence, including a User-Agent of its own. http.Session is a requests.Session that sends the header.
  • Call sites: the 179 requests.<verb>(...) calls in 24 modules under roboflow/ call the helper of the same name, and so do the five Batch Processing adapters that hand requests.get/requests.post to _batch_processing_request. Workspace.search_export builds its session with http.Session(). The two urllib image fetches in roboflow/util/prediction.py send the header too.
  • roboflow/__init__.py: __version__ moves to the top, so roboflow.util.http can read it while the package's imports run. setup.py reads it with a regex, which still matches.
  • pyproject.toml: ruff's banned-api (TID251) rejects requests.get, post, put, patch, delete, head and request outside roboflow/util/http.py, with a message that names the helper to use instead. requests.Session stays allowed because type annotations use it.
  • requests itself is not patched: code in the user's process that calls requests directly keeps its own User-Agent.

Two commits: the helper, then the switch of the call sites and tests, which is mechanical.

Related Issue(s): none.

Type of Change

  • New feature (non-breaking change that adds functionality)

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:

  • tests/util/test_http.py:
    • each helper and http.Session send the header, the caller's headers are kept, and a caller's own User-Agent wins;
    • Roboflow() → workspace → project → version, and Project.upload with an annotation (the upload and annotate POSTs), send it on every request. Both fail when the header is dropped, and the upload test also fails if the upload goes through a plain requests.Session();
    • the hosted-image fetch in PredictionGroup.save sends it.
  • Tests that mocked roboflow.<module>.requests.<verb> now mock roboflow.util.http.requests.<verb>, where the call happens (153 patch targets in 11 files, nothing else changed in them). The three tests that compared the whole headers dict now expect the User-Agent too.
  • python -m unittest: 1241 tests OK, 1 skipped, on Python 3.12 and 3.10 (macOS). The slim job (requirements-slim.txt, then tests.test_slim_compat tests.test_vision_events) passes on 3.10. make check_code_quality passes (ruff 0.17.1, mypy 2.4.0).
  • On the wire, against a local HTTP server set as API_URL: Roboflow(api_key=...) and roboflow --json auth status --api-key ... send python-requests/2.34.2 on main and roboflow-python/1.7.1 on this branch.
  • Not run against staging or production.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable): not applicable

Additional Context

🤖 Generated with Claude Code

iurisilvio and others added 2 commits October 19, 2026 09:52
The helpers wrap requests' get, post, put, patch, delete, head and request
and add `User-Agent: roboflow-python/<version>`; headers the caller passes
take precedence. `http.Session` is a requests.Session that sends the same
header.

`__version__` moves to the top of roboflow/__init__.py, so the helper can
read it while the package's own imports are still running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every requests.<verb>(...) call in roboflow/, and the request functions the
Batch Processing adapters pass by reference, now calls the
roboflow.util.http helper of the same name. Workspace.search_export builds
its session with http.Session, and the two urllib image fetches in
roboflow.util.prediction send the header too.

ruff's banned-api (TID251) rejects requests.get, post, put, patch, delete,
head and request outside roboflow/util/http.py, so a new call can't skip the
header.

Tests that mocked roboflow.<module>.requests.<verb> now mock
roboflow.util.http.requests.<verb>, where the call happens, and the three
that compared the whole headers dict expect the User-Agent too. New tests
check the header end to end on Roboflow() -> workspace -> project -> version
and on an annotated image upload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@iurisilvio
iurisilvio requested a review from a team October 19, 2026 08:02
@iurisilvio iurisilvio self-assigned this Oct 19, 2026
@iurisilvio iurisilvio mentioned this pull request Oct 19, 2026
6 of 7 tasks

@digaobarbosa digaobarbosa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@iurisilvio
iurisilvio merged commit 3ec3d04 into main Oct 19, 2026
15 checks passed
iurisilvio added a commit that referenced this pull request Oct 19, 2026
Image uploads and annotation saves keep going through the per-thread upload
session, which is now an http.Session so they send the SDK's User-Agent
(#559).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants