Skip to content

Easy EU region selection: ROBOFLOW_REGION, auth login --region, auth set-region - #513

Open
imbgar-roboflow wants to merge 8 commits into
mainfrom
brandongarcia/inf-314-cli-eu-region
Open

imbgar-roboflow wants to merge 8 commits into
mainfrom
brandongarcia/inf-314-cli-eu-region

Conversation

@imbgar-roboflow

@imbgar-roboflow imbgar-roboflow commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

One switch points the SDK + CLI at the Roboflow EU data-residency platform (Linear INF-314), and a second selects staging, with the same ROBOFLOW_REGION × ROBOFLOW_ENVIRONMENT matrix as roboflow/inference#2701.

roboflow auth login --region eu    # browser auth flow opens/validates against https://app.roboflow.eu/auth-cli
roboflow auth set-region eu        # persistent config setting, shown in 'roboflow auth status'
ROBOFLOW_REGION=eu ...             # env var, for CI/scripts
ROBOFLOW_ENVIRONMENT=staging ...   # roboflow.one (US) / roboflow-eu.one (EU)

How it works

  • config.py resolves every default URL from a (region, environment) matrix. Precedence: explicit URL env var > explicit config-file key > region/environment default > US production default, so API_URL-style overrides keep working unchanged.
  • Hosts mirror the platform's packages/shared/environments/* and the dedicated-deployment ingresses; all verified live:
Service US prod US staging EU prod EU staging
API / app api/app.roboflow.com api/app.roboflow.one api/app.roboflow.eu api/app.roboflow-eu.one
Serverless (detection, instance-seg, classification, keypoint, VLM) serverless.roboflow.com serverless.roboflow.one serverless.roboflow.eu serverless.roboflow-eu.one
Universe universe.roboflow.com universe.roboflow.one universe.roboflow.com universe.roboflow.one
Dedicated deployments roboflow.cloud staging.roboflow.cloud eu.roboflow.cloud eu.staging.roboflow.cloud
Semantic segmentation segment.roboflow.com lambda-semantic-segmentation.staging.roboflow.com refused refused
  • Hosted inference stays in the selected region: classification, keypoint and VLM models no longer hardcode serverless.roboflow.com (new SERVERLESS_URL key), and webcam() defaults to the region's host. Semantic segmentation has no EU deployment, so in EU it raises instead of sending images to the US, unless SEMANTIC_SEGMENTATION_URL is set explicitly. The check and the request use the same URL lookup.
  • login() resolves the app host at call time and rebinds the import-time URL constants, so the running process switches region immediately. A forced re-login keeps the old credentials until the token exchange succeeds.
  • A config counts as logged in only when it holds credentials. A region-only config from set-region goes through normal login.
  • The platform that issued the credentials is stored as ROBOFLOW_CREDENTIALS_REGION, separately from the ROBOFLOW_REGION preference. Login re-authenticates when the two differ, and API-key login on the other platform replaces the old workspace keys instead of merging them.
  • An explicit --region / region= that an exported ROBOFLOW_REGION would override is refused before authenticating, so credentials and requests stay on one platform.
  • --api-key --region eu validates against the EU API. auth status (text and --json) reports region, environment and effective URLs. set-region warns only when stored credentials came from the other platform.
  • app.roboflow.eu and staging (app.roboflow.one, app.roboflow-eu.one, universe.roboflow.one) links are accepted by load_model / download_dataset.
  • Unknown region/environment values warn (not in --json mode) and fall back to us / prod; they never raise at import. ROBOFLOW_ENVIRONMENT accepts prod and staging; any other value warns and falls back to prod.

With nothing set, every URL constant is byte-identical to today, guarded by a test.

Testing

  • 1105 tests green on Python 3.10 and 3.13 (python -m unittest), including the full region × environment matrix, precedence, EU inference routing, semantic-segmentation refusal, login persistence/re-auth/credential provenance, CLI status/set-region, and invalid-value handling
  • make check_code_quality (ruff + mypy) clean
  • Live smoke (July) against real EU endpoints for both login paths; all matrix hosts re-verified live 2026-09-30

