Standards for writing correct, maintainable code in the RTX Remix Toolkit. These are not style preferences — violating them produces broken or unmaintainable code.
Fix the root cause. Never paper over a problem.
When something doesn't work, the fix belongs at the source of the problem. A workaround applied at the wrong layer creates two problems: the original one (still unfixed) and the workaround (now load-bearing).
Never do these:
| Anti-pattern | Why it's wrong |
|---|---|
| Apply a fix in the wrong layer (e.g. compensating for a core bug in a widget) | Hides the root cause; makes the widget brittle and unpredictable |
| Widen tolerances or add retries to hide flaky behavior | The flakiness is the bug; you've just made it less visible |
Swallow exceptions silently: except Exception: pass |
Turns a crash into a silent corruption or hang |
| Add a feature flag or code path bypass to avoid fixing broken behavior | You now maintain two code paths; the broken one never gets fixed |
| Leave dead code, legacy fallbacks, or half-completed migrations | Future readers can't tell what's intentional; creates confusion and drift |
| Write 200+ line functions | No single function should need that much explanation — split it |
Use hasattr/getattr to probe for attributes on your own typed objects |
Means the type hierarchy is wrong — fix the types, don't guess at runtime |
Suppress lint warnings with # noqa instead of fixing the code |
Fix the root cause — rename the variable, restructure the code, or remove the dead code |
Do not use hasattr() or getattr() to access attributes on objects whose types you control. These calls
are a sign that the type signature is lying — the code claims to accept a base type but actually requires fields
from a subclass it refuses to name.
| Situation | Solution |
|---|---|
A method accepts a base class but accesses fields only defined on a subclass (e.g., getattr(item, "path", "") when path exists on FileItem but not Item) |
Narrow the parameter type to the subclass and access the field directly. If the method only works with FileItem, type it as FileItem. |
| An override of an abstract method needs a more specific type than the base declares | Use the concrete subclass type in the override. If the dispatch mechanism already guarantees the type (e.g., a registry or isinstance gate), the override signature should reflect that. |
Multiple unrelated classes share an optional capability (e.g., some plugins have a visible property, others don't) |
Define a typing.Protocol (with @runtime_checkable if you need runtime checks) and use isinstance() instead of hasattr(). |
| A function operates on a base dataclass but needs fields from a specific subclass | Type the parameter as the subclass directly. If the function must remain generic, make the class generic over a TypeVar bound to the base (e.g., C = TypeVar("C", bound=BaseContext)). |
Iterating over dataclasses.fields() for generic comparison, serialization, or copy |
Acceptable — field names are guaranteed to exist by the dataclass definition. This is reflective metaprogramming, not attribute guessing. |
Checking whether an external library exposes an API across versions (e.g., hasattr(UsdLux, "LightAPI")) |
Acceptable — you don't control the external type and need version-portable code. |
- Static analysis breaks. Type checkers, IDE navigation, and refactoring tools cannot follow
getattrcalls. - Errors move to runtime. A typo like
getattr(item, "pahts", [])silently returns[]instead of failing at import time. With direct attribute access, the typo is caught immediately by the type checker and IDE. - Intent is hidden.
getattr(item, "paths", [])doesn't tell the reader why this field might be missing, which subclass types have it, or whether the fallback is actually expected.
- One component, one job. If describing a function requires "and", split it.
- The component that creates the problem owns the fix. Don't compensate at a higher layer.
- Expose the minimum public surface consumers actually need. Every additional public method is a contract to maintain.
Import the specific class, function, or constant you need — do not use deep dotted namespace access in code.
| Do | Don't |
|---|---|
from omni.flux.job_queue.core.execute import JobExecutor |
omni.flux.job_queue.core.execute.JobExecutor(...) inline in code |
from pxr import UsdGeom |
pxr.UsdGeom.Xformable scattered through the file |
Long dotted paths create hidden coupling — if the module path changes, every call site needs updating instead of a single import line. They also hurt readability and make grep-based refactoring unreliable.
When importing from the same extension, use relative imports (from .module import Class) instead of
fully-qualified paths.
| Do | Don't |
|---|---|
from .pipeline_context import UsdPipelineContext |
from lightspeed.trex.asset_pipeline.core.pipeline_context import UsdPipelineContext |
from .display_adapter import JobDisplayAdapter |
from omni.flux.job_queue.widget.display_adapter import JobDisplayAdapter |
Relative imports make it clear the dependency is internal to the extension and survive extension renames. Use absolute imports only for cross-extension dependencies.
Stop and rethink if you hit any of these:
| Smell | What it means |
|---|---|
"It works if I add a sleep" |
Broken async or data flow — something is racing |
| "It works if I read from the widget instead of from storage" | State is out of sync between the UI and the backing store |
| "It passes alone but fails alongside other tests" | Shared mutable state is leaking between tests |
| "I need a flag to skip this code path" | Ask: why does that path run when it shouldn't? Fix the condition, not the path. |
"I'll just use getattr with a default" |
The type hierarchy is incomplete — add the field to a base class or protocol. |