跳转至

credentials

feishu.auth.credentials

Credential

Bases: ABC

应用凭据抽象基类。

凭据负责换取应用级访问凭证(tenant_access_tokenapp_access_token), 并为缓存提供一个能区分不同凭据、凭证类型与服务器地址的缓存键。 feishu.auth.tokens.TokenManager 依赖本接口完成凭证的获取与缓存。

自建应用请使用 feishu.auth.credentials.InternalCredential

源代码位于: feishu/auth/credentials.py
Python
class Credential(ABC):
    r"""
    应用凭据抽象基类。

    凭据负责换取应用级访问凭证(`tenant_access_token` 或 `app_access_token`),
    并为缓存提供一个能区分不同凭据、凭证类型与服务器地址的缓存键。
    [feishu.auth.tokens.TokenManager][] 依赖本接口完成凭证的获取与缓存。

    自建应用请使用 [feishu.auth.credentials.InternalCredential][]。
    """

    @abstractmethod
    def cache_key(self, token_type: str, base_url: str) -> str:
        r"""
        生成凭证的缓存键。

        缓存键需在凭据、凭证类型与服务器地址三个维度上互不冲突,
        以保证不同应用、不同凭证类型、不同区域之间的凭证不会相互覆盖。

        Args:
            token_type: 凭证类型,`tenant` 或 `app`。
            base_url: 飞书开放平台服务器地址。

        Returns:
            唯一的缓存键。
        """

    @abstractmethod
    async def fetch(self, transport: Transport, token_type: str) -> tuple[str, int]:
        r"""
        换取应用级访问凭证。

        Args:
            transport: 用于发起请求的传输层。
            token_type: 凭证类型,`tenant` 或 `app`。

        Returns:
            由访问凭证与有效期(秒)组成的二元组。
        """

cache_key abstractmethod

Python
cache_key(token_type: str, base_url: str) -> str

生成凭证的缓存键。

缓存键需在凭据、凭证类型与服务器地址三个维度上互不冲突, 以保证不同应用、不同凭证类型、不同区域之间的凭证不会相互覆盖。

参数:

名称 类型 描述 默认
token_type
str

凭证类型,tenantapp

必需
base_url
str

飞书开放平台服务器地址。

必需

返回:

类型 描述
str

唯一的缓存键。

源代码位于: feishu/auth/credentials.py
Python
@abstractmethod
def cache_key(self, token_type: str, base_url: str) -> str:
    r"""
    生成凭证的缓存键。

    缓存键需在凭据、凭证类型与服务器地址三个维度上互不冲突,
    以保证不同应用、不同凭证类型、不同区域之间的凭证不会相互覆盖。

    Args:
        token_type: 凭证类型,`tenant` 或 `app`。
        base_url: 飞书开放平台服务器地址。

    Returns:
        唯一的缓存键。
    """

fetch abstractmethod async

Python
fetch(transport: Transport, token_type: str) -> tuple[str, int]

换取应用级访问凭证。

参数:

名称 类型 描述 默认
transport
Transport

用于发起请求的传输层。

必需
token_type
str

凭证类型,tenantapp

必需

返回:

类型 描述
tuple[str, int]

由访问凭证与有效期(秒)组成的二元组。

源代码位于: feishu/auth/credentials.py
Python
@abstractmethod
async def fetch(self, transport: Transport, token_type: str) -> tuple[str, int]:
    r"""
    换取应用级访问凭证。

    Args:
        transport: 用于发起请求的传输层。
        token_type: 凭证类型,`tenant` 或 `app`。

    Returns:
        由访问凭证与有效期(秒)组成的二元组。
    """

InternalCredential

Bases: Credential

自建应用凭据。

使用应用的 app_idapp_secret 换取 tenant_access_tokenapp_access_token。两个秘钥均在请求体中传递,因此换取凭证的请求本身不携带任何鉴权头。

参数:

名称 类型 描述 默认

app_id

str

应用唯一标识,以 cli_ 开头。

必需

app_secret

str

应用秘钥。

必需

引发:

类型 描述
ValueError

