Tutorial

using VisibleObject

We give a simple example of a everyday network of objects.

Background is a framework to build a schedule of classes in a school. First we introduce Student, Teacher and ClassRoom as basic items.

Then we add container (lists) of them. And finally a Lesson as a struct with a Teacher, a ClassRoom and a list of Student.

A list of Lesson gives a Schedule and lists of Student, Teacher and ClassRoom together with a schedule build a School.

# -*- coding: utf-8 -*-

# unicum
# ------
# Python library for simple object cache and factory.
#
# Author:   sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version:  0.3, copyright Wednesday, 18 September 2019
# Website:  https://github.com/sonntagsgesicht/unicum
# License:  Apache License 2.0 (see LICENSE file)


import logging
import sys

sys.path.append('..')
logging.basicConfig()

from unicum import VisibleObject, VisibleAttributeList, VisibleObjectList, VisibleDataRange


class Person(VisibleObject):
    def __init__(self, name=''):
        super(Person, self).__init__(name)
        self._age_ = 0


class Student(Person):
    def __init__(self, name=''):
        super(Student, self).__init__(name)
        self._school_class_ = SchoolClass()


class Teacher(Person):
    pass


class ClassRoom(VisibleObject):
    pass


class StudentList(VisibleAttributeList):
    def __init__(self, iterable=None):
        super(StudentList, self).__init__(iterable, Student)


class TeacherList(VisibleAttributeList):
    def __init__(self, iterable=None):
        super(TeacherList, self).__init__(iterable, Teacher)


class ClassRoomList(VisibleAttributeList):
    def __init__(self, iterable=None):
        super(ClassRoomList, self).__init__(iterable, ClassRoom)


class SchoolClass(VisibleObject):
    def __init__(self, name=''):
        super(SchoolClass, self).__init__(name)
        self._students_ = StudentList()


class Lesson(VisibleObject):
    def __init__(self):
        super(Lesson, self).__init__()
        self._subject_ = ''
        self._teacher_ = Teacher()
        self._class_room_ = ClassRoom()
        self._school_class_ = SchoolClass()
        self._day_ = 'Monday'
        self._time_ = '8:30'
        self._hour_ = 1


class Schedule(VisibleAttributeList):
    def __init__(self, iterable=None, object_type=Lesson,
                 value_types=(float, int, str, type(None), VisibleObject)):
        super(Schedule, self).__init__(iterable, object_type, value_types)


class School(VisibleObject):
    def __init__(self):
        super(School, self).__init__()
        self._teachers_ = TeacherList()
        self._students_ = StudentList()
        self._class_rooms_ = ClassRoomList()
        self._schedule_ = Schedule()


