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.