mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 22:26:53 +00:00
refactor(secrets): epoch timestamps, a named list request, and unit tests
`SecretInfo` carries `created_at_millis` / `updated_at_millis` as `i64` rather than RFC 3339 strings, matching `created_at_millis` elsewhere in the platform. A caller comparing two timestamps no longer parses anything, and the PyO3 layer hands Python integers instead of stringifying them. `updated_at_millis` is the only observable that a rotation landed -- no API returns a credential -- so it is worth being a number a caller can compare. `RemoteListSecretsRequest` is declared alongside its response instead of being an inline object built field by field, so a reader of one finds the other. `function.rs` gains unit tests for the pure parts that had none: canonical JSON sorts keys at every depth and leaves array order alone -- it is what the version hash is taken over, so both matter -- floats are refused at any depth, and the unknown-key check inspects only the level it is handed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UfmeJ533rQDnPBkMtjerV6
This commit is contained in:
co-authored by
Claude Opus 5
parent
2f74efbca1
commit
be7968cd51
@@ -2444,8 +2444,13 @@ class AsyncConnection(object):
|
||||
|
||||
async def describe_secret(self, name: str) -> SecretInfo:
|
||||
"""What this database records about a Secret. Never the value."""
|
||||
return SecretInfo.from_json(
|
||||
await self._inner.describe_secret(validate_secret_name(name))
|
||||
name, created_at_millis, updated_at_millis = await self._inner.describe_secret(
|
||||
validate_secret_name(name)
|
||||
)
|
||||
return SecretInfo(
|
||||
name=name,
|
||||
created_at_millis=created_at_millis,
|
||||
updated_at_millis=updated_at_millis,
|
||||
)
|
||||
|
||||
async def list_jobs(self) -> List[JobInfo]:
|
||||
|
||||
@@ -116,12 +116,12 @@ class SecretInfo:
|
||||
[DBConnection.describe_secret][lancedb.db.DBConnection.describe_secret].
|
||||
"""
|
||||
|
||||
__slots__ = ("_name", "_created_at", "_updated_at")
|
||||
__slots__ = ("_name", "_created_at_millis", "_updated_at_millis")
|
||||
|
||||
def __init__(self, name: str, created_at: str, updated_at: str):
|
||||
def __init__(self, name: str, created_at_millis: int, updated_at_millis: int):
|
||||
self._name = name
|
||||
self._created_at = created_at
|
||||
self._updated_at = updated_at
|
||||
self._created_at_millis = created_at_millis
|
||||
self._updated_at_millis = updated_at_millis
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@@ -129,35 +129,40 @@ class SecretInfo:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def created_at(self) -> str:
|
||||
"""When the Secret was created, as an RFC 3339 timestamp."""
|
||||
return self._created_at
|
||||
def created_at_millis(self) -> int:
|
||||
"""When the Secret was created, in milliseconds since the Unix epoch."""
|
||||
return self._created_at_millis
|
||||
|
||||
@property
|
||||
def updated_at(self) -> str:
|
||||
"""When the Secret's value was last rotated, as an RFC 3339 timestamp."""
|
||||
return self._updated_at
|
||||
def updated_at_millis(self) -> int:
|
||||
"""When the Secret's value was last rotated, in epoch milliseconds.
|
||||
|
||||
The only observable that a rotation landed: no API returns a credential,
|
||||
so a caller confirms ``alter_secret`` took effect by watching this move.
|
||||
"""
|
||||
return self._updated_at_millis
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, value: dict) -> "SecretInfo":
|
||||
return cls(
|
||||
name=value["name"],
|
||||
created_at=value["created_at"],
|
||||
updated_at=value["updated_at"],
|
||||
created_at_millis=value["created_at_millis"],
|
||||
updated_at_millis=value["updated_at_millis"],
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"SecretInfo(name={self._name!r}, created_at={self._created_at!r}, "
|
||||
f"updated_at={self._updated_at!r})"
|
||||
f"SecretInfo(name={self._name!r}, "
|
||||
f"created_at_millis={self._created_at_millis!r}, "
|
||||
f"updated_at_millis={self._updated_at_millis!r})"
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, SecretInfo)
|
||||
and other._name == self._name
|
||||
and other._created_at == self._created_at
|
||||
and other._updated_at == self._updated_at
|
||||
and other._created_at_millis == self._created_at_millis
|
||||
and other._updated_at_millis == self._updated_at_millis
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -740,17 +740,14 @@ impl Connection {
|
||||
})
|
||||
}
|
||||
|
||||
/// Name and timestamps as a plain mapping. `SecretInfo` carries no value,
|
||||
/// so there is none to filter out here.
|
||||
/// Name and timestamps as a plain tuple. `SecretInfo` carries no value, so
|
||||
/// there is none to filter out here. Timestamps stay integers rather than
|
||||
/// going through a string, so the caller can compare two without parsing.
|
||||
pub fn describe_secret(self_: PyRef<'_, Self>, name: String) -> PyResult<Bound<'_, PyAny>> {
|
||||
let inner = self_.get_inner()?.clone();
|
||||
future_into_py(self_.py(), async move {
|
||||
let info = inner.describe_secret(name).await.infer_error()?;
|
||||
Ok(HashMap::from([
|
||||
("name".to_string(), info.name),
|
||||
("created_at".to_string(), info.created_at),
|
||||
("updated_at".to_string(), info.updated_at),
|
||||
]))
|
||||
Ok((info.name, info.created_at_millis, info.updated_at_millis))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user