Skip to content

[Bug]: The documentation does not indicate snake case should be used #23

Description

@stuartofmt

Which Duet products are you using?

  • Duet2-Wifi
  • Duet2-Ethernet
  • Duet Expansion Breakout Board
  • Duex2
  • Duex5
  • Duet2-Maestro
  • Maestro Dual Driver Expansion
  • Duet3-6HC
  • Duet3-3HC
  • Duet3-1XD
  • Duet3-1LC
  • Duet3-Tool Distribution Board
  • Duet3-Mini5+
  • Duet3-Mini2+
  • Raspberry Pi or other SBC
  • SmartEffector
  • Magnetic Filament Sensor
  • Laser Filament Sensor
  • PT100 Daughterboard
  • Thermocouple Daughterboard
  • PanelDue
  • Other
  • None

Firmware Version

3.7

Duet Web Control Version

3.7

Are you using a Single Board Computer (RaspberryPi) with your Duet?

  • Yes I use a SBC.
  • No I do not use a SBC.

Please upload the results of sending M122 in the gcode console.

M122 Report

Please upload the content of your config.g file.

Config.g

Please upload the content of any other releveant macro files.

Details specific to your printer.

Links to additional info.

No response

What happened?

I am labelling this as a bug because I view missing / unclear documentation as such.

Attached is a script that can assist others in the future - my suggestion is that it could be added to the repository.

"""Dump every dsf-python ObjectModel property path as snake_case (Python) alongside camelCase (JSON).

Walks the model classes, not a live printer, so empty collections (axes, heaters, ...) are still listed.
Usage: python om_reference.py [output_file]     (default: om_reference.txt)

How it works:

-No printer needed. The script reads the dsf-python model classes directly. For lists like axes and heaters, it builds one blank item to document its fields, so they show up even when empty. [n] stands for a list index and [key] for a dictionary key.
-Only public properties are listed. Those are the names you use with getattr.
-NoneType in the type column means the field defaults to None until the printer fills it in (usually an int, float or string). Real types appear once you're connected to a printer.
-Only the default version of some objects is shown. For example, move.kinematics is listed with its default class's fields. A different kinematics type on the printer may have extra fields.
-One odd name: JSON global is globals in Python, because global is a reserved word. The script accounts for this.
"""

import sys
from enum import Enum

from dsf.object_model import ObjectModel
from dsf.object_model.model_object import ModelObject
from dsf.object_model.model_collection import ModelCollection
from dsf.object_model.model_dictionary import ModelDictionary
from dsf.utils import snake_to_camel


def type_name(value):
    if isinstance(value, Enum):
        return f"{type(value).__name__} (enum: {', '.join(m.name for m in type(value))})"
    return type(value).__name__


def new_item(constructor):
    """Create a default instance of a collection/dictionary item type, or None if not possible"""
    if not isinstance(constructor, type) or constructor is object:
        return None
    try:
        return constructor()
    except Exception:
        return None


def walk(obj, py_path, json_path, rows, seen):
    if isinstance(obj, ModelObject):
        if type(obj) in seen:               # guard against recursive types
            return
        seen = seen | {type(obj)}
        for name in sorted(dir(type(obj))):
            if name.startswith('_') or not isinstance(getattr(type(obj), name), property):
                continue
            json_name = 'global' if name == 'globals' else snake_to_camel(name)
            try:
                value = getattr(obj, name)
            except Exception:
                continue
            p, j = f"{py_path}.{name}", f"{json_path}.{json_name}"
            rows.append((p, j, type_name(value)))
            walk(value, p, j, rows, seen)
    elif isinstance(obj, ModelCollection):
        item = obj[0] if len(obj) else new_item(getattr(obj, '_item_constructor', None))
        if item is not None:
            walk(item, f"{py_path}[n]", f"{json_path}[n]", rows, seen)
    elif isinstance(obj, ModelDictionary):
        item = new_item(getattr(obj, '_item_constructor', None))
        if item is not None:
            walk(item, f"{py_path}[key]", f"{json_path}[key]", rows, seen)


def main():
    out_file = sys.argv[1] if len(sys.argv) > 1 else 'om_reference.txt'
    rows = []
    walk(ObjectModel(), 'object_model', 'om', rows, frozenset())
    w1 = max(len(r[0]) for r in rows)
    w2 = max(len(r[1]) for r in rows)
    with open(out_file, 'w') as f:
        f.write(f"{'Python (snake_case)':{w1}}  {'JSON (camelCase)':{w2}}  Type\n")
        f.write(f"{'-' * w1}  {'-' * w2}  ----\n")
        for p, j, t in rows:
            f.write(f"{p:{w1}}  {j:{w2}}  {t}\n")
    print(f"Wrote {len(rows)} properties to {out_file}")


if __name__ == '__main__':
    main()


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentation

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions