refactor(api): client rename, per-backend constants, shared types, tenacity engine

Review round on the K3 kit, four moves:

_client.py/_client.ts -> client.py/client.ts across every backend, both
languages (the underscore said plumbing; the kit made the client a named
layer). The sweep covers dotted module paths, module-object imports
(postgres), monkeypatch string targets (including the multiline
commands.builtin.postgres.tail one), vi.mock specifiers, namespace-import
aliases, the mypy override list, and dify's test__client mirror; attribute
spellings like self._client and the s3 _client_kwargs symbol are
deliberately untouched.

API bases, versions and thresholds move out of the four migrated clients
into per-backend constants.py/constants.ts (discord, notion, google,
dropbox), following slack's existing convention; the remaining backends
move with their own K3 migrations.

ErrorOf and PageFetch move to mirage.types / core/src/types.ts as the
shared contracts. The python alias is quoted behind TYPE_CHECKING so
importing mirage.types still never loads aiohttp at runtime.

The python engine's retry loop is now tenacity (already a dependency,
and dify's client is the in-repo idiom): the attempt raises a
_RetryableStatus carrying the body text it already read, the wait
callback derives the delay from it synchronously, and exhaustion maps
through the same error_of hook as a plain error status. TypeScript keeps
the hand-rolled loop - there is no tenacity twin and core takes no new
dependency for a 15-line loop.