Docs

  • CLI-COMMANDS.md: region flag, set-region, ROBOFLOW_REGION, ROBOFLOW_ENVIRONMENT, endpoint table
  • README.md: "Using Roboflow EU" section

@imbgar-roboflow

Copy link
Copy Markdown
Contributor Author

CI note: the check_code_quality failures here are unrelated to this diff — ruff 0.16.0 (released today) stabilized the CPY copyright rules, which select = ["ALL"] + unpinned CI ruff now enforce repo-wide. Main is red with the same 72 CPY001 errors. One-line fix in #514; will rebase/rerun once that merges.

imbgar-roboflow

This comment was marked as outdated.

…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
- auth login --region <other> now re-authenticates an existing user instead
  of returning "Already logged in" (stored creds belong to their region)
- roboflow.login(region=...) rebinds the URL constants other modules
  imported at import time, so the SDK talks to the new region immediately
- load_model / download_dataset accept app.roboflow.eu URLs
- set-region only warns about stored credentials when switching region
  with credentials present
- unknown-region fallback is a RegionWarning (warnings module), suppressed
  in --json mode; auth status reports it as a region_warning field instead
- help-output test strips ANSI so it passes when Rich forces colors in CI
- README: minimum Python is 3.10 (matches python_requires)
@imbgar-roboflow
imbgar-roboflow force-pushed the brandongarcia/inf-314-cli-eu-region branch from 6ad02bc to 031c4d3 Compare September 29, 2026 19:18
Classification, keypoint and VLM models hardcoded serverless.roboflow.com, so
'roboflow infer' in the EU region sent images to the US. Route them through a
region-aware SERVERLESS_URL, default webcam() to the region's detection host,
and refuse hosted semantic segmentation in EU (no EU deployment) unless
SEMANTIC_SEGMENTATION_URL is set explicitly.
URL defaults now come from a (region, environment) matrix, so
ROBOFLOW_REGION=eu ROBOFLOW_ENVIRONMENT=staging reaches EU staging here as it
does in inference instead of EU production. Every service with a staging
deployment follows it (API, app, serverless, Universe, dedicated deployments,
US semantic segmentation); EU semantic segmentation still refuses. Explicit
URL overrides keep precedence. Unknown values warn and fall back to prod
rather than treating any non-prod value as staging. 'auth status' reports
the environment.
@imbgar-roboflow

Copy link
Copy Markdown
Contributor Author

/mina superduper sugg

EU production async-serverless deployments set ROBOFLOW_ENVIRONMENT=production.
Treat it as prod here, as the matching inference change does, so the value
never needs a warning or selects the wrong hosts.
Out of scope for this PR; the EU prod values are fixed at the source in
roboflow/async-serverless#461.
@imbgar-roboflow

Copy link
Copy Markdown
Contributor Author

/jarbas review

@imbgar-roboflow imbgar-roboflow left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TL;DR

Needs work — 5 fixes before merge · 6 follow-ups.

Required before merge

  • Fix before merge: [astra] Allow authentication when the config contains only a region — roboflow/cli/handlers/auth.py:247
    Exact replacement attached as an inline suggestion.
  • Fix before merge: [astra] Validate the semantic endpoint that will actually receive the image — roboflow/models/semantic_segmentation.py:43
    Exact replacement attached as an inline suggestion.
  • Fix before merge: [astra] Keep authentication and subsequent SDK requests on the same platform — roboflow/__init__.py:145
    Exact replacement attached as an inline suggestion.
  • Fix before merge: [astra] Store credential provenance separately from the region preference — roboflow/cli/handlers/auth.py:135
    Exact replacement attached as an inline suggestion.
  • Fix before merge: [fable-5.1] set-region writes a credential-less config that makes login() and auth login report already logged in — roboflow/__init__.py:94
    Exact replacement attached as an inline suggestion.