if __name__ == '__main__':
    School().register()  # turns School() into an `unicum` class (with only one `unnamed` instance)
    School().modify_object('Schedule', Schedule())  # mark School().Schedule as modified

    # fill the Schedule with Lessons
    School().get_property('Schedule').append(
        Lesson.create(
            Subject='Math',
            Teacher='Mr. Logan',
            SchoolClass='FreshMen',
            ClassRoom='Room 1',
            Time='8:30'
        ))

    School().get_property('Schedule').append(
        Lesson.create(
            Subject='Physics',
            Teacher='Mr. Logan',
            SchoolClass='Senior',
            ClassRoom='Room 2',
            Time='10:15'
        ))

    School().get_property('Schedule').append(
        Lesson.create(
            Subject='Math',
            Teacher='Mr. Logan',
            SchoolClass='Senior',
            ClassRoom='Room 2',
            Time='12:00'
        ))

    School().get_property('Schedule').append(
        Lesson.create(
            Subject='History',
            Teacher='Mrs. Smith',
            SchoolClass='Senior',
            ClassRoom='Room 2',
            Time='8:30'
        ))

    School().get_property('Schedule').append(
        Lesson.create(
            Subject='Sports',
            Teacher='Mrs. Smith',
            SchoolClass='FreshMen',
            ClassRoom='Hall',
            Time='10:15'
        ))

    School().get_property('Schedule').append(
        Lesson.create(
            Subject='History',
            Teacher='Mrs. Smith',
            SchoolClass='FreshMen',
            ClassRoom='Room 1',
            Time='12:00'
        ))

    # fill VisibleAttributeList
    School().modify_object('Teachers', TeacherList(('Mr. Logan', 'Mrs. Smith')).register())
    School().modify_object('Students', StudentList(('Tom', 'Ben', 'Luisa', 'Peter', 'Paul', 'Mary')).register())
    School().modify_object('ClassRooms', ClassRoomList(('Room 1', 'Room 2', 'Hall')).register())

    # give students an assigned class which makes the object tree circular:
    # School().Students[0] in School().Students[0].SchoolClass.Students
    # (hence, the object tree cannot be drawn as a json at once.)
    SchoolClass('FreshMen').register().modify_object('Students', School().get_property('Students')[:3])
    for s in SchoolClass('FreshMen').get_property('Students'):
        s.modify_object('SchoolClass', SchoolClass('FreshMen'))

    SchoolClass('Senior').register().modify_object('Students', School().get_property('Students')[3:])
    for s in SchoolClass('Senior').get_property('Students'):
        s.modify_object('SchoolClass', SchoolClass('Senior'))

    # now all items are stored in - can can be reconstructed from School() json
    print(School().to_json(all_properties_flag=True, indent=2))

    """
    {
      "Name": "School",
      "Class": "School",
      "Module": "__main__",
      "ClassRooms": [
        [     "Class" ,  "Module" ,  "Name" ],
        [ "ClassRoom" ,"__main__" ,"Room 1" ],
        [ "ClassRoom" ,"__main__" ,"Room 2" ],
        [ "ClassRoom" ,"__main__" ,  "Hall" ]
      ],
      "Schedule": [
        [  "Class" ,"ClassRoom" ,  "Module" ,  "Name" ,"SchoolClass" ,"Subject" ,   "Teacher" , "Time" ],
        [ "Lesson" ,   "Room 1" ,"__main__" ,"Lesson" ,   "FreshMen" ,   "Math" , "Mr. Logan" , "8:30" ],
        [ "Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,"Physics" , "Mr. Logan" ,"10:15" ],
        [ "Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,   "Math" , "Mr. Logan" ,"12:00" ],
        [ "Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,"History" ,"Mrs. Smith" , "8:30" ],
        [ "Lesson" ,     "Hall" ,"__main__" ,"Lesson" ,   "FreshMen" , "Sports" ,"Mrs. Smith" ,"10:15" ],
        [ "Lesson" ,   "Room 1" ,"__main__" ,"Lesson" ,   "FreshMen" ,"History" ,"Mrs. Smith" ,"12:00" ]
      ],
      "Students": [
        [   "Class" ,  "Module" , "Name" ,"SchoolClass" ],
        [ "Student" ,"__main__" ,  "Tom" ,   "FreshMen" ],
        [ "Student" ,"__main__" ,  "Ben" ,   "FreshMen" ],
        [ "Student" ,"__main__" ,"Luisa" ,   "FreshMen" ],
        [ "Student" ,"__main__" ,"Peter" ,     "Senior" ],
        [ "Student" ,"__main__" , "Paul" ,     "Senior" ],
        [ "Student" ,"__main__" , "Mary" ,     "Senior" ]
      ],
      "Teachers": [
        [   "Class" ,  "Module" ,      "Name" ],
        [ "Teacher" ,"__main__" , "Mr. Logan" ],
        [ "Teacher" ,"__main__" ,"Mrs. Smith" ]
      ]
    }
    """

    # for didactic purpose we set Schedule as a VisibleDataRange and all other lists as VisibleObjectList
    # (since `modify_object` would cast a VisibleObjectList to a TeacherList, StudentList or ClassRoomList
    #  we have to workaround here.)

    School()._teachers_ = VisibleObjectList(School().get_property('Teachers'))
    School()._students_ = VisibleObjectList(School().get_property('Students'))
    School()._class_rooms_ = VisibleObjectList(School().get_property('ClassRooms'))
    School()._schedule_ = VisibleDataRange(School().get_property('Schedule').to_serializable())

    # now we can not reconstructed from School() json as teachers, students and class rooms are only given by name
    print(School().to_json(all_properties_flag=True, indent=2))

    """
    {
      "Name": "School",
      "Class": "School",
      "Module": "__main__",
      "ClassRooms": [
        "Room 1",
        "Room 2",
        "Hall"
      ],
      "Schedule": [
        [ null , "Class" ,"ClassRoom" ,  "Module" ,  "Name" ,"SchoolClass" ,"Subject" ,   "Teacher" , "Time" ],
        [    0 ,"Lesson" ,   "Room 1" ,"__main__" ,"Lesson" ,   "FreshMen" ,   "Math" , "Mr. Logan" , "8:30" ],
        [    1 ,"Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,"Physics" , "Mr. Logan" ,"10:15" ],
        [    2 ,"Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,   "Math" , "Mr. Logan" ,"12:00" ],
        [    3 ,"Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,"History" ,"Mrs. Smith" , "8:30" ],
        [    4 ,"Lesson" ,     "Hall" ,"__main__" ,"Lesson" ,   "FreshMen" , "Sports" ,"Mrs. Smith" ,"10:15" ],
        [    5 ,"Lesson" ,   "Room 1" ,"__main__" ,"Lesson" ,   "FreshMen" ,"History" ,"Mrs. Smith" ,"12:00" ]
      ],
      "Students": [
        "Tom",
        "Ben",
        "Luisa",
        "Peter",
        "Paul",
        "Mary"
      ],
      "Teachers": [
        "Mr. Logan",
        "Mrs. Smith"
      ]
    }
    """

    # but we can extract all items we have so far and reconstruct from them
    for obj in VisibleObject.filter():
        print(VisibleObject(obj).to_json(all_properties_flag=False, indent=2))
        print()
        pass

    """
    {
      "Name": "School",
      "Class": "School",
      "Module": "__main__",
      "ClassRooms": [
        "Room 1",
        "Room 2",
        "Hall"
      ],
      "Schedule": [
        [ null , "Class" ,"ClassRoom" ,  "Module" ,  "Name" ,"SchoolClass" ,"Subject" ,   "Teacher" , "Time" ],
        [    0 ,"Lesson" ,   "Room 1" ,"__main__" ,"Lesson" ,   "FreshMen" ,   "Math" , "Mr. Logan" , "8:30" ],
        [    1 ,"Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,"Physics" , "Mr. Logan" ,"10:15" ],
        [    2 ,"Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,   "Math" , "Mr. Logan" ,"12:00" ],
        [    3 ,"Lesson" ,   "Room 2" ,"__main__" ,"Lesson" ,     "Senior" ,"History" ,"Mrs. Smith" , "8:30" ],
        [    4 ,"Lesson" ,     "Hall" ,"__main__" ,"Lesson" ,   "FreshMen" , "Sports" ,"Mrs. Smith" ,"10:15" ],
        [    5 ,"Lesson" ,   "Room 1" ,"__main__" ,"Lesson" ,   "FreshMen" ,"History" ,"Mrs. Smith" ,"12:00" ]
      ],
      "Students": [
        "Tom",
        "Ben",
        "Luisa",
        "Peter",
        "Paul",
        "Mary"
      ],
      "Teachers": [
        "Mr. Logan",
        "Mrs. Smith"
      ]
    }

    {
      "Name": "Mr. Logan",
      "Class": "Teacher",
      "Module": "__main__"
    }

    {
      "Name": "Mrs. Smith",
      "Class": "Teacher",
      "Module": "__main__"
    }

    {
      "Name": "Tom",
      "Class": "Student",
      "Module": "__main__",
      "SchoolClass": "FreshMen"
    }

    {
      "Name": "Ben",
      "Class": "Student",
      "Module": "__main__",
      "SchoolClass": "FreshMen"
    }

    {
      "Name": "Luisa",
      "Class": "Student",
      "Module": "__main__",
      "SchoolClass": "FreshMen"
    }

    {
      "Name": "Peter",
      "Class": "Student",
      "Module": "__main__",
      "SchoolClass": "Senior"
    }

    {
      "Name": "Paul",
      "Class": "Student",
      "Module": "__main__",
      "SchoolClass": "Senior"
    }

    {
      "Name": "Mary",
      "Class": "Student",
      "Module": "__main__",
      "SchoolClass": "Senior"
    }

    {
      "Name": "Room 1",
      "Class": "ClassRoom",
      "Module": "__main__"
    }

    {
      "Name": "Room 2",
      "Class": "ClassRoom",
      "Module": "__main__"
    }

    {
      "Name": "Hall",
      "Class": "ClassRoom",
      "Module": "__main__"
    }

    {
      "Name": "FreshMen",
      "Class": "SchoolClass",
      "Module": "__main__",
      "Students": [
        [   "Class" ,  "Module" , "Name" ,"SchoolClass" ],
        [ "Student" ,"__main__" ,  "Tom" ,   "FreshMen" ],
        [ "Student" ,"__main__" ,  "Ben" ,   "FreshMen" ],
        [ "Student" ,"__main__" ,"Luisa" ,   "FreshMen" ]
      ]
    }

    {
      "Name": "Senior",
      "Class": "SchoolClass",
      "Module": "__main__",
      "Students": [
        [   "Class" ,  "Module" , "Name" ,"SchoolClass" ],
        [ "Student" ,"__main__" ,"Peter" ,     "Senior" ],
        [ "Student" ,"__main__" , "Paul" ,     "Senior" ],
        [ "Student" ,"__main__" , "Mary" ,     "Senior" ]
      ]
    }
    """

