Bug Description
QProgram.register_vendor refuses a vendor name that shadows a QProgram attribute, because __getattr__ only runs after normal lookup, so program.play could never reach the namespace. The other three registration sites, register_vendor_operation, register_vendor_block and register_vendor_version, check only RESERVED_VENDOR_NAMES, which holds the reserved keywords plus core and no attribute names. An extension whose __init__.py registers its operations before its namespace therefore half-registers, leaving the serialization registries holding a vendor no user can call; a .qp file with require play 0.1 and a play.foo line then loads and round-trips even though the Python API can never build it. One shared vendor-name validator called from all four sites would settle it.
Minimal Reproducible Example
import qprogram as qp
from qprogram.operations import Operation
class FooOp(Operation):
pass
qp.register_vendor_operation("play", "foo", FooOp) # accepted
qp.register_vendor_version("play", "0.1") # accepted
qp.QProgram.register_vendor("play", qp.VendorNamespace)
Expected Behavior
All four registration sites apply the same name check, so play is refused at the first call rather than the last.
Actual Behavior
The first two calls return normally. The third raises:
raise ValueError(msg)
ValueError: vendor name 'play' collides with a QProgram attribute; the namespace would be unreachable because normal attribute lookup wins over vendor dispatch
System Information
qprogram 0.1.0
python 3.14.3 | Linux-7.0.0-30-generic-x86_64-with-glibc2.43
vendors none
Bug Description
QProgram.register_vendorrefuses a vendor name that shadows aQProgramattribute, because__getattr__only runs after normal lookup, soprogram.playcould never reach the namespace. The other three registration sites,register_vendor_operation,register_vendor_blockandregister_vendor_version, check onlyRESERVED_VENDOR_NAMES, which holds the reserved keywords pluscoreand no attribute names. An extension whose__init__.pyregisters its operations before its namespace therefore half-registers, leaving the serialization registries holding a vendor no user can call; a.qpfile withrequire play 0.1and aplay.fooline then loads and round-trips even though the Python API can never build it. One shared vendor-name validator called from all four sites would settle it.Minimal Reproducible Example
Expected Behavior
All four registration sites apply the same name check, so
playis refused at the first call rather than the last.Actual Behavior
System Information
qprogram 0.1.0 python 3.14.3 | Linux-7.0.0-30-generic-x86_64-with-glibc2.43 vendors none