Follow-ups

  • [astra] Accept the staging platform URLs introduced by the endpoint matrix — roboflow/__init__.py:179
  • [astra] Resolve the login host after handling an unreadable saved config — roboflow/__init__.py:82
  • [fable-5.1] Implicit forced re-login now deletes stored credentials before the token exchange can fail — roboflow/__init__.py:108
  • [fable-5.1] Region of the issuing platform is only recorded when passed explicitly; env-driven and env-overridden logins diverge — roboflow/__init__.py:134
  • [fable-5.1] Data-residency refusal is opt-in per call site; the EU constant still points at the US host — roboflow/config.py:184
  • [fable-5.1] No test covers set-region followed by login — tests/cli/test_auth_region.py:106
How this verdict was reached
  • Jury: superduper-trio (reviewer) · superduper-adversarial (cross_examiner) · superduper-glm (adjudicator)
  • Reviewer seats: astra · fable-5.1 · kimi-k3
  • Work: 20 raised · 20 accepted · 0 refuted · 0 unverifiable · 5 blocking
  • Verifier: ran on blocking candidates
  • Withheld: 8 take-or-leave notes (below the public evidence bar)
  • Coverage: superduper-trio reviewed 16/16 files and 46/46 hunks · superduper-adversarial independently reviewed 16/16 files and 46/46 hunks

Failure architecture

Why does auth set-region eu on a fresh install make the next auth login report an existing session without credentials?

set-region saves a config holding only ROBOFLOW_REGION; both CLI and SDK login paths decide logged-in status purely from config-file existence, so a credential-less file is treated as an authenticated session and login never runs.

flowchart LR
  D1N1["source: Fresh install: user runs auth set-region eu known"]
  D1N2["component: Config file written containing ROBOFLOW_REGION but no credentials known"]
  D1N3["decision: Login path decides logged-in status via os.path.isfile(conf_location) only known"]
  D1N4["failure: CLI prints 'Already logged in' with workspace 'unknown'; no authentication occurs known"]
  D1N5["outcome: roboflow.login() prints the same message and returns None unauthenticated known"]
  D1N1 -->|"_set_region calls _save_config even when config has no workspaces known"| D1N2
  D1N2 -->|"Config file exists on disk, so the existence-only check passes known"| D1N3
  D1N3 -->|"File existence is misread as a usable session; region match also suppresses force known"| D1N4
  D1N2 -->|"roboflow.login() applies the same existence-only check to the region-only config known"| D1N5
Loading

Scope: CLI and SDK login decisions after set-region writes a credential-less config (roboflow/cli/handlers/auth.py:247,368-372; roboflow/init.py:94,99)
Assumptions: The trace verifications cited in the adjudications were executed at head and reproduce the described behavior; No other code path forces reauthentication when the saved region already matches the requested one; The merge at init.py:99 only runs when force is True, as stated in P7
_Unresolved: Actual diff hunks were not visible to this pass; node truth rests on finding evidence and adjudication traces, not direct code inspection; Internal implementation lines of save_config and the isfile check beyond the cited anchors are unverified; Whether the new status test's unauthenticated classification was intended to gate a later login attempt is unresolved; Behavior of auth login --region eu when the saved region differs from the request is not traced here

How does a post-import SEMANTIC_SEGMENTATION_URL override send images to the US default endpoint despite the guard?

The guard reads the live override and permits prediction, but the model's self.api_url was fixed from an imported constant at construction, so the actual request still targets segment.roboflow.com and the residency intent is bypassed.

