UIClasses - Data-Modeling for User Interfaces

Introduction

UIClasses is a library that leverages data-modeling for the user-interface layer of your python project.

It provides the Model class that adds Data Classe features to its subclasses.

With UIClasses you can separate backend from frontend data modeling, preventing coupling in your python project.

This is not an ORM

UIClasses is designed to work in tandem with your favorite ORM, not a replacement for it.

Java has DAO (Data-access objects) and POJO (Plain Old Java Objects), usually POJOs are used at the UI-layer of an application with data mapped from a DAO whose data came from the storage layer of an application.

In this context, UIClasses are POJOs, and ORM Models, for example SQLAlchemy, are DAOs.

Bags of Variables

  • Objects optimized for user interfaces.

  • Methods to traverse nested dicts, convert to and from json

  • ModelList and ModelSet collections for robust manipulation of collections of models.

  • No I/O happens in models.

  • Collections can be easily cached to leverage responsive user interfaces.

How to install

pip3 install uiclasses

Tutorial

This is a basic guide to the most basic usage of the module.

In this guide we will define data models for data returned by the Github API to retrieve users https://developer.github.com/v3/users/ and present this data to the UI layer in a concise way.

At the end we will also build a very simple client to the Github API.

Declaring a Model for user interfaces

The model below is defined according to the Single User Response from the Github V3 API.

Take a look here to see what a full json response looks like before continuing so the model definition below will make more sense.

Okay, so you see there are several fields, but only a few of the User properties are relevant for user-interface purposes.

from uiclasses import Model

class GithubUser(Model):
    login: str
    email: str
    hireable: bool
    public_repos: int
    public_gists: int
    followers: int
    following: int

Every field declared with type annotations is considered to be visible in the user interface.

This is this is powered by dataclasses.fields().

Two ways of instantiating models

1. Passing a dict
octocat = GithubUser({
    "login": "octocat",
})

print(octocat.to_dict())
{
    "login": "octocat",
}
1. Passing a keyword-arguments
octocat = GithubUser(
    login="octocat",
)
print(octocat.to_dict())
{
    "login": "octocat",
}

Automatic getters and setters

Every visible field becomes a property that can be accessed directly via instance as if it were a regular @property

user1 = GithubUser()
user1.login = "octocat"

print(user1.to_dict())
{
    "login": "octocat",
}

Invisible Getters/Setters

Sometimes it can be useful to define properties that act on the internal data of a model without making them visible to the user interface.

UIClasses provides special annotations to achieve this with 3 variations:

  • Read-only Getters

  • Write-only Setters

  • Read-Write Properties

Read-only Getters
from uiclasses import Model


class User(Model):
    id: int
    username: str
    token: Getter[str]


foobar = User(id=1, username="foobar", token="some-data")
print(foobar.to_dict())
print(foobar.token)
print(foobar.get_table_columns())

try:
    foobar.token = 'another-value'
except Exception as e:
    print(e)
{
    "id": 1,
    "username": "foobar",
    "token": "some-data",
}
"some-data"
["id", "username"]
"'User' object has no attribute 'token'"
Write-only Getters
from uiclasses import Model


class User(Model):
    id: int
    username: str
    token: Setter[str]


foobar = User(id=1, username="foobar", token="some-data")
print(foobar.to_dict())
foobar.token = 'another-value'
print(foobar.to_dict())
print(foobar.get_table_columns())

try:
    print(foobar.token)
except Exception as e:
    print(e)
{
    "id": 1,
    "username": "foobar",
    "token": "some-data",
}
{
    "id": 1,
    "username": "foobar",
    "token": "another-value",
}
["id", "username"]
"'User' object has no attribute 'token'"
Read-write Properties
from uiclasses import Model


class User(Model):
    id: int
    username: str
    token: Property[str]


foobar = User(id=1, username="foobar", token="some-data")
print(foobar.token)
print(foobar.to_dict())
foobar.token = 'another-value'
print(foobar.token)
print(foobar.to_dict())
print(foobar.get_table_columns())
"some-data"
{
    "id": 1,
    "username": "foobar",
    "token": "some-data",
}
"another-value"
{
    "id": 1,
    "username": "foobar",
    "token": "another-value",
}
["id", "username"]

API Reference

Model

>>> from uiclasses import Model
>>>
>>> class User(Model):
...     email: str
...
class uiclasses.Model(__data__: dict = None, *args, **kw)[source]

Base class for User-interface classes.

Allows declaring what instance attributes are visible via type annotations or __visible_attributes__.

Example:

from uiclasses.base import Model

class BlogPost(Model):
    id: int
    title: str


 post = BlogPost(
     id=1,
     title='test',
     body='lorem ipsum dolor sit amet'
 )

 print(str(post))
 print(repr(post))
 print(post.format_robust_table())

Model.List

>>> user1 = User(email="aaaa@test.com")
>>> user2 = User(email="bbbb@test.com")
>>>
>>> users = User.List([user1, user2, user1])
>>> users
User.List[user1, user2, user3]
class uiclasses.collections.ModelList(children: Iterable[uiclasses.base.Model])[source]

Implementation of IterableCollection for the list type.

Model.Set

An ordered set for managing unique items.

>>> user1 = User(email="aaaa@test.com")
>>> user2 = User(email="bbbb@test.com")
>>>
>>> users = User.Set([user1, user2, user1])
>>> users
User.Set[user1, user2]
class uiclasses.collections.ModelSet(*args, **kwds)[source]

Implementation of IterableCollection for the OrderedSet type.

DataBag

class uiclasses.DataBag(__data__: dict = None)[source]

base-class for config containers, behaves like a dictionary but is a enhanced proxy to manage data from its internal dict __data__ as well as traversing nested dictionaries within it.

UserFriendlyObject

class uiclasses.UserFriendlyObject[source]

DataBagChild

class uiclasses.DataBagChild(data, *location)[source]

Represents a nested dict within a DataBag that is aware of its location within the parent.

IterableCollection

class uiclasses.collections.IterableCollection[source]

Base mixin for ModelList and ModelSet, provides methods to manipulate iterable collections in ways take advantage of the behavior of models.

For example it supports filtering by instance attributes through a cal to the attribute_matches() method of each children.

Features:

  • sorted_by() - sort by a single attribute

  • filter_by() - to filter by a single attribute

  • sorted() - alias to MyModel.List(sorted(my_model_collection)) or .Set()

  • filter() - alias to MyModel.List(filter(callback, my_model_collection))

  • format_robust_table()

  • format_pretty_table()

Utils

File-System Helpers

runtime helper functions used for leveraging idiosyncrasies of testing.

Meta

Release History

Changes in 2.3.2

  • Add same only_visible of serialize() to to_dict() and serialize_field().

  • Pass correct only_visible within serialize_all() and serialize_visible().

Changes in 2.3.1

  • Change behavior of serialize_visible() to omit None values.

Changes in 2.3.0

  • Add method serialize_visible() and serialize_all() to Model and IterableCollection.

  • Add boolean parameter only_visible in uiclasses.base.Model.serialize() - to decide whether to call serialize_visible() or serialize_all().

Changes in 2.2.1

  • Add behavior to uiclasses.collections.is_iterable() consider anything with a callable __iter__ attribute a callable.

Changes in 2.2.0

  • Change behavior of explicit __visible_attributes__ declaration: when declared, the visible fields will be exactly those. If not declared, visible fields will be extracted from type annotations.

  • The old behavior of __visible_attributes__ is now available through Model.__declared_attributes__ which __visible_attributes__ (if any) with types from annotations.

  • Support casting IterableCollection with itself and introduce uiclasses.collections.is_iterable() helper function.

  • Show RuntimeWarning if typing module is installed as distribution package in python > 3.6.1.

Changes in 2.1.0

  • Support nested model types.

  • Cast values to their known type when instantiating a new Model.

Changes in 2.0.3

  • perform super()__setattr__ behavior even when an explicit setter is not defined and the attribute does not exist in the instance.

Changes in 2.0.2

  • fix python 3.6 support.

Changes in 2.0.1

  • don’t try to cast annotations containing typing.Generic or typing.Any.

Changes in 2.0.0

  • support explicit declaration of getters and setters that are not visible properties.

  • implement type casting for all model attributes.

  • automatic parsing of boolean-looking strings for fields of type bool.

Changes in 1.1.1

  • Allow Model(x) where x is not a dict but can be cast into a dict.

Changes in 1.1.0

  • Model.Set() and Model.List() not support generators.

Indices and tables