app_idapp_secret 为空时抛出。

飞书文档

tenant_access_token (自建应用): https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal

app_access_token (自建应用): https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal

示例:

Python Console Session
1
2
3
4
5
6
7
>>> cred = InternalCredential("cli_demo", "secret")
>>> cred.app_id
'cli_demo'
>>> InternalCredential("", "secret")
Traceback (most recent call last):
    ...
ValueError: InternalCredential requires both app_id and app_secret
源代码位于: feishu/auth/credentials.py
Python
class InternalCredential(Credential):
    r"""
    自建应用凭据。

    使用应用的 `app_id` 与 `app_secret` 换取 `tenant_access_token` 或
    `app_access_token`。两个秘钥均在请求体中传递,因此换取凭证的请求本身不携带任何鉴权头。

    Args:
        app_id: 应用唯一标识,以 `cli_` 开头。
        app_secret: 应用秘钥。

    Raises:
        ValueError: 当 `app_id` 或 `app_secret` 为空时抛出。

    飞书文档:
        `tenant_access_token` (自建应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal

        `app_access_token` (自建应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal

    Examples:
        >>> cred = InternalCredential("cli_demo", "secret")
        >>> cred.app_id
        'cli_demo'
        >>> InternalCredential("", "secret")
        Traceback (most recent call last):
            ...
        ValueError: InternalCredential requires both app_id and app_secret
    """

    def __init__(self, app_id: str, app_secret: str) -> None:
        if not app_id or not app_secret:
            raise ValueError("InternalCredential requires both app_id and app_secret")
        self.app_id = app_id
        self.app_secret = app_secret

    def cache_key(self, token_type: str, base_url: str) -> str:
        r"""
        生成自建应用凭证的缓存键。

        缓存键由应用标识、凭证类型与服务器地址组成,确保不同应用、不同凭证类型、
        不同区域之间的凭证互不冲突。

        Args:
            token_type: 凭证类型,`tenant` 或 `app`。
            base_url: 飞书开放平台服务器地址。

        Returns:
            唯一的缓存键。

        Examples:
            >>> InternalCredential("cli_demo", "secret").cache_key("tenant", "https://open.feishu.cn")
            'internal:cli_demo:tenant:https://open.feishu.cn'
        """
        return f"internal:{self.app_id}:{token_type}:{base_url}"

    async def fetch(self, transport: Transport, token_type: str) -> tuple[str, int]:
        r"""
        换取自建应用的访问凭证。

        `app_id` 与 `app_secret` 在请求体中传递,因此该请求不携带任何鉴权头。

        Args:
            transport: 用于发起请求的传输层。
            token_type: 凭证类型,`tenant` 或 `app`。

        Returns:
            由访问凭证与有效期(秒)组成的二元组,有效期为正整数。

        Raises:
            ValueError: 当 `token_type` 不是 `tenant` 或 `app` 时抛出。
            FeishuError: 当响应中的 `expire` 缺失、非整数或不是正整数时抛出。

        飞书文档:
            `tenant_access_token` (自建应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal

            `app_access_token` (自建应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal

        Examples:
            >>> cred = InternalCredential("cli_demo", "secret")
            >>> token, expire = await cred.fetch(transport, "tenant")  # doctest: +SKIP
            >>> token, expire  # doctest: +SKIP
            ('t-xxxxxxxx', 7200)
        """
        if token_type not in ("tenant", "app"):
            raise ValueError(f"unsupported token_type {token_type!r}")
        path = f"auth/v3/{token_type}_access_token/internal"
        body = {"app_id": self.app_id, "app_secret": self.app_secret}
        envelope = await transport.request("POST", path, json=body, token=None)
        return envelope[f"{token_type}_access_token"], _parse_expire(envelope.get("expire"))

cache_key

Python
cache_key(token_type: str, base_url: str) -> str

生成自建应用凭证的缓存键。

缓存键由应用标识、凭证类型与服务器地址组成,确保不同应用、不同凭证类型、 不同区域之间的凭证互不冲突。

参数:

名称 类型 描述 默认
token_type
str

凭证类型,tenantapp

必需
base_url
str

飞书开放平台服务器地址。

必需

返回:

类型 描述
str

唯一的缓存键。

示例:

Python Console Session
>>> InternalCredential("cli_demo", "secret").cache_key("tenant", "https://open.feishu.cn")
'internal:cli_demo:tenant:https://open.feishu.cn'
源代码位于: feishu/auth/credentials.py
Python
def cache_key(self, token_type: str, base_url: str) -> str:
    r"""
    生成自建应用凭证的缓存键。

    缓存键由应用标识、凭证类型与服务器地址组成,确保不同应用、不同凭证类型、
    不同区域之间的凭证互不冲突。

    Args:
        token_type: 凭证类型,`tenant` 或 `app`。
        base_url: 飞书开放平台服务器地址。

    Returns:
        唯一的缓存键。

    Examples:
        >>> InternalCredential("cli_demo", "secret").cache_key("tenant", "https://open.feishu.cn")
        'internal:cli_demo:tenant:https://open.feishu.cn'
    """
    return f"internal:{self.app_id}:{token_type}:{base_url}"

fetch async

Python
fetch(transport: Transport, token_type: str) -> tuple[str, int]

换取自建应用的访问凭证。

app_idapp_secret 在请求体中传递,因此该请求不携带任何鉴权头。

参数:

名称 类型 描述 默认
transport
Transport

用于发起请求的传输层。

必需
token_type
str

凭证类型,tenantapp

必需

返回:

类型 描述
tuple[str, int]

由访问凭证与有效期(秒)组成的二元组,有效期为正整数。

引发:

类型 描述
ValueError

token_type 不是 tenantapp 时抛出。

FeishuError

当响应中的 expire 缺失、非整数或不是正整数时抛出。

飞书文档

tenant_access_token (自建应用):

Text Only
1
`app_access_token` (自建应用):

https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal

示例:

Python Console Session
1
2
3
4
>>> cred = InternalCredential("cli_demo", "secret")
>>> token, expire = await cred.fetch(transport, "tenant")
>>> token, expire
('t-xxxxxxxx', 7200)
源代码位于: feishu/auth/credentials.py
Python
async def fetch(self, transport: Transport, token_type: str) -> tuple[str, int]:
    r"""
    换取自建应用的访问凭证。

    `app_id` 与 `app_secret` 在请求体中传递,因此该请求不携带任何鉴权头。

    Args:
        transport: 用于发起请求的传输层。
        token_type: 凭证类型,`tenant` 或 `app`。

    Returns:
        由访问凭证与有效期(秒)组成的二元组,有效期为正整数。

    Raises:
        ValueError: 当 `token_type` 不是 `tenant` 或 `app` 时抛出。
        FeishuError: 当响应中的 `expire` 缺失、非整数或不是正整数时抛出。

    飞书文档:
        `tenant_access_token` (自建应用):
    https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal

        `app_access_token` (自建应用):
    https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal

    Examples:
        >>> cred = InternalCredential("cli_demo", "secret")
        >>> token, expire = await cred.fetch(transport, "tenant")  # doctest: +SKIP
        >>> token, expire  # doctest: +SKIP
        ('t-xxxxxxxx', 7200)
    """
    if token_type not in ("tenant", "app"):
        raise ValueError(f"unsupported token_type {token_type!r}")
    path = f"auth/v3/{token_type}_access_token/internal"
    body = {"app_id": self.app_id, "app_secret": self.app_secret}
    envelope = await transport.request("POST", path, json=body, token=None)
    return envelope[f"{token_type}_access_token"], _parse_expire(envelope.get("expire"))

AppTicketStore

Bases: Protocol

app_ticket 存储协议。

商店应用(ISV)无法直接用 app_id + app_secret 换取 app_access_token, 还需一个由飞书每隔约一小时通过 app_ticket 事件推送的 app_ticket。 实现该协议即可作为 feishu.auth.credentials.StoreCredentialapp_ticket 存储后端: 收到并解密 app_ticket 事件后调用 set 写入,换取凭证时由凭据调用 get 读取。 默认实现为进程内存储 feishu.auth.credentials.InMemoryAppTicketStore;如需在多个进程或 实例间共享,可自行实现基于 Redis 等外部存储的后端。