flowchart LR
  D2N1["source: User sets os.environ 'SEMANTIC_SEGMENTATION_URL' to a custom endpoint after import known"]
  D2N2["component: Guard reads SEMANTIC_SEGMENTATION_URL from current configuration known"]
  D2N3["component: Model __init__ built self.api_url from an imported constant (US default) known"]
  D2N4["decision: Guard sees the new override and permits prediction known"]
  D2N5["failure: Image is submitted to segment.roboflow.com while the guard believed a custom endpoint was in force known"]
  D2N6["outcome: Added test reproduces the setup but never asserts the request destination known"]
  D2N1 -->|"Override is visible in current configuration at guard time known"| D2N2
  D2N2 -->|"Non-default override present, so the guard allows prediction known"| D2N4
  D2N3 -->|"self.api_url was captured at construction and ignores the later override known"| D2N5
  D2N4 -->|"Permitted prediction submits the image to the static api_url known"| D2N5
  D2N5 -->|"Test mocks parent prediction, so the wrong destination goes undetected known"| D2N6
Loading

Scope: Semantic segmentation request URL selection between the live guard and the statically captured api_url (roboflow/models/semantic_segmentation.py:40-46)
Assumptions: The imported constant resolves to segment.roboflow.com under EU defaults, as stated in the finding; TrainedModel exhibits the same live-guard versus static-returned-URL mismatch described in P2 but is not separately traced; The added test mocks the parent prediction call, so no real request is issued in the test itself
Unresolved: Diff text for semantic_segmentation.py was not visible to this pass; truth rests on the finding's trace verification; Exact guard and init implementation lines beyond the cited anchor are unverified; Whether other model classes share the imported-constant URL pattern is unresolved; The concrete line in tests/test_region_models.py that mocks the parent prediction is approximated, not pinned

Why do authentication and subsequent SDK requests end up on different platforms after an explicit-region login?

login() stores credentials using the explicit region argument, but refresh_region_urls() re-resolves without it so the environment wins; provenance is also read from the mutable region preference, so keys survive platform switches.

flowchart LR
  D3N1["source: ROBOFLOW_REGION us exported, no saved credentials; user calls roboflow.login(region 'eu') known"]
  D3N2["component: login resolves the app URL with the explicit eu argument and stores EU credentials known"]
  D3N3["component: refresh_region_urls() resolves without the explicit argument; environment wins, API_URL stays US known"]
  D3N4["failure: Credential/backend mismatch: EU key submitted to US API; US key retained under EU label known"]
  D3N5["component: _stored_region() reads the mutable ROBOFLOW_REGION preference as the credentials' issuing region known"]
  D3N6["outcome: After US login, set-region eu then login --region eu skips reauth; US key kept and config relabeled EU known"]
  D3N1 -->|"Explicit eu argument drives app URL resolution and credential storage known"| D3N2
  D3N2 -->|"refresh_region_urls() runs without the explicit region argument known"| D3N3
  D3N3 -->|"API_URL remains US while the saved key is EU; Roboflow() submits it to the US API known"| D3N4
  D3N2 -->|"Issuing platform is recorded only via the mutable region preference known"| D3N5
  D3N5 -->|"set-region rewrites the preference without changing credentials, so reauth is skipped known"| D3N6
  D3N6 -->|"US-issued key remains selectable and paired with the effective request target assumed"| D3N4
Loading

Scope: Region resolution and credential provenance across login, refresh_region_urls, and set-region (roboflow/init.py:145; roboflow/cli/handlers/auth.py:135)
Assumptions: The trace verification for P3 ran at head with ROBOFLOW_REGION=us exported and no saved credentials; The CLI API-key login path exhibits the equivalent mismatch on the next invocation, as stated but not separately traced; P4's mechanism is mechanically checkable against the diff per its adjudication, though no executing trace was cited for it
Unresolved: Diff hunks were not visible to this pass; P3 truth rests on its trace verification and P4 on adjudicated diff structure; Exact lines of refresh_region_urls and check_key beyond the cited anchor are unverified; Whether set-region's existing conflict reporting covers the login-time conflict is unresolved; The EU workspace merge into the US workspace map in the API-key path is described but not traced to a line

Reviewed commit bd0b6a5f3ed1124a72574696e6aa6870d579e6f3.


quota · @imbgar-roboflow · superduperduper 109/3 · superduper 9/6 · mina 71/15

