Easy EU region selection: ROBOFLOW_REGION, auth login --region, auth set-region - #513
Easy EU region selection: ROBOFLOW_REGION, auth login --region, auth set-region#513imbgar-roboflow wants to merge 1 commit into
Conversation
|
CI note: the |
…egion, auth set-region
One switch selects the Roboflow platform region (us default, eu):
resolved env var > config file > region default > existing US default,
so explicit API_URL/APP_URL-style overrides keep working unchanged.
- config.py: region registry + runtime resolve_url()/get_effective_region();
EU maps api/app to api,app.roboflow.eu, detection/instance-seg to
serverless.roboflow.eu, dedicated deployments to eu.roboflow.cloud.
Universe and semantic-seg stay global .com (no EU instance today).
- login(): browser auth flow prints and validates the token against the
region's app host at call time; forced re-login now merges the prior
config instead of clobbering it (preserves region + URL overrides).
- CLI: auth login --region {us,eu} (interactive + --api-key paths),
auth set-region, region + effective URLs in auth status (text/JSON),
root login alias forwards --region.
- Docs: CLI-COMMANDS.md region section, README 'Using Roboflow EU'.
- Tests: back-compat guard (no region = byte-identical US defaults),
precedence matrix, EU map, login persistence/merge, CLI paths.
INF-314
0e1d22a to
6ad02bc
Compare
imbgar-roboflow
left a comment
There was a problem hiding this comment.
TL;DR
Needs work — 6 fixes before merge · 5 follow-ups · 1 question.
Required before merge
- Fix before merge: [sol-ultra] New annotations make the package unimportable on advertised Python versions —
roboflow/config.py:29
Exact replacement attached as an inline suggestion. - Fix before merge: [sol-ultra] The advertised login command cannot switch an existing user to EU —
roboflow/cli/handlers/auth.py:242
Exact replacement attached as an inline suggestion. - Fix before merge: [sol-ultra] Programmatic EU login leaves the SDK on its import-time API endpoint —
roboflow/__init__.py:80
Exact replacement attached as an inline suggestion. - Fix before merge: [sol-ultra] Invalid regions corrupt JSON-mode stderr —
roboflow/config.py:76
Exact replacement attached as an inline suggestion. - Fix before merge: [sol-ultra] Public URL helpers still reject EU application URLs —
README.md:100
Exact replacement attached as an inline suggestion. - Fix before merge: [fable-xhigh] set-region always warns about stored credentials, even when none exist or region is unchanged —
roboflow/cli/handlers/auth.py:360
Exact replacement attached as an inline suggestion.
Follow-ups
- [fable-xhigh] Region allow-list is duplicated in three places with inconsistent normalization —
roboflow/__init__.py:72 - [fable-xhigh] Forced re-login still deletes the config before the token exchange, defeating the new merge on failure —
roboflow/__init__.py:105 - [fable-xhigh] Not-logged-in JSON path bypasses output_error and diverges from text mode —
roboflow/cli/handlers/auth.py:309 - [fable-xhigh] Stored credentials are not region-scoped; US and EU workspaces share one map and one global region —
roboflow/cli/handlers/auth.py:208 - [fable-xhigh] README/login docs omit that roboflow.login(region=...) does not affect same-session SDK constants —
README.md:109
Questions
- [fable-xhigh] Should an explicit --region lose silently to an API_URL/APP_URL env override? —
roboflow/config.py:98
Could not be verified
Raised by the congregation but neither confirmed nor refuted — the review context lacked the evidence. Treat as open questions, not clearances.
tests/cli/test_auth_region.py:161[fable-xhigh] Test reads result.stderr from a default CliRunner, which requires Click >= 8.2 — Cross-examination verdict was uncertain with voter-a=refute and voter-b=confirm; the adversarial layer did not confirm this finding, so it is rejected per the rule that only confirmed findings may be accepted.
How this verdict was reached
- Jury: superduper-trio (reviewer) · superduper-adversarial (cross_examiner) · superduperduper-glm (adjudicator)
- Work: 18 raised · 17 accepted · 0 refuted · 1 unverifiable · 6 blocking
- Verifier: ran on blocking candidates
- Withheld: 4 take-or-leave notes (below the public evidence bar)
- Coverage: superduper-trio reviewed 9/9 files and 26/26 hunks · superduper-adversarial independently reviewed 9/9 files and 26/26 hunks
Failure architecture
Why does importing roboflow fail on Python 3.8, which the README still advertises?
config.py evaluates set[str] at module import without postponed-annotation import; Python 3.8 lacks set.class_getitem, so the subscript raises TypeError before any region logic runs.
flowchart LR
D1N1["source: README advertises Python 3.8 or higher known"]
D1N2["component: config.py line 29 uses set str at module level with no __future__ import known"]
D1N3["decision: Python 3.8 evaluates set str as runtime subscript (no class_getitem) known"]
D1N4["failure: TypeError at import: roboflow unimportable on advertised Python 3.8 known"]
D1N1 -->|"3.8 users install per README known"| D1N2
D1N2 -->|"no postponed annotations; subscript evaluated at runtime known"| D1N3
D1N3 -->|"set.__class_getitem__ absent in 3.8 raises TypeError known"| D1N4
Scope: roboflow/config.py module import on Python 3.8
Assumptions: str | None in resolve_url would also fail on 3.9, but set[str] fails first at module import
Unresolved: Exact Python 3.8 traceback not captured in supplied diff; failure inferred from language semantics stated in finding
Why does roboflow.login(region='eu') not route subsequent SDK operations to the EU API?
API_URL is bound by value at import time; login only dynamically resolves APP_URL and persists region, so check_key and Roboflow() keep using the stale US API_URL with the newly stored EU key.
flowchart LR
D2N1["source: Process imports roboflow; API_URL bound by value at import time known"]
D2N2["component: login(region 'eu') dynamically resolves only APP_URL, persists EU region known"]
D2N3["component: check_key and Roboflow() use stale API_URL constant known"]
D2N4["failure: EU key submitted to US API endpoint until process restart known"]
D2N1 -->|"API_URL already bound before login runs known"| D2N2
D2N2 -->|"login does not re-resolve API_URL known"| D2N3
D2N3 -->|"stale US endpoint used with EU credentials known"| D2N4
Scope: roboflow/init.py import-time binding vs runtime login
Assumptions: Restarting the process would re-import and re-bind API_URL to the persisted EU region
Unresolved: Exact line where API_URL is imported by value not isolated beyond finding anchor; check_key implementation details not shown in diff
How does an invalid ROBOFLOW_REGION corrupt --json output on stderr?
_normalize_region prints a plain-text warning to stderr during import-time init; _status then writes its JSON error to the same stream, so consumers receive a warning line followed by JSON instead of one parseable document.
flowchart LR
D3N1["source: ROBOFLOW_REGION set to invalid value; --json mode requested known"]
D3N2["component: _normalize_region prints plain-text warning to stderr during import-time init known"]
D3N3["component: _status writes JSON error to same stderr stream known"]
D3N4["failure: Consumer receives warning line + JSON, not one parseable document known"]
D3N1 -->|"invalid region triggers unconditional warning known"| D3N2
D3N2 -->|"warning precedes JSON on same stderr stream known"| D3N3
D3N3 -->|"mixed-format stderr breaks JSON parsing known"| D3N4
_Scope: stderr stream shared between _normalize_region warning and status JSON output
_Assumptions: No credentials are present in the scenario, so status produces an error JSON rather than a success payload
_Unresolved: Exact status output line in auth.py not isolated; stdout vs stderr separation for other commands not analyzed
Reviewed commit 6ad02bcf67357433fb2348dfdce3659ba0f429fd.
| ``` | ||
|
|
||
| ### Using Roboflow EU | ||
|
|
There was a problem hiding this comment.
fix_before_merge · api · 92% — [sol-ultra] Public URL helpers still reject EU application URLs
The new README states that the same package supports the EU platform, but the supplied complete roboflow/init.py still permits only literal app.roboflow.com or universe.roboflow.com URLs in both load_model and download_dataset. An app.roboflow.eu model or dataset URL is rejected before parsing, even when the effective region is EU.
Why this matters — regional-url-compatibility: Public helpers that accept platform URLs must recognize the corresponding hostname of every region the package claims to support.
| # the module-level URL constants were resolved when roboflow was imported. | ||
| from roboflow.config import resolve_url | ||
|
|
||
| app_url = resolve_url("APP_URL", region=normalized_region) |
There was a problem hiding this comment.
fix_before_merge · bug · 96% — [sol-ultra] Programmatic EU login leaves the SDK on its import-time API endpoint
login(region="eu") dynamically resolves only APP_URL and persists the region after authentication. The same module imported API_URL by value before login, and check_key plus subsequent Roboflow operations continue using that stale constant. Thus a notebook or process that calls roboflow.login(region="eu") and then constructs roboflow.Roboflow() can submit the newly stored EU key to the US API until the process is restarted.
Why this matters — runtime-region-consistency: Changing authentication region within a process must update every endpoint used by the immediately following SDK operations.
| return | ||
|
|
||
| roboflow.login(workspace=workspace_id, force=force) | ||
| roboflow.login(workspace=workspace_id, force=force, region=region) |
There was a problem hiding this comment.
fix_before_merge · bug · 98% — [sol-ultra] The advertised login command cannot switch an existing user to EU
_login validates --region, but its interactive branch returns early whenever the config file already exists and --force is false. Consequently, an existing US user running the documented roboflow auth login --region eu never reaches the changed call, opens no EU authentication flow, and does not persist EU; it only reports that the user is already logged in.
Why this matters — explicit-option-must-take-effect: A newly documented explicit selection option must not silently become a no-op in the primary migration scenario.
| effective_region = get_effective_region() | ||
| api_url = resolve_url("API_URL") | ||
| app_url = resolve_url("APP_URL") | ||
| warning = ( |
There was a problem hiding this comment.
fix_before_merge · bug · 65% — [fable-xhigh] set-region always warns about stored credentials, even when none exist or region is unchanged
roboflow/cli/handlers/auth.py:360-364 builds warning unconditionally and emits it in both text (line 379) and JSON (line 377). previous_region (line 351) is the effective region before the save. Running auth set-region us when already on us, or on an empty config (exactly what tests/cli/test_auth_region.py:156 does before any login), tells the user 'Stored credentials were issued by the previously configured US platform ... login --force ... may be needed', which is false in both cases. When ROBOFLOW_REGION is set in the environment, 'previously configured' also reflects the env value rather than the saved one. For --json consumers the always-present warning key becomes noise. Gate the message on previous_region != region and config.get('workspaces') and only include the key when it applies; test_set_region_then_status_shows_eu_endpoints (which asserts the warning) would still pass since it has stored credentials and switches us->eu.
Why this matters — warn-only-when-actionable: A warning that fires regardless of state is misleading in the false-positive cases and trains users and agents to ignore it when it matters.
| } | ||
|
|
||
| _UNSET = object() | ||
| _WARNED_UNKNOWN_REGIONS: set[str] = set() |
There was a problem hiding this comment.
fix_before_merge · compat · 99% — [sol-ultra] New annotations make the package unimportable on advertised Python versions
roboflow/config.py has no postponed-annotation import, but now evaluates set[str] at module import and later evaluates str | None in resolve_url. set[str] fails on Python 3.8, while str | None fails on Python 3.8 and 3.9. The supplied README still advertises Python 3.8 or higher, so importing roboflow will fail before any region logic runs on supported installations.
Why this matters — supported-python-importability: Code imported at package initialization must remain syntactically and semantically importable on every advertised Python version.
| normalized_region = region.strip().lower() if isinstance(region, str) else "" | ||
| if normalized_region in REGION_URL_DEFAULTS: | ||
| return normalized_region | ||
|
|
There was a problem hiding this comment.
fix_before_merge · api · 95% — [sol-ultra] Invalid regions corrupt JSON-mode stderr
_normalize_region prints a plain-text warning directly to stderr, including during import-time URL initialization. For a command such as ROBOFLOW_REGION=bogus roboflow --json auth status with no credentials, _status subsequently writes its JSON error to the same stderr stream. Consumers receive a warning line followed by JSON rather than one parseable JSON document.
Why this matters — json-output-integrity: Machine-readable mode must not mix unstructured diagnostics into the stream designated for structured errors.
Description
One switch points the SDK + CLI at the Roboflow EU data-residency platform (Linear INF-314). Three ways to set it:
Sibling PR for
inference-cli: roboflow/inference#2701.How it works
config.pygains a region registry (usdefault /eu) and runtime resolvers (get_effective_region(),resolve_url()). Per-URL precedence: explicit env var > explicit config-file key > region default > existing US default — soAPI_URL-style overrides (the staging escape hatch) keep working unchanged.api/app→api/app.roboflow.eu, detection + instance-seg →serverless.roboflow.eu, dedicated deployments →eu.roboflow.cloud. Universe and semantic-seg stay on global.com— no EU instance exists.login()resolves the app host at call time (an import-time constant can't see a--regionflag passed in the same process) and now merges the prior config on forced re-login instead of clobbering it, so region + URL overrides survive.auth login --api-key --region euvalidates the key against the EU API;auth status(text +--json) reports region and effective URLs; the hidden rootroboflow loginalias forwards--region.us(never raise at import).With no region set anywhere, every URL constant is byte-identical to today — guarded by a test.
Testing
python -m unittest), including 16 new ones: back-compat guard, precedence matrix, EU mapping, login persistence/merge (incl.--force), CLI paths, invalid-region handlingmake check_code_quality(ruff + mypy) cleanapp.roboflow.eu,--api-keypath validates againstapi.roboflow.eu, error behavior identical to USDocs
CLI-COMMANDS.md: region flag,set-region,ROBOFLOW_REGION, endpoint tableREADME.md: "Using Roboflow EU" section