源代码位于: feishu/auth/credentials.py
Python
class AppTicketStore(Protocol):
    r"""
    `app_ticket` 存储协议。

    商店应用(ISV)无法直接用 `app_id` + `app_secret` 换取 `app_access_token`,
    还需一个由飞书每隔约一小时通过 `app_ticket` 事件推送的 `app_ticket`。
    实现该协议即可作为 [feishu.auth.credentials.StoreCredential][] 的 `app_ticket` 存储后端:
    收到并解密 `app_ticket` 事件后调用 `set` 写入,换取凭证时由凭据调用 `get` 读取。
    默认实现为进程内存储 [feishu.auth.credentials.InMemoryAppTicketStore][];如需在多个进程或
    实例间共享,可自行实现基于 Redis 等外部存储的后端。
    """

    async def get(self, app_id: str) -> str | None:
        r"""
        读取应用的 `app_ticket`。

        Args:
            app_id: 应用唯一标识,以 `cli_` 开头。

        Returns:
            命中的 `app_ticket`;未命中时返回 `None`。
        """

    async def set(self, app_id: str, app_ticket: str) -> None:
        r"""
        写入应用的 `app_ticket`。

        Args:
            app_id: 应用唯一标识,以 `cli_` 开头。
            app_ticket: 飞书通过 `app_ticket` 事件推送并经解密后的最新 `app_ticket`。
        """

get async

Python
get(app_id: str) -> str | None

读取应用的 app_ticket

参数:

名称 类型 描述 默认
app_id
str

应用唯一标识,以 cli_ 开头。

必需

返回:

类型 描述
str | None

命中的 app_ticket;未命中时返回 None

源代码位于: feishu/auth/credentials.py
Python
async def get(self, app_id: str) -> str | None:
    r"""
    读取应用的 `app_ticket`。

    Args:
        app_id: 应用唯一标识,以 `cli_` 开头。

    Returns:
        命中的 `app_ticket`;未命中时返回 `None`。
    """

set async

Python
set(app_id: str, app_ticket: str) -> None

写入应用的 app_ticket

参数:

名称 类型 描述 默认
app_id
str

应用唯一标识,以 cli_ 开头。

必需
app_ticket
str

飞书通过 app_ticket 事件推送并经解密后的最新 app_ticket

必需
源代码位于: feishu/auth/credentials.py
Python
async def set(self, app_id: str, app_ticket: str) -> None:
    r"""
    写入应用的 `app_ticket`。

    Args:
        app_id: 应用唯一标识,以 `cli_` 开头。
        app_ticket: 飞书通过 `app_ticket` 事件推送并经解密后的最新 `app_ticket`。
    """

InMemoryAppTicketStore

进程内 app_ticket 存储。

feishu.auth.credentials.AppTicketStore 的默认实现,将 app_ticket 保存在进程内存中。 app_ticket 不会在进程间共享,进程退出后即失效;如需跨进程共享,请自行实现 AppTicketStore 协议。

示例:

