Skip to content

yggdrasil.environ.userinfo

userinfo

UserInfo

UserInfo(
    hostname: str = "",
    *,
    _key: Any = ...,
    _email: Any = ...,
    _first_name: Any = ...,
    _last_name: Any = ...,
    _product: Any = ...,
    _product_version: Any = ...,
    singleton_ttl: Any = ...
)

Bases: Singleton

Snapshot of the user identity for the current process.

hostname is populated eagerly (socket.gethostname() is cheap and answers reliably without I/O fan-out). Every other identity field is a lazy, memoized property:

  • key / email consult the Databricks notebook context (free, in-process) and AWS-managed env vars before shelling out to whoami / whoami /UPN or calling the Databricks IAM SDK.
  • first_name / last_name are derived from email — free once email has been resolved, but pulling them eagerly forces email resolution.
  • product / product_version walk parent directories looking for pyproject.toml / setup.py and parse TOML.
  • cwd / url / git_url probe the runtime, dbutils context, and the on-disk .git tree.

All of those firing at construction would tax callers that only care about a subset (typical of request/response sanitization on the hot path). Each cache fires at most once per instance, and once a value is resolved — or supplied via :meth:from_struct_dict or constructor kwargs — it persists for the life of the instance.

:class:Singleton integration:

  • The constructor caches one instance per (cls, hostname, key, email, first/last name, product, product version). Two UserInfo.current() calls collapse to the same instance; :meth:with_email / :meth:from_struct_dict produce different identities and live as separate instances under the same hostname.
  • Per-process derived caches (_cwd_cache / _url_cache / _git_url_cache) are listed in :attr:_TRANSIENT_STATE_ATTRS so cross-process pickle drops them; the receiver re-derives from its own context.

Construction:

  • :meth:current returns the singleton for the local hostname.
  • :meth:from_struct_dict rebuilds an instance from a wire payload, with every supplied field pre-populated so no resolution fires on the receiver.
  • The constructor accepts the underscore-prefixed cache fields directly — useful for tests and for :meth:with_email.

hash property

hash: int

xxh3_64 digest over (key, hostname, email, url, git_url) — int64.

cwd property

cwd: str

Resolve the current working directory lazily (memoized).

url property

url: URL | None

Compute URL (Databricks link or local:// for the cwd) lazily.

Result is memoized per instance — the resolution probes the Databricks runtime / dbutils context and does a path normalization, neither of which should fire repeatedly on the request hot path.

git_url property

git_url: URL | None

Compute the git remote URL lazily (walks parents of cwd).

Memoized — the walk hits the filesystem (HEAD, packed-refs, config) and UserInfo.git_url is read once per request in sanitization paths.

to_struct_dict

to_struct_dict() -> dict[str, Any]

Flatten into the dict shape that matches :data:USERINFO_STRUCT.

cwd / url / git_url are intentionally excluded — they're per-process derived values, not part of the wire contract; the receiver re-derives them from its own context. Reading the lazy properties here forces resolution and pins the result on the cache slots so subsequent reads are free.

from_struct_dict classmethod

from_struct_dict(value: Mapping[str, Any]) -> 'UserInfo'

Inverse of :meth:to_struct_dict — drops derived fields.

Wire values land directly in the lazy cache slots so the receiver never re-resolves them. hash is recomputed by the property; cwd / url / git_url are per-process and are ignored if present on the input — reconstructing them from the struct would defeat the lazy contract on the receiving side, which re-derives them locally.

to_singleton

to_singleton(ttl: Any = ...) -> 'Singleton'

Promote this instance into the per-class _INSTANCES cache.

Hot listing paths (iterdir / _ls / glob) build children with singleton_ttl=False so the bounded cache doesn't fill up with thousands of short-lived entries. When a caller decides one of those children is worth keeping around (handing it to a long-running worker, returning it from an API), :meth:to_singleton registers self into the cache so the next constructor call with the same key collapses to the same instance.

ttl defaults to the subclass's _SINGLETON_TTL (... = no caching, None = process lifetime, or a seconds count). When a different instance is already cached under this key, that pre-existing one wins and is returned unchanged — the cache is the source of truth.

invalidate_singleton

invalidate_singleton(remove_global: bool = True) -> None

Pop self from the per-class _INSTANCES cache.

Mutating ops on a Singleton-cached object (writes, deletes, schema invalidations on a Databricks table, put_object on an :class:S3Path) want to make sure the next caller asking for the same key gets a fresh build rather than collapsing onto this stale handle — that's what remove_global=True (the default) does. The pop is :meth:identity-guarded: only an entry that still points at self is removed, so a concurrent re-construction that already raced past this thread is left alone.

remove_global=False is a no-op. The keyword exists so subclass invalidators (invalidate_singleton, _invalidate_entity_tag_cache, …) can offer the same switch without branching at the call site.

get_user_info

get_user_info(*, refresh: bool = False) -> UserInfo

Return the cached :class:UserInfo for the current process.

Only hostname is resolved eagerly — every other field is a lazy property on the instance that fires once on first access and persists. refresh=True drops the cached singleton and rebuilds, which also abandons any previously memoized lazy fields (the new instance starts unresolved).

The cache is the per-class :attr:UserInfo._INSTANCES slot inherited from :class:Singleton — same primitive every other config-keyed singleton in the codebase uses.

normalize_abs_path_for_url

normalize_abs_path_for_url(path: str) -> str

Normalize an absolute filesystem path into a URL-safe POSIX string.

Examples::

/a//b///c              -> /a/b/c
C:\Users\me\proj    -> /C:/Users/me/proj
\\server\share\dir -> //server/share/dir