Python API 레퍼런스

'bootstrap(), open_model(), 컴파일 강력 보호에서 자동으로 동작하는 게이트, 그리고 코드에서 처리해야 할 수도 있는 예외들.'

직접 작성해서 호출하는 런타임 API는 최상위 함수 두 개뿐입니다 — 그 외 나머지(활성화, 라이선싱, 게이트 자체)는 모두 이들 뒤에서 일어납니다. import하거나 사용할 데코레이터는 없습니다 — magiclock build가 라이선스 게이트 검사를 컴파일된 모든 모듈에 자동으로 삽입합니다(컴파일 빌드 참고).

magiclock.bootstrap()

python
magiclock.bootstrap(vault_dir=None, *, app_version="1.0.0") -> MagicLockRuntime

이 머신의 활성화 상태에 대해 라이선스 게이트를 현재 프로세스에서 가동합니다. magiclock build 산출물에서는 이 호출이 진입 모듈에 자동으로 삽입됩니다 — 직접 호출해야 하는 경우는 컴파일하지 않은 일반 스크립트에서, 첫 open_model() 호출 전에 호출할 때뿐입니다.

  • vault_dir — 로컬 활성화 상태가 저장되는 위치를 재정의합니다. 보통은 필요하지 않습니다.
  • app_version — 라이선스에 설정된 max_app_version과 대조해 검사됩니다. 릴리스를 버전으로 잠글 때 의미가 있습니다.
  • 런타임 객체를 반환합니다(직접 필요한 경우는 드물며, 대부분의 코드는 부수 효과를 위해 bootstrap()만 호출합니다).
  • 이 머신이 아직 활성화되지 않았거나, 활성화는 되어 있지만 이 머신에 유효하지 않은 경우 예외를 발생시킵니다(아래 오류 참고).

magiclock.open_model()

python
magiclock.open_model(path, *, vault_dir=None, passphrase=None, key=None) -> bytes

protect-model이 만든 .enc 봉투를 복호화하고 평문 바이트를 반환합니다 — 오직 메모리에만 존재하며 디스크에 기록되지 않습니다.

  • path.enc 파일 경로.
  • passphrase / key — 패스프레이즈 포터블(--passphrase), 키 포터블(--emit-key), 또는 2단계(--bind-machine --passphrase) 산출물용. 키 없는 포터블(기본값)이거나 단순 머신 잠금 산출물이라면 생략하세요.
  • 머신 잠금(--bind-machine) 산출물의 경우 먼저 bootstrap()이 실행되어 있어야 합니다. 포터블 봉투는 그것 없이도 복호화됩니다.
python
import magiclock

magiclock.bootstrap()
weights = magiclock.open_model("model.onnx.enc")

컴파일 강력 보호의 게이트

호출해야 할 함수나 데코레이터가 없습니다 — 머신 잠금(--bind-machine) 또는 클라우드 제어(--web-gate) 빌드에서는 magiclock build가 소스 트리를 순회하며, 컴파일하기 전에 모든 모듈에 게이트 검사(python_protect 역량 확인)를 하나씩 삽입합니다(기본값인 포터블 빌드에는 게이트가 컴파일되지 않습니다 — 그 보호는 네이티브 컴파일 자체입니다).

python
# 여러분의 소스 코드, 여러분이 직접 바꾸는 것은 없습니다:
def export_report(data: str) -> bytes:
    ...

# `magiclock build`가 실제로 컴파일하는 내용(개념적으로는):
current_runtime().require_feature("python_protect")

def export_report(data: str) -> bytes:
    ...

이 검사는 각 모듈당 한 번, 프로세스가 그 모듈을 처음 import할 때 실행됩니다 — 호출마다 실행되는 것이 아닙니다. 삭제 가능한 하나의 공유 데코레이터 뒤에 몰려 있는 대신 컴파일된 모든 모듈에 짜여 들어가 있기 때문에, 공격자는 모듈을 하나씩 찾아서 patch해야 합니다. 진입 모듈이 아닌 모듈의 실패는 일반적인 예외로 그대로 전파됩니다(라이브러리로 import될 수 있기 때문입니다). 진입 모듈의 실패는 그것 없이는 다른 어떤 것도 실행될 수 없으므로, 깔끔한 메시지를 출력하고 종료합니다.

magiclock.revalidate()

python
magiclock.revalidate() -> None

보호 검사를 지금 즉시 다시 실행합니다. 게이트는 임포트 시 모듈마다 한 번만 실행됩니다. 스크립트에는 적절한 비용이지만, 오래 실행되는 프로세스(서버, 워커, 데몬)에서는 시작 시점에 내린 판단을 계속 믿는다는 뜻이 됩니다. 적절한 주기로 호출하세요. 한 시간에 한 번이 일반적입니다.