setup a web api service

The second demo shows how to build a simple web service.

# -*- coding: utf-8 -*-

# unicum
# ------
# Python library for simple object cache and factory.
#
# Author:   sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version:  0.3, copyright Wednesday, 18 September 2019
# Website:  https://github.com/sonntagsgesicht/unicum
# License:  Apache License 2.0 (see LICENSE file)


from datetime import datetime
from hashlib import md5

from flask import Flask, request, jsonify
from flask.helpers import make_response

from unicum import SessionHandler, VisibleObject


class DemoServer(Flask):
    """ restful api class """

    def __init__(self, session_handler=SessionHandler(), *args, **kwargs):

        # store session properties
        self._session_handler = session_handler

        # initialize Flask
        kwargs['import_name'] = kwargs.get('import_name', 'unicum_web_service')
        super(DemoServer, self).__init__(*args, **kwargs)
        self.config['JSONIFY_PRETTYPRINT_REGULAR'] = False

        # initialize url routes/rules to manage session
        self.add_url_rule('/', view_func=self._start_session, methods=["GET"])
        self.add_url_rule('/<session_id>', view_func=self._validate_session, methods=["GET"])
        self.add_url_rule('/<session_id>', view_func=self._stop_session, methods=["DELETE"])
        self.add_url_rule('/<session_id>/<func>', view_func=self._call_session, methods=["GET", "POST"])

    # manage sessions
    def _start_session(self):
        """ starts a session """
        assert request.method == 'GET'

        hash_str = str(request.remote_addr) + str(datetime.now())
        session_id = md5(hash_str.encode()).hexdigest()

        session_id = self._session_handler.start_session(session_id)
        return make_response(session_id, 200)

    def _validate_session(self, session_id):
        result = self._session_handler.validate_session(session_id)
        return make_response(jsonify(result), 200)

    def _call_session(self, session_id, func=''):
        """ create object """

        assert request.method in ('GET', 'POST')
        if session_id not in request.base_url:
            return make_response(jsonify('session id %s does not match.' % session_id), 500)

        # get key word arguments
        kwargs = dict()
        if request.method == 'GET':
            kwargs = request.args
        elif request.method == 'POST':
            kwargs = request.get_json(force=True)

        result = self._session_handler.call_session(session_id, func, kwargs)

        if isinstance(result, (bool, int, float, str)):
            result = str(result)
        else:
            result = jsonify(result)

        return make_response(result)

    def _stop_session(self, session_id):
        """ closes a session """
        assert request.method in ('DELETE', 'GET')
        assert session_id in request.base_url

        result = self._session_handler.stop_session(session_id)
        return make_response(jsonify(result), 200)

    # manage server
    def _shutdown(self):
        for session_id in self._sessions:
            self._session_handler.stop_session(session_id)

        request.environ.get('werkzeug.server.shutdown')()
        res = 'shutting down...'
        return make_response(jsonify(res))