Python Console Session
1
2
3
4
5
6
7
>>> import asyncio
>>> store = InMemoryAppTicketStore()
>>> asyncio.run(store.set("cli_demo", "ticket-1"))
>>> asyncio.run(store.get("cli_demo"))
'ticket-1'
>>> asyncio.run(store.get("cli_missing")) is None
True
源代码位于: feishu/auth/credentials.py
Python
class InMemoryAppTicketStore:
    r"""
    进程内 `app_ticket` 存储。

    [feishu.auth.credentials.AppTicketStore][] 的默认实现,将 `app_ticket` 保存在进程内存中。
    `app_ticket` 不会在进程间共享,进程退出后即失效;如需跨进程共享,请自行实现 `AppTicketStore` 协议。

    Examples:
        >>> import asyncio
        >>> store = InMemoryAppTicketStore()
        >>> asyncio.run(store.set("cli_demo", "ticket-1"))
        >>> asyncio.run(store.get("cli_demo"))
        'ticket-1'
        >>> asyncio.run(store.get("cli_missing")) is None
        True
    """

    def __init__(self) -> None:
        self._store: dict[str, str] = {}

    async def get(self, app_id: str) -> str | None:
        r"""
        读取应用的 `app_ticket`。

        Args:
            app_id: 应用唯一标识。

        Returns:
            命中的 `app_ticket`;未命中时返回 `None`。
        """
        return self._store.get(app_id)

    async def set(self, app_id: str, app_ticket: str) -> None:
        r"""
        写入应用的 `app_ticket`。

        Args:
            app_id: 应用唯一标识。
            app_ticket: 待存储的 `app_ticket`。
        """
        self._store[app_id] = app_ticket

get async

Python
get(app_id: str) -> str | None

读取应用的 app_ticket

参数:

名称 类型 描述 默认
app_id
str

应用唯一标识。

必需

返回:

类型 描述
str | None

命中的 app_ticket;未命中时返回 None

源代码位于: feishu/auth/credentials.py
Python
async def get(self, app_id: str) -> str | None:
    r"""
    读取应用的 `app_ticket`。

    Args:
        app_id: 应用唯一标识。

    Returns:
        命中的 `app_ticket`;未命中时返回 `None`。
    """
    return self._store.get(app_id)

set async

Python
set(app_id: str, app_ticket: str) -> None

写入应用的 app_ticket

参数:

名称 类型 描述 默认
app_id
str

应用唯一标识。

必需
app_ticket
str

待存储的 app_ticket

必需
源代码位于: feishu/auth/credentials.py
Python
async def set(self, app_id: str, app_ticket: str) -> None:
    r"""
    写入应用的 `app_ticket`。

    Args:
        app_id: 应用唯一标识。
        app_ticket: 待存储的 `app_ticket`。
    """
    self._store[app_id] = app_ticket

StoreCredential

Bases: Credential

商店应用(ISV)凭据。

商店应用无法直接用 app_id + app_secret 换取凭证,还需一个由飞书每隔约一小时通过 app_ticket 事件推送的 app_ticket。换取流程为:用 app_id + app_secret + app_ticket 换取 app_access_token,再用该 app_access_token + tenant_key 换取某租户的 tenant_access_token。所有秘钥与凭据均在请求体中传递,因此换取凭证的请求本身不携带任何鉴权头。

app_ticketfeishu.auth.credentials.AppTicketStore 提供:在收到并解密 app_ticket 事件后,调用 await store.set(app_id, app_ticket) 写入;若存储中没有可用的 app_ticket, 本凭据会先请求飞书重新推送(auth/v3/app_ticket/resend)再抛出 feishu.errors.FeishuError, 待 app_ticket 事件到达并写入后重试即可。

参数:

名称 类型 描述 默认

app_id

str

应用唯一标识,以 cli_ 开头。

必需

app_secret

str

应用秘钥。

必需

tenant_key

str

租户唯一标识,用于换取对应租户的 tenant_access_token

必需

app_ticket_store

AppTicketStore | None

app_ticket 存储后端。默认为 feishu.auth.credentials.InMemoryAppTicketStore

None

引发:

类型 描述
ValueError

app_idapp_secrettenant_key 为空时抛出。

飞书文档

app_access_token (商店应用): https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token

tenant_access_token (商店应用): https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token

示例:

Python Console Session
1
2
3
4
5
6
7
8
9
>>> from feishu.auth.credentials import InMemoryAppTicketStore
>>> store = InMemoryAppTicketStore()
>>> cred = StoreCredential("cli_demo", "secret", "tenant-1", app_ticket_store=store)
>>> cred.app_id, cred.tenant_key
('cli_demo', 'tenant-1')
>>> StoreCredential("", "secret", "tenant-1")
Traceback (most recent call last):
    ...
ValueError: StoreCredential requires app_id, app_secret and tenant_key

