Source code for geeViz.eeAuth

"""Earth Engine multi-tenant auth proxy + client helpers.

A drop-in toolkit for running Earth Engine work behind a token-injecting
HTTP proxy. Lets a single Python process talk to EE on behalf of many
service accounts concurrently — bypassing the `ee.Initialize()` global-
credential limitation that normally forces process-per-tenant designs.

What "by default" gets you
--------------------------
**You usually don't need to touch this module directly.** Calling
``Map.view()`` in :mod:`geeViz.geeView` auto-starts the proxy on first
use. The flow is:

1. ``Map.view()`` → ``eeCreds.ensure_started("auto")``
2. :func:`~geeViz.eeAuth.eeCreds.EECreds.discover` finds whatever
   credentials are visible in the environment
   (``$GOOGLE_APPLICATION_CREDENTIALS``, the EE persistent file,
   ``gcloud`` ADC, env-var SAs).
3. If anything was discovered, a local ``uvicorn`` proxy is spawned in
   a daemon thread (or reused if already running).
4. ``ee.Initialize`` is pointed at the proxy and EE traffic routes
   through it for the rest of the process.

Single-credential users get this for free. Multi-credential workflows
register additional creds with
:meth:`~geeViz.eeAuth.eeCreds.EECreds.addCreds` and switch with
:meth:`~geeViz.eeAuth.eeCreds.EECreds.use` / ``with eeCreds.use(...)``.
Every browser tab that ``Map.view()`` opens is pinned to the tenant
that was current at the time of the call — subsequent ``use()``
switches in Python can't drift open tabs to a different credential.

Layout
------
- :mod:`geeViz.eeAuth.eeCreds`  — high-level ``eeCreds`` singleton & API
- :mod:`geeViz.eeAuth.registry` — lower-level env-var-driven SA cache
- :mod:`geeViz.eeAuth.tags`     — workload-tag construction (EE billing attribution)
- :mod:`geeViz.eeAuth.client`   — initialize the ``ee`` SDK to route through a proxy
- :mod:`geeViz.eeAuth.server`   — FastAPI proxy app (mountable in your own
                                   FastAPI app OR runnable standalone)

Typical use — multi-credential (when you want it)
-------------------------------------------------
::

    from geeViz.eeAuth import eeCreds
    import ee

    eeCreds.addCreds("/path/to/sa-prod.json", "prod")
    eeCreds.addCreds("/path/to/sa-training.json", "training")
    eeCreds.start()                  # spins up the local proxy

    eeCreds.use("prod")
    ee.Number(1).getInfo()           # routes through prod SA

    with eeCreds.use("training"):
        ee.Number(2).getInfo()       # routes through training SA
    # back to prod here

Typical use — running the proxy standalone (Cloud Run, Docker, etc.)
--------------------------------------------------------------------
::

    python -m geeViz.eeAuth --port 8888

Embedded in your FastAPI app::

    from fastapi import FastAPI
    from geeViz.eeAuth.server import build_proxy_router
    from geeViz.eeAuth import eeCreds

    eeCreds.discover()
    app = FastAPI()
    app.include_router(
        build_proxy_router(creds=eeCreds), prefix="/ee-api",
    )

The proxy resolves which SA to use for each request from (in order):

1. ``X-geeViz-Creds`` request header
2. ``?tenant=`` query string parameter
3. ``/ee-api/t/<tenant>/`` path-prefix (used by ``Map.view()`` to pin
   each browser tab to its tenant)
4. Default tenant (``GEE_SERVICE_ACCOUNT_B64`` env var, or the first
   registered credential in ``eeCreds``)

Add SAs by setting env vars matching ``GEE_<NAME>_SERVICE_ACCOUNT``
where NAME is a tenant id. The value is a base64-encoded SA JSON key.

Why this exists
---------------
The Earth Engine Python SDK stores credentials in module-level state via
``ee.Initialize()``. Two tenants can't run concurrently in one process
without racing each other's auth. By routing every REST call through a
local proxy that injects the right SA per request, you keep one Python
process, one ``ee.Initialize`` call, and get full multi-tenant
concurrency.
"""
# Public API re-exports
from .registry import (
    SARegistry,
    get_registry,
    reset_registry,
    DEFAULT_TENANT,
)
from .tags import (
    build_workload_tag,
    sanitize_workload_tag_part,
)
from .client import (
    initialize_via_proxy,
    tenant_context,
    set_tenant,
    reset_tenant,
    TenantAwareHttp,
    CURRENT_TENANT,
)
from .server import (
    build_proxy_router,
    create_proxy_app,
)
from .eeCreds import (
    eeCreds,
    EECreds,
)


[docs] def robust_init(*, verbose: bool = False, interactive: bool = True) -> dict: """Module-level convenience: ``eeCreds.robust_init(...)``. Centralizes the "get EE up and running, prefer the proxy, never silently fall through to gcloud ADC without saying so" bootstrap that ``geeViz.geeView`` and external callers both want. """ return eeCreds.robust_init(verbose=verbose, interactive=interactive)
__all__ = [ # High-level API (recommended for most users) "eeCreds", "EECreds", "robust_init", # Lower-level building blocks "SARegistry", "get_registry", "reset_registry", "DEFAULT_TENANT", # Tags "build_workload_tag", "sanitize_workload_tag_part", # Client-side EE init "initialize_via_proxy", "tenant_context", "set_tenant", "reset_tenant", "TenantAwareHttp", "CURRENT_TENANT", # Server-side proxy "build_proxy_router", "create_proxy_app", ]