Comment thread roboflow/__init__.py Outdated
Comment thread roboflow/__init__.py
Comment thread roboflow/cli/handlers/auth.py Outdated
Comment thread roboflow/cli/handlers/auth.py Outdated
Comment thread roboflow/models/semantic_segmentation.py Outdated
…their platform

- A config holding only a region (from auth set-region) no longer counts as a
  session in roboflow.login() or roboflow auth login.
- Record the platform that issued the credentials (ROBOFLOW_CREDENTIALS_REGION)
  separately from the ROBOFLOW_REGION preference; re-authenticate when they
  differ, and drop the other platform's keys on API-key login.
- Refuse an explicit region that an exported ROBOFLOW_REGION would override,
  so credentials and requests stay on one platform.
- Forced login keeps the old credentials until the token exchange succeeds.
- Semantic segmentation resolves its destination with the same lookup as the
  residency check, so an explicit override is where the image actually goes.
- Accept staging app/universe URLs in load_model and download_dataset.
@imbgar-roboflow

Copy link
Copy Markdown
Contributor Author

Follow-ups from the 2026-09-30 review (reviewed at bd0b6a5). These had no inline threads, so I'm answering them here. The code changes are in 1bc120c.

Fixed

  • Accept the staging platform URLs (__init__.py:179): load_model and download_dataset now also accept app.roboflow.one, app.roboflow-eu.one and universe.roboflow.one. Test: test_load_model_accepts_staging_app_urls.
  • Forced re-login deletes credentials before the token exchange (__init__.py:108): the os.remove is gone. The existing config is loaded, and only overwritten after the exchange returns credentials. Test: test_failed_forced_login_keeps_existing_credentials (a 500 from the exchange leaves the file untouched).
  • Issuing region only recorded when passed explicitly (__init__.py:134): both login paths now always write ROBOFLOW_CREDENTIALS_REGION with the region they authenticated against, whether explicit or the effective one from the environment or config. ROBOFLOW_REGION is still written only for an explicit choice, so a login driven by the environment doesn't pin the preference. Test: test_environment_region_is_recorded_as_credentials_region.
  • No test covers set-region followed by login (tests/cli/test_auth_region.py:106): added for a fresh install (test_login_after_set_region_on_fresh_install_authenticates) and for existing US credentials (test_set_region_does_not_relabel_existing_credentials), plus the SDK equivalents in tests/test_login_region.py.

Partly addressed

  • Residency refusal is opt-in per call site; the EU constant still points at the US host (config.py:184):
    • I agree the guard shouldn't depend on each caller remembering it. Both consumers of SEMANTIC_SEGMENTATION_URL (SemanticSegmentationModel, TrainedModel) now go through resolve_available_url(), which checks residency and resolves in one step. Nothing in the package reads the module constant anymore.
    • I kept the constant resolving to the US default. roboflow.config resolves every URL at import, so making it raise or hold a sentinel would break import roboflow for every EU user, not just semantic segmentation. It stays importable only for backward compatibility.

Not changing

  • Resolve the login host after handling an unreadable saved config (__init__.py:82):
    • I moved the host resolution after the config load anyway, but it doesn't change anything. get_conditional_configuration_variable reads the config with an unguarded json.load during import roboflow, so a corrupt config already fails the import. Verified at HEAD: import roboflow with a {bad config raises JSONDecodeError, the same as main.
    • login(force=True) can't be reached with an unreadable config. Making import tolerate corrupt configs is a behavior change for main and out of scope here.

Local results: 1105 tests pass on Python 3.10 and 3.13; ruff and mypy are clean. The 18 new tests fail against bd0b6a5 and pass at 1bc120c.

@imbgar-roboflow imbgar-roboflow left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Inline responses to the six follow-ups from the 2026-09-30 review.

Comment thread roboflow/__init__.py Outdated
Comment thread roboflow/__init__.py Outdated
Comment thread roboflow/__init__.py Outdated
Comment thread roboflow/__init__.py
Comment thread roboflow/config.py
Comment thread tests/cli/test_auth_region.py

This branch has not been deployed

No deployments
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