class Analytic(BaseModule): '''`Analytic` module provides data type and controller from `Analytics Workflow` and accompanying analytics docs. It uses `pre_create` handler to assure no events duplications occur and all occurrences of the same event are recorded in one doc.''' collection = 'analytics' attrs = { 'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'), 'event': ATTR.STR(desc='Analytics event name.'), 'subevent': ATTR.ANY( desc= 'Analytics subevent distinguishing attribute. This is usually `STR`, or `ID` but it is introduced in the module as `ANY` to allow wider use-cases by developers.' ), 'occurrences': ATTR.LIST( desc='All occurrences of the event as list.', list=[ ATTR.DICT( desc='Single occurrence of the event details.', dict={ 'args': ATTR.DICT( desc= 'Key-value `dict` containing event args, if any.', dict={ '__key': ATTR.STR(), '__val': ATTR.ANY() }), 'score': ATTR.INT( desc='Numerical score for occurrence of the event.' ), 'create_time': ATTR.DATETIME( desc= 'Python `datetime` ISO format of the occurrence of the event.' ), }) ]), 'score': ATTR.INT( desc= 'Total score of all scores of all occurrences of the event. This can be used for data analysis.' ), } unique_attrs = [('user', 'event', 'subevent')] methods = { 'read': { 'permissions': [PERM(privilege='read')] }, 'create': { 'permissions': [PERM(privilege='__sys')], 'doc_args': { 'event': ATTR.STR(), 'subevent': ATTR.ANY(), 'args': ATTR.DICT(dict={ '__key': ATTR.STR(), '__val': ATTR.ANY() }), }, }, 'update': { 'permissions': [PERM(privilege='__sys')] }, 'delete': { 'permissions': [PERM(privilege='delete')] }, } async def pre_create(self, skip_events, env, query, doc, payload): analytic_results = await self.read( skip_events=[Event.PERM], env=env, query=[ { 'user': env['session'].user._id, 'event': doc['event'], 'subevent': doc['subevent'], }, { '$limit': 1 }, ], ) if analytic_results.args.count: analytic_results = await self.update( skip_events=[Event.PERM], env=env, query=[{ '_id': analytic_results.args.docs[0]._id }], doc={ 'occurrences': { '$append': { 'args': doc['args'], 'score': doc['score'] if 'score' in doc.keys() else 0, 'create_time': datetime.datetime.utcnow().isoformat(), } }, 'score': { '$add': doc['score'] if 'score' in doc.keys() else 0 }, }, ) return analytic_results else: doc = { 'event': doc['event'], 'subevent': doc['subevent'], 'occurrences': [{ 'args': doc['args'], 'score': doc['score'] if 'score' in doc.keys() else 0, 'create_time': datetime.datetime.utcnow().isoformat(), }], 'score': doc['score'] if 'score' in doc.keys() else 0, } return (skip_events, env, query, doc, payload)
class Group(BaseModule): '''`Group` module provides data type and controller for groups in LIMP eco-system.''' collection = 'groups' attrs = { 'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'), 'name': ATTR.LOCALE(desc='Name of the groups as `LOCALE`.'), 'desc': ATTR.LOCALE( desc= 'Description of the group as `LOCALE`. This can be used for dynamic generated groups that are meant to be exposed to end-users.' ), 'privileges': ATTR.DICT( desc='Privileges that any user is a member of the group has.', dict={ '__key': ATTR.STR(), '__val': ATTR.LIST(list=[ATTR.STR()]) }), 'settings': ATTR.DICT( desc= '`Setting` docs to be created, or required for members users when added to the group.', dict={ '__key': ATTR.STR(), '__val': ATTR.ANY() }), 'create_time': ATTR.DATETIME( desc='Python `datetime` ISO format of the doc creation.'), } defaults = { 'desc': {locale: '' for locale in Config.locales}, 'privileges': {}, 'settings': {}, } methods = { 'read': { 'permissions': [PERM(privilege='admin')] }, 'create': { 'permissions': [PERM(privilege='admin')] }, 'update': { 'permissions': [ PERM(privilege='admin'), PERM( privilege='update', query_mod={'user': '******'}, doc_mod={'privileges': None}, ), ], 'query_args': { '_id': ATTR.ID() }, }, 'delete': { 'permissions': [ PERM(privilege='admin'), PERM(privilege='delete', query_mod={'user': '******'}), ], 'query_args': { '_id': ATTR.ID() }, }, } async def pre_create(self, skip_events, env, query, doc, payload): return (skip_events, env, query, doc, payload) async def pre_update(self, skip_events, env, query, doc, payload): # [DOC] Make sure no attrs overwriting would happen if 'attrs' in doc.keys(): results = await self.read(skip_events=[Event.PERM], env=env, query=query) if not results.args.count: return self.status(status=400, msg='Group is invalid.', args={'code': 'INVALID_GROUP'}) if results.args.count > 1: return self.status( status=400, msg= 'Updating group attrs can be done only to individual groups.', args={'code': 'MULTI_ATTRS_UPDATE'}, ) results.args.docs[0]['attrs'].update({ attr: doc['attrs'][attr] for attr in doc['attrs'].keys() if doc['attrs'][attr] != None and doc['attrs'][attr] != '' }) doc['attrs'] = results.args.docs[0]['attrs'] return (skip_events, env, query, doc, payload)
class Diff(BaseModule): '''`Diff` module provides data type and controller for `Diff Workflow`. It is meant for use by internal calls only. Best practice to accessing diff docs is by creating proxy modules or writing LIMP methods that expose the diff docs.''' collection = 'diff' attrs = { 'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'), 'module': ATTR.STR(desc='Name of the module the original doc is part of.'), 'doc': ATTR.ID(desc='`_id` of the original doc.'), 'vars': ATTR.DICT( desc= 'Key-value `dict` containing all attrs that have been updated from the original doc.', dict={ '__key': ATTR.STR(), '__val': ATTR.ANY() }), 'remarks': ATTR.STR( desc= 'Human-readable remarks of the doc. This is introduced to allow developers to add log messages to diff docs.' ), 'create_time': ATTR.DATETIME( desc='Python `datetime` ISO format of the doc creation.'), } defaults = {'doc': None, 'remarks': ''} methods = { 'read': { 'permissions': [PERM(privilege='read')] }, 'create': { 'permissions': [PERM(privilege='__sys')] }, 'delete': { 'permissions': [PERM(privilege='delete')] }, } async def pre_create(self, skip_events, env, query, doc, payload): # [DOC] Detect non-_id update query: if '_id' not in query: results = await Config.modules[doc['module'] ].read(skip_events=[Event.PERM], env=env, query=query) if results.args.count > 1: query.append( {'_id': { '$in': [doc._id for doc in results.args.docs] }}) elif results.args.count == 1: query.append({'_id': results.args.docs[0]._id}) else: return self.status(status=400, msg='No update docs matched.', args={'code': 'NO_MATCH'}) if '_id' in query and type(query['_id'][0]) == list: for i in range(len(query['_id'][0]) - 1): self.create( skip_events=[Event.PERM], env=env, query=[{ '_id': query['_id'][0][i] }], doc=doc, ) query['_id'][0] = query['_id'][0][-1] doc['doc'] = ObjectId(query['_id'][0]) return (skip_events, env, query, doc, payload)
class Setting(BaseModule): '''`Setting` module module provides data type and controller for settings in LIMP eco-system. This is used by `User` module tp provide additional user-wise settings. It also allows for global-typed settings.''' collection = 'settings' attrs = { 'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'), 'var': ATTR.STR( desc= 'Name of the setting. This is unique for every `user` in the module.' ), 'val': ATTR.ANY(desc='Value of the setting.'), 'type': ATTR.LITERAL( desc= 'Type of the setting. This sets whether setting is global, or belong to user, and whether use can update it or not.', literal=['global', 'user', 'user_sys']), } diff = True unique_attrs = [('user', 'var', 'type')] extns = { 'val': ATTR_MOD( condition=lambda skip_events, env, query, doc, scope: type(scope) == dict and '__extn' in scope.keys(), default=lambda skip_events, env, query, doc, scope: { '__extn': EXTN( module=scope['__extn']['__module'], attrs=scope['__extn']['__attrs'], force=scope['__extn']['__force'], ), '__val': scope['__extn']['__val'], }, ) } methods = { 'read': { 'permissions': [ PERM(privilege='admin', query_mod={'$limit': 1}), PERM( privilege='read', query_mod={ 'user': '******', 'type': ATTR_MOD( condition=lambda skip_events, env, query, doc: 'type' in doc.keys() and doc['type'] == 'user_sys', default=lambda skip_events, env, query, doc: InvalidAttrException( attr_name='type', attr_type=ATTR.LITERAL(literal= ['global', 'user']), val_type=str, ), ), '$limit': 1, }, ), ], 'query_args': [ { '_id': ATTR.ID(), 'type': ATTR.LITERAL(literal=['global', 'user', 'user_sys']), }, { 'var': ATTR.STR(), 'type': ATTR.LITERAL(literal=['global']), }, { 'var': ATTR.STR(), 'user': ATTR.ID(), 'type': ATTR.LITERAL(literal=['user', 'user_sys']), }, ], }, 'create': { 'permissions': [ PERM(privilege='admin'), PERM(privilege='create', doc_mod={'type': 'user'}), ] }, 'update': { 'permissions': [ PERM(privilege='admin', query_mod={'$limit': 1}), PERM( privilege='update', query_mod={ 'type': 'user', 'user': '******', '$limit': 1 }, doc_mod={'type': None}, ), ], 'query_args': [ { '_id': ATTR.ID(), 'type': ATTR.LITERAL(literal=['global', 'user', 'user_sys']), }, { 'var': ATTR.STR(), 'type': ATTR.LITERAL(literal=['global']), }, { 'var': ATTR.STR(), 'user': ATTR.ID(), 'type': ATTR.LITERAL(literal=['user', 'user_sys']), }, ], 'doc_args': { 'val': ATTR.ANY() }, }, 'delete': { 'permissions': [PERM(privilege='admin', query_mod={'$limit': 1})], 'query_args': [{ '_id': ATTR.ID() }, { 'var': ATTR.STR() }], }, 'retrieve_file': { 'permissions': [PERM(privilege='*', query_mod={'type': 'global'})], 'get_method': True, }, } async def pre_create(self, skip_events, env, query, doc, payload): if (type(doc['val']) == list and len(doc['val']) == 1 and type(doc['val'][0]) == dict and 'content' in doc['val'][0].keys()): doc['val'] = doc['val'][0] return (skip_events, env, query, doc, payload) async def on_create(self, results, skip_events, env, query, doc, payload): if doc['type'] in ['user', 'user_sys']: if doc['user'] == env['session'].user._id: env['session'].user.settings[doc['var']] = doc['val'] return (results, skip_events, env, query, doc, payload) async def pre_update(self, skip_events, env, query, doc, payload): if (type(doc['val']) == list and len(doc['val']) == 1 and type(doc['val'][0]) == dict and 'content' in doc['val'][0].keys()): doc['val'] = doc['val'][0] return (skip_events, env, query, doc, payload) async def on_update(self, results, skip_events, env, query, doc, payload): if query['type'][0] in ['user', 'user_sys']: if query['user'][0] == env['session'].user._id: if type(doc['val']) == dict and '$add' in doc['val'].keys(): env['session'].user.settings[query['var'] [0]] += doc['val']['$add'] elif type(doc['val'] ) == dict and '$multiply' in doc['val'].keys(): env['session'].user.settings[ query['var'][0]] *= doc['val']['$multiply'] elif type( doc['val']) == dict and '$append' in doc['val'].keys(): env['session'].user.settings[query['var'][0]].append( doc['val']['$append']) elif type( doc['val']) == dict and '$remove' in doc['val'].keys(): env['session'].user.settings[query['var'][0]].remove( doc['val']['$remove']) else: env['session'].user.settings[query['var'][0]] = doc['val'] return (results, skip_events, env, query, doc, payload)