question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

UIFields and dataclasses: near-term roadmap for magicgui

See original GitHub issue

Just wanted to lay out in writing some of the near-term plans that I have for magicgui:

Widget “fields” not “parameters”

I regret that function signatures were chosen as the most common way to instantiate and present a group of widgets in magicgui. I think it is one or two levels “too high”. A think a better parallel would have been dataclasses. So many things fall into the general pattern of name: annotation = default value beyond just functions, such as typing.NamedTuple, typing.TypedDict, dataclasses.dataclass, pydantic.BaseModel, attrs.define… and, yes, function signatures.

Rather than framing a widget as having a relation to an inspect.Parameter (see magicgui/signature.py), i think a better analogue would have been dataclasses.Field. Currently, if one wants to make a compound widget in magicgui, you either manually construct it using the direct widget API with Container and create_widget, or you create a function and use @magicgui (which then uses inspect.signature and widgets are created for each Parameter with MagicParameter.to_widget()).

I’ve seen people do this:

@magicgui
def dummy_function(name: str, age: int = 0): ...

…just to create a widget. Which is a good indicator that the abstraction was too specific. There are plenty of use cases where one just wants to collect some data without necessarily passing them all to a specific function.

a collection of UiFields

In upcoming PRs, I’ll be creating a “parallel API” (probably under a v2 or datagui namespace) that instead adds a magicgui.UiField object, (akin to dataclasses.Field, or pydantic.fields.ModelField or attrs.Attribute) that stores the metadata associated with a widget. This will still applicable to function signatures, but will make it much easier to construct a widget using a dataclass, or pydantic model, or attrs class, etc…:

# all of the representations we could easily support

@dataclass
class Person:
    name: str
    age: int = 0

@attrs.define
class Person:
    name: str
    age: int = 0

class Person(BaseModel):
    name: str
    age: int = 0

class Person(typing.NamedTuple):
    name: str
    age: int = 0

class Person(typing.TypedDict):
    name: str
    age: int

# functions are just a special case
def Person(name: str, age: int = 0):
    ...

# JSON Schema
Person = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "number", "default": 0},
    }
}

create_widget(Person)

under the hood, each of these is reduced to a gui model that consists of a sequence of UIFields:

model = [UiField(name='name', type=str), UiField(name='age', type=int, default=0)]

and then to make a widget:

container = Container(widgets=[field.create_widget() for field in model])

what about @magicgui for functions

Functions are still easy: you can always connect a callback to be called with the current values of the widgets… but it should be much easier to create a widget representing a set of fields without having to associate them with some callback. So none of this needs to affect the use case for functions… it’s more about how we conceive of the underlying model here.

JSON schema connection

I’d like UiField to have a direct parallel with other schema languages, with JSON schema being the reference. So, all of the keywords in the validation vocabulary (things like maximum, maxLength, multipleOf, etc…) would have direct parallels in the UiField object (similar to a pydantic FieldInfo object). This would provide an easier connection with non-python representations of data schema, and would make it easier to do things in browsers (e.g. jupyter) with one of many javascript libraries that create UIs from schemas.

TODO:

  • cleanup forward reference resolution: #448
  • add a UiField class: #475
  • create functions that convert dataclasses, pydantic objects, attrs classes, functions, etc… To some UiModel representation
  • cleanup the type -> widget_type mapping in type_map.py (it has become messy)
  • review all of the parameter naming, and __init__ signatures, possibly deprecate/rename things (mostly done in https://github.com/pyapp-kit/magicgui/pull/519)
  • convert internals of @magicgui to use the UiField stuff instead of MagicSignature, etc
  • dramatically simplify documentation (#527). Here are the things I would emphasize:
    1. magicgui has an abstraction for various widgets (SpinBox,Slider, etc…))
    2. magicgui maps python types to widget types (e.g. int -> SpinBox)
    3. for any collection of names, types, & default values (e.g. dataclasses, models, function), magicgui can create a model of types and constraints as a set of UiFields
    4. that collection of UiFields can be converted to a Container widget… which has parallels to a dataclass API
    5. finally, the direct Widget API could be used

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:2
  • Comments:6 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
tlambert03commented, Oct 25, 2022

Yeah, I recognize that buttons are still problematic. They are, of course, fundamentally a bit different than all of the other ValueWidgets in that they don’t (really) contain a value. So, keeping with that, I don’t see them as part of the dataclass & UiField abstraction per se… but rather something that would come on top of it / in addition to it.

I do like something along the lines of the @button decorator in @brisvag’s example above. We can definitely do something like that:

@dataclass
class Person:
    name: str
    age: int = 0

    @button(label='Say Hi', order=0)
    def _say_hi(self):
        print(f"hi {self.name}!")

then:

model = build_model(Person)
# [
#    UiField(name='_say_hi', label='Say Hi', widget='PushButton', on_click=Person._say_hi)
#    UiField(name='name', type=str), 
#    UiField(name='age', type=int, default=0),
# ]
#  ... or something like that

I think the direct widget API still needs more emphasis.

I agree with this, and that would be part of the docs update mentioned in the original post

1reaction
brisvagcommented, Oct 24, 2022

Love this! It came up before several times, and I’m one of the many who use magicgui in the way you described. Couldn’t be more on board!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Issues · pyapp-kit/magicgui - GitHub
Contribute to pyapp-kit/magicgui development by creating an account on GitHub. ... UIFields and dataclasses: near-term roadmap for magicgui.
Read more >
magicgui - bytemeta
UIFields and dataclasses : near-term roadmap for magicgui · Verbose fail on lack of matching widget for provided type.
Read more >
napari > magicgui - Coder Social Home
napari commented on November 21, 2022 magicgui.widgets. ... UIFields and dataclasses: near-term roadmap for magicgui HOT 6; Bug: Annotated types only work ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found