17 mai 2026

[Dynamo += Python] Génération de stubs avec Dynamo

 



De la DLL au Type Hint : dans les coulisses de la génération de stubs pour VSCode (Python) avec Dynamo

#DynamoBIM #Python #RevitAPI  #VSCode #API #autocomplétion #AutodeskExpertElite #AutodeskCommunity 

if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )

Si vous utilisez VSCode (souvent indispensable lorsque l'on a de longs codes), voici un générateur de stubs (.pyi) depuis Dynamo qui reflète l'architecture des espaces de noms .NET et reconstruit la signature des classes pour les rendre intelligibles par Python, permettant l'auto-complétion sous VSCode.

  • Quelques points sur les principes de ce générateur


    • Reflection : ce générateur parcourt chaque classe, chaque méthode et chaque propriété grâce à la Reflection (System.Reflection) en prenant en compte l'héritage.

    • Métaclasses : permet de gérer des héritages de membres statiques complexes avec une maitrise des types d'objets retournés et permet une autocomplétion avancée avec Pylance

    • LINQ : génération d'un wrapper spécial pour LINQ avec exemples de syntaxe spécifique pour DynamoPythonNet3 (il vous suffira de copier les exemples de documentation dans VSCode)



À propos de l'utilisation des métaclasses ici plutôt que des décorateurs @staticmethod ou @classmethod

Nous l'utilisons ici comme une solution de contournement pour Pylance. (le but premier des métaclasses est de personnaliser/créer des classes Python)
 

  • Les décorateurs Python :

@property fonctionne uniquement sur une instance (via self). Si l'on fait MaClasse.ma_propriete, Python renverra l'objet property lui-même, pas la valeur.

@classmethod fonctionne pour des méthodes, mais l'IDE attendra que l'on mettes des parenthèses : MaClasse.ma_methode().

  • La solution Métaclasse :

Puisque la classe est elle-même une "instance" de sa métaclasse, si on met un @property dans la métaclasse, elle devient une propriété réelle pour la classe.

Résultat pour l'IDE : Quand tu tapes Line., l'IDE voit Bound comme une variable (une propriété) et non comme une fonction.


Exemple 1 avec des méthodes d'instances


Exemple 2 avec les méthodes d'extension LINQ


..

  • Le code (PythonNet3)


import os
import clr
import System
import keyword
import textwrap
from System import Reflection
from System.Reflection import BindingFlags

# -----------------------------------------------------------------------------
# LOAD ASSEMBLIES
# -----------------------------------------------------------------------------

assemblies_to_load = [
    'RevitAPI',
    'RevitAPIUI',
    'RevitServices',
    'ProtoGeometry',
    'DynamoServices',
    'RevitNodes',
    'System',
    'System.Core',
    'System.Drawing',
    'System.Drawing.Common',
    'System.Windows.Forms',
    'WindowsBase',
    'PresentationCore',
    'PresentationFramework',
    'System.Xaml',
    'UIAutomationTypes',
    'UIAutomationProvider',
    'WindowsFormsIntegration',
    'PresentationUI',
    'ReachFramework'
]

for asm_name in assemblies_to_load:
    try:
        clr.AddReference(asm_name)
    except Exception as ex:
        print(f'Could not load {asm_name}: {ex}')

from Autodesk.DesignScript.Geometry import *
from Dynamo.Events import *

try:
    fullPathScript = ExecutionEvents.ActiveSession.CurrentWorkspacePath
except Exception:
    fullPathScript = None

# -----------------------------------------------------------------------------
# CONFIG + MAPPING
# -----------------------------------------------------------------------------

OPERATOR_MAP = {
    'op_Equality': '__eq__', 'op_Inequality': '__ne__', 'op_Addition': '__add__',
    'op_Subtraction': '__sub__', 'op_Multiply': '__mul__', 'op_Division': '__truediv__',
    'op_GreaterThan': '__gt__', 'op_LessThan': '__lt__', 'op_GreaterThanOrEqual': '__ge__',
    'op_LessThanOrEqual': '__le__', 'op_UnaryNegation': '__neg__', 'op_UnaryPlus': '__pos__',
    'op_BitwiseAnd': '__and__', 'op_BitwiseOr': '__or__', 'op_ExclusiveOr': '__xor__',
    'op_LeftShift': '__lshift__', 'op_RightShift': '__rshift__'
}

KW = set(keyword.kwlist)
NL = chr(10)
Q = chr(39) * 3

SYS_MAP = {
    'System.String': 'str',
    'System.Char': 'str',
    'System.Boolean': 'bool',
    'System.Int16': 'int',
    'System.Int32': 'int',
    'System.Int64': 'int',
    'System.UInt16': 'int',
    'System.UInt32': 'int',
    'System.UInt64': 'int',
    'System.Byte': 'int',
    'System.SByte': 'int',
    'System.Single': 'float',
    'System.Double': 'float',
    'System.Decimal': 'float',
    'System.Object': 'object',
    'System.Void': 'None',
    'System.Type': 'type'
}

SIMPLE = {'Any', 'None', 'object', 'int', 'float', 'str', 'bool', 'bytes', 'type'}

GENERIC_MAP = {
    'System.Collections.Generic.IEnumerable`1': 'Iterable',
    'System.Collections.Generic.ICollection`1': 'List',
    'System.Collections.Generic.IList`1': 'List',
    'System.Collections.Generic.List`1': 'List',
    'System.Collections.ObjectModel.Collection`1': 'List',
    'System.Collections.Generic.IReadOnlyCollection`1': 'Collection',
    'System.Collections.Generic.IReadOnlyList`1': 'Sequence',
    'System.Collections.ObjectModel.ReadOnlyCollection`1': 'Sequence',
    'System.Collections.Generic.HashSet`1': 'Set',
    'System.Collections.Generic.ISet`1': 'Set',
    'System.Collections.Generic.Dictionary`2': 'Dict',
    'System.Collections.Generic.IDictionary`2': 'Dict',
    'System.Collections.Generic.IReadOnlyDictionary`2': 'Dict',
    'System.Collections.Generic.KeyValuePair`2': 'Tuple',
    'System.Nullable`1': 'Optional'
}

_TUPLE_DEFS = set(
    [f'System.Tuple`{i}' for i in range(1, 9)] +
    [f'System.ValueTuple`{i}' for i in range(1, 9)]
)
_IENUM = 'System.Collections.IEnumerable'
_GIENUM_DEF = 'System.Collections.Generic.IEnumerable`1'
_LINQ_EXCLUDE = {'System.String'}


# -----------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------

def _n_id(n, i=0):
    n = n or (f'p{i}')
    n = n.replace(' ', '_')
    if n in KW:
        n += '_'
    if n[0].isdigit():
        n = 'p_' + n
    return n


def _t_name(t):
    try:
        n = t.Name
        return n.split('`')[0] if '`' in n else n
    except Exception:
        return 'object'


def _t_fqn(t):
    try:
        fn = t.FullName
        if not fn:
            return _t_name(t)
        if '`' in fn:
            fn = fn.split('`')[0]
        return fn.replace('+', '.')
    except Exception:
        return 'object'


def _g_def(t):
    try:
        if t and t.IsGenericType:
            return t.GetGenericTypeDefinition().FullName
    except Exception:
        pass
    return None


def _g_args(t):
    try:
        a = t.GetGenericArguments()
        return [a[i] for i in range(a.Length)]
    except Exception:
        return []


def _fmt_args(args, ns):
    return ', '.join([get_type_hint(x, ns) for x in args])


def _fmt_generic(n, args, ns):
    a = _fmt_args(args, ns)
    return f'{n}[{a}]' if a else n


def get_type_hint(t, ns=None):
    if t is None:
        return 'None'
    try:
        if t.IsByRef:
            t = t.GetElementType()
        if getattr(t, 'IsGenericParameter', False):
            return 'Any'
        if getattr(t, 'IsPointer', False):
            return 'Any'
        if getattr(t, 'IsArray', False):
            return f'List[{get_type_hint(t.GetElementType(), ns)}]'

        fn = t.FullName
        if fn in SYS_MAP:
            return SYS_MAP[fn]
        if fn == 'System.Action':
            return 'Callable[[], None]'

        gd = _g_def(t)
        if gd:
            ga = _g_args(t)
            if gd in GENERIC_MAP:
                return _fmt_generic(GENERIC_MAP[gd], ga, ns)
            if gd in _TUPLE_DEFS:
                return _fmt_generic('Tuple', ga, ns)
            if gd == 'System.Predicate`1' and len(ga) == 1:
                return f'Callable[[{get_type_hint(ga[0], ns)}], bool]'
            if gd.startswith('System.Func`') and len(ga) > 0:
                ps = _fmt_args(ga[:-1], ns)
                return f'Callable[[{ps}], {get_type_hint(ga[-1], ns)}]'
            if gd.startswith('System.Action`'):
                return f'Callable[[{_fmt_args(ga, ns)}], None]'
            return f'{_t_fqn(t)}[{_fmt_args(ga, ns)}]'

        if ns and t.Namespace == ns:
            return _t_name(t)
        return _t_fqn(t)
    except Exception:
        return 'object'


def ann(h):
    if not h:
        return 'Any'
    return h if h in SIMPLE else repr(h)


def _pi_kind(pi):
    try:
        if pi.IsOut:
            return 'out'
        if pi.ParameterType and pi.ParameterType.IsByRef:
            return 'ref'
    except Exception:
        pass
    return 'in'


def _sig_params(ps, ns):
    a, d = [], []
    for i in range(ps.Length):
        p = ps[i]
        n = _n_id(p.Name, i)
        k = _pi_kind(p)
        t = get_type_hint(p.ParameterType, ns)
        s = f'{n}: {ann(t)}'
        try:
            if p.IsOptional:
                s += ' = ...'
        except Exception:
            pass
        a.append(s)
        d.append((n, t, k))
    return a, d


def _doc_args(d):
    if not d:
        return 'Args:' + NL + '    (none)'
    ls = ['Args:']
    for n, t, k in d:
        ks = '' if k == 'in' else f' [{k}]'
        ls.append(f'    {n} ({t}):{ks}')
    return NL.join(ls)


def _doc_ret(r):
    return f'Returns:{NL}    {r}'


def _doc_overloads(mis, ns, t_self=None, lim=12):
    ls, c = ['Overloads:'], 0
    for mi in mis:
        if c >= lim:
            ls.append('    ...')
            break
        ps = mi.GetParameters()
        a, _ = _sig_params(ps, ns)
        try:
            if getattr(mi, 'IsConstructor', False):
                rt = 'None'
            elif t_self is not None and mi.ReturnType == mi.DeclaringType:
                rt = t_self
            else:
                rt = get_type_hint(mi.ReturnType, ns)
        except Exception:
            rt = 'object'
        ls.append(f"    ({', '.join(a)}) -> {rt}")
        c += 1
    return NL.join(ls)


def _ienum_elem(t):
    try:
        if t is None:
            return None
        fn = getattr(t, 'FullName', None)
        if fn in _LINQ_EXCLUDE:
            return None
        if getattr(t, 'IsArray', False):
            return t.GetElementType()
        if _g_def(t) == _GIENUM_DEF:
            ga = _g_args(t)
            return ga[0] if ga else System.Object
        if hasattr(t, 'GetInterfaces'):
            for it in t.GetInterfaces():
                if _g_def(it) == _GIENUM_DEF:
                    ga = _g_args(it)
                    return ga[0] if ga else System.Object
            for it in t.GetInterfaces():
                try:
                    if it.FullName == _IENUM:
                        return System.Object
                except Exception:
                    pass
        if fn == _IENUM:
            return System.Object
    except Exception:
        pass
    return None


def _linq_type_hint(t, ns, quote_arg=False):
    et = _ienum_elem(t)
    if et is None:
        return None
    h = get_type_hint(et, ns)
    if quote_arg and h not in SIMPLE:
        h = repr(h)
    return f'IEnumerableLinq[{h}]'


def _ret_hint(rt_type, ns, tself, decl_type):
    try:
        if rt_type == decl_type:
            return tself
    except Exception:
        pass
    lq = _linq_type_hint(rt_type, ns)
    return lq if lq else get_type_hint(rt_type, ns)


def _base_hint(t, ns):
    lq = _linq_type_hint(t, ns, True)
    return lq if lq else 'object'


def _fp_self(a):
    return ', '.join(['self'] + a)


def _fp_static(a):
    return ', '.join(a)


def _fp_meta(a):
    return ', '.join(['cls'] + a)


def _write_doc(f, txt):
    f.write('        ' + Q + txt + Q + NL)


def _has_index_params(p):
    try:
        return p.GetIndexParameters().Length > 0
    except Exception:
        return False


def _is_static_property(p):
    try:
        gm = p.GetGetMethod()
        sm = p.GetSetMethod()
        return (gm is not None and gm.IsStatic) or (sm is not None and sm.IsStatic)
    except Exception:
        return False


def _meta_name(tn):
    return _n_id(f'_{tn}Static')


def _write_static_meta(f, t, ns, max_ov=8, max_doc_ov=12):
    flags = BindingFlags.Public | BindingFlags.Static
    tn = _t_name(t)
    mn_meta = _meta_name(tn)
    sprops = [p for p in t.GetProperties(flags) if not _has_index_params(p) and _is_static_property(p)]
    sms = [m for m in t.GetMethods(flags) if not (m.IsSpecialName and not m.Name.startswith('op_'))]
    sms = [m for m in sms if not OPERATOR_MAP.get(m.Name, m.Name).startswith('__')]

    if not sprops and not sms:
        return None

    f.write(f'class {mn_meta}(type):' + NL)
    wrote = False

    for p in sorted(sprops, key=lambda x: x.Name):
        pn = _n_id(p.Name)
        rt = _ret_hint(p.PropertyType, ns, tn, t)
        wrote = True
        f.write('    @property' + NL)
        f.write(f'    def {pn}(cls) -> {ann(rt)}:' + NL)
        _write_doc(f, _doc_ret(rt))
        f.write('        ...' + NL)

    grp = {}
    for m in sms:
        grp.setdefault(OPERATOR_MAP.get(m.Name, m.Name), []).append(m)

    for mn, mis in sorted(grp.items(), key=lambda x: x[0]):
        wrote = True
        if len(mis) == 1:
            mi = mis[0]
            a, d = _sig_params(mi.GetParameters(), ns)
            rt = _ret_hint(mi.ReturnType, ns, tn, mi.DeclaringType)
            f.write(f'    def {mn}({_fp_meta(a)}) -> {ann(rt)}:' + NL)
            _write_doc(f, _doc_args(d) + NL + _doc_ret(rt))
            f.write('        ...' + NL)
        else:
            lim = min(len(mis), max_ov)
            for i in range(lim):
                mi = mis[i]
                a, d = _sig_params(mi.GetParameters(), ns)
                rt = _ret_hint(mi.ReturnType, ns, tn, mi.DeclaringType)
                f.write('    @overload' + NL)
                f.write(f'    def {mn}({_fp_meta(a)}) -> {ann(rt)}:' + NL)
                _write_doc(f, _doc_args(d) + NL + _doc_ret(rt))
                f.write('        ...' + NL)
            if len(mis) > max_ov:
                rt0 = _ret_hint(mis[0].ReturnType, ns, tn, mis[0].DeclaringType)
                f.write(f'    def {mn}(cls, *args: Any, **kwargs: Any) -> {ann(rt0)}:' + NL)
                _write_doc(f, _doc_args([]) + NL + _doc_ret(rt0) + NL + _doc_overloads(mis, ns, tn, max_doc_ov))
                f.write('        ...' + NL)

    if not wrote:
        f.write('    ...' + NL)
    f.write(NL)
    return mn_meta


# -----------------------------------------------------------------------------
# MAIN CLASS GENERATOR STUB
# -----------------------------------------------------------------------------

class GlobalStubGenerator:
    def __init__(self, root_path):
        self.root = root_path
        self.folders = {
            'full': os.path.join(root_path, 'stubs'),
            'mini': os.path.join(root_path, 'stubs.mini')
        }
        self.ns_data, self.ns_assemblies = {}, {}
        for p in self.folders.values():
            if not os.path.exists(p):
                os.makedirs(p)

    def _ensure_pkg_tree(self, base_path, rel_dir):
        cur = base_path
        for p in [x for x in rel_dir.split(os.sep) if x]:
            cur = os.path.join(cur, p)
            if not os.path.exists(cur):
                os.makedirs(cur)
            ip = os.path.join(cur, '__init__.pyi')
            if not os.path.exists(ip):
                with open(ip, 'w', encoding='utf-8') as f:
                    f.write('')

    def _load_assembly(self, asm_in):
        if not isinstance(asm_in, str):
            return asm_in
        try:
            return Reflection.Assembly.Load(asm_in)
        except Exception:
            try:
                for a in System.AppDomain.CurrentDomain.GetAssemblies():
                    if a.GetName().Name == asm_in:
                        return a
            except Exception:
                pass
        return None

    def collect_from_assembly(self, asm_in):
        asm = self._load_assembly(asm_in)
        if asm is None:
            return
        try:
            ts = asm.GetExportedTypes()
        except Exception as ex:
            try:
                ts = ex.Types
            except Exception:
                return
        for t in ts:
            if t is None or not t.Namespace:
                continue
            ks = self.ns_data.setdefault(t.Namespace, {})
            ks[_t_fqn(t)] = t
            self.ns_assemblies.setdefault(t.Namespace, set()).add(asm.FullName)

    def write_all(self, max_ov=8, max_doc_ov=12):
        for ns, tdct in self.ns_data.items():
            ts = list(tdct.values())
            rel_dir = os.path.join(*ns.split('.'))
            asm_info = ', '.join(sorted(list(self.ns_assemblies[ns])))

            hdr = (
                f'# encoding: utf-8{NL}'
                f'# stub module {ns}{NL}'
                f'# from {asm_info}{NL}{NL}'
                f'from __future__ import annotations{NL}'
                f'from typing import Any, Callable, Collection, Dict, Iterable, Iterator, '
                f'List, Optional, Protocol, Sequence, Set, Tuple, TypeVar, Generic, overload{NL}'
                f'from _linq import IEnumerableLinq, IOrderedEnumerableLinq, IGroupingLinq{NL}{NL}'
            )

            for _, base in self.folders.items():
                self._ensure_pkg_tree(base, rel_dir)
                td = os.path.join(base, rel_dir)
                with open(os.path.join(td, '__init__.pyi'), 'w', encoding='utf-8') as f:
                    f.write(hdr)
                    f.write('class IDisposable: ...' + NL)
                    f.write('class IGraphicItem: ...' + NL)
                    f.write('class IInstanceableGraphicItem: ...' + NL)
                    f.write('class DesignScriptEntity: ...' + NL)
                    f.write('class Enum: ...' + NL + NL)

                    for t in sorted(ts, key=lambda x: x.Name):
                        if not (t.IsClass or t.IsInterface or t.IsValueType or t.IsEnum):
                            continue

                        tn = _t_name(t)

                        if t.IsEnum:
                            f.write(f'class {tn}(Enum):' + NL)
                            fs = t.GetFields(BindingFlags.Public | BindingFlags.Static)
                            wrote = False
                            for fi in fs:
                                if fi.Name == 'value__':
                                    continue
                                f.write(f'    {_n_id(fi.Name)}: Any' + NL)
                                wrote = True
                            if not wrote:
                                f.write('    ...' + NL)
                            f.write(NL)
                            continue

                        smeta = _write_static_meta(f, t, ns, max_ov, max_doc_ov)
                        base_hint = _base_hint(t, ns)
                        if smeta:
                            f.write(f'class {tn}({base_hint}, metaclass={smeta}):' + NL)
                        else:
                            f.write(f'class {tn}({base_hint}):' + NL)
                        wrote = False

                        cs = t.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
                        if cs is not None and cs.Length > 0:
                            wrote = True
                            if cs.Length == 1:
                                a, d = _sig_params(cs[0].GetParameters(), ns)
                                f.write(f'    def __init__({_fp_self(a)}) -> None:' + NL)
                                _write_doc(f, _doc_args(d) + NL + _doc_ret('None'))
                                f.write('        ...' + NL)
                            else:
                                lim = min(cs.Length, max_ov)
                                for i in range(lim):
                                    a, d = _sig_params(cs[i].GetParameters(), ns)
                                    f.write('    @overload' + NL)
                                    f.write(f'    def __init__({_fp_self(a)}) -> None:' + NL)
                                    _write_doc(f, _doc_args(d) + NL + _doc_ret('None'))
                                    f.write('        ...' + NL)
                                if cs.Length > max_ov:
                                    f.write('    def __init__(self, *args: Any) -> None:' + NL)
                                    _write_doc(f, _doc_args([]) + NL + _doc_ret('None') + NL + _doc_overloads(cs, ns, None, max_doc_ov))
                                    f.write('        ...' + NL)

                        iflags = BindingFlags.Public | BindingFlags.Instance
                        aflags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static

                        for p in t.GetProperties(iflags):
                            if _has_index_params(p) or _is_static_property(p):
                                continue

                            pn = _n_id(p.Name)
                            rt = _ret_hint(p.PropertyType, ns, tn, t)
                            sm = p.GetSetMethod()

                            wrote = True
                            f.write('    @property' + NL)
                            f.write(f'    def {pn}(self) -> {ann(rt)}:' + NL)
                            _write_doc(f, _doc_ret(rt))
                            f.write('        ...' + NL)
                            if sm is not None:
                                f.write(f'    @{pn}.setter' + NL)
                                f.write(f'    def {pn}(self, v: {ann(rt)}) -> None:' + NL)
                                _write_doc(f, _doc_args([('v', rt, 'in')]) + NL + _doc_ret('None'))
                                f.write('        ...' + NL)

                        ms = [m for m in t.GetMethods(aflags) if not (m.IsSpecialName and not m.Name.startswith('op_'))]
                        ms = [m for m in ms if (not m.IsStatic) or OPERATOR_MAP.get(m.Name, m.Name).startswith('__')]
                        grp = {}
                        for m in ms:
                            n = OPERATOR_MAP.get(m.Name, m.Name)
                            k = (n, m.IsStatic)
                            grp.setdefault(k, []).append(m)

                        for (mn, is_static), mis in sorted(grp.items(), key=lambda x: (x[0][0], x[0][1])):
                            wrote = True
                            tself = tn

                            if len(mis) == 1:
                                mi = mis[0]
                                a, d = _sig_params(mi.GetParameters(), ns)
                                rt = _ret_hint(mi.ReturnType, ns, tself, mi.DeclaringType)

                                if is_static and not mn.startswith('__'):
                                    f.write('    @staticmethod' + NL)
                                    f.write(f'    def {mn}({_fp_static(a)}) -> {ann(rt)}:' + NL)
                                else:
                                    fp = _fp_self(a) if not is_static else _fp_static(a)
                                    f.write(f'    def {mn}({fp}) -> {ann(rt)}:' + NL)

                                _write_doc(f, _doc_args(d) + NL + _doc_ret(rt))
                                f.write('        ...' + NL)
                            else:
                                lim = min(len(mis), max_ov)
                                for i in range(lim):
                                    mi = mis[i]
                                    a, d = _sig_params(mi.GetParameters(), ns)
                                    rt = _ret_hint(mi.ReturnType, ns, tself, mi.DeclaringType)

                                    f.write('    @overload' + NL)
                                    if is_static and not mn.startswith('__'):
                                        f.write('    @staticmethod' + NL)
                                        f.write(f'    def {mn}({_fp_static(a)}) -> {ann(rt)}:' + NL)
                                    else:
                                        fp = _fp_self(a) if not is_static else _fp_static(a)
                                        f.write(f'    def {mn}({fp}) -> {ann(rt)}:' + NL)

                                    _write_doc(f, _doc_args(d) + NL + _doc_ret(rt))
                                    f.write('        ...' + NL)

                                if len(mis) > max_ov:
                                    rt0 = _ret_hint(mis[0].ReturnType, ns, tself, mis[0].DeclaringType)
                                    if is_static and not mn.startswith('__'):
                                        f.write('    @staticmethod' + NL)
                                        f.write(f'    def {mn}(*args: Any, **kwargs: Any) -> {ann(rt0)}:' + NL)
                                    else:
                                        f.write(f'    def {mn}(self, *args: Any, **kwargs: Any) -> {ann(rt0)}:' + NL)
                                    _write_doc(f, _doc_args([]) + NL + _doc_ret(rt0) + NL + _doc_overloads(mis, ns, tself, max_doc_ov))
                                    f.write('        ...' + NL)

                        if not wrote:
                            f.write('    ...' + NL)
                        f.write(NL)
                        
    def create_clr_stub(self):
        c = (
            f"# encoding: utf-8{NL}"
            f"# stub module clr{NL}"
            f"from typing import Any{NL}{NL}"
            f"def AddReference(name: str) -> Any: ...{NL}"
        )
        for m in ['stubs', 'stubs.mini']:
            with open(os.path.join(self.root, m, 'clr.pyi'), 'w', encoding='utf-8') as f:
                f.write(c)
                        
    def create_linq_stub(self):
        lq = textwrap.dedent('''\
            # encoding: utf-8
            # stub helpers for CLR LINQ extension methods
            from __future__ import annotations
            from typing import Any, Callable, Generic, Iterable, Iterator, List, Optional, Protocol, Sequence, TypeVar, overload

            T = TypeVar('T')
            U = TypeVar('U')
            K = TypeVar('K')

        class _Where(Protocol, Generic[T]):
            @overload
            def __call__(self, predicate: Callable[[T], bool]) -> 'IEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:\n
            .Where(System.Func[System.Object, System.Boolean](lambda x: x.Condition))
        \nReturns:
            IEnumerableLinq[T]"""
                ...
        
            @overload
            def __call__(self, predicate: Any) -> 'IEnumerableLinq[Any]':
                """Code syntax for Dynamo PythonNet3:\n
            .Where(System.Func[System.Object, System.Boolean](lambda x: ...))
        \nReturns:
            IEnumerableLinq[Any]"""
                ...
        
            @overload
            def __getitem__(self, types: Any) -> Callable[[Callable[[T], bool]], 'IEnumerableLinq[T]']: ...
            @overload
            def __getitem__(self, types: Any) -> Callable[[Any], 'IEnumerableLinq[Any]']: ...
        
        class _Select(Protocol, Generic[T]):
            @overload
            def __call__(self, selector: Callable[[T], U]) -> 'IEnumerableLinq[U]':
                """Code syntax for Dynamo PythonNet3:\n
            .Select[System.Object, System.Object](System.Func[System.Object, System.Object](lambda x: x.Property))
        \nReturns:
            IEnumerableLinq[U]"""
                ...
        
            @overload
            def __call__(self, selector: Any) -> 'IEnumerableLinq[Any]':
                """Code syntax for Dynamo PythonNet3:\n
            .Select[System.Object, System.Object](System.Func[System.Object, System.Object](lambda x: x.Property))        Returns:
            IEnumerableLinq[Any]"""
                ...
        
            @overload
            def __getitem__(self, types: Any) -> Callable[[Callable[[T], U]], 'IEnumerableLinq[U]']: ...
            @overload
            def __getitem__(self, types: Any) -> Callable[[Any], 'IEnumerableLinq[Any]']: ...
        
        class _SelectMany(Protocol, Generic[T]):
            @overload
            def __call__(self, selector: Callable[[T], Iterable[U]]) -> 'IEnumerableLinq[U]':
                """Code syntax for Dynamo PythonNet3:\n
            .SelectMany[System.Object, System.Object](System.Func[System.Object, System.Collections.Generic.IEnumerable[System.Object]](lambda x: x.Items))
        \nReturns:
            IEnumerableLinq[U]"""
                ...
        
            @overload
            def __call__(self, selector: Any) -> 'IEnumerableLinq[Any]':
                """Code syntax for Dynamo PythonNet3:\n
            .SelectMany[System.Object, System.Object](System.Func[System.Object, System.Object](lambda x: x.Items))
        \nReturns:
            IEnumerableLinq[Any]"""
                ...
        
            @overload
            def __getitem__(self, types: Any) -> Callable[[Callable[[T], Iterable[U]]], 'IEnumerableLinq[U]']: ...
            @overload
            def __getitem__(self, types: Any) -> Callable[[Any], 'IEnumerableLinq[Any]']: ...
        
        class _OrderBy(Protocol, Generic[T]):
            @overload
            def __call__(self, keySelector: Callable[[T], K]) -> 'IOrderedEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:\n
            .OrderBy[System.Object, System.String](System.Func[System.Object, System.String](lambda x: x.Key))
        \nReturns:
            IOrderedEnumerableLinq[T]"""
                ...
        
            @overload
            def __call__(self, keySelector: Any) -> 'IOrderedEnumerableLinq[Any]':
                """Code syntax for Dynamo PythonNet3:\n
            .OrderBy[System.Object, System.String](System.Func[System.Object, System.String](lambda x: x.Key))
        \nReturns:
            IOrderedEnumerableLinq[Any]"""
                ...
        
            @overload
            def __getitem__(self, types: Any) -> Callable[[Callable[[T], K]], 'IOrderedEnumerableLinq[T]']: ...
            @overload
            def __getitem__(self, types: Any) -> Callable[[Any], 'IOrderedEnumerableLinq[Any]']: ...
        
        class _GroupBy(Protocol, Generic[T]):
            @overload
            def __call__(self, keySelector: Callable[[T], K]) -> 'IEnumerableLinq[IGroupingLinq[K, T]]':
                """Code syntax for Dynamo PythonNet3:\n
            .GroupBy[System.Object, System.String](System.Func[System.Object, System.String](lambda x: x.Key))
        \nReturns:
            IEnumerableLinq[IGroupingLinq[K, T]]"""
                ...
        
            @overload
            def __call__(self, keySelector: Any) -> 'IEnumerableLinq[IGroupingLinq[Any, Any]]':
                """Code syntax for Dynamo PythonNet3:\n
            .GroupBy[System.Object, System.String](System.Func[System.Object, System.String](lambda x: x.Key))
        \nReturns:
            IEnumerableLinq[IGroupingLinq[Any, Any]]"""
                ...
        
            @overload
            def __getitem__(self, types: Any) -> Callable[[Callable[[T], K]], 'IEnumerableLinq[IGroupingLinq[K, T]]']: ...
            @overload
            def __getitem__(self, types: Any) -> Callable[[Any], 'IEnumerableLinq[IGroupingLinq[Any, Any]]']: ...
        
        class IEnumerableLinq(Generic[T]):
            # callable attributes (supporting obj.Method[...] syntax)
            Where: _Where[T]
            Select: _Select[T]
            SelectMany: _SelectMany[T]
            OrderBy: _OrderBy[T]
            OrderByDescending: _OrderBy[T]
            GroupBy: _GroupBy[T]
        
            def __iter__(self) -> Iterator[T]: ...
        
            def Any(self, predicate: Optional[Callable[[T], bool]] = None) -> bool:
                """Code syntax for Dynamo PythonNet3:
            .Any()
            .Any(System.Func[System.Object, System.Boolean](lambda x: x == value))
        \nReturns:
            bool"""
                ...
        
            def All(self, predicate: Callable[[T], bool]) -> bool:
                """Code syntax for Dynamo PythonNet3:
            .All(System.Func[System.Object, System.Boolean](lambda x: x.IsValid))
        \nReturns:
            bool"""
                ...
        
            def First(self, predicate: Optional[Callable[[T], bool]] = None) -> T:
                """Code syntax for Dynamo PythonNet3:
            .First()
            .First(System.Func[System.Object, System.Boolean](lambda x: x.Id == 1))
        \nReturns:
            T"""
                ...
        
            def FirstOrDefault(self, predicate: Optional[Callable[[T], bool]] = None) -> Optional[T]:
                """Code syntax for Dynamo PythonNet3:
            .FirstOrDefault()
            .FirstOrDefault(System.Func[System.Object, System.Boolean](lambda x: x.Name == "item"))
        \nReturns:
            Optional[T]"""
                ...
        
            def Last(self, predicate: Optional[Callable[[T], bool]] = None) -> T:
                """Code syntax for Dynamo PythonNet3:
            .Last()
            .Last(System.Func[System.Object, System.Boolean](lambda x: x.Value))
        \nReturns:
            T"""
                ...
        
            def LastOrDefault(self, predicate: Optional[Callable[[T], bool]] = None) -> Optional[T]:
                """Code syntax for Dynamo PythonNet3:
            .LastOrDefault()
            .LastOrDefault(System.Func[System.Object, System.Boolean](lambda x: x.Id == 1))
        \nReturns:
            Optional[T]"""
                ...
        
            def Single(self, predicate: Optional[Callable[[T], bool]] = None) -> T:
                """Code syntax for Dynamo PythonNet3:
            .Single()
            .Single(System.Func[System.Object, System.Boolean](lambda x: x.Id == 1))
        \nReturns:
            T"""
                ...
        
            def SingleOrDefault(self, predicate: Optional[Callable[[T], bool]] = None) -> Optional[T]:
                """Code syntax for Dynamo PythonNet3:
            .SingleOrDefault()
            .SingleOrDefault(System.Func[System.Object, System.Boolean](lambda x: x.Id == 1))
        \nReturns:
            Optional[T]"""
                ...
        
            def Count(self, predicate: Optional[Callable[[T], bool]] = None) -> int:
                """Code syntax for Dynamo PythonNet3:
            .Count()
            .Count(System.Func[System.Object, System.Boolean](lambda x: x.Id == 1))
        \nReturns:
            int"""
                ...
        
            def ToList(self) -> List[T]:
                """Returns:
            List[T]"""
                ...
        
            def ToArray(self) -> Sequence[T]:
                """Returns:
            Sequence[T]"""
                ...
        
            def Cast(self, tp: Any) -> 'IEnumerableLinq[Any]':
                """Code syntax for Dynamo PythonNet3:
            .Cast(SomeClrType)
        \nReturns:
            IEnumerableLinq[Any]"""
                ...
        
            def OfType(self, tp: Any) -> 'IEnumerableLinq[Any]':
                """Code syntax for Dynamo PythonNet3:
            .OfType(SomeClrType)
        \nReturns:
            IEnumerableLinq[Any]"""
                ...
        
            def Distinct(self) -> 'IEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:
            .Distinct()
        \nReturns:
            IEnumerableLinq[T]"""
                ...
        
            def Take(self, count: int) -> 'IEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:
            .Take(10)
        \nReturns:
            IEnumerableLinq[T]"""
                ...
        
            def Skip(self, count: int) -> 'IEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:
            .Skip(10)
        \nReturns:
            IEnumerableLinq[T]"""
                ...
        
        class IGroupingLinq(IEnumerableLinq[T], Generic[K, T]):
            @property
            def Key(self) -> K:
                """Returns:
            K"""
                ...
        
        class IOrderedEnumerableLinq(IEnumerableLinq[T]):
            def ThenBy(self, keySelector: Callable[[T], K]) -> 'IOrderedEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:
            .ThenBy(System.Func[System.Object, System.Object](lambda x: x.Key))
        \nReturns:
            IOrderedEnumerableLinq[T]"""
                ...
        
            def ThenByDescending(self, keySelector: Callable[[T], K]) -> 'IOrderedEnumerableLinq[T]':
                """Code syntax for Dynamo PythonNet3:
            .ThenByDescending(System.Func[System.Object, System.Object](lambda x: x.Key))
        \nReturns:
            IOrderedEnumerableLinq[T]"""
                ...
        '''
        )
        for m in ['stubs', 'stubs.mini']:
            with open(os.path.join(self.root, m, '_linq.pyi'), 'w', encoding='utf-8') as f:
                f.write(lq)


# -----------------------------------------------------------------------------
# START HERE
# -----------------------------------------------------------------------------

out_path = IN[0]
if not System.IO.Directory.Exists(out_path):
    out_path = System.IO.Path.GetDirectoryName(fullPathScript)

gen = GlobalStubGenerator(out_path)
gen.collect_from_assembly(Reflection.Assembly.GetAssembly(System.Object))

for a in assemblies_to_load:
    gen.collect_from_assembly(a)

gen.write_all(max_ov=8, max_doc_ov=12)
gen.create_clr_stub()
gen.create_linq_stub()

OUT = 'Success! .pyi stubs generated'

  • Utilisation :



  1. Copier le code dans un nœud PytonNet3 avec en entrée le chemin du dossier où sera généré le stub.

  2. Éditer la liste des assemblies à "générer" assemblies_to_load (optionnel) 

  3. Sauvegarder et lancer le script

  4. Rajouter les chemins du dossier stubs dans VSCode comme ceci
    (raccourci Ctrl + , ). 
Vous pouvez désactiver le "python.analysis.typeCheckingMode" si vous ne voulez pas que VSCode suggère qu'il y a une erreur sur la syntaxe des méthodes d'extension LINQ 




{
    "python.analysis.fixAll": [],
    "python.analysis.extraPaths": [
    "D:/MyFolder/01 Dynamo Stubs generator for VSCode/stubs"
],
    //"python.analysis.typeCheckingMode": "basic",
    "python.analysis.stubPath": "D:/MyFolder/01 Dynamo Stubs generator for VSCode/stubs",
    "python.analysis.diagnosticSeverityOverrides": {},
    "python.analysis.aiCodeActions": {
    


        "implementAbstractClasses": true,
        "generateSymbol": true,
        "convertFormatString": true
    }
}

  • Rappel sur les types hints (cast)

Bien que Python soit dynamiquement typé, il peut être utile de déclarer (caster) le type de certaines variables pour
l'autocomplétion : 
    • soit via le Type Hinting (annotations)
    • soit avec l'utilisation de typing.cast

Exemple avec le Type Hinting

## EXAMPLE 1 ##
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc : DB.Document = DocumentManager.Instance.CurrentDBDocument

# Now, when typing "doc.", the IDE will suggest .Export(), .GetElement(), etc.

## EXAMPLE 2 ##
from Autodesk.Revit.DB import Wall

walls = FilteredElementCollector(doc).OfClass(Wall).ToElements()

for wall in walls:
    # type hinting
    wall: Wall 
    
    # autocompletion enable on wall
    print(wall.Width)

Exemple avec typing.cast

from typing import cast
from Autodesk.Revit.DB import Wall

for item in walls:
    # typing.cast
    wall = cast(Wall, item)



« A chaque fois que vous vous retrouvez à penser comme la majorité des gens, faites une pause, et réfléchissez. »
Mark Twain

0 commentaires:

Enregistrer un commentaire