2017-09-20 22:30:38 +10:00
# Eos - Verifiable elections
# Copyright © 2017 RunasSudo (Yingtong Li)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try :
__pragma__ = __pragma__
is_python = False
except :
is_python = True
def __pragma__ ( * args ) :
pass
2017-09-25 17:18:02 +10:00
# Libraries
# =========
2017-09-20 22:30:38 +10:00
if is_python :
__pragma__ ( ' skip ' )
2017-09-25 17:18:02 +10:00
import pymongo
from bson . binary import UUIDLegacy
import base64
2017-11-24 20:26:18 +11:00
from datetime import datetime
2017-09-25 17:18:02 +10:00
import hashlib
import json
import uuid
2017-09-20 22:30:38 +10:00
__pragma__ ( ' noskip ' )
else :
2017-09-28 20:02:20 +10:00
# Load json.js
2017-09-25 17:18:02 +10:00
lib = __pragma__ ( ' js ' , '''
( function ( ) { {
{ }
var exports = { { } } ;
exports . stringify = stringify_main ;
return exports ;
2017-09-28 20:02:20 +10:00
} } ) ( ) ''' , __include__( ' eos/core/objects/json.js ' ))
2017-09-25 17:18:02 +10:00
# Database
# ========
2017-11-25 22:37:20 +11:00
class DBInfo :
def __init__ ( self ) :
self . client = None
self . db = None
dbinfo = DBInfo ( )
def db_connect ( db_name , mongo_uri = ' mongodb://localhost:27017/ ' ) :
dbinfo . client = pymongo . MongoClient ( mongo_uri )
dbinfo . db = dbinfo . client [ db_name ]
2017-09-25 17:18:02 +10:00
# Fields
# ======
class Field :
def __init__ ( self , * args , * * kwargs ) :
2017-11-23 20:42:22 +11:00
self . default = kwargs [ ' default ' ] if ' default ' in kwargs else kwargs [ ' py_default ' ] if ' py_default ' in kwargs else None
self . is_protected = kwargs [ ' is_protected ' ] if ' is_protected ' in kwargs else False
self . is_hashed = kwargs [ ' is_hashed ' ] if ' is_hashed ' in kwargs else not self . is_protected
2017-09-25 17:18:02 +10:00
class PrimitiveField ( Field ) :
2017-11-24 18:09:43 +11:00
def serialise ( self , value , for_hash = False , should_protect = False ) :
2017-09-25 17:18:02 +10:00
return value
def deserialise ( self , value ) :
return value
DictField = PrimitiveField
IntField = PrimitiveField
StringField = PrimitiveField
class EmbeddedObjectField ( Field ) :
def __init__ ( self , object_type = None , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . object_type = object_type
2017-11-24 18:09:43 +11:00
def serialise ( self , value , for_hash = False , should_protect = False ) :
return EosObject . serialise_and_wrap ( value , self . object_type , for_hash , should_protect )
2017-09-25 17:18:02 +10:00
def deserialise ( self , value ) :
return EosObject . deserialise_and_unwrap ( value , self . object_type )
class ListField ( Field ) :
def __init__ ( self , element_field = None , * args , * * kwargs ) :
super ( ) . __init__ ( default = EosList , * args , * * kwargs )
self . element_field = element_field
2017-11-24 18:09:43 +11:00
def serialise ( self , value , for_hash = False , should_protect = False ) :
2017-11-24 19:37:48 +11:00
return [ self . element_field . serialise ( x , for_hash , should_protect ) for x in ( value . impl if isinstance ( value , EosList ) else value ) ]
2017-09-25 17:18:02 +10:00
def deserialise ( self , value ) :
2017-11-24 19:37:48 +11:00
return EosList ( [ self . element_field . deserialise ( x ) for x in value ] )
2017-09-25 17:18:02 +10:00
class EmbeddedObjectListField ( Field ) :
def __init__ ( self , object_type = None , * args , * * kwargs ) :
super ( ) . __init__ ( default = EosList , * args , * * kwargs )
self . object_type = object_type
2017-11-24 18:09:43 +11:00
def serialise ( self , value , for_hash = False , should_protect = False ) :
2017-11-24 19:37:48 +11:00
# TNYI: Doesn't know how to deal with iterators like EosList
return [ EosObject . serialise_and_wrap ( x , self . object_type , for_hash , should_protect ) for x in ( value . impl if isinstance ( value , EosList ) else value ) ]
2017-09-25 17:18:02 +10:00
def deserialise ( self , value ) :
2017-11-24 18:09:43 +11:00
return EosList ( [ EosObject . deserialise_and_unwrap ( x , self . object_type ) for x in value ] )
2017-09-25 17:18:02 +10:00
if is_python :
class UUIDField ( Field ) :
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( default = uuid . uuid4 , * args , * * kwargs )
2017-11-24 18:09:43 +11:00
def serialise ( self , value , for_hash = False , should_protect = False ) :
2017-09-25 17:18:02 +10:00
return str ( value )
def deserialise ( self , value ) :
return uuid . UUID ( value )
else :
UUIDField = PrimitiveField
2017-11-24 20:26:18 +11:00
class DateTimeField ( Field ) :
def pad ( self , number ) :
if number < 10 :
return ' 0 ' + str ( number )
return str ( number )
def serialise ( self , value , for_hash = False , should_protect = False ) :
if value is None :
return None
if is_python :
return value . strftime ( ' % Y- % m- %d T % H: % M: % SZ ' )
else :
return value . getUTCFullYear ( ) + ' - ' + self . pad ( value . getUTCMonth ( ) + 1 ) + ' - ' + self . pad ( value . getUTCDate ( ) ) + ' T ' + self . pad ( value . getUTCHours ( ) ) + ' : ' + self . pad ( value . getUTCMinutes ( ) ) + ' : ' + self . pad ( value . getUTCSeconds ( ) ) + ' Z '
def deserialise ( self , value ) :
if value is None :
return None
if is_python :
return datetime . strptime ( value , ' % Y- % m- %d T % H: % M: % SZ ' )
else :
2017-11-25 23:16:29 +11:00
return __pragma__ ( ' js ' , ' {} ' , ' new Date(value) ' )
2017-11-24 20:26:18 +11:00
@staticmethod
def now ( ) :
if is_python :
return datetime . utcnow ( )
else :
return __pragma__ ( ' js ' , ' {} ' , ' new Date() ' )
2017-09-25 17:18:02 +10:00
# Objects
# =======
class EosObjectType ( type ) :
def __new__ ( meta , name , bases , attrs ) :
cls = type . __new__ ( meta , name , bases , attrs )
cls . _name = ( ( cls . __module__ if is_python else meta . __next_class_module__ ) + ' . ' + cls . __name__ ) . replace ( ' .js. ' , ' . ' ) . replace ( ' .python. ' , ' . ' ) #TNYI: module and qualname
if name != ' EosObject ' :
EosObject . objects [ cls . _name ] = cls
2017-09-28 16:46:30 +10:00
if ' _db_name ' not in attrs :
# Don't inherit _db_name, use only if explicitly given
cls . _db_name = cls . _name
2017-09-25 17:18:02 +10:00
return cls
class EosObject ( metaclass = EosObjectType ) :
objects = { }
def __init__ ( self ) :
self . _instance = ( None , None )
self . _inited = False
def post_init ( self ) :
self . _inited = True
def recurse_parents ( self , cls ) :
2017-11-24 18:09:43 +11:00
#if not isinstance(cls, type):
if isinstance ( cls , str ) :
2017-09-28 16:46:30 +10:00
cls = EosObject . objects [ cls ]
2017-09-25 17:18:02 +10:00
if isinstance ( self , cls ) :
return self
if self . _instance [ 0 ] :
return self . _instance [ 0 ] . recurse_parents ( cls )
return None
2017-11-24 19:51:10 +11:00
def __eq__ ( self , other ) :
if not isinstance ( other , EosObject ) :
return False
return EosObject . serialise_and_wrap ( self ) == EosObject . serialise_and_wrap ( other )
2017-09-25 17:18:02 +10:00
@staticmethod
2017-11-23 20:42:22 +11:00
def serialise_and_wrap ( value , object_type = None , for_hash = False , should_protect = False ) :
2017-09-25 17:18:02 +10:00
if object_type :
2017-09-28 19:37:10 +10:00
if value :
2017-11-23 20:42:22 +11:00
return value . serialise ( for_hash , should_protect )
2017-09-28 19:37:10 +10:00
return None
2017-11-23 20:42:22 +11:00
return { ' type ' : value . _name , ' value ' : ( value . serialise ( for_hash , should_protect ) if value else None ) }
2017-09-25 17:18:02 +10:00
@staticmethod
def deserialise_and_unwrap ( value , object_type = None ) :
if object_type :
return object_type . deserialise ( value )
return EosObject . objects [ value [ ' type ' ] ] . deserialise ( value [ ' value ' ] )
@staticmethod
def to_json ( value ) :
if is_python :
return json . dumps ( value , sort_keys = True )
else :
return lib . stringify ( value )
@staticmethod
def from_json ( value ) :
if is_python :
return json . loads ( value )
else :
return JSON . parse ( value )
class EosList ( EosObject ) :
def __init__ ( self , * args ) :
2017-09-25 17:25:06 +10:00
super ( ) . __init__ ( )
2017-09-25 17:18:02 +10:00
self . impl = list ( * args )
2017-09-28 19:37:10 +10:00
def post_init ( self ) :
for i in range ( len ( self . impl ) ) :
val = self . impl [ i ]
2017-11-24 19:37:48 +11:00
# Check if object has writeable attributes
if is_python and hasattr ( val , ' __dict__ ' ) :
val . _instance = ( self , i )
if not val . _inited :
val . post_init ( )
def __repr__ ( self ) :
return ' <EosList {} > ' . format ( repr ( self . impl ) )
2017-09-28 19:37:10 +10:00
2017-09-25 17:18:02 +10:00
# Lists in JS are implemented as native Arrays, so no cheating here :(
def __len__ ( self ) :
return len ( self . impl )
def __getitem__ ( self , idx ) :
return self . impl [ idx ]
def __setitem__ ( self , idx , val ) :
self . impl [ idx ] = val
2017-09-28 16:46:30 +10:00
val . _instance = ( self , idx )
if not val . _inited :
val . post_init ( )
2017-09-25 17:18:02 +10:00
def __contains__ ( self , val ) :
return val in self . impl
2017-11-24 19:37:48 +11:00
# For sorting, etc.
def __eq__ ( self , other ) :
if isinstance ( other , EosList ) :
other = other . impl
return self . impl == other
def __lt__ ( self , other ) :
if isinstance ( other , EosList ) :
other = other . impl
return self . impl < other
2017-09-25 17:18:02 +10:00
def append ( self , value ) :
if isinstance ( value , EosObject ) :
value . _instance = ( self , len ( self ) )
if not value . _inited :
value . post_init ( )
return self . impl . append ( value )
class DocumentObjectType ( EosObjectType ) :
def __new__ ( meta , name , bases , attrs ) :
cls = EosObjectType . __new__ ( meta , name , bases , attrs )
# Process fields
fields = { }
if hasattr ( cls , ' _fields ' ) :
fields = cls . _fields . copy ( ) if is_python else Object . create ( cls . _fields )
for attr in list ( dir ( cls ) ) :
if not is_python :
# We must skip things with getters or else they will be called here (too soon)
if Object . getOwnPropertyDescriptor ( cls , attr ) . js_get :
continue
val = getattr ( cls , attr )
if isinstance ( val , Field ) :
val . _instance = ( cls , name )
fields [ attr ] = val
delattr ( cls , attr )
cls . _fields = fields
# Make properties
if is_python :
def make_property ( name , field ) :
def field_getter ( self ) :
return self . _field_values [ name ]
def field_setter ( self , value ) :
2017-11-24 18:09:43 +11:00
self . _field_values [ name ] = value
2017-09-25 17:18:02 +10:00
if isinstance ( value , EosObject ) :
value . _instance = ( self , name )
if not value . _inited :
value . post_init ( )
return property ( field_getter , field_setter )
for attr , val in fields . items ( ) :
setattr ( cls , attr , make_property ( attr , val ) )
else :
# Handled at instance level
pass
return cls
class DocumentObject ( EosObject , metaclass = DocumentObjectType ) :
_ver = StringField ( default = ' 0.1 ' )
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( )
self . _field_values = { }
# Different to Python
for attr , val in self . _fields . items ( ) :
if is_python :
# Properties handled above
pass
else :
def make_property ( name , field ) :
def field_getter ( ) :
return self . _field_values [ name ]
def field_setter ( value ) :
2017-11-24 18:09:43 +11:00
self . _field_values [ name ] = value
2017-09-25 17:18:02 +10:00
if isinstance ( value , EosObject ) :
value . _instance = ( self , name )
if not value . _inited :
value . post_init ( )
return ( field_getter , field_setter )
prop = make_property ( attr , val )
# TNYI: No support for property()
Object . defineProperty ( self , attr , {
' get ' : prop [ 0 ] ,
' set ' : prop [ 1 ]
} )
if attr in kwargs :
setattr ( self , attr , kwargs [ attr ] )
else :
default = val . default
if default is not None and callable ( default ) :
default = default ( )
setattr ( self , attr , default )
# TNYI: Strange things happen with py_ attributes
2017-11-23 20:42:22 +11:00
def serialise ( self , for_hash = False , should_protect = False ) :
2017-11-24 18:09:43 +11:00
return { ( attr [ 3 : ] if attr . startswith ( ' py_ ' ) else attr ) : val . serialise ( getattr ( self , attr ) , for_hash , should_protect ) for attr , val in self . _fields . items ( ) if ( ( val . is_hashed or not for_hash ) and ( not should_protect or not val . is_protected ) ) }
2017-09-25 17:18:02 +10:00
@classmethod
def deserialise ( cls , value ) :
2017-11-23 23:10:57 +11:00
if value is None :
return None
2017-11-23 20:42:22 +11:00
attrs = { }
for attr , val in cls . _fields . items ( ) :
json_attr = attr [ 3 : ] if attr . startswith ( ' py_ ' ) else attr
if json_attr in value :
attrs [ attr ] = val . deserialise ( value [ json_attr ] )
return cls ( * * attrs )
2017-09-25 17:18:02 +10:00
class TopLevelObject ( DocumentObject ) :
def save ( self ) :
#res = db[self._name].replace_one({'_id': self.serialise()['_id']}, self.serialise(), upsert=True)
2017-11-25 22:37:20 +11:00
res = dbinfo . db [ self . _db_name ] . replace_one ( { ' _id ' : self . _fields [ ' _id ' ] . serialise ( self . _id ) } , EosObject . serialise_and_wrap ( self ) , upsert = True )
2017-11-22 23:23:24 +11:00
@classmethod
def get_all ( cls ) :
2017-11-25 22:37:20 +11:00
return [ EosObject . deserialise_and_unwrap ( x ) for x in dbinfo . db [ cls . _db_name ] . find ( ) ]
2017-11-22 23:23:24 +11:00
@classmethod
def get_by_id ( cls , _id ) :
2017-11-25 22:37:20 +11:00
return EosObject . deserialise_and_unwrap ( dbinfo . db [ cls . _db_name ] . find_one ( _id ) )
2017-09-25 17:18:02 +10:00
class EmbeddedObject ( DocumentObject ) :
pass