收到并解密 app_ticket 事件后写入存储,再据此构造客户端:

Python Console Session
1
2
3
4
>>> await store.set("cli_demo", app_ticket)
>>> client = FeishuClient(
...     credential=StoreCredential("cli_demo", "secret", "tenant-1", app_ticket_store=store)
... )
源代码位于: feishu/auth/credentials.py
Python
class StoreCredential(Credential):
    r"""
    商店应用(ISV)凭据。

    商店应用无法直接用 `app_id` + `app_secret` 换取凭证,还需一个由飞书每隔约一小时通过
    `app_ticket` 事件推送的 `app_ticket`。换取流程为:用 `app_id` + `app_secret` + `app_ticket`
    换取 `app_access_token`,再用该 `app_access_token` + `tenant_key` 换取某租户的
    `tenant_access_token`。所有秘钥与凭据均在请求体中传递,因此换取凭证的请求本身不携带任何鉴权头。

    `app_ticket` 由 [feishu.auth.credentials.AppTicketStore][] 提供:在收到并解密 `app_ticket`
    事件后,调用 `await store.set(app_id, app_ticket)` 写入;若存储中没有可用的 `app_ticket`,
    本凭据会先请求飞书重新推送(`auth/v3/app_ticket/resend`)再抛出 [feishu.errors.FeishuError][],
    待 `app_ticket` 事件到达并写入后重试即可。

    Args:
        app_id: 应用唯一标识,以 `cli_` 开头。
        app_secret: 应用秘钥。
        tenant_key: 租户唯一标识,用于换取对应租户的 `tenant_access_token`。
        app_ticket_store: `app_ticket` 存储后端。默认为
            [feishu.auth.credentials.InMemoryAppTicketStore][]。

    Raises:
        ValueError: 当 `app_id`、`app_secret` 或 `tenant_key` 为空时抛出。

    飞书文档:
        `app_access_token` (商店应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token

        `tenant_access_token` (商店应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token

    Examples:
        >>> from feishu.auth.credentials import InMemoryAppTicketStore
        >>> store = InMemoryAppTicketStore()
        >>> cred = StoreCredential("cli_demo", "secret", "tenant-1", app_ticket_store=store)
        >>> cred.app_id, cred.tenant_key
        ('cli_demo', 'tenant-1')
        >>> StoreCredential("", "secret", "tenant-1")
        Traceback (most recent call last):
            ...
        ValueError: StoreCredential requires app_id, app_secret and tenant_key

        收到并解密 `app_ticket` 事件后写入存储,再据此构造客户端:

        >>> await store.set("cli_demo", app_ticket)  # doctest: +SKIP
        >>> client = FeishuClient(  # doctest: +SKIP
        ...     credential=StoreCredential("cli_demo", "secret", "tenant-1", app_ticket_store=store)
        ... )
    """

    def __init__(
        self,
        app_id: str,
        app_secret: str,
        tenant_key: str,
        *,
        app_ticket_store: AppTicketStore | None = None,
    ) -> None:
        if not app_id or not app_secret or not tenant_key:
            raise ValueError("StoreCredential requires app_id, app_secret and tenant_key")
        self.app_id = app_id
        self.app_secret = app_secret
        self.tenant_key = tenant_key
        self.app_ticket_store: AppTicketStore = app_ticket_store or InMemoryAppTicketStore()

    def cache_key(self, token_type: str, base_url: str) -> str:
        r"""
        生成商店应用凭证的缓存键。

        缓存键由应用标识、租户标识、凭证类型与服务器地址组成。其中租户标识确保不同租户的凭证
        互不冲突——这是商店应用与自建应用的关键区别,缺少它会导致不同租户的 `tenant_access_token`
        相互覆盖。

        Args:
            token_type: 凭证类型,`tenant` 或 `app`。
            base_url: 飞书开放平台服务器地址。

        Returns:
            唯一的缓存键。

        Examples:
            >>> StoreCredential("cli_demo", "secret", "tenant-1").cache_key("tenant", "https://open.feishu.cn")
            'store:cli_demo:tenant-1:tenant:https://open.feishu.cn'
        """
        return f"store:{self.app_id}:{self.tenant_key}:{token_type}:{base_url}"

    async def _app_access_token(self, transport: Transport) -> tuple[str, int]:
        r"""
        换取商店应用的 `app_access_token`。

        从存储中读取本应用的 `app_ticket`;若缺失或为空,则先请求飞书重新推送 `app_ticket`
        事件(`auth/v3/app_ticket/resend`),再抛出 [feishu.errors.FeishuError][],待事件到达
        并写入存储后重试。`app_id`、`app_secret` 与 `app_ticket` 均在请求体中传递,因此请求不
        携带任何鉴权头。

        Args:
            transport: 用于发起请求的传输层。

        Returns:
            由 `app_access_token` 与有效期(秒)组成的二元组。

        Raises:
            FeishuError: 当存储中没有可用的 `app_ticket` 时抛出(已同时请求重新推送)。
        """
        app_ticket = await self.app_ticket_store.get(self.app_id)
        if not app_ticket:
            # No app_ticket yet: ask Feishu to re-push the app_ticket event, then bail out.
            # Both secrets travel in the body, so this request carries no auth header either.
            await transport.request(
                "POST",
                "auth/v3/app_ticket/resend",
                json={"app_id": self.app_id, "app_secret": self.app_secret},
                token=None,
            )
            raise FeishuError(
                -1,
                f"app_ticket unavailable for app {self.app_id}; a resend was requested "
                "-- retry once the app_ticket event is received and stored",
            )
        body = {"app_id": self.app_id, "app_secret": self.app_secret, "app_ticket": app_ticket}
        envelope = await transport.request("POST", "auth/v3/app_access_token", json=body, token=None)
        return envelope["app_access_token"], _parse_expire(envelope.get("expire"))

    async def fetch(self, transport: Transport, token_type: str) -> tuple[str, int]:
        r"""
        换取商店应用的访问凭证。

        `app` 类型直接返回 `app_access_token`;`tenant` 类型先换取 `app_access_token`,
        再用其与 `tenant_key` 换取对应租户的 `tenant_access_token`。所有秘钥与凭据均在请求体中
        传递,因此这些请求均不携带任何鉴权头。

        Args:
            transport: 用于发起请求的传输层。
            token_type: 凭证类型,`tenant` 或 `app`。

        Returns:
            由访问凭证与有效期(秒)组成的二元组,有效期为正整数。

        Raises:
            ValueError: 当 `token_type` 不是 `tenant` 或 `app` 时抛出。
            FeishuError: 当存储中没有可用的 `app_ticket`,或响应中的 `expire` 缺失、非整数或不是
                正整数时抛出。

        飞书文档:
            `app_access_token` (商店应用):
            https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token

            `tenant_access_token` (商店应用):
            https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token

        Examples:
            >>> token, expire = await cred.fetch(transport, "tenant")  # doctest: +SKIP
            >>> token, expire  # doctest: +SKIP
            ('t-xxxxxxxx', 7200)
        """
        if token_type == "app":
            return await self._app_access_token(transport)
        if token_type == "tenant":
            app_access_token, _ = await self._app_access_token(transport)
            body = {"app_access_token": app_access_token, "tenant_key": self.tenant_key}
            envelope = await transport.request("POST", "auth/v3/tenant_access_token", json=body, token=None)
            return envelope["tenant_access_token"], _parse_expire(envelope.get("expire"))
        raise ValueError(f"unsupported token_type {token_type!r}")

