"""Composition read boundary, independent of optional audition/reference tools.""" from __future__ import annotations import contextlib import contextvars import os from pathlib import Path import sys ACTIVE=contextvars.ContextVar('composition_reads',default=None) def _read(event,args): scope=ACTIVE.get() if scope is None or event!='open':return name,mode,flags=args if not isinstance(name,(str,bytes,os.PathLike)):return # r+ and w+ can read; they must obey the same boundary as a plain read. if flags & os.O_WRONLY:return path=Path(os.fsdecode(name)).resolve() data,code,cache,reads=scope python_runtime=any(path.is_relative_to(Path(root).resolve()) for root in (sys.base_prefix,sys.prefix)) allowed=path.is_relative_to(data) or path.is_relative_to(code) or python_runtime if cache and path.is_relative_to(cache):allowed=True if not allowed:raise ValueError('Forbidden composition input: '+str(path)) reads.add(str(path)) sys.addaudithook(_read) @contextlib.contextmanager def composition(data,role,cache=None): reads=set() code=Path(__file__).resolve().parent token=ACTIVE.set((Path(data).resolve(),code,Path(cache).resolve()/role if cache else None,reads)) try:yield reads finally:ACTIVE.reset(token)