Skip to content

Typing guidelines

This page defines typing conventions used in Qubex code.

Naming

See Naming guidelines for TypeVar naming and declaration rules.

Compatibility note

Qubex currently supports Python >=3.10. When writing runtime code that must remain compatible with Python 3.10 and 3.11, use TypeVar.

Sentinel values (MISSING)

Use the shared sentinel from qubex.core.sentinel when an API must distinguish between:

  • argument omitted
  • argument explicitly set to None

Prefer the shared MISSING sentinel over ad hoc object() instances.

When to use MISSING

Use MISSING when None is already a meaningful value and you need a third state for "not provided".

Typical cases:

  • optional keyword arguments with inheritance or fallback behavior
  • deprecated-option normalization
  • compatibility APIs that need to preserve legacy omission semantics

Do not use MISSING when None already means "use the default" and there is no observable difference between omitted and None.

Annotation pattern

Annotate these parameters with typing_extensions.Sentinel.

from typing_extensions import Sentinel

from qubex.core.sentinel import MISSING


def configure(
    *,
    figure: Figure | None | Sentinel = MISSING,
) -> None:
    ...

Avoid broad fallback annotations such as object for this pattern. They hide intent and weaken type checking.

Comparison rule

Check the sentinel by identity.

if value is MISSING:
    ...

Do not compare sentinels by equality.

Narrowing pattern

When a helper improves readability, use a local TypeGuard to narrow away Sentinel.

from typing import TypeGuard, TypeVar
from typing_extensions import Sentinel

from qubex.core.sentinel import MISSING

T = TypeVar("T")


def _is_given(value: T | Sentinel) -> TypeGuard[T]:
    return value is not MISSING

This is the preferred pattern when a value is read multiple times after the missing check.

Defining new sentinels

Prefer reusing the shared MISSING sentinel.

Create a new sentinel with make_sentinel() only when a separate sentinel name is part of a real API contract and sharing MISSING would make behavior less clear.

Dynamic attribute access (getattr)

Prefer explicit attribute and method access over dynamic getattr-based dispatch. Use getattr only when attribute names are selected at runtime and cannot be represented clearly with static code.

Appropriate use cases

  • Plugin or command systems where handler names come from validated runtime input.
  • Compatibility shims that probe optional attributes across dependency versions.
  • Generic proxy/wrapper utilities that intentionally forward attributes dynamically.

Avoid getattr when

  • The target attribute or method is known at development time.
  • A small finite branch is clearer as dictionary dispatch or if/match.
  • The code is core domain logic where static typing and refactor safety are priorities.

Required safeguards

  • Constrain attribute names with an explicit allowlist.
  • Raise explicit errors for unsupported names.
  • Do not silently swallow missing attributes with broad defaults unless the fallback is explicitly required.
  • Add tests for both accepted and rejected attribute names.