cache_key

Python
cache_key(token_type: str, base_url: str) -> str

生成商店应用凭证的缓存键。

缓存键由应用标识、租户标识、凭证类型与服务器地址组成。其中租户标识确保不同租户的凭证 互不冲突——这是商店应用与自建应用的关键区别,缺少它会导致不同租户的 tenant_access_token 相互覆盖。

参数:

名称 类型 描述 默认
token_type
str

凭证类型,tenantapp

必需
base_url
str

飞书开放平台服务器地址。

必需

返回:

类型 描述
str

唯一的缓存键。

示例:

Python Console Session
>>> StoreCredential("cli_demo", "secret", "tenant-1").cache_key("tenant", "https://open.feishu.cn")
'store:cli_demo:tenant-1:tenant:https://open.feishu.cn'
源代码位于: feishu/auth/credentials.py
Python
def cache_key(self, token_type: str, base_url: str) -> str:
    r"""
    生成商店应用凭证的缓存键。

    缓存键由应用标识、租户标识、凭证类型与服务器地址组成。其中租户标识确保不同租户的凭证
    互不冲突——这是商店应用与自建应用的关键区别,缺少它会导致不同租户的 `tenant_access_token`
    相互覆盖。

    Args:
        token_type: 凭证类型,`tenant` 或 `app`。
        base_url: 飞书开放平台服务器地址。

    Returns:
        唯一的缓存键。

    Examples:
        >>> StoreCredential("cli_demo", "secret", "tenant-1").cache_key("tenant", "https://open.feishu.cn")
        'store:cli_demo:tenant-1:tenant:https://open.feishu.cn'
    """
    return f"store:{self.app_id}:{self.tenant_key}:{token_type}:{base_url}"