python
import magiclock

def periodic_check():
    magiclock.revalidate()   # 라이선스나 승인이 더 이상 유효하지 않으면 예외

로컬 라이선스 게이트 전체를 다시 실행하고, 이 프로세스에 로드된 클라우드 제어(--web-gate) 산출물에 대해서는 서버에서 새로운 서명 승인을 강제로 받아옵니다. 뒷부분이 중요합니다. 포털에서의 비활성화가 장기 실행 서비스에 즉시 적용될지, 다음 자연스러운 확인 시점까지 기다릴지를 결정하기 때문입니다. 클라우드 제어 산출물을 장기 실행 프로세스에 넣는다면, 이 호출 여부가 "몇 초 안에 멈춤"과 "언젠가 멈춤"의 차이입니다.

임포트 시 게이트와 동일한 타입의 오류를 발생시키며, 모두 유효하면 None을 반환합니다.

magiclock.revalidate_every()

python
magiclock.revalidate_every(seconds, *, on_error=None)

백그라운드 데몬 스레드에서 revalidate()를 계속 주기적으로 실행합니다. bootstrap() 근처에서 한 번만 호출하세요.

python
import magiclock

magiclock.bootstrap()
magiclock.revalidate_every(3600)     # 매시간 재확인

검사가 실패하면 기본적으로 프로세스를 종료합니다. 백그라운드 스레드가 실패를 삼키면 더 이상 유효하지 않은 라이선스로 서비스를 계속 제공하게 되고, 데몬 스레드의 트레이스백은 아무도 읽지 않습니다. on_error를 전달해 직접 처리하세요 — 연결을 정리하고 readiness 프로브를 내린 뒤 종료하면 됩니다.

python
magiclock.revalidate_every(3600, on_error=lambda exc: my_graceful_shutdown(exc))

반환된 스레드에는 정상 종료용 stop()이 있습니다. on_error 자체가 예외를 발생시켜도 프로세스는 종료됩니다 — 고장 난 핸들러가 계속 실행되는 수단이 되어서는 안 됩니다.

처리해야 할 수도 있는 오류

bootstrap()open_model()은 "아직 활성화되지 않음"부터 "이 산출물이 만료됨"까지 다양한 상황에서 예외를 발생시킵니다. 대부분의 통합에서는 넓게 잡아서 예외 클래스 이름으로 분기합니다.

python
try:
    magiclock.bootstrap()
    data = magiclock.open_model("model.onnx.enc")
except Exception as exc:
    name = type(exc).__name__
    if name == "NotActivatedError":
        ...  # this machine hasn't been activated — run `magiclock activate`
    elif name == "TrialExpiredError":
        ...  # the artifact's expiry window has passed
    else:
        raise
예외발생 조건
NotActivatedError이 머신에서 활성화 이력을 찾을 수 없음 — magiclock activate를 실행하세요(또는 첫 암호화 시 CLI가 자동 활성화하도록 두세요).
VaultLockedError로컬 활성화 상태를 여기서 열 수 없음 — 잘못된 머신이거나, 볼트 파일이 이동/변조되었습니다. 이 두 경우는 설계상 구분할 수 없습니다. 보안 모델 참고.
TrialExpiredError--trial, --expires-in, --expires-at 산출물이 복호화 가능 기간을 지났음.
SubscriptionExpiredError플랜이 만료됨. 이는 오직 새로운 암호화만 막으며, 복호화 경로에서는 결코 발생하지 않습니다.
SubscriptionInvalidError구독 자격 증명이 필요한 곳에 없거나, 형식이 잘못되었거나, 서명 검증에 실패함.
PortableFormatError포터블 봉투(기본값, 또는 --passphrase/--emit-key)가 손상되었거나 지원하지 않는 버전임.
PortableKeyErroropen_model()/run --key에 전달한 패스프레이즈 또는 키가 포터블 산출물과 일치하지 않음.
DebuggerDetectedError복호화 경계에서 디버거가 연결되어 있음이 감지됨.
RuntimeNotInitializedError(from magiclock_host.errors import RuntimeNotInitializedError)이 프로세스에서 bootstrap()이 실행되기 전에 게이트가 걸린 모듈의 검사가 먼저 실행됨.

호환성 참고 사항

.pya/.enc 봉투는 이를 생성한 CPython 메이저/마이너 버전을 내부에 담고 있습니다. 3.12에서 암호화된 산출물은 3.13에서 로드되지 않습니다 — 인터프리터를 업그레이드한 뒤에는 하나의 산출물을 여러 Python 버전에 걸쳐 사용하려 하지 말고 protect/protect-model을 다시 실행하세요.