|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import re |
4 | | -from typing import TYPE_CHECKING, Any, Generic, cast |
| 4 | +from typing import TYPE_CHECKING, Any, Generic, cast, overload |
5 | 5 |
|
6 | 6 | from typing_extensions import TypeVar |
7 | 7 |
|
@@ -350,3 +350,45 @@ def get(self) -> PeerTSync: |
350 | 350 | return cast("PeerTSync", self._client.store.get(key=self.hfid_str, branch=self._branch)) |
351 | 351 |
|
352 | 352 | raise ValueError("Node must have at least one identifier (ID or HFID) to query it.") |
| 353 | + |
| 354 | + |
| 355 | +class RelationshipAttribute(Generic[PeerT]): |
| 356 | + """Typing descriptor for a cardinality-one relationship on a generated protocol. |
| 357 | +
|
| 358 | + It reads back as ``RelatedNode[PeerT]`` (so ``.peer`` keeps the peer type) but accepts |
| 359 | + assignment of an id string, an HFID, a peer node, or ``None`` — mirroring the runtime |
| 360 | + ``InfrahubNode.__setattr__`` behaviour, which wraps the assigned value in a ``RelatedNode``. |
| 361 | +
|
| 362 | + This type only appears in generated protocols (it is never instantiated at runtime), so it |
| 363 | + exists purely to give ``node.rel`` separate read and assignment types under a type checker. |
| 364 | + """ |
| 365 | + |
| 366 | + @overload |
| 367 | + def __get__(self, instance: None, owner: Any = None) -> RelationshipAttribute[PeerT]: ... |
| 368 | + |
| 369 | + @overload |
| 370 | + def __get__(self, instance: object, owner: Any = None) -> RelatedNode[PeerT]: ... |
| 371 | + |
| 372 | + def __get__(self, instance: object | None, owner: Any = None) -> RelationshipAttribute[PeerT] | RelatedNode[PeerT]: |
| 373 | + raise NotImplementedError # typing-only descriptor; never invoked at runtime |
| 374 | + |
| 375 | + def __set__(self, instance: object, value: str | list[str] | PeerT | None) -> None: |
| 376 | + raise NotImplementedError # typing-only descriptor; never invoked at runtime |
| 377 | + |
| 378 | + |
| 379 | +class RelationshipAttributeSync(Generic[PeerTSync]): |
| 380 | + """Synchronous counterpart of :class:`RelationshipAttribute`.""" |
| 381 | + |
| 382 | + @overload |
| 383 | + def __get__(self, instance: None, owner: Any = None) -> RelationshipAttributeSync[PeerTSync]: ... |
| 384 | + |
| 385 | + @overload |
| 386 | + def __get__(self, instance: object, owner: Any = None) -> RelatedNodeSync[PeerTSync]: ... |
| 387 | + |
| 388 | + def __get__( |
| 389 | + self, instance: object | None, owner: Any = None |
| 390 | + ) -> RelationshipAttributeSync[PeerTSync] | RelatedNodeSync[PeerTSync]: |
| 391 | + raise NotImplementedError # typing-only descriptor; never invoked at runtime |
| 392 | + |
| 393 | + def __set__(self, instance: object, value: str | list[str] | PeerTSync | None) -> None: |
| 394 | + raise NotImplementedError # typing-only descriptor; never invoked at runtime |
0 commit comments