fetch async

Python
fetch(transport: Transport, token_type: str) -> tuple[str, int]

换取商店应用的访问凭证。

app 类型直接返回 app_access_tokentenant 类型先换取 app_access_token, 再用其与 tenant_key 换取对应租户的 tenant_access_token。所有秘钥与凭据均在请求体中 传递,因此这些请求均不携带任何鉴权头。

参数:

名称 类型 描述 默认
transport
Transport

用于发起请求的传输层。

必需
token_type
str

凭证类型,tenantapp

必需

返回:

类型 描述
tuple[str, int]

由访问凭证与有效期(秒)组成的二元组,有效期为正整数。

引发:

类型 描述
ValueError

token_type 不是 tenantapp 时抛出。

FeishuError

当存储中没有可用的 app_ticket,或响应中的 expire 缺失、非整数或不是 正整数时抛出。

飞书文档

app_access_token (商店应用): https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token

tenant_access_token (商店应用): https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token

示例:

Python Console Session
1
2
3
>>> token, expire = await cred.fetch(transport, "tenant")
>>> token, expire
('t-xxxxxxxx', 7200)
源代码位于: feishu/auth/credentials.py
Python
async def fetch(self, transport: Transport, token_type: str) -> tuple[str, int]:
    r"""
    换取商店应用的访问凭证。

    `app` 类型直接返回 `app_access_token`;`tenant` 类型先换取 `app_access_token`,
    再用其与 `tenant_key` 换取对应租户的 `tenant_access_token`。所有秘钥与凭据均在请求体中
    传递,因此这些请求均不携带任何鉴权头。

    Args:
        transport: 用于发起请求的传输层。
        token_type: 凭证类型,`tenant` 或 `app`。

    Returns:
        由访问凭证与有效期(秒)组成的二元组,有效期为正整数。

    Raises:
        ValueError: 当 `token_type` 不是 `tenant` 或 `app` 时抛出。
        FeishuError: 当存储中没有可用的 `app_ticket`,或响应中的 `expire` 缺失、非整数或不是
            正整数时抛出。

    飞书文档:
        `app_access_token` (商店应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token

        `tenant_access_token` (商店应用):
        https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token

    Examples:
        >>> token, expire = await cred.fetch(transport, "tenant")  # doctest: +SKIP
        >>> token, expire  # doctest: +SKIP
        ('t-xxxxxxxx', 7200)
    """
    if token_type == "app":
        return await self._app_access_token(transport)
    if token_type == "tenant":
        app_access_token, _ = await self._app_access_token(transport)
        body = {"app_access_token": app_access_token, "tenant_key": self.tenant_key}
        envelope = await transport.request("POST", "auth/v3/tenant_access_token", json=body, token=None)
        return envelope["tenant_access_token"], _parse_expire(envelope.get("expire"))
    raise ValueError(f"unsupported token_type {token_type!r}")