호환성 정책

1.0부터 django-wireview는 여기서 공개라고 적은 것을 마이너·패치 릴리스에서 깨지 않는다. 공개가 아닌 것은 예고 없이 바뀔 수 있다. 1.0 전(0.x)에는 마이너 릴리스도 호환을 깰 수 있다.

무엇이 공개인가#

Python API: wireview의 __all__#

공개 Python API는 최상위 패키지 wireview가 __all__로 내보내는 이름뿐이다(#98).

from wireview import Component, LiveComponent, JS, mount

wireview.core.*, wireview.features.*, wireview.consumer, wireview.repository 같은 하위 모듈은 모두 내부다. 그 안의 이름을 직접 import하면 마이너 릴리스에서 경로가 바뀌어도 알림이 없다. 필요한 이름이 __all__에 없으면 이슈로 요청한다.

telemetry는 모듈 자체가 공개 이름이다(from wireview import telemetry). 시그널을 모아 둔 네임스페이스이고, 그 모듈의 __all__이 공개 범위다.

Component와 LiveComponent는 이름뿐 아니라 멤버도 약속한다. 무엇이 공개인지는 Component API가 정본이다. 밑줄이 없어도 거기 없는 멤버는 내부다.

마이너 릴리스는 Component와 LiveComponent에 멤버를 더할 수 있다. 더한 이름은 프레임워크의 것이 되므로, 사용자 컴포넌트에 같은 이름의 메서드가 있으면 그 메서드는 그날부터 이벤트 핸들러가 아니다(노출 규칙, Component API). 그 이름에 바인딩한 {% on %}은 렌더할 때 오류로 알려 주고, manage.py check는 그런 메서드를 wireview.W018로 미리 알린다. 같은 이름의 필드는 멤버를 가리며 Pydantic이 클래스를 정의할 때 UserWarning("shadows an attribute in parent")을 낸다. 그래서 새 멤버는 항상 CHANGELOG.md에 이름과 함께 적고, 흔히 쓰일 만한 이름이면 그 사실을 따로 적는다. 1.0 직전(rc4 뒤)에 on_upload_complete가 같은 방식으로 프레임워크의 것이 되었다 — 문서가 가르친 그 이름의 콜백이 클라이언트 이벤트로도 열려 있었기 때문이다.

WireviewMeta는 타입 주석용 이름으로만 공개다(wire: WireviewMeta). 생성자와 멤버는 아래 self.wire 행이 정한 넷만 공개다.

tests/test_public_api.py가 이 경계를 지킨다. __all__, 지연 로딩 표, 타입 검사용 import가 서로 같은지 보고, 사용자용 문서(README.md, docs/features/, docs/tutorials/, 앱 개발자용 스킬)와 examples/가 from wireview import ...로만 import하며 그 이름이 모두 공개인지 본다.

통합 지점#

이름을 import하지 않고 경로나 문자열로 가리키는 것도 공개다.

무엇 형태
Django 앱 INSTALLED_APPS의 "wireview"
URL include("wireview.urls"), wireview.urls.websocket_urlpatterns, 그리고 둘이 여는 경로 /__wireview__(WebSocket)와 /__wireview_upload__/…(업로드). 프록시·CSP connect-src·방화벽이 이 경로를 적으므로 경로도 약속이다. 루트에 마운트해야 한다 — 클라이언트와 업로드 토큰이 이 경로를 루트에서 찾으므로, 접두사 아래(path("app/", include(...)))나 하위 경로 배포(SCRIPT_NAME)에서는 동작하지 않는다
템플릿 태그 {% load wireview %}와 그 태그들
설정 settings.WIREVIEW의 키 (wireview/settings.py의 DEFAULT)
관리 명령 wireview_stubs, wireview_lsp, wireview_agent_setup, wireview_upload_gc와 문서화된 옵션. wireview_lsp의 출력 JSON은 그 안의 version 필드로 따로 관리한다 — 키를 더하면 minor, 있던 키의 뜻이나 모양을 바꾸면 major를 올리고, 읽는 쪽은 major를 본다(editor-support). editors/vscode의 편집기 확장은 이 JSON만 읽는 별개의 산출물이라 라이브러리의 공개 API가 아니고 버전도 따로 매긴다
스타터 템플릿 설치된 패키지의 wireview/project_template/ 디렉터리(startproject --template의 대상, 시작하기 튜토리얼). 약속은 그 경로와, 만든 프로젝트가 manage.py check에 아무것도 보고하지 않는다는 것이다. 만들어진 파일은 사용자의 코드이므로 안의 내용은 릴리스마다 바뀔 수 있다
시스템 체크 id wireview.W001~. 없앤 번호는 다시 쓰지 않는다
컴포넌트 클래스 설정 class Meta:의 키(ComponentOptions의 필드)와 get_subscriptions()
템플릿 컨텍스트 컴포넌트 템플릿의 this, 슬롯의 let 이름
훅 파일 위치 앱의 static/<app_label>/hooks/*.js (hooks)
모델 채널 이름 AUTO_BROADCAST가 알리는 채널: <app_label>.<model>, <app_label>.<model>.<pk>, 가리키는 행의 <app_label>.<model>.<pk>.<related_name>, m2m은 양쪽 행의 <app_label>.<model>.<pk>.<field>(어느 쪽에서 바꿨든 같다). 밑줄은 하이픈이 된다. 알리는 모델은 senders에 적은 것뿐이고(비우면 없다), m2m은 바꾼 쪽의 모델이 senders에 있을 때 알린다. senders는 집합 또는 모델→필드 매핑이고, 집합은 모든 필드를 보낸다
self.wire params, redirect_to, replace_to, push_to만(navigation). 나머지는 프레임워크 내부이고, 같은 일은 Component의 메서드(put_flash, push_js, push_title, defer 등)로 한다
클라이언트 window.wireview의 문서화된 멤버, docs/features/에 문서화된 wire-* DOM 속성·wireview-* CSS 클래스·wireview:* DOM 이벤트, 훅 객체의 문서화된 멤버(hooks). 접두사가 맞는다고 공개가 아니다 — 아래 "내부" 참고
테스트 도구 mount()가 돌려주는 MountedComponent의 문서화된 멤버(testing). 그 view.wire는 컴포넌트의 self.wire와 같은 범위만 공개다. sent_messages·stream_ops의 항목과 render_diff()의 diff는 와이어 메시지라 모양은 공개가 아니다 — render_diff()는 None인지만 약속한다

내부 (공개처럼 보이지만 아닌 것)#

무엇 왜
템플릿 태그가 출력하는 마크업 계약은 태그다. {% on %}이 내는 wire-on-* 속성과 그 JSON 값, 업로드 태그가 내는 wire-upload·wire-upload-select·wire-upload-drop·wire-preview(값 name:ref), {% tag_header %}가 내는 wireview-component·wireview-live 표식과 data-name·data-state·data-is-live·data-parent, join이 실패한 컴포넌트에 붙는 wire-join-failed, {% wireview_header %}의 <meta>
훅 객체의 __ 멤버 __hookId, __manager 등
window.wireview.debug의 반환값 개발 도구다. 함수 이름은 남기지만 돌려주는 객체의 모양은 약속하지 않는다
static의 번들 밖 파일 wireview.min.js만 페이지가 싣는다. wireview.js, *.mjs, types.d.ts, .map은 빌드 재료다
하위 모듈 위 Python API 절

와이어 프로토콜#

서버와 브라우저 사이의 메시지 형태는 공개가 아니다. 대신 번들과 서버가 버전이 달라도 페이지가 깨지지 않는다는 것을 약속한다. 규칙은 wire-protocol §7이다.

없애는 절차#

공개 API를 없앨 때는 한 메이저 버전 동안 동작하게 두고 경고를 낸다.

  1. 옛 이름은 계속 동작하고, 쓰일 때 wireview.WireviewDeprecationWarning을 낸다. 경고는 새 이름과 제거될 버전을 말한다(wireview/deprecation.py의 warn_deprecated).
  2. 다음 메이저 릴리스에서 지운다.
  3. 두 단계 모두 CHANGELOG.md에 적는다.

WireviewDeprecationWarning은 DeprecationWarning의 하위 클래스다. 그래서 파이썬의 기본 필터 아래에서는 운영 서버의 로그에 보이지 않는다 — 기본 필터는 __main__에서 난 DeprecationWarning만 보여 준다. 폐기 예정인 것을 쓰는지는 테스트(pytest는 보여 준다)나 python -W default::DeprecationWarning·python -X dev로 띄운 서버에서 확인한다. 테스트에서 경고를 오류로 바꾸려면:

import warnings

from wireview import WireviewDeprecationWarning

warnings.simplefilter("error", WireviewDeprecationWarning)

현재 진행 중인 것:

옛 것 새 것 제거
wireview.component 모듈 from wireview import ...로 Component·WireviewMeta·broadcast. 이 모듈이 함께 내보내던 ComponentNotFound·MessagePayload는 공개였던 적이 없어 대체 없이 같이 없어진다 2.0
테스트의 view.wire.broadcasts view.broadcasts 2.0
테스트의 view.wire.presence_broadcasts view.presence_broadcasts 2.0
DOM 이벤트 upload:added·progress·complete·error·cancel wireview:upload-added 등 (둘 다 나간다) 2.0

지원 범위#

Django가 보안 지원하는 Django 버전과, 그 버전들이 지원하는 Python 중 3.12 이상을 지원한다. 지금은 Django 5.2 LTS·6.0·6.1, Python 3.12·3.13·3.14다.

  • Django가 한 버전의 지원을 끝내면 그다음 마이너 릴리스에서 그 버전을 뺀다. 메이저를 올리지 않는다 — Django가 이미 끝낸 버전을 붙잡는 것은 사용자를 지키는 일이 아니다.
  • 새 Django·Python 버전은 매트릭스를 통과하면 패치 릴리스로 더한다.
  • Django에 상한을 두지 않는다(django>=5.2). 그래서 매트릭스를 아직 통과하지 않은 새 Django(예: 7.0)도 설치된다. 그 조합은 위 줄의 패치 릴리스가 나오기 전까지 지원 범위 밖이다. 검증된 조합에 머물려면 프로젝트에서 상한을 직접 건다(django<7).
  • 정본은 pyproject.toml(의존성·classifier)과 .github/workflows/ci.yml의 매트릭스다. 같은 격자를 로컬에서 make test-matrix로 돈다(CI는 수동으로만 돈다).
  • 의존성의 하한도 약속이다. pyproject.toml이 허용하는 가장 오래된 조합(지금은 Django 5.2, channels 4.2.1, pydantic 2.7.0)을 make test-lowest가 Python 3.12에서 설치해 스위트를 돈다. CI의 test-lowest 잡이 같은 것이다. 하한 조합이 깨지면 하한을 올리고 CHANGELOG.md에 적는다(#132). 반대쪽 끝, 새 설치가 받는 최신 해는 make test-latest다.
  • 설치되지 않거나 지원하는 기능(채널 레이어 포함)에서 동작하지 않던 하한을 올리는 것은 패치 릴리스다. 그 하한에 머문 사용자는 그 기능이 동작하는 조합을 갖고 있지 않았다. 지원하는 모든 기능에서 동작하던 하한을 올리는 것은 마이너 릴리스다.
  • channels의 하한은 tests/test_nats_layer.py가 지킨다. 그 테스트는 nats-server 바이너리가 없으면 로컬에서는 건너뛰지만 CI(CI 환경 변수)에서는 실패한다. CI의 단위 테스트 잡들은 E2E와 같은 nats 이미지에서 바이너리를 꺼내 쓴다.

채널 레이어#

프로세스가 둘 이상이면 브로커가 있는 레이어가 필요하다. 지원하는 것은 아래 둘이고, 둘 다 E2E 스위트 전체를 돈다(#130).

레이어 패키지 검증한 버전 브로커 여러 프로세스 가득 찼을 때
NATS channels-nats 0.6.1 nats-server 2.14 된다 받는 쪽 프로세스가 channels_nats 로거에 WARNING을 남기고 버린다
Redis channels-redis 4.3.0 Redis 8 된다 send는 ChannelFull, group_send는 channels_redis.core 로거에 INFO를 남기고 버린다
InMemory channels에 포함 — 없음 안 된다 group_send가 아무 흔적 없이 버린다
  • InMemory는 단일 프로세스 전용이다. 여러 프로세스에 두면 브로드캐스트가 같은 프로세스의 연결에만 닿고 오류는 나지 않는다. 개발 서버와 단일 프로세스 배포에만 쓴다.
  • 검증한 버전은 uv.lock이 고정한 버전이다. CI의 E2E 잡이 레이어마다 한 번씩 그 버전으로 돈다 (make ci-test-e2e LAYER=nats|redis, 브로커는 표의 릴리스 태그 이미지를 서비스 컨테이너로 띄운다). 로컬에서는 make test-e2e(NATS)와 make test-e2e LAYER=redis(REDIS_URL의 redis-server)다.
  • channels-nats는 channels 4.2.1 이상에서만 동작한다. 4.2.1에서 생긴 require_valid_channel_name을 부른다. 0.2.1부터는 자신도 channels>=4.2.1을 선언하지만 0.2.0은 channels>=4라고 선언했고 여전히 설치된다. django-wireview의 하한이 channels>=4.2.1인 이유다(#132).
  • channels-nats 0.7.0부터는 Python 3.13 이상이 필요하다. 그래서 표의 버전은 Python 3.12에서도 설치되는 0.6.x다. 저장소의 dev 의존성이 channels-nats<0.7로 묶여 있어 uv.lock이 Python 버전에 따라 갈라지지 않는다 — 갈라지면 CI의 E2E 레인(Python 3.12)이 한 버전만 검증한다(#150). 3.13 이상에서 pip가 고르는 0.7 이후 버전은 이 표가 검증한 것이 아니다. 표의 버전을 쓰려면 channels-nats<0.7로 고정한다.
  • channels-nats 0.3.0에서 와이어 형식이 바뀌었다(본문 msgpack 고정, 일반 채널의 그룹 구독을 큐 그룹으로). 0.2.x와 0.3.0 이상의 프로세스를 한 NATS에 섞으면 서로의 메시지를 읽지 못하므로 모든 프로세스를 함께 올린다. 같은 릴리스에서 CONFIG의 serializer 키가 없어졌다. 적어 두었다면 지운다(남아 있으면 레이어 생성이 TypeError로 실패한다).
  • AUTO_BROADCAST의 필드를 줄이는 설정(senders 매핑)은 1.0.0rc4 이전 프로세스가 남아 있지 않을 때 켠다. 그 프로세스는 필드 일부만 담긴 알림을 받으면 빠진 필드를 기본값으로 채운다. 1.0부터는 받는 쪽이 빠진 필드를 deferred로 둔다(#153)(배포).
  • 유실을 세는 방법은 배포 가이드의 관측 절이다.
  • uv.lock에서 레이어 패키지를 올리면 두 E2E 레인을 돌리고 이 표의 버전을 같이 고친다. tests/test_supported_versions.py가 표와 uv.lock, CI의 E2E 매트릭스가 같은 레이어를 말하는지, ci.yml이 쓰는 브로커 이미지 태그가 표의 브로커 릴리스와 같은지 본다.