geeViz.eeAuth.client

Client-side helpers for routing the Earth Engine Python SDK through a token-injecting proxy.

The pattern:

  1. Run a proxy server (see geeViz.eeAuth.server) that holds the SA credentials and substitutes the right bearer token per request based on a tenant header / query param.

  2. Tell the EE SDK to send all REST calls through that proxy instead of directly to Google. Pass anonymous credentials — the proxy supplies the real ones.

  3. Switch tenants on the client side by setting a ContextVar; the custom HTTP transport reads it and stamps the routing header on every outbound request.

That gives you full multi-tenant concurrency in a single Python process, which the bare EE SDK can’t do because ee.Initialize() stores credentials in module-global state.

Quick start

from geeViz.eeAuth import initialize_via_proxy, tenant_context
import ee

initialize_via_proxy("http://localhost:8888/ee-api")
# Now ee.X calls go through the proxy with the default tenant

with tenant_context("training"):
    ee.Image(1).getInfo()  # uses the training SA

Functions

initialize_via_proxy(proxy_url[, ...])

Initialize the Earth Engine SDK to route all REST calls through proxy_url.

reset_tenant(token)

Restore the tenant to what it was before the matching set_tenant call.

set_tenant(tenant)

Set the current tenant for subsequent EE calls in this context.

tenant_context(tenant)

Scoped tenant switch.

Classes

TenantAwareHttp([tenant_header])

httplib2.Http subclass that stamps the tenant header on every outbound request and strips whatever Authorization the SDK injected.

geeViz.eeAuth.client.set_tenant(tenant: str)[source]

Set the current tenant for subsequent EE calls in this context.

Returns a token that can be passed to reset_tenant to restore the previous value. Prefer tenant_context() for scoped use.

geeViz.eeAuth.client.reset_tenant(token) None[source]

Restore the tenant to what it was before the matching set_tenant call.

Parameters:

token – The token object returned by the paired set_tenant() call. Passing a token from a different scope raises ValueError.

geeViz.eeAuth.client.tenant_context(tenant: str)[source]

Scoped tenant switch.

with tenant_context("training"):
    ee.Image(1).getInfo()
# back to previous tenant here
class geeViz.eeAuth.client.TenantAwareHttp(tenant_header: str = 'X-geeViz-Creds')[source]

Bases: object

httplib2.Http subclass that stamps the tenant header on every outbound request and strips whatever Authorization the SDK injected.

Subclassed at first instantiation so httplib2 is only imported when actually used — keeps unit tests that mock the EE init path from needing the dependency. The header name is set per-instance so you can run multiple proxies with different conventions in the same process if you really need to.

Thread safety

httplib2.Http is NOT thread-safe — its per-host connection cache (self.connections) is a plain dict mutated from inside request() and socket.HTTPConnection objects hold per-instance socket state. When the EE SDK shares one transport across a ThreadPoolExecutor (as Map.testLayers() does with 8 workers), concurrent threads tear down each other’s sockets mid-request, surfacing as 'NoneType' object has no attribute 'close' and Windows WinError 10038/10057 socket errors.

Workaround: route each thread’s request() call to its OWN httplib2.Http instance stored in threading.local(). EE’s SDK only consults the transport for request() — it doesn’t reach into self.connections directly — so per-thread instances are a safe drop-in.

geeViz.eeAuth.client.initialize_via_proxy(proxy_url: str, tenant_header: str = 'X-geeViz-Creds', project: str | None = None) bool[source]

Initialize the Earth Engine SDK to route all REST calls through proxy_url.

Uses AnonymousCredentials since the proxy holds the real SA credentials. The SDK’s bearer-token header is stripped by TenantAwareHttp before reaching the proxy anyway.

Parameters:
  • proxy_url – Base URL of the EE proxy, e.g. "http://localhost:8888/ee-api". No trailing slash.

  • tenant_header – Header name the proxy expects for tenant routing. Default X-geeViz-Creds matches geeViz.eeAuth.server.

  • project – Placeholder project id passed to ee.Initialize (EE requires one but the proxy overrides per-tenant via x-goog-user-project). Default "ee-proxy-placeholder".

Returns:

True on success, False if init failed (caller should fall back to direct ee.Initialize or surface the error). Prints any underlying exception to stderr; doesn’t re-raise.