본문으로 건너뛰기

BackendV2Client

Synapse Backend API v2 (/v2/*) 용 sync + async 클라이언트입니다.

v2 클라이언트는 기존 v1 클라이언트(BackendClient)와 나란히 존재하며 opt-in 방식입니다. 한 프로세스 안에서 둘을 함께 사용할 수 있습니다. v2 는 v1 과 세 가지 핵심 지점에서 다릅니다.

  • Cursor 페이지네이션{next, previous, results} (count 필드 없음).
  • Auth 헤더 포맷SYNAPSE-ACCESS-TOKEN: syn_<token> 와 raw SYNAPSE-Tenant: <code> (기본값에서 Token prefix 미사용).
  • 리소스 속성 접근client.list_projects(...) 가 아닌 client.projects.list(...) 형식.

Synapse Backend 릴리즈 v2026.1.6 (API contract 2.0.0) 을 타겟합니다. 백엔드가 새 호환 릴리즈를 배포하면 make sync-v2-all 로 스키마를 재동기화 하고 synapse_sdk.clients.backend_v2.SYNAPSE_BACKEND_VERSION 상수를 업데이트하세요.

Overview

v2 백엔드는 22 개 도메인에 걸쳐 102 paths / 149 operations 를 노출합니다. SDK 는 현재 23 개 도메인 을 구현합니다 — 주요 CLI 워크플로우에 필요한 P0 15 개 + Phase 3 에서 추가된 P1 8 개:

GroupResources
Auth / infraauth, tenants, tokens, schemas
Datadata_collections (+ nested groups), data_units, data_files
MLground_truth_datasets (+ nested versions), ground_truths, models, experiments
Workflowprojects (+ nested tags), tasks, assignments, reviews
Workspace (P1)members, groups (top-level — data-collection groups 와 다름), validation_scripts, workshops
Execution (P1)jobs, job_logs, plugins, plugin_releases

전체 엔드포인트 카탈로그와 v1↔v2 diff 표 (v1 ↔ v2 endpoint diff 참조) 는 INVENTORY.md 에 있습니다.

Constructor

BackendV2Client(
base_url: str,
*,
access_token: str | None = None,
drf_token: str | None = None,
tenant: str | None = None,
tenant_token_prefix: bool = False,
timeout: dict[str, int] | None = None,
allow_insecure: bool = False,
)

Parameters

ParameterTypeRequiredDefault설명
base_urlstrYes백엔드 호스트. /v2 prefix 는 포함하지 마세요 — 리소스가 자동으로 추가합니다.
access_tokenstr | NoneNoNonesyn_* access token. SYNAPSE_BACKEND_V2_ACCESS_TOKEN 환경변수로 폴백.
drf_tokenstr | NoneNoNone/users/login/ 의 DRF token. SYNAPSE_BACKEND_V2_DRF_TOKEN 환경변수로 폴백.
tenantstr | NoneNoNone테넌트 코드. SYNAPSE_BACKEND_V2_TENANT 환경변수로 폴백.
tenant_token_prefixboolNoFalseTrue 일 때 v1 호환을 위해 SYNAPSE-Tenant: Token <code> 형식으로 전송.
timeoutdict[str, int] | NoneNo{'connect': 5, 'read': 15}Connect / read timeout (초).
allow_insecureboolNoFalsebase_url 이 평문 HTTP 일 때 경고를 억제.

AsyncBackendV2Client 는 동일한 인자를 받습니다. 단, timeout 은 dict 대신 httpx.Timeout 인스턴스를 받습니다.

Quick start

Synchronous

from synapse_sdk.clients.backend_v2 import BackendV2Client

client = BackendV2Client(
'https://api.test.synapse.sh',
access_token='syn_xxxxx',
tenant='acme',
)

# Cursor 페이지네이션 — 단일 페이지
page = client.projects.list(category='image', per_page=50)
for project in page.results:
print(project.id, project.title)
if page.next:
next_page = client.projects.list(cursor=page.next.split('cursor=')[-1])

# 모든 페이지 lazy 스트리밍
for project in client.projects.list(category='image', per_page=50, list_all=True):
print(project.id, project.title)

Asynchronous

import asyncio
from synapse_sdk.clients.backend_v2 import AsyncBackendV2Client

async def main() -> None:
async with AsyncBackendV2Client(
'https://api.test.synapse.sh',
access_token='syn_xxxxx',
tenant='acme',
) as client:
page = await client.projects.list(per_page=50).first_page()
async for project in client.projects.list(per_page=50):
print(project.id, project.title)

asyncio.run(main())

Authentication

v2 백엔드는 네 가지 인증 스킴을 지원합니다. 클라이언트는 전달된 인자/환경 변수에 따라 헤더를 조합합니다.

HeaderValueSource
SYNAPSE-ACCESS-TOKENsyn_<token>access_token= 인자 또는 SYNAPSE_BACKEND_V2_ACCESS_TOKEN env
AuthorizationToken <drf>drf_token= 인자, SYNAPSE_BACKEND_V2_DRF_TOKEN env, 또는 client.auth.login() 반환값
SYNAPSE-Tenant<code> (기본) 또는 Token <code> (legacy)tenant= 인자 또는 SYNAPSE_BACKEND_V2_TENANT env. tenant_token_prefix=True 시 legacy prefix 적용
tenant_token_prefix 기본값 — v2-native

api.test.synapse.sh 에서 OQ-1 라이브 검증을 마친 이후 (Open items pending live verification 참조), tenant_token_prefix 의 기본값은 False 입니다 — SDK 가 raw 테넌트 코드(SYNAPSE-Tenant: <code>) 를 전송합니다. dev 백엔드는 양쪽 포맷을 모두 수용하므로, 아직 Token <code> prefix 를 기대하는 v1 스타일 배포는 tenant_token_prefix=True 로 명시적으로 opt-in 할 수 있습니다.

client = BackendV2Client(
'https://api.example.com',
access_token='syn_...',
tenant='acme',
tenant_token_prefix=True, # ⇒ "SYNAPSE-Tenant: Token acme"
)

mask_token() 헬퍼는 시크릿을 로그-안전 형식으로 변환합니다.

from synapse_sdk.clients.backend_v2 import mask_token

mask_token('syn_12345678901234') # 'syn_***1234'
mask_token(None) # '<unset>'

Login → access-token bootstrap

client = BackendV2Client('https://api.test.synapse.sh', tenant='acme')

# 1. 자격증명을 DRF token 으로 교환 (클라이언트에 자동 저장).
client.auth.login(email='[email protected]', password='...')

# 2. 장기간 유효한 access token 발급.
created = client.tokens.create({'description': 'sdk-cli'})
print(created.token) # 'syn_xxxxx' — 생성 시점에만 평문 반환

# 3. 클라이언트를 새 토큰으로 전환.
client.set_access_token(created.token)
client.set_drf_token(None)

Pagination

v2 백엔드는 DRF CursorPagination 을 사용합니다. transport 가 {data, meta} envelope 을 unwrap 한 뒤 (Response envelope handling 참조), 리소스 모듈은 다음 CursorPage 형태를 받습니다.

{
"next": "<cursor-or-null>",
"previous": "<cursor-or-null>",
"results": [...]
}

count 필드는 없습니다. 클라이언트는 페이지네이션을 다음과 같이 노출합니다.

호출 형태반환
client.projects.list(per_page=50)CursorPage[ProjectV2List] (단일 페이지)
client.projects.list(per_page=50, list_all=True)Iterator[ProjectV2List] (모든 페이지 스트리밍)
await client.projects.list(per_page=50).first_page() (async)CursorPage[ProjectV2List]
async for project in client.projects.list(per_page=50) (async)모든 페이지 스트리밍

총 개수가 필요한 경우 사용자 코드에서 카운터를 직접 구현해야 합니다.

Response envelope handling

v2 백엔드는 모든 성공 응답을 {"data": ..., "meta": ...} envelope 으로 래핑합니다. transport (BackendV2Client._request / AsyncBackendV2Client._request) 는 모든 성공 페이로드에 대해 unwrap_envelope 를 호출하여 리소스 모듈이 legacy 형태를 그대로 소비할 수 있게 합니다.

Backend payloadUnwrap 후
{"data": [...], "meta": {"pagination": {"next_cursor": "...", "previous_cursor": null, "per_page": 50}}}{"results": [...], "next": "...", "previous": null}
{"data": {"id": 1, ...}, "meta": {"request_id": "..."}}{"id": 1, ...}
{"data": null, "meta": {...}}null
루트에 data 또는 meta하나라도 누락된 페이로드변경 없이 통과 (forward-compat passthrough)

unwrap 은 엄격하게 조건부 입니다 — 루트에 datameta 가 모두 존재할 때만 트리거됩니다. v1 스타일 페이로드, 픽스처, envelope 미적용 프록시는 그대로 통과합니다.

meta.request_id 는 unwrap 이 meta 를 폐기하기 전에 per-context 슬롯에 캡처되며, client.last_request_idclient.capture_request_ids() 로 로그 상관관계 추적이 가능합니다.

Validation & Models

각 리소스는 OpenAPI 스키마 이름을 따른 Pydantic 모델을 반환합니다.

Suffix용도
*V2ListList 응답 (slim payload)
*V2DetailRetrieve / create / update 응답
*V2CreateRequestPOST body
Patched*V2CreateRequestPATCH body

response_model= / request_model= kwargs

transport 헬퍼 (client._get, client._post, client._put, client._patch, client._delete 와 async 대응 메서드) 는 두 개의 선택적 Pydantic kwarg 를 받습니다. 리소스 모듈이 Model.model_validate(...) 를 직접 호출하는 대신 이 kwarg 에 의존할 수 있습니다.

# Before — 메서드마다 수동 검증
return ProjectV2Detail.model_validate(self._get(f'/v2/projects/{pid}/'))

# After — kwarg 기반 (리소스 메서드가 한 줄로 축약됨)
return self._get(f'/v2/projects/{pid}/', response_model=ProjectV2Detail)
# POST 도 Pydantic 인스턴스를 직접 받을 수 있음
return self._post(
'/v2/projects/',
data=ProjectV2CreateRequest(title='demo'), # sync 는 `data=`
request_model=ProjectV2CreateRequest,
response_model=ProjectV2Detail,
)
# Async 는 `data=` 대신 `json=` 을 사용합니다.

동작:

  • response_model=Model — raw dict 가 아닌 완성된 Model 인스턴스를 반환.
  • request_model=Model — Pydantic 인스턴스 또는 dict 를 받으며, 둘 다 model_dump(by_alias=True, exclude_none=True, mode='json') 으로 직렬화됩니다. 이는 기존 resources/_helpers.model_dump 헬퍼와 동일한 wire 규약입니다. caller 가 Pydantic 인스턴스를 보내든 dict 를 보내든 wire shape 은 동일합니다.
  • 응답 디코딩 중 발생한 pydantic.ValidationErrorsynapse_sdk.exceptions.ValidationError 로 wrap 됩니다. 원본 예외는 __cause__ 로 보존됩니다.
  • 요청 검증 중 발생한 pydantic.ValidationError 는 그대로 전파됩니다 — caller 가 제공한 데이터는 디버깅을 위해 pydantic 의 풍부한 에러 정보가 유용하기 때문입니다 (의도적 비대칭).
  • 두 kwarg 모두 생략하면 transport 동작은 변경되지 않습니다.
Phase 2 마이그레이션 완료

SYN-6854 번들에서 read 와 write 도메인의 60+ 리소스 메서드 전부가 response_model= / request_model= kwarg 패턴으로 전환되었습니다. 호출자 시그니처와 wire shape 은 변하지 않았습니다 — 순수 내부 변경이지만, 이제 pydantic.ValidationError 가 SDK 호출 경로에서 누설되지 않습니다. synapse_sdk.exceptions.ValidationError 를 catch 하고 raw pydantic detail 이 필요하면 __cause__ 를 확인하세요.

보안 — 응답 페이로드는 ValidationError.detail 로 누설되지 않습니다

mixin 은 wrap 전에 pydantic.ValidationError.errors(include_input=False) 를 호출하여 각 에러 엔트리에서 raw 응답 본문을 제거합니다. 응답에는 시크릿 (예: tokens.create() 가 반환하는 평문 syn_* token) 이 포함될 수 있고, SDK 사용자는 종종 ValidationError.detail 을 외부 로깅 sink 로 전달합니다. 완전한 진단 정보는 exc.__cause__ (원본 pydantic.ValidationError) 를 통해 그대로 접근할 수 있습니다.

from synapse_sdk.exceptions import ValidationError as SdkValidationError
import pydantic

try:
project = client.projects.retrieve(42)
except SdkValidationError as exc:
# 단일 SDK 고유 예외 타입 — bare pydantic 은 절대 노출되지 않음
print(exc.detail['model']) # 'ProjectV2Detail'
print(exc.detail['detail']) # 에러 dict 리스트 ('input' 키 없음)
if isinstance(exc.__cause__, pydantic.ValidationError):
# 진단용 전체 pydantic detail (raw input 포함).
for err in exc.__cause__.errors():
print(err['loc'], err['msg'])

kwarg 경로는 BackendV2ValidationMixin 에 구현되어 있고 sync 와 async 클라이언트가 MRO 를 통해 공유합니다.

Error handling

BackendV2Client 는 v1 클라이언트와 동일한 표준 SDK 예외 계층 (synapse_sdk.exceptions) 을 raise 합니다.

from synapse_sdk.exceptions import (
AuthenticationError,
AuthorizationError,
NotFoundError,
RateLimitError,
ServerError,
ValidationError,
)

try:
project = client.projects.retrieve(42)
except NotFoundError:
...
except AuthenticationError:
# 재로그인 또는 access token 갱신.
...
except ValidationError as e:
# 400 / 422 — 백엔드 `detail` 페이로드가 예외에 보존됨.
# `response_model=` 디코딩 실패 시에도 raise — Validation & Models 참조.
...

Resource map

동일한 표면적이 BackendV2Client (sync) 와 AsyncBackendV2Client (async) 양쪽에서 제공됩니다.

Top-level resources

AttributeEndpointsNotable methods
client.authPOST /users/login/login(email, password, set_drf_token=True)
client.tenantsGET /v2/tenants/(...)list, retrieve(code)
client.tokens*/v2/tokens/(...)list, create, retrieve(id), destroy(id)
client.schemasGET /v2/schemas/...annotation_configurations(), file_specifications()
client.data_collections*/v2/data-collections/(...)full CRUD + membership + nested groups(dc_id)
client.data_units*/v2/data-units/(...)full CRUD + permission trio
client.data_filesGET /v2/data-files/(...)list, retrieve(id)
client.ground_truth_datasets*/v2/ground-truth-datasets/(...)full CRUD + nested versions(dataset_id)
client.ground_truthsGET /v2/ground-truths/(...)list, retrieve(id)
client.modelsGET /v2/models/(...)list, retrieve(id)
client.experiments*/v2/experiments/(...)full CRUD + membership family
client.projects*/v2/projects/(...)full CRUD + membership + nested tags(project_id)
client.tasks*/v2/tasks/(...)full CRUD + permission trio
client.assignmentsGET /v2/assignments/(...)list, retrieve(id) + permission trio
client.reviews*/v2/reviews/(...)list, create, retrieve(id) + permission trio
client.members*/v2/members/(...)list, retrieve(id), create
client.groups*/v2/groups/(...)full CRUD (top-level workspace groups)
client.validation_scripts*/v2/validation-scripts/(...)full CRUD
client.workshops*/v2/workshops/(...)list, retrieve(id) + permission trio
client.jobsGET /v2/jobs/(...)list, retrieve(id) (UUID-keyed)
client.job_logsGET /v2/job-logs/(...)list, retrieve(id)
client.pluginsGET /v2/plugins/(...)list, retrieve(id)
client.plugin_releasesGET /v2/plugin-releases/(...)list, retrieve(id)

Membership family

Top-level 리소스 (projects, experiments, data_collections) 는 다음을 노출합니다.

.check_permission(id, permission='view')
.permissions(id)
.roles(id)
.grantable_roles(id)
.default_roles()
.invite(id, payload)
.get_join_requests(id)
.request_join(id, payload)
.confirm_join_request(id, payload)
.my_join_requests()
.my_join_request_detail(join_request_id)

Permission trio

자식 / leaf 리소스 (tasks, data_units, assignments, reviews, workshops, nested groups (data-collections 하위), nested versions, nested tags) 는 다음만 노출합니다.

.check_permission(id, permission='view')
.permissions(id)
.roles(id)

Membership helpers — typed payloads

Top-level 리소스 (projects, experiments, data_collections) 의 membership 메서드는 mutating verb 에 대해 타입이 지정된 payload 모델 을 받습니다.

from synapse_sdk.clients.backend_v2.models import (
MemberRoleJoinRequestCreateRequest,
PatchedMemberRoleJoinRequestConfirmRequest,
TargetRoleCreateRequest,
)

# 1) 대상 사용자/그룹에 role 을 부여하면서 invite.
client.projects.invite(
project_id,
TargetRoleCreateRequest(target=42, role=7),
)

# 2) 기존 프로젝트 가입 요청.
client.projects.request_join(
project_id,
MemberRoleJoinRequestCreateRequest(role=7, message='please add me'),
)

# 3) 가입 요청 confirm / reject.
client.projects.confirm_join_request(
project_id,
PatchedMemberRoleJoinRequestConfirmRequest(confirm=True),
)

dict 를 그대로 전달해도 됩니다 — wire shape 은 동일합니다.

helpers 자체는 resources/_membership.py 에 두 @runtime_checkable Protocol (SyncMembershipResource / AsyncMembershipResource) 뒤에 위치합니다. 정적 타입 체커 (mypy, pyright) 와 런타임 가드가 sync↔async cross-wiring 을 거부하므로 coroutine was never awaited 가 코드 리뷰를 슬쩍 통과하지 못합니다.

P1 도메인 quick examples

# Workspace members (top-level /v2/members/)
page = client.members.list(per_page=20)
member = client.members.retrieve(123)

# Workspace groups (top-level — data-collection groups 와 다름)
group = client.groups.create({'name': 'reviewers'})
client.groups.partial_update(group.id, {'name': 'qa-reviewers'})

# Validation scripts
script = client.validation_scripts.retrieve(7)

# Workshops (read-only + permission trio)
ws = client.workshops.retrieve(3)
client.workshops.check_permission(3, permission='view')

# Async jobs (UUID-keyed) 와 console logs
job = client.jobs.retrieve('00000000-0000-0000-0000-000000000001')
for log in client.job_logs.list(job=job.id, list_all=True):
print(log.message)

# Plugins + releases (read-only)
client.plugins.list(per_page=10)
client.plugin_releases.retrieve(42)

Common patterns

자동 생성 모델로 요청 본문 만들기

Pydantic 모델은 Validation & Models 에 정리되어 있습니다. 빠른 예시:

from synapse_sdk.clients.backend_v2.models import ProjectV2CreateRequest

payload = ProjectV2CreateRequest(
title='New project',
category='image',
data_collection=10,
)
project = client.projects.create(payload)

dict 를 그대로 전달해도 됩니다 — 백엔드가 검증합니다.

환경변수로 부트스트랩

import os

os.environ['SYNAPSE_BACKEND_V2_ACCESS_TOKEN'] = 'syn_...'
os.environ['SYNAPSE_BACKEND_V2_TENANT'] = 'acme'

# 세 값 모두 생성자에서 생략 가능.
client = BackendV2Client('https://api.test.synapse.sh')

중첩 리소스 사용

# Data collection groups
client.data_collections.groups(dc_id=7).list(per_page=20)
client.data_collections.groups(dc_id=7).create({'name': 'g1'})

# Ground truth dataset versions
client.ground_truth_datasets.versions(dataset_id=3).list()

# Project tags
client.projects.tags(project_id=42).list(per_page=10)
client.projects.tags(project_id=42).create({'name': 'urgent', 'category': 'project'})

Observability — last_request_id trace

성공한 모든 v2 응답에는 백엔드가 해당 요청에 대해 로깅하는 meta.request_id 가 포함됩니다. transport 는 unwrap_envelopemeta 를 폐기하기 전에, 이를 contextvars.ContextVar (설계상 async-safe / thread-safe) 에 캡처합니다. 두 가지 표면이 노출됩니다.

Surface반환사용처
client.last_request_id (property)str | None현재 컨텍스트에서 관측된 가장 최근 request_id 를 읽기.
client.capture_request_ids() (context manager)None진입 시 슬롯을 reset, 종료 시 외부 값을 복원 — 작업 블록을 자체 request_id 범위로 한정.
project = client.projects.retrieve(42)
print(client.last_request_id) # 예: 'req_01H8...'

with client.capture_request_ids():
page = client.projects.list(per_page=20)
log.info('listed', request_id=client.last_request_id)
# 외부 컨텍스트의 request_id 가 여기서 복원됩니다.

async 클라이언트도 같은 표면을 제공합니다 — last_request_idcapture_request_ids() 모두 AsyncBackendV2Client 에서 사용 가능합니다. ContextVarasyncio.Task 컨텍스트 캡처에 참여하므로 asyncio.gather(call_a(), call_b()) 는 race 가 발생하지 않습니다 — 각 태스크가 자체 last_request_id 를 봅니다.

구현체: observability.py. record_request_idunwrap_envelope 와 동일한 게이트를 사용합니다 (루트 에 datameta 가 모두 있을 때만 트리거) — envelope 미적용 응답은 슬롯 을 건드리지 않으므로 이전 request_id 값이 보존됩니다.

CLI — synapse v2 ...

SDK 는 BackendV2Client 를 감싸는 read-only CLI sub-command 를 제공합니다 — 즉석 작업과 테넌트 smoke 테스트용입니다. 인증은 기존 synapse login 플로우 (~/.synapse/config.json) 와 공유됩니다.

synapse v2 projects list --tenant acme --per-page 20
synapse v2 projects retrieve 42 --tenant acme
synapse v2 tokens list --tenant acme
synapse v2 tokens retrieve 7 --tenant acme
synapse v2 tenants list
synapse v2 tenants retrieve 1
Flag / env용도
--tenant테넌트 코드. SYNAPSE_BACKEND_V2_TENANT 환경변수로 폴백.
--hostAPI 호스트. synapse login 의 호스트가 기본값.
--token / -tAccess token. synapse login 의 토큰이 기본값.
--drf-tokenLegacy DRF token. 보통 생략.
--per-pageList 페이지 크기 (기본 20).

list 명령은 Rich 테이블을 출력하고, projects list 는 추가로 client.last_request_id 를 echo 하므로 백엔드 로그 상관 추적이 셸 명령 한 번에 끝납니다. mutating verb (create / update / destroy) 는 follow-up PR 로 보류됐습니다 — 더 풍부한 입력 (JSON body, confirm prompt, 이중 인증) 이 필요해 smoke MVP 의 표면을 부풀립니다.

CLI 는 synapse_sdk/cli/v2.py 에 위치하며 synapse_sdk.cli.main 에서 cli.add_typer(v2_app, name='v2') 로 등록됩니다.

Performance — lazy imports

BackendV2Client 는 import 비용이 가볍게 설계됐습니다. 두 레이어가 참여합니다.

  1. 리소스 속성 — lazy @property 접근자입니다. client.projects 는 첫 사용 시점에만 리소스 객체를 생성합니다.
  2. Pydantic 모델synapse_sdk.clients.backend_v2.models 는 자동 생성된 _generated 모듈을 PEP 562 __getattr__ 로 re-export 합니다. 1900 줄짜리 _generated 모듈 (137 개 pydantic 클래스) 은 import 시점이 아닌 첫 attribute 접근 시점에 로드됩니다.
# Import-time 작업: 최소 — _generated load 없음.
from synapse_sdk.clients.backend_v2 import BackendV2Client

# _generated import 트리거 (1회, cold ~140ms).
from synapse_sdk.clients.backend_v2.models import ProjectV2List

BackendV2Client 만 사용하고 모델 클래스를 직접 import 하지 않는 워크플로 우는 ~140ms cold-import 비용을 절감합니다. 호출자 관점에서 lazy export 는 투명합니다 — from synapse_sdk.clients.backend_v2.models import ProjectV2List 는 이전과 정확히 동일하게 동작합니다.

Optional dependencies

email-validator 는 Phase 4 에서 런타임 의존성에서 제거 되었습니다.

유일한 EmailStr 사용처 (UserThumbnailInfo.email) 는 read-only 응답 필드입니다 — 백엔드 검증이 권위적이므로 클라이언트 측 RFC 5322 강제는 가치가 없습니다. codegen 후 패치 (scripts/sync_v2_schema.py_patch_email_str) 가 생성된 모든 EmailStr 어노테이션을 str 로 재작성 하면서 max_length=254 제약은 보존하므로, 모델은 email-validator 런타임 의존성 없이도 문서화된 길이를 계속 검증합니다.

UserThumbnailInfo 를 적대적 입력으로 직접 구성하면서 RFC 5322 검증이 필요한 워크플로우라면, email-validator 를 직접 설치해 값을 사전 검증 하세요. 대부분의 호출자는 이 작업이 필요하지 않습니다.

v1 ↔ v2 endpoint diff

INVENTORY.md 의 "Diff vs v1" 섹션은 make sync-v2-all (또는 단독 타겟 make gen-v2-diff) 가 자동 생성합니다. v1 경로는 synapse_sdk/clients/backend/*.py 에서 introspect 하고 v2 스키마와 비교 합니다 (매칭 전에 /v2/ prefix 제거).

BucketCount의미
v1 only31BackendClient 만 노출하는 엔드포인트 — v2 대응이 없음. v1 클라이언트를 계속 사용하세요 (agents, storages, legacy serve_applications/ 등).
v2 only101v2 고유의 신규 표면 — membership / permission trio 라우트, workshops 등.
Shared5양쪽에서 동일한 method+path (예: GET /jobs/, POST /tasks/). 편한 시점에 마이그레이션.

전체 표는 INVENTORY.md 에 있습니다. 백엔드가 새 릴리즈를 배포할 때마다 make sync-v2-all 을 재실행하면 표가 in-place 로 업데이트됩니다.

Migration from v1 (BackendClient)

항목v1v2
Paginationcount/next/previous/results (offset)next/previous/results (cursor; count 없음)
페이지 크기 파라미터page_size=per_page=
Tenant 헤더Synapse-Tenant: Token <code>SYNAPSE-Tenant: <code> (기본; tenant_token_prefix=True 로 opt-back)
Access token 헤더Synapse-Access-Token: Token <token>SYNAPSE-ACCESS-TOKEN: syn_<token>
메서드 형태flat (client.list_projects(...))namespaced (client.projects.list(...))
응답raw dicttyped Pydantic model (*V2List / *V2Detail)

두 클라이언트는 공존 가능합니다. v2 엔드포인트가 사용 워크로드에 가용해질 때마다 호출 지점을 점진적으로 전환하세요.

Schema sync & drift handling

Pydantic 모델과 INVENTORY.md 는 캐시된 OpenAPI 스키마 (schema/openapi.yaml) 에서 파생됩니다. 백엔드가 새 릴리즈를 배포하면 다음 Make 타겟으로 갱신하세요.

make sync-v2-schema       # 스키마만 다운로드 + 캐시
make gen-v2-models # Pydantic 모델 재생성
make gen-v2-inventory # INVENTORY.md 재생성
make sync-v2-all # 전부 실행

스크립트 위치: scripts/sync_v2_schema.py. 기본적으로 api.test.synapse.sh 에서 다운로드합니다 — --host 로 override 가능.

라이브 백엔드와 OpenAPI 의 drift (Path γ)

dev 백엔드가 OpenAPI 스키마에 인코딩되어 있지 않거나 잘못 인코딩된 형태를 때때로 반환하는 경우가 있습니다. 이 때 생성된 모델을 직접 손대는 대신, SDK 는 codegen + override 하이브리드 ("Path γ") 를 사용합니다.

  1. make gen-v2-models 가 캐시된 스키마를 단일 출처로 삼아 datamodel-codegen 을 실행합니다.
  2. scripts/sync_v2_schema.py 가 생성된 트리에 선언적 build-time override 를 적용합니다.
  3. 각 override 는 자가 진단됩니다 — 상위 스키마가 보정되면 override 가 SystemExit 를 raise 하여 contributor 가 drift 가 해소되었음을 인지하고 override 를 제거할 수 있도록 합니다.

현재 트래킹 중인 override:

Drift위치Override
Category10 enum 의 data 멤버 누락 (라이브 백엔드는 category=data 를 반환하지만 스키마가 누락)_patch_enum_drift_ENUM_DRIFT = {'Category10': ['data']}codegen 후 누락된 enum 멤버 추가
JobV2Detail.completed / JobV2List.completednullable+required 로 선언되었으나 스키마 본문에 누락동일 스크립트의 모델 patcher속성을 Optional[...] 로 격상하여 라이브 null 페이로드 검증 통과
새 override 추가 시

스크립트 헤더에 상위 이슈를 문서화하고, 자가 진단 가드 (SystemExit 스타일 assertion) 를 포함하여 스키마가 수정되는 즉시 override 가 코드베이스에서 스스로 제거되도록 하세요. 문서화되지 않은 장기 override 는 안티패턴입니다.

Open items pending live verification

다음 항목들은 자격증명이 필요한 dev 백엔드 액세스를 요구합니다. 클라이언트는 안전한 기본값으로 출하되지만, 테넌트 환경이 다르다면 연결된 노브를 조정하세요.

  • OQ-1 (해결됨 — SYN-6854)SYNAPSE-Tenant 값 형식. dev 백엔드는 raw 테넌트 코드와 Token <code> 를 모두 수용합니다. SDK 기본값은 raw 값 (tenant_token_prefix=False) 입니다. v1 스타일 배포는 tenant_token_prefix=True 로 opt-back 가능합니다.
  • OQ-2 (보류 — 라이브 데이터 필요)per_page 상한. dev 백엔드가 1 ~ 10 000 모든 페이지 크기에 대해 빈 결과 셋과 함께 200 을 반환하여 실제 cap 을 아직 관측할 수 없습니다. SDK 는 cap 을 강제하지 않습니다 — specs/backend-v2-followup-roadmap/ 에서 트래킹.
  • OQ-3 (해결됨 — SYN-6854)tokens.create() 평문 token 노출. dev 백엔드가 create 응답에 평문 syn_* token 을 포함하여 반환합니다 (AccessTokenCreateResponse.token: SecretStr | None).

See also