TDI Docs expansion: Best way to access modules in both TD and VS Code

The TDI Library is already a game-changer since 2025 released and and the TDI docs are a wealth of knowledge but I have a serious stumbling block that would help with an authoritative technique and adds to the docs here.

Based on the import model of picking from sibling DATs then local/modules then modules in sys, referring between python files to get class definitions to build the Intellisense in VS Code is very difficult. Modules in the filesystem do not look up in the same priority as inside TD’s interpreter, and even using a tool like Embody which does save in the operator path, then building those import declarations is tricky and heavy in boilerplate.

Here are some common scenarios. If:

  • I am referring to an Extension in the same Comp and they are both synced:
    In the below case ComplexMultiTouchExt.py exists synced to ComplexMultiTouchExt the DAT, while CMT is the extension name defined on the parent COMP.
from ComplexMultiTouchExt import ComplexMultiTouchExt

def getExtension() -> ComplexMultiTouchExt:
	return ext.CMT

In this case I am grabbing the file location (which is easiest with siblings) to patch types on a class found automagically by ext.

  • I am referring to an extension in another folder/COMP path and they are both synced:

For this to work I have to block the inside-TD interpreter from saying “does not exist” because it doesn’t like the .. path referencing. Only when TYPE_CHECKING is true (in VS Code type hints) then navigate up the file hierarchy only to look for modules, but this ONLY works for types, never variables or classes because they would then be not imported once type checking is no longer true.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..UI_Table.UI1Ext import UI1Ext
    from ..TangibleEngine.ComplexMultiTouchExt import ComplexMultiTouchExt
    from ...Components.Metrics_Logger.MetricsLoggerExt import MetricsLoggerExt

...
# Use my class definitions but not for instancing, only method defs
_uiComp: UI1COMP = op.UI1  # type: ignore
_teComp: ComplexMultiTouchCOMP = op.TE  # type: ignore
_metricsComp: MetricsCOMP = op.METRICS  # type: ignore

In this case TD “finds” by Global shortcut, but Python linting finds by path to file. This is a huge block needed for even one import, never mind three.

  • I am referring to a COMP which has elevated the UpperCase methods:

At the end of every extension script I add another class, undefined if running normally, which marries the operator class with the extension class. Of course this pollutes the joined class with all the lowercase as well but I can’t selectively promote without redeclaring them all here (haven’t tried that yet).

if TYPE_CHECKING:
    class StateCOMP(baseCOMP, StateExt):
        pass

This can then be imported in another file in the same way as before:

if TYPE_CHECKING:
    from ..State.StateExt import StateCOMP

And go from there. Once again its not going to allow me to actually import or reuse any of these classes/methods/etc, just use them to describe an operator we find using op() or op. or parent. referencing.

  • … and I haven’t figured out how to match up the local/modules approach yet with TDI typing.

So this is asking for trouble.

Pulling in a type by one location method then the actual object by another method is fragile. If I’m not saving these in identical positions between op path and name and file path and name, its going to break. There is obviously the class reuse limitations beyond siblings in the same COMP, and there is the general hunting for definitions already done in another script in the project as the approach for cross-module referencing is not documented.

So I’m here to ask if this is a skill issue, if there is a better way!

Can someone from Derivative share their approaches here, and what the “right way” for these would be? I turn on type checking and am still flooded by type errors that require loads of # type: ignore flags. I would like to consolidate some approaches/scenarios to both help my self and bolster out the docs which are already a good FAQ.

Cheers,
Chris

In the Discord #python channel, @plusplusone mentioned:

You can add your folder with your modules to the extra path of the venv definition and can then import them. Only caveat at the moment is that there is no hot reloading and you explicitly need to import the TD module.

I am not currently using this because of the hot-reload issue, but if that was fixed, could be a path forward to have “scripts” like ParExecs in DATs but Extension Classes external where only filesystem referencing modules applies?

Overall trying to get advice on current state though, adding features would be bonus.

Yeah I’m afraid you’ve hit the edge of TDI functionality. The goal was to make sure you could get help on built-in TD functionality in vs code. We have a number of important features coming in the next year that will affect how user Python is arranged in TD, so we didn’t want to add features for things like extensions and other custom modules until that development has settled down.

We fully recognize that the way Python was developed in TD early on creates some issues for module loading in standard Python and IDEs. Alas, it will take some time for us to unravel this in a sensible way with the new features coming in 2026.

Until then,@plusplusone is a great source of heavy Python dev wisdom, and we will check in with him and others (including you if you keep at it) when the time comes to build a better solution for this.

1 Like

Thanks for the reply, Ivan. And I am intensely interested in this workflow evolving, even more than POPs.

TDI plus TDPyEnvManager are both great additions but they open up this gateway to MORE.

TDI aside, is there a stopgap “best practices” which could be assembled on module typing and cross-referencing, since types are now in the template callback dats throughout TD 2025? Or just a reaction to if the above are patterns or antipatterns.

I can’t say I’ve done a really deep dive on getting TDI type hints for extensions. But generally, these are the things I was dabbling in when I tried last:

  1. create a union type (or class) of your extension and the operator class that it gets used on. Use the operator class from TDI, not from builtins.

  2. create a globally accessible module or object that contains all the union types for your project. Use that to make the classes available for type hints.

  3. this is what I’ve got in mind for custom pars… make a script that generates a TDI-like file from a custom component so that all the parameter hinting works. It will be a subclass of the tdi operator class that the component is built on. Will have to be easy to run again to update the class, and the definition files need to be in a standardized place.

Uniony Types do not work with the .asType method of ops so I shifted to inheritance as it works just fine.

For custom pars there is the RnsureExtension class in the TouchUtilCollection which allows you to define custom pars fully as part of the extension which does work quite well with code completion.