From 468a2e33e6238d439f98b115ae0db1906718a710 Mon Sep 17 00:00:00 2001 From: Estella Song Date: Thu, 21 May 2026 11:57:55 -0700 Subject: [PATCH 1/4] docs(python): document `environment=` kwarg on `arcjet()` / `arcjet_sync()` Covers the pydantic-settings escape hatch added in arcjet/arcjet-py#127: pass `environment=settings.ARCJET_ENV` so dev-mode gating (loopback IP fallback, `X-Arcjet-Ip` override, dev-mode timeout) works when the config library doesn't propagate `.env` into `os.environ`. Co-Authored-By: Claude Opus 4.7 --- src/content/docs/environment.mdx | 8 +++++ src/content/docs/reference/python.mdx | 49 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/content/docs/environment.mdx b/src/content/docs/environment.mdx index 96a5cbc4..d7613608 100644 --- a/src/content/docs/environment.mdx +++ b/src/content/docs/environment.mdx @@ -46,6 +46,14 @@ and production mode otherwise. `NODE_ENV` is a convention used in Node.js apps — the Python SDK reads `ARCJET_ENV` only. +If you load configuration with a library that does not propagate values into +`os.environ` (for example `pydantic-settings`, which reads `.env` into a typed +`BaseSettings` object instead), `ARCJET_ENV` will appear unset to the Python +SDK and dev mode will silently fail to engage. Pass the value explicitly via +the `environment=` kwarg on `arcjet()` / `arcjet_sync()` instead — see the +Python SDK reference +for an example. + In development, the timeout for connecting to Cloud API is increased. In production, the timeout is quicker. This is because in production your app typically runs close to an Arcjet server, diff --git a/src/content/docs/reference/python.mdx b/src/content/docs/reference/python.mdx index fadcb3c6..5027dce2 100644 --- a/src/content/docs/reference/python.mdx +++ b/src/content/docs/reference/python.mdx @@ -129,6 +129,13 @@ The optional fields are: This is useful if you are behind a load balancer or proxy that sets the client IP address in a header. See [Load balancers & proxies](#load-balancers--proxies) below for an example. +- `environment` (`str | None`) — Development/production mode override for + callers whose config library does not write to `os.environ` (e.g. + `pydantic-settings`). When `None` (default), the SDK falls back to reading + the + `ARCJET_ENV` + environment variable. See [Pydantic-settings users](#pydantic-settings-users) + below. @@ -201,6 +208,48 @@ for more info. The `ARCJET_KEY` environment variable is not read automatically and must be passed explicitly via the `key` argument. +#### Pydantic-settings users + +The SDK reads +`ARCJET_ENV` +via `os.getenv`, but +[`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) — +the de-facto FastAPI config pattern — intentionally does not push `.env` +values into `os.environ`. If you rely on it, the SDK will silently default +to production mode: loopback IPs are dropped and (with `fail_open=True`) +local requests return `ALLOW` with no dashboard visibility. + +Pass the value explicitly through the `environment` kwarg so the SDK gates +development behavior (loopback IP fallback, `X-Arcjet-Ip` header override, +and the longer dev-mode request timeout) on your settings object instead of +`os.environ`: + +```py +from arcjet import arcjet +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env") + + ARCJET_KEY: str + ARCJET_ENV: str = "production" + + +settings = Settings() + +aj = arcjet( + key=settings.ARCJET_KEY, + rules=[...], + environment=settings.ARCJET_ENV, +) +``` + +The same kwarg is accepted by `arcjet_sync()`. When `environment=None` (the +default) the SDK falls back to `os.getenv("ARCJET_ENV")`, so existing +callers that already set `ARCJET_ENV` in the process environment do not +need to change. + #### Load balancers & proxies If your application is behind a load balancer, Arcjet will only see the IP From b0332337a0f4e4c788ef32e6830e4fcfe9220411 Mon Sep 17 00:00:00 2001 From: Estella Song Date: Thu, 21 May 2026 12:24:15 -0700 Subject: [PATCH 2/4] docs(python): clarify `environment=` kwarg copy after customer-perspective audit - Lead the pydantic-settings subsection with the directive (use the kwarg) and explain the library design difference rather than framing it as silent SDK failure. - State accepted values (`"development"` / `"production"`) inline on the optional-fields bullet so readers don't have to dig into the subsection to learn what strings the kwarg accepts. - Trim the trailing migration note; keep the `arcjet_sync()` mention. - Mirror the tone change in `environment.mdx`. Co-Authored-By: Claude Opus 4.7 --- src/content/docs/environment.mdx | 11 ++++---- src/content/docs/reference/python.mdx | 36 +++++++++++---------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/content/docs/environment.mdx b/src/content/docs/environment.mdx index d7613608..f48d15f9 100644 --- a/src/content/docs/environment.mdx +++ b/src/content/docs/environment.mdx @@ -46,11 +46,12 @@ and production mode otherwise. `NODE_ENV` is a convention used in Node.js apps — the Python SDK reads `ARCJET_ENV` only. -If you load configuration with a library that does not propagate values into -`os.environ` (for example `pydantic-settings`, which reads `.env` into a typed -`BaseSettings` object instead), `ARCJET_ENV` will appear unset to the Python -SDK and dev mode will silently fail to engage. Pass the value explicitly via -the `environment=` kwarg on `arcjet()` / `arcjet_sync()` instead — see the +If you load configuration with a library that doesn't propagate values into +`os.environ` (e.g. `pydantic-settings`, which loads `.env` into a typed +`BaseSettings` object rather than writing values back to the process +environment), `ARCJET_ENV` won't reach the Python SDK through `os.getenv`. +Pass the value explicitly via the `environment=` kwarg on `arcjet()` / +`arcjet_sync()` — see the Python SDK reference for an example. diff --git a/src/content/docs/reference/python.mdx b/src/content/docs/reference/python.mdx index 5027dce2..02fef971 100644 --- a/src/content/docs/reference/python.mdx +++ b/src/content/docs/reference/python.mdx @@ -129,13 +129,13 @@ The optional fields are: This is useful if you are behind a load balancer or proxy that sets the client IP address in a header. See [Load balancers & proxies](#load-balancers--proxies) below for an example. -- `environment` (`str | None`) — Development/production mode override for - callers whose config library does not write to `os.environ` (e.g. - `pydantic-settings`). When `None` (default), the SDK falls back to reading +- `environment` (`str | None`) — Explicit development/production mode + (`"development"` or `"production"`). When `None` (default), falls back to the `ARCJET_ENV` - environment variable. See [Pydantic-settings users](#pydantic-settings-users) - below. + environment variable. Pass this when your config library doesn't propagate + `.env` into `os.environ` (e.g. `pydantic-settings`). See + [Pydantic-settings users](#pydantic-settings-users) below. @@ -210,19 +210,16 @@ passed explicitly via the `key` argument. #### Pydantic-settings users -The SDK reads +If you use +[`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/), +pass `ARCJET_ENV` -via `os.getenv`, but -[`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) — -the de-facto FastAPI config pattern — intentionally does not push `.env` -values into `os.environ`. If you rely on it, the SDK will silently default -to production mode: loopback IPs are dropped and (with `fail_open=True`) -local requests return `ALLOW` with no dashboard visibility. - -Pass the value explicitly through the `environment` kwarg so the SDK gates -development behavior (loopback IP fallback, `X-Arcjet-Ip` header override, -and the longer dev-mode request timeout) on your settings object instead of -`os.environ`: +through the `environment=` kwarg. By design, pydantic-settings loads `.env` +into a typed `BaseSettings` object rather than writing values back to +`os.environ`, so the SDK — which reads `ARCJET_ENV` via `os.getenv` — won't +pick up the value through that channel. Without the kwarg, the SDK defaults +to production, so dev-mode behavior (loopback IP fallback, `X-Arcjet-Ip` +header override, and the longer dev-mode request timeout) won't engage. ```py from arcjet import arcjet @@ -245,10 +242,7 @@ aj = arcjet( ) ``` -The same kwarg is accepted by `arcjet_sync()`. When `environment=None` (the -default) the SDK falls back to `os.getenv("ARCJET_ENV")`, so existing -callers that already set `ARCJET_ENV` in the process environment do not -need to change. +`arcjet_sync()` accepts the same kwarg. #### Load balancers & proxies From b265b4f0649140dc5da92507ae7e2a8807d0dcc9 Mon Sep 17 00:00:00 2001 From: Estella Song Date: Thu, 21 May 2026 12:40:49 -0700 Subject: [PATCH 3/4] docs(python): trim environment kwarg copy - Drop the dev-mode behavior enumeration from the pydantic-settings subsection; the cross-link to `environment.mdx#arcjet-env` carries that detail. - Compact the pointer paragraph in `environment.mdx` to a single line so the page stays a concept reference rather than absorbing Python-SDK-specific mechanism notes. Co-Authored-By: Claude Opus 4.7 --- src/content/docs/environment.mdx | 12 ++++-------- src/content/docs/reference/python.mdx | 3 +-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/content/docs/environment.mdx b/src/content/docs/environment.mdx index f48d15f9..a69fb37e 100644 --- a/src/content/docs/environment.mdx +++ b/src/content/docs/environment.mdx @@ -46,14 +46,10 @@ and production mode otherwise. `NODE_ENV` is a convention used in Node.js apps — the Python SDK reads `ARCJET_ENV` only. -If you load configuration with a library that doesn't propagate values into -`os.environ` (e.g. `pydantic-settings`, which loads `.env` into a typed -`BaseSettings` object rather than writing values back to the process -environment), `ARCJET_ENV` won't reach the Python SDK through `os.getenv`. -Pass the value explicitly via the `environment=` kwarg on `arcjet()` / -`arcjet_sync()` — see the -Python SDK reference -for an example. +Python SDK users: if your config library doesn't propagate `.env` into +`os.environ` (e.g. `pydantic-settings`), pass `ARCJET_ENV` via the +`environment=` kwarg on `arcjet()` / `arcjet_sync()` — see the +Python SDK reference. In development, the timeout for connecting to Cloud API is increased. In production, the timeout is quicker. diff --git a/src/content/docs/reference/python.mdx b/src/content/docs/reference/python.mdx index 02fef971..5a805af0 100644 --- a/src/content/docs/reference/python.mdx +++ b/src/content/docs/reference/python.mdx @@ -218,8 +218,7 @@ through the `environment=` kwarg. By design, pydantic-settings loads `.env` into a typed `BaseSettings` object rather than writing values back to `os.environ`, so the SDK — which reads `ARCJET_ENV` via `os.getenv` — won't pick up the value through that channel. Without the kwarg, the SDK defaults -to production, so dev-mode behavior (loopback IP fallback, `X-Arcjet-Ip` -header override, and the longer dev-mode request timeout) won't engage. +to production mode. ```py from arcjet import arcjet From 3946e115a55f8566731d47131f5ed6c1c7c7d350 Mon Sep 17 00:00:00 2001 From: Estella Song Date: Thu, 21 May 2026 13:05:24 -0700 Subject: [PATCH 4/4] docs(python): default ARCJET_ENV to "development" in pydantic-settings example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The example is illustrating the exact case where customers hit the issue — local dev. Defaulting to "development" matches that intent and makes the wiring testable without setting the value in `.env`. Co-Authored-By: Claude Opus 4.7 --- src/content/docs/reference/python.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/reference/python.mdx b/src/content/docs/reference/python.mdx index 5a805af0..10603d43 100644 --- a/src/content/docs/reference/python.mdx +++ b/src/content/docs/reference/python.mdx @@ -229,7 +229,7 @@ class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env") ARCJET_KEY: str - ARCJET_ENV: str = "production" + ARCJET_ENV: str = "development" settings = Settings()