Verified again end to end: layout parity 290=baseline, spec trees regen
zero-diff, knip/eslint/tsc clean, core 8824 / node 2255 / browser 184,
py suite green (known local deepagents drift excepted), integ both hosts
7423 passed / 0 failed across the same eight targets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bytecii
2026-08-16 18:33:18 -07:00
parent 77d44fd636
commit 0290b003c7
798 changed files with 1552 additions and 1374 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.core.box._client import BoxTokenManager
from mirage.core.box.client import BoxTokenManager
from mirage.core.box.config import BoxConfig
+1 -1
View File
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING
from mirage.accessor.base import Accessor
if TYPE_CHECKING:
from mirage.core.dropbox._client import DropboxTokenManager
from mirage.core.dropbox.client import DropboxTokenManager
from mirage.resource.dropbox.config import DropboxConfig
+1 -1
View File
@@ -16,7 +16,7 @@ from datetime import date, datetime
from mirage.accessor.base import Accessor
from mirage.core.gcal.day import zone
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.resource.gcal.config import GCalConfig
+1 -1
View File
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
class GDocsAccessor(Accessor):
+1 -1
View File
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
class GDriveAccessor(Accessor):
+1 -1
View File
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
class GmailAccessor(Accessor):
+1 -1
View File
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
class GSheetsAccessor(Accessor):
+1 -1
View File
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
class GSlidesAccessor(Accessor):
+1 -1
View File
@@ -15,7 +15,7 @@
import asyncssh
from mirage.accessor.base import Accessor
from mirage.core.ssh._client import _connect_kwargs
from mirage.core.ssh.client import _connect_kwargs
from mirage.core.ssh.config import SSHConfig
@@ -25,7 +25,7 @@ from mirage.commands.builtin.generic_bind.builders.common import \
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.core.discord._client import discord_get
from mirage.core.discord.client import discord_get
from mirage.core.discord.history import date_to_snowflake
from mirage.core.discord.read import read as discord_read
from mirage.core.discord.render import history_jsonl_bytes
+1 -1
View File
@@ -25,7 +25,7 @@ from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_headers
from mirage.core.email.client import fetch_headers
from mirage.core.email.readdir import _date_bucket, _sanitize
from mirage.core.email.readdir import readdir as _readdir
from mirage.core.email.scope import extract_folder
+1 -1
View File
@@ -25,7 +25,7 @@ from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_message
from mirage.core.email.client import fetch_message
from mirage.core.email.read import read as email_read
from mirage.core.email.readdir import readdir as _readdir
from mirage.core.email.render import message_json_text
@@ -28,8 +28,8 @@ from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.core.langfuse._client import (fetch_datasets, fetch_prompts,
fetch_sessions, fetch_traces)
from mirage.core.langfuse.client import (fetch_datasets, fetch_prompts,
fetch_sessions, fetch_traces)
from mirage.core.langfuse.read import read as langfuse_read
from mirage.core.langfuse.readdir import readdir as _readdir
from mirage.core.langfuse.scope import detect_scope
@@ -26,8 +26,8 @@ from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.core.langfuse._client import (fetch_datasets, fetch_prompts,
fetch_sessions, fetch_traces)
from mirage.core.langfuse.client import (fetch_datasets, fetch_prompts,
fetch_sessions, fetch_traces)
from mirage.core.langfuse.read import read as langfuse_read
from mirage.core.langfuse.readdir import readdir as _readdir
from mirage.core.langfuse.scope import detect_scope
@@ -26,7 +26,7 @@ from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.core.mongodb._client import list_databases
from mirage.core.mongodb.client import list_databases
from mirage.core.mongodb.read import read as mongodb_read
from mirage.core.mongodb.readdir import readdir as _readdir
from mirage.core.mongodb.scope import (MongoDBDatabaseScope,
+1 -1
View File
@@ -25,7 +25,7 @@ from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.core.mongodb._client import list_databases
from mirage.core.mongodb.client import list_databases
from mirage.core.mongodb.read import read as mongodb_read
from mirage.core.mongodb.readdir import readdir as _readdir
from mirage.core.mongodb.scope import (MongoDBDatabaseScope,
+1 -1
View File
@@ -23,7 +23,7 @@ from mirage.commands.builtin.mongodb.io import IO
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.core.mongodb._client import count_documents
from mirage.core.mongodb.client import count_documents
from mirage.core.mongodb.scope import MongoDBDocumentsScope, detect_scope
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -26,7 +26,7 @@ from mirage.commands.builtin.utils.paths import has_unresolved_glob
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.core.postgres import _client
from mirage.core.postgres import client
from mirage.core.postgres.read import read as postgres_read
from mirage.core.postgres.scope import PostgresEntityRowsScope, detect_scope
from mirage.io.types import ByteSource, IOResult
@@ -53,14 +53,14 @@ async def tail(accessor: PostgresAccessor, paths: list[PathSpec],
limit = min(counts.lines, accessor.config.default_row_limit)
pool = await accessor.pool()
async with pool.acquire() as conn:
total = await _client.count_rows(conn, scope.schema,
scope.entity)
total = await client.count_rows(conn, scope.schema,
scope.entity)
offset = max(0, total - limit)
rows = await _client.fetch_rows(conn,
scope.schema,
scope.entity,
limit=limit,
offset=offset)
rows = await client.fetch_rows(conn,
scope.schema,
scope.entity,
limit=limit,
offset=offset)
data = b""
if rows:
data = ("\n".join(
@@ -22,7 +22,7 @@ from mirage.commands.builtin.postgres.io import IO
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.core.postgres import _client
from mirage.core.postgres import client
from mirage.core.postgres.read import read as postgres_read
from mirage.core.postgres.scope import PostgresEntityRowsScope, detect_scope
from mirage.io.types import ByteSource, IOResult
@@ -53,8 +53,8 @@ async def wc(accessor: PostgresAccessor, paths: list[PathSpec],
pool = await accessor.pool()
async with pool.acquire() as conn:
for p, scope in zip(resolved, row_scopes):
count = await _client.count_rows(conn, scope.schema,
scope.entity)
count = await client.count_rows(conn, scope.schema,
scope.entity)
rows.append((WCCounts(lines=count), p.raw_path))
total += count
return format_count_rows(rows, WCCounts(lines=total), len(resolved),
@@ -21,10 +21,10 @@ from mirage.accessor.trello import TrelloAccessor
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Operand
from mirage.core.trello._client import (get_board, get_card, list_board_labels,
list_board_lists, list_board_members,
list_card_comments, list_list_cards,
list_workspace_boards, list_workspaces)
from mirage.core.trello.client import (get_board, get_card, list_board_labels,
list_board_lists, list_board_members,
list_card_comments, list_list_cards,
list_workspace_boards, list_workspaces)
from mirage.core.trello.normalize import (normalize_board, normalize_card,
normalize_comment, normalize_label,
normalize_list, normalize_member,
@@ -18,7 +18,7 @@ from mirage.accessor.trello import TrelloAccessor
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import card_assign
from mirage.core.trello.client import card_assign
from mirage.core.trello.normalize import normalize_card
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -20,7 +20,7 @@ from mirage.commands.builtin.trello._input import (file_operand,
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import comment_create
from mirage.core.trello.client import comment_create
from mirage.core.trello.normalize import normalize_comment
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -20,7 +20,7 @@ from mirage.commands.builtin.trello._input import (file_operand,
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import comment_update
from mirage.core.trello.client import comment_update
from mirage.core.trello.normalize import normalize_comment
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -20,7 +20,7 @@ from mirage.commands.builtin.trello._input import (file_operand,
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import card_create
from mirage.core.trello.client import card_create
from mirage.core.trello.normalize import normalize_card
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -18,7 +18,7 @@ from mirage.accessor.trello import TrelloAccessor
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import card_add_label
from mirage.core.trello.client import card_add_label
from mirage.core.trello.normalize import normalize_card
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -18,7 +18,7 @@ from mirage.accessor.trello import TrelloAccessor
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import card_remove_label
from mirage.core.trello.client import card_remove_label
from mirage.core.trello.normalize import normalize_card
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -18,7 +18,7 @@ from mirage.accessor.trello import TrelloAccessor
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import card_move
from mirage.core.trello.client import card_move
from mirage.core.trello.normalize import normalize_card
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -20,7 +20,7 @@ from mirage.commands.builtin.trello._input import (file_operand,
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec.types import CommandSpec, FlagView, Option
from mirage.core.trello._client import card_update
from mirage.core.trello.client import card_update
from mirage.core.trello.normalize import normalize_card
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -16,7 +16,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.discord._client import discord_get
from mirage.core.discord.client import discord_get
from mirage.core.discord.config import DiscordConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
+1 -1
View File
@@ -18,7 +18,7 @@ import re
from mirage.commands.cli.builtin.gh.accessor import json_out, text_out
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.github._client import github_request
from mirage.core.github.client import github_request
from mirage.core.github.config import GhConfig
from mirage.core.github.placeholder import expand
from mirage.core.jq import jq_eval
@@ -26,10 +26,9 @@ from mirage.commands.cli.builtin.gws.methods import (GWS_METHODS,
from mirage.commands.cli.types import CLIInvocation, CLISpec
from mirage.commands.errors import UsageError
from mirage.commands.spec.types import FlagValue, FlagView, Option
from mirage.core.google._client import (TokenManager, drive_base,
google_delete, google_get,
google_get_bytes, google_patch,
google_post)
from mirage.core.google.client import (TokenManager, drive_base, google_delete,
google_get, google_get_bytes,
google_patch, google_post)
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gdocs.write import append_text
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gmail.send import forward_message
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gmail.messages import get_message_processed
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gmail.send import reply_message
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gmail.send import reply_all_message
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gmail.send import send_message
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -18,7 +18,7 @@ from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.gmail.messages import (_extract_header, get_message_raw,
list_messages)
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult
@@ -15,9 +15,9 @@
from collections.abc import Callable
from dataclasses import dataclass
from mirage.core.google._client import (TokenManager, calendar_base, docs_base,
drive_base, forms_base, gmail_base,
sheets_base, slides_base)
from mirage.core.google.client import (TokenManager, calendar_base, docs_base,
drive_base, forms_base, gmail_base,
sheets_base, slides_base)
# The official gws CLI generates one command per Discovery method and
# speaks raw API resources: `--params` carries path/query parameters,
@@ -17,7 +17,7 @@ import json
from mirage.commands.cli.builtin.gws.sheets.write import values_json_from_flags
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.core.gsheets.write import append_values
from mirage.io.stream import yield_bytes
@@ -14,7 +14,7 @@
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.core.gsheets.read import read_values
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ import json
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.google._client import TokenManager
from mirage.core.google.client import TokenManager
from mirage.core.google.config import GoogleConfig
from mirage.core.gsheets.write import update_values
from mirage.io.stream import yield_bytes
@@ -17,7 +17,7 @@ from email.message import Message
from mirage.accessor.email import EmailAccessor
from mirage.commands.cli.builtin.himalaya.smtp import send_raw
from mirage.core.email._client import list_folder_entries, quote_mailbox
from mirage.core.email.client import list_folder_entries, quote_mailbox
from mirage.core.email.config import EmailConfig
logger = logging.getLogger(__name__)
@@ -17,7 +17,7 @@ from mirage.commands.cli.builtin.himalaya.builder import Source
from mirage.commands.cli.builtin.himalaya.util import first_text, route
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_message
from mirage.core.email.client import fetch_message
from mirage.core.email.config import EmailConfig
from mirage.io.types import ByteSource, IOResult
@@ -18,7 +18,7 @@ from mirage.commands.cli.builtin.himalaya.query import (page_slice,
uid_budget)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_headers, list_message_uids
from mirage.core.email.client import fetch_headers, list_message_uids
from mirage.core.email.config import EmailConfig
from mirage.core.email.render import messages_json_bytes
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ from mirage.accessor.email import EmailAccessor
from mirage.commands.cli.builtin.himalaya.util import first_text
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_message, fetch_raw_message
from mirage.core.email.client import fetch_message, fetch_raw_message
from mirage.core.email.config import EmailConfig
from mirage.core.email.render import message_json_bytes
from mirage.io.stream import yield_bytes
@@ -17,7 +17,7 @@ from mirage.commands.cli.builtin.himalaya.builder import Source
from mirage.commands.cli.builtin.himalaya.util import first_text, route
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_message
from mirage.core.email.client import fetch_message
from mirage.core.email.config import EmailConfig
from mirage.io.types import ByteSource, IOResult
@@ -20,7 +20,7 @@ from mirage.commands.cli.builtin.himalaya.query import (page_slice,
uid_budget)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_headers, list_message_uids
from mirage.core.email.client import fetch_headers, list_message_uids
from mirage.core.email.config import EmailConfig
from mirage.core.email.render import messages_json_bytes
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ from mirage.commands.cli.builtin.linear.util import (first_text, resolve_issue,
text_or_stdin)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import comment_create
from mirage.core.linear.client import comment_create
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_comment, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -15,7 +15,7 @@
from mirage.commands.cli.builtin.linear.util import text_or_stdin
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import comment_update
from mirage.core.linear.client import comment_update
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_comment, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ from mirage.commands.cli.builtin.linear.util import (first_text, resolve_issue,
resolve_label_id)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import get_issue, issue_update
from mirage.core.linear.client import get_issue, issue_update
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -15,7 +15,7 @@
from mirage.commands.cli.builtin.linear.util import first_text, resolve_issue
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import issue_update, resolve_user_id
from mirage.core.linear.client import issue_update, resolve_user_id
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -15,7 +15,7 @@
from mirage.commands.cli.builtin.linear.util import text_or_stdin
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import issue_create, resolve_team
from mirage.core.linear.client import issue_create, resolve_team
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -15,7 +15,7 @@
from mirage.commands.cli.builtin.linear.util import first_text, resolve_issue
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import issue_update
from mirage.core.linear.client import issue_update
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ from mirage.commands.cli.builtin.linear.util import (first_text, resolve_issue,
resolve_project_id)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import get_issue, issue_update
from mirage.core.linear.client import get_issue, issue_update
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ from mirage.commands.cli.builtin.linear.util import (first_text, resolve_issue,
resolve_state_id)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import issue_update
from mirage.core.linear.client import issue_update
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -16,7 +16,7 @@ from mirage.commands.cli.builtin.linear.util import (first_text, resolve_issue,
text_or_stdin)
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import issue_update
from mirage.core.linear.client import issue_update
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import normalize_issue, to_json_bytes
from mirage.io.stream import yield_bytes
@@ -18,12 +18,11 @@ from collections.abc import Awaitable, Callable
from mirage.commands.cli.builtin.linear.util import first_text, resolve_issue
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.spec.types import FlagView
from mirage.core.linear._client import (get_issue, list_issue_comments,
list_team_cycles, list_team_documents,
list_team_issues, list_team_labels,
list_team_members, list_team_projects,
list_teams, resolve_team,
search_issues)
from mirage.core.linear.client import (get_issue, list_issue_comments,
list_team_cycles, list_team_documents,
list_team_issues, list_team_labels,
list_team_members, list_team_projects,
list_teams, resolve_team, search_issues)
from mirage.core.linear.config import LinearConfig
from mirage.core.linear.normalize import (normalize_comment, normalize_cycle,
normalize_document, normalize_issue,
@@ -14,8 +14,8 @@
import re
from mirage.core.linear._client import (list_team_labels, list_team_projects,
list_teams, resolve_issue_id)
from mirage.core.linear.client import (list_team_labels, list_team_projects,
list_teams, resolve_issue_id)
from mirage.core.linear.config import LinearConfig
from mirage.io.types import ByteSource
@@ -22,8 +22,8 @@ from mirage.commands.cli.builtin.ntn.util import (compact_json, first_text,
from mirage.commands.cli.types import CLIInvocation
from mirage.commands.errors import UsageError
from mirage.commands.spec.types import FlagView
from mirage.core.notion._client import (notion_delete, notion_get,
notion_patch, notion_post, notion_put)
from mirage.core.notion.client import (notion_delete, notion_get, notion_patch,
notion_post, notion_put)
from mirage.core.notion.config import NotionConfig
from mirage.io.stream import yield_bytes
from mirage.io.types import ByteSource, IOResult, materialize
@@ -21,7 +21,7 @@ from mirage.commands.cli.builtin.ntn.util import (first_text, notion_config,
from mirage.commands.cli.types import CLIDoors, CLIInvocation
from mirage.commands.errors import UsageError
from mirage.commands.spec.types import FlagView
from mirage.core.notion._client import NotionAPIError
from mirage.core.notion.client import NotionAPIError
from mirage.core.notion.config import NotionConfig
from mirage.core.notion.pages import (get_data_source, get_database,
query_data_source_page)
@@ -15,7 +15,7 @@
from collections.abc import Awaitable, Callable
from mirage.commands.cli.types import CLIInvocation
from mirage.core.notion._client import NotionAPIError
from mirage.core.notion.client import NotionAPIError
from mirage.core.notion.config import NotionConfig
from mirage.io.types import ByteSource, IOResult
+66 -26
View File
@@ -14,15 +14,16 @@
import asyncio
import json
from collections.abc import Callable, Mapping
from collections.abc import Mapping
from dataclasses import dataclass
from functools import partial
from typing import Any, Literal
import aiohttp
from tenacity import (AsyncRetrying, RetryCallState, retry_if_exception_type,
stop_after_attempt)
from mirage.types import JsonValue
ErrorOf = Callable[[aiohttp.ClientResponse, str], Exception]
from mirage.types import ErrorOf, JsonValue
@dataclass(frozen=True, slots=True)
@@ -47,6 +48,21 @@ class RetryPolicy:
NO_RETRY = RetryPolicy()
class _RetryableStatus(Exception):
"""A response whose status the policy retries, carried to tenacity.
The body is read inside the attempt because the wait callback is
synchronous and cannot await it; the response object stays readable
for headers, status and request info after its connection returns to
the pool.
"""
def __init__(self, resp: aiohttp.ClientResponse, text: str) -> None:
super().__init__(f"retryable HTTP {resp.status}")
self.resp = resp
self.text = text
def status_error(resp: aiohttp.ClientResponse) -> Exception:
"""The error ``resp.raise_for_status()`` raises, built without raising.
@@ -84,11 +100,39 @@ def _body_delay(text: str) -> float:
return 1.0
async def _retry_delay(resp: aiohttp.ClientResponse, attempt: int,
retry: RetryPolicy) -> float:
def _retry_delay(retry_state: RetryCallState, retry: RetryPolicy) -> float:
outcome = retry_state.outcome
error = outcome.exception() if outcome is not None else None
if not isinstance(error, _RetryableStatus):
return 1.0
if retry.delay_source == "body":
return _body_delay(await resp.text())
return _header_delay(resp, attempt, retry)
return _body_delay(error.text)
return _header_delay(error.resp, retry_state.attempt_number - 1, retry)
async def _attempt(
session: aiohttp.ClientSession,
method: str,
url: str,
error_of: ErrorOf,
headers: Mapping[str, str] | None,
params: Mapping[str, Any] | None,
json_body: JsonValue,
retry: RetryPolicy,
read: Literal["json", "none"],
) -> Any:
async with session.request(method,
url,
headers=headers,
params=params,
json=json_body) as resp:
if resp.status in retry.statuses:
raise _RetryableStatus(resp, await resp.text())
if resp.status >= 400:
raise error_of(resp, await resp.text())
if read == "none":
return None
return await resp.json()
async def api_request(
@@ -118,22 +162,18 @@ async def api_request(
retry (RetryPolicy): which statuses to retry and how long to wait.
read (str): "json" parses the response body; "none" ignores it.
"""
retrying = AsyncRetrying(
sleep=asyncio.sleep,
stop=stop_after_attempt(retry.max_retries + 1),
wait=partial(_retry_delay, retry=retry),
retry=retry_if_exception_type(_RetryableStatus),
reraise=True,
)
async with aiohttp.ClientSession() as session:
attempt = 0
while True:
async with session.request(method,
url,
headers=headers,
params=params,
json=json_body) as resp:
if (resp.status in retry.statuses
and attempt < retry.max_retries):
await asyncio.sleep(await
_retry_delay(resp, attempt, retry))
attempt += 1
continue
if resp.status >= 400:
raise error_of(resp, await resp.text())
if read == "none":
return None
return await resp.json()
try:
return await retrying(_attempt, session, method, url, error_of,
headers, params, json_body, retry, read)
except _RetryableStatus as exhausted:
# retries ran dry: the final retryable response maps through
# the same hook a plain error status does
raise error_of(exhausted.resp, exhausted.text) from None
+1 -2
View File
@@ -12,10 +12,9 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from collections.abc import Awaitable, Callable
from typing import Any
PageFetch = Callable[[str | None], Awaitable[dict[str, Any]]]
from mirage.types import PageFetch
async def cursor_items(fetch_page: PageFetch,
+4 -4
View File
@@ -15,10 +15,10 @@
from collections.abc import AsyncIterator
from typing import Any
from mirage.core.box._client import (BoxTokenManager, box_delete, box_get,
box_get_bytes, box_get_stream,
box_post_json, box_put_json,
box_upload_multipart)
from mirage.core.box.client import (BoxTokenManager, box_delete, box_get,
box_get_bytes, box_get_stream,
box_post_json, box_put_json,
box_upload_multipart)
from mirage.utils.ranges import ByteWindow
LIST_FIELDS = "id,name,type,size,modified_at,etag,sha1,parent"
+1 -1
View File
@@ -16,8 +16,8 @@ import logging
from typing import Any
from mirage.accessor.box import BoxAccessor
from mirage.core.box._client import BoxApiError
from mirage.core.box.api import search_content
from mirage.core.box.client import BoxApiError
from mirage.core.box.resolve import path_parts, resolve_item, root_id
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key, mount_prefix_of
+1 -1
View File
@@ -2,7 +2,7 @@ from mirage.accessor.chroma import ChromaAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.grep_helper import compile_pattern, grep_lines
from mirage.commands.builtin.utils.lines import split_lines
from mirage.core.chroma._client import fetch_page_chunks, query_contains
from mirage.core.chroma.client import fetch_page_chunks, query_contains
from mirage.core.chroma.path import resolve_path
from mirage.core.chroma.walk import walk
from mirage.types import PathSpec
+1 -1
View File
@@ -3,7 +3,7 @@ from collections.abc import AsyncIterator
from mirage.accessor.chroma import ChromaAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.core.chroma._client import iter_page_chunks, page_chunks
from mirage.core.chroma.client import iter_page_chunks, page_chunks
from mirage.core.chroma.path import resolve_path
from mirage.core.chroma.render import render_page
from mirage.types import PathSpec
+1 -1
View File
@@ -1,6 +1,6 @@
from mirage.accessor.chroma import ChromaAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore, IndexEntry
from mirage.core.chroma._client import pages_chunks
from mirage.core.chroma.client import pages_chunks
from mirage.core.chroma.render import render_page
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Any
from mirage.accessor.chroma import ChromaAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore, IndexEntry
from mirage.core.chroma._client import fetch_path_tree
from mirage.core.chroma.client import fetch_path_tree
from mirage.utils.path import gnu_basename, parent
+1 -1
View File
@@ -4,7 +4,7 @@ from typing import Any
from mirage.accessor.dify import DifyAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.core.dify._client import get_document_segments, iter_segment_pages
from mirage.core.dify.client import get_document_segments, iter_segment_pages
from mirage.core.dify.path import resolve_path
from mirage.types import PathSpec
from mirage.utils.ranges import slice_window
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Any
from mirage.accessor.dify import DifyAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore, IndexEntry
from mirage.core.dify._client import dify_post
from mirage.core.dify.client import dify_post
from mirage.core.dify.path import resolve_path
from mirage.core.dify.read import segment_text
from mirage.core.dify.tree import normalize_slug
+1 -1
View File
@@ -2,7 +2,7 @@ from datetime import datetime, timezone
from mirage.accessor.dify import DifyAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.core.dify._client import get_document_detail
from mirage.core.dify.client import get_document_detail
from mirage.core.dify.path import resolve_path
from mirage.core.dify.tree import extract_document_size
from mirage.types import FileStat, FileType, JsonValue, PathSpec
+1 -1
View File
@@ -4,7 +4,7 @@ from typing import Any
from mirage.accessor.dify import DifyAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore, IndexEntry
from mirage.core.dify._client import list_all_documents
from mirage.core.dify.client import list_all_documents
from mirage.types import JsonValue
from mirage.utils.path import gnu_basename, parent
+1 -1
View File
@@ -15,7 +15,7 @@
from collections.abc import AsyncIterator
from typing import Any
from mirage.core.discord._client import discord_get
from mirage.core.discord.client import discord_get
from mirage.core.discord.config import DiscordConfig
TEXT_CHANNEL_TYPES = (0, 5, 15)
@@ -19,10 +19,9 @@ import aiohttp
from mirage.core.api.client import RetryPolicy, api_request, status_error
from mirage.core.discord.config import DiscordConfig
from mirage.core.discord.constants import DISCORD_API, MAX_RETRIES
from mirage.resource.secrets import reveal_secret
DISCORD_API = "https://discord.com/api/v10"
MAX_RETRIES = 3
# GET is the only verb that waits out a 429: reads are safe to repeat, and
# the delay comes from the JSON body's retry_after (Discord's convention).
# Mutations surface the 429 immediately so the caller decides.
+16
View File
@@ -0,0 +1,16 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
DISCORD_API = "https://discord.com/api/v10"
MAX_RETRIES = 3
+1 -1
View File
@@ -16,7 +16,7 @@ from collections.abc import AsyncIterator
from datetime import datetime, timezone
from typing import Any
from mirage.core.discord._client import discord_get
from mirage.core.discord.client import discord_get
from mirage.core.discord.config import DiscordConfig
from mirage.core.discord.paginate import after_id_pages
from mirage.core.discord.render import history_jsonl_bytes
+1 -1
View File
@@ -15,7 +15,7 @@
from collections.abc import AsyncIterator
from typing import Any
from mirage.core.discord._client import discord_get
from mirage.core.discord.client import discord_get
from mirage.core.discord.config import DiscordConfig
from mirage.core.discord.paginate import after_id_pages
+1 -1
View File
@@ -15,7 +15,7 @@
from collections.abc import AsyncIterator, Callable
from typing import Any
from mirage.core.discord._client import discord_get
from mirage.core.discord.client import discord_get
from mirage.core.discord.config import DiscordConfig
+2 -2
View File
@@ -14,8 +14,8 @@
from typing import Any
from mirage.core.discord._client import (discord_delete, discord_patch,
discord_post)
from mirage.core.discord.client import (discord_delete, discord_patch,
discord_post)
from mirage.core.discord.config import DiscordConfig
+1 -1
View File
@@ -14,7 +14,7 @@
from urllib.parse import quote
from mirage.core.discord._client import discord_put
from mirage.core.discord.client import discord_put
from mirage.core.discord.config import DiscordConfig
+1 -1
View File
@@ -14,7 +14,7 @@
from typing import Any
from mirage.core.discord._client import discord_post
from mirage.core.discord.client import discord_post
from mirage.core.discord.config import DiscordConfig
+1 -1
View File
@@ -14,7 +14,7 @@
from typing import Any
from mirage.core.dropbox._client import DropboxTokenManager, dropbox_rpc
from mirage.core.dropbox.client import DropboxTokenManager, dropbox_rpc
SEARCH_PAGE = 1000
# search_v2 + search/continue_v2 serve at most 10,000 matches total; a
@@ -19,15 +19,14 @@ from typing import Any
import aiohttp
from mirage.core.api.oauth import TokenManager as OAuthTokenManager
from mirage.core.dropbox.constants import (DROPBOX_API_BASE,
DROPBOX_CONTENT_BASE,
DROPBOX_TOKEN_URL,
TOKEN_BUFFER_SECONDS)
from mirage.resource.dropbox.config import DropboxConfig
from mirage.resource.secrets import reveal_secret
from mirage.utils.ranges import ByteWindow, range_header, window_of
DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token"
DROPBOX_API_BASE = "https://api.dropboxapi.com/2"
DROPBOX_CONTENT_BASE = "https://content.dropboxapi.com/2"
TOKEN_BUFFER_SECONDS = 300
class DropboxApiError(RuntimeError):
+18
View File
@@ -0,0 +1,18 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token"
DROPBOX_API_BASE = "https://api.dropboxapi.com/2"
DROPBOX_CONTENT_BASE = "https://content.dropboxapi.com/2"
TOKEN_BUFFER_SECONDS = 300
+1 -1
View File
@@ -16,8 +16,8 @@ import time
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.context import invalidate_after_write, invalidate_ancestors
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import copy_path, delete_path, get_metadata
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.observe.context import record
from mirage.types import PathSpec
+1 -1
View File
@@ -13,8 +13,8 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.dropbox import DropboxAccessor
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import get_metadata
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.types import PathSpec
+1 -1
View File
@@ -16,8 +16,8 @@ import time
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.context import invalidate_after_write, invalidate_ancestors
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import create_folder, get_metadata
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.observe.context import record
from mirage.types import PathSpec
+2 -2
View File
@@ -19,8 +19,8 @@ from functools import partial
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore, IndexEntry
from mirage.cache.index.warm import entry_or_warm
from mirage.core.dropbox._client import (DropboxApiError, dropbox_download,
dropbox_download_stream)
from mirage.core.dropbox.client import (DropboxApiError, dropbox_download,
dropbox_download_stream)
from mirage.core.dropbox.readdir import readdir
from mirage.types import PathSpec
from mirage.utils.errors import enoent
+1 -1
View File
@@ -17,8 +17,8 @@ from typing import Any
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore, IndexEntry
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import get_metadata, list_folder
from mirage.core.dropbox.client import DropboxApiError
from mirage.types import PathSpec
from mirage.utils.errors import listing_error
from mirage.utils.key_prefix import mount_prefix_of
+1 -1
View File
@@ -16,9 +16,9 @@ import time
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.context import invalidate_after_unlink, invalidate_ancestors
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import (delete_path, get_metadata, list_folder,
move_path)
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.observe.context import record
from mirage.types import PathSpec
+1 -1
View File
@@ -16,8 +16,8 @@ import time
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.context import invalidate_after_unlink, invalidate_ancestors
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import delete_path
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.observe.context import record
from mirage.types import PathSpec
+1 -1
View File
@@ -18,8 +18,8 @@ import time
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.context import invalidate_after_unlink, invalidate_ancestors
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import delete_path, get_metadata, list_folder
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.observe.context import record
from mirage.types import PathSpec
+1 -1
View File
@@ -15,8 +15,8 @@
import logging
from mirage.accessor.dropbox import DropboxAccessor
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import search_files
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key, mount_prefix_of
+1 -1
View File
@@ -17,8 +17,8 @@ from typing import Any
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import get_metadata
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.core.dropbox.readdir import readdir
from mirage.types import FileStat, FileType, PathSpec
+1 -1
View File
@@ -16,8 +16,8 @@ import time
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.context import invalidate_after_unlink, invalidate_ancestors
from mirage.core.dropbox._client import DropboxApiError
from mirage.core.dropbox.api import delete_path, get_metadata
from mirage.core.dropbox.client import DropboxApiError
from mirage.core.dropbox.paths import dropbox_path_of
from mirage.observe.context import record
from mirage.types import PathSpec

Some files were not shown because too many files have changed in this diff Show More