class DemoObject(VisibleObject):
    def __init__(self, *args, **kwargs):
        super(DemoObject, self).__init__(*args, **kwargs)
        self._folder_ = ''
        self._float_ = 0.


if __name__ == '__main__':
    import requests
    from _thread import start_new_thread

    ################################################
    # start server at http://127.0.0.1:64001
    ################################################

    url, port = '127.0.0.1', '64001'
    start_new_thread(DemoServer(SessionHandler('demo_server', 'DemoObject')).run, (url, port))

    ################################################
    # start session
    ################################################

    base_url = 'http://%s:%s/' % (url, port)
    session_id = requests.get(url=base_url)

    ################################################
    # call session
    ################################################

    # ----------------------------------------------
    # create object
    # ----------------------------------------------

    url = base_url + session_id.text
    name = 'MyName'
    folder = 'MyFolder'
    res = requests.get(
        url=url + '/create',
        params={
            'name': name,
            'register_flag': True
        })
    assert res.text == name

    # ----------------------------------------------
    # modify object
    # ----------------------------------------------

    res = requests.get(
        url=url + '/modify_object',
        params={
            'self': name,
            'property_name': 'Folder',
            'property_value_variant': folder
        })
    assert res.text == name

    res = requests.get(
        url=url + '/modify_object',
        params={
            'self': name,
            'property_name': 'Float',
            'property_value_variant': 123.321
        })
    assert res.text == name

    # ----------------------------------------------
    # get properties
    # ----------------------------------------------

    res = requests.get(
        url=url + '/get_property',
        params={
            'self': name,
            'property_name': 'Class'
        })
    assert res.text == 'DemoObject'

    res = requests.get(
        url=url + '/get_property',
        params={
            'self': name,
            'property_name': 'Folder'
        })
    assert res.text == folder

    res = requests.get(
        url=url + '/get_property',
        params={
            'self': name,
            'property_name': 'Float'
        })
    assert abs(float(res.text) - 123.321) < 1e-10

    ################################################
    # close session
    ################################################

    session_id = requests.delete(url=url)

    ################################################
    # stop server
    ################################################

    requests.delete(url=base_url)

call service from spread sheet

The third demo is a demo_workbook.xlsm which shows how call the web service from a spread sheet.