Skip to content

Commit

Permalink
PROTON openSourced! v0.0.1 is public facing. Supports PostgreSql
Browse files Browse the repository at this point in the history
  • Loading branch information
1x-eng committed Sep 28, 2018
1 parent 1345f7e commit f90e190
Show file tree
Hide file tree
Showing 59 changed files with 1,534 additions and 0 deletions.
13 changes: 13 additions & 0 deletions configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
__author__ = "Pruthvi Kumar, pruthvikumar.123@gmail.com"
__copyright__ = "Copyright (C) 2018 Pruthvi Kumar | http://www.apricity.co.in"
__license__ = "Public Domain"
__version__ = "1.0"

import os

class ProtonConfig(object):

def __init__(self):
super(ProtonConfig, self).__init__()
self.ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
self.CACHE_LIFESPAN = 86400 # equivalent of 1 day = 86400 s.
6 changes: 6 additions & 0 deletions databaseConfig.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[postgresql]
host=localhost
database=postgres
user=postgres
password=root
port=5433
75 changes: 75 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
__author__ = "Pruthvi Kumar, pruthvikumar.123@gmail.com"
__copyright__ = "Copyright (C) 2018 Pruthvi Kumar | http://www.apricity.co.in"
__license__ = "Public Domain"
__version__ = "1.0"

"""
PROTON executor: Point WSGI server to this file and reach out to available routes!
"""

import json
import falcon
from apispec import APISpec
from falcon_apispec import FalconPlugin
from falcon_cors import CORS
from configuration import ProtonConfig
from mic.iface.middlewares.iface_watch import Iface_watch



class DefaultRouteHandler(object):
"""
PROTON's default route handler.
"""
def __init__(self):
super(DefaultRouteHandler, self).__init__()

def on_get(self, req, resp):
response = {
'message': 'PROTON is successfully initialized!',
'availableRoutes': []
}

resp.body = json.dumps(response)
resp.status = falcon.HTTP_200

class FastServe(object):
"""
This is a sink to service dynamically routed entries from cache!
"""

def __init__(self):
super(FastServe, self).__init__()

def on_get(self, req, resp):
resp.status = falcon.HTTP_200


cors = CORS(allow_all_origins=['http://localhost:3000'])
app = falcon.API(middleware=[cors.middleware, Iface_watch()])

app.add_route('/', DefaultRouteHandler())
app.add_route('/fast-serve', FastServe())





# Open API Specs
spec = APISpec(
title='PROTON STACK',
version='1.0.0',
openapi_version='2.0',
plugins=[
FalconPlugin(app),
],
)



# OPEN API specs will be generated during runtime.
with open('{}/mic/iface/openApi/specs.json'.format(ProtonConfig().ROOT_DIR), 'w+') as sjf:
sjf.write(json.dumps(spec.to_dict()))

with open('{}/mic/iface/openApi/specs.yaml'.format(ProtonConfig().ROOT_DIR), 'w+') as syf :
syf.write(spec.to_yaml())
1 change: 1 addition & 0 deletions mic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__author__ = 'pruthvi kumar'
Empty file added mic/controllers/__init__.py
Empty file.
Empty file added mic/iface/__init__.py
Empty file.
Empty file.
Empty file.
124 changes: 124 additions & 0 deletions mic/iface/middlewares/iface_watch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
__author__ = "Pruthvi Kumar, pruthvikumar.123@gmail.com"
__copyright__ = "Copyright (C) 2018 Pruthvi Kumar | http://www.apricity.co.in"
__license__ = "Public Domain"
__version__ = "1.0"

import time
import json
from colorama import Fore, Style
from nucleus.db.cacheManager import CacheManager


class Iface_watch(CacheManager):
"""
This is the middleware wired to main executor. This middleware tracks all the clients requesting respective route
and response time for each of those respective requests.
"""

def __init__(self):
super(Iface_watch, self).__init__()
self.logger = self.getLogger(logFileName='interface_logs',
logFilePath='{}/trace/interface_logs.log'.format(self.ROOT_DIR))
self.timer = {"start": 0, "end": 0}
self.cacheInstance = None
self.cacheExists = False

def process_request(self, req, resp):
"""
:param req:
:param resp:
:return:
"""
self.timer["start"] = time.time()
self.logger.info('[Iface_watch] | Requested Route: {}'.format(req.path))

if (req.path in ['/', '/fast-serve']):
pass
else:
# Check if response can be served from cache.
# Instantiate Cache
self.cacheInstance = self.cacheProcessor()['initCache']()
self.cacheExistance = self.cacheProcessor()['pingCache'](self.cacheInstance)

if (self.cacheExistance):

print(Fore.MAGENTA + 'PROTON stack has the instantiated Cache! We are on STEROIDS now!!' + Style.RESET_ALL)
try:
routePathContents = (req.path).split('_')
cacheKey = routePathContents[len(routePathContents) - 2] + '_' + \
routePathContents[len(routePathContents) - 1]

cacheResponse = self.cacheProcessor()['getFromCache'](self.cacheInstance, 'c_' + cacheKey)
timeWhenCacheWasSet = (self.cacheProcessor()['getFromCache'](self.cacheInstance, 'c_setTime_'
+ cacheKey))
cacheSetTime = 0 if timeWhenCacheWasSet==None else int(timeWhenCacheWasSet)
currentTime = int(time.time())
if (cacheSetTime != None):
cacheDeltaForRoute = currentTime - cacheSetTime
else:
cacheDeltaForRoute = 0

if (cacheResponse != None):
if (cacheDeltaForRoute > self.CACHE_LIFESPAN):
self.cacheProcessor()['deleteFromCache'](self.cacheInstance, 'c_' + cacheKey)
self.cacheProcessor()['deleteFromCache'](self.cacheInstance, 'c_setTime_' + cacheKey)
self.logger.info('Cache is deleted for route {}. It has exceeded its '
'lifespan!'.format(req.path))
else:
print(Fore.GREEN + 'Response is served from cache for route {}. DB service of PROTON stack '
'is spared!'.format(req.path) + Style.RESET_ALL)
resp.body = cacheResponse
req.path = '/fast-serve'
else:
# Go through conventional PROTON stack.
pass
except Exception as e:
self.logger.exception('[Iface_watch]. Error while extracting response from cache. '
'Details: {}'.format(str(e)))
# Letting the request go through to conventional PROTON stack.
pass
else:
print(Fore.LIGHTMAGENTA_EX + 'Cache is unavailable. PROTON will continue to rely on database & function'
' as usual.' + Style.RESET_ALL)

def process_response(self, req, resp, resource, req_succeded):
"""
:param req:
:param resp:
:param resource:
:param req_succeded:
:return:
"""
self.logger.info('[Iface_watch] | Response status: {} | '
'Response time: {} seconds'.format(req_succeded, time.time() - self.timer["start"]))

if (req.path in ['/', '/fast-serve']):
pass
else:
if (self.cacheExistance):
try:
routePathContents = (req.path).split('_')
cacheKey = routePathContents[len(routePathContents) - 2] + '_' + \
routePathContents[len(routePathContents) - 1]

cacheResponse = self.cacheProcessor()['getFromCache'](self.cacheInstance, 'c_' + cacheKey)
if (cacheResponse == None):
self.cacheProcessor()['setToCache'](self.cacheInstance, 'c_' + cacheKey, json.loads(resp.body))
timeWhenSet = int(time.time())
self.cacheProcessor()['setToCache'](self.cacheInstance, 'c_setTime_' + cacheKey, timeWhenSet)
self.logger.info('Cache set for key : {} @ {}'.format('c_'+ cacheKey, timeWhenSet))
print(Fore.GREEN + 'Cache is set for route {}. Subsequent requests for this route will be '
'serviced by cache.'.format(req.path) + Style.RESET_ALL)
else:
# Cache is already set to this route,
pass

except Exception as e:
self.logger.exception('[Iface_watch]. Error while extracting response from cache. '
'Details: {}'.format(str(e)))
# Letting the request go through to conventional PROTON stack.
pass
else:
pass

1 change: 1 addition & 0 deletions mic/iface/openApi/specs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"info": {"title": "PROTON STACK", "version": "1.0.0"}, "paths": {}, "tags": [], "swagger": "2.0", "definitions": {}, "parameters": {}}
6 changes: 6 additions & 0 deletions mic/iface/openApi/specs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
definitions: {}
info: {title: PROTON STACK, version: 1.0.0}
parameters: {}
paths: {}
swagger: '2.0'
tags: []
Empty file added mic/models/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions nucleus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__author__ = "Pruthvi Kumar, pruthvikumar.123@gmail.com"
__copyright__ = "Copyright (C) 2018 Pruthvi Kumar | http://www.apricity.co.in"
__license__ = "Public Domain"
__version__ = "1.0"
Empty file added nucleus/db/__init__.py
Empty file.
85 changes: 85 additions & 0 deletions nucleus/db/cacheManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
__author__ = "Pruthvi Kumar, pruthvikumar.123@gmail.com"
__copyright__ = "Copyright (C) 2018 Pruthvi Kumar | http://www.apricity.co.in"
__license__ = "Public Domain"
__version__ = "1.0"

import redis
from configuration import ProtonConfig
from nucleus.generics.logUtilities import LogUtilities

class CacheManager(ProtonConfig, LogUtilities):

def __init__(self):
super(CacheManager, self).__init__()
self.__redisConfig = {
'host': 'localhost',
'port': 6379,
'db': 0
}
self.logger = self.getLogger(logFileName='cacheManager_logs',
logFilePath='{}/trace/cacheManager_logs.log'.format(self.ROOT_DIR))
self.cacheProcessor = self.__processor


def __processor(self):
"""
Closure for CacheManager
:return: A dictionary of all methods processed by CacheManager.
"""

def instantiateCache():
try:
redisInstance = redis.StrictRedis(host=self.__redisConfig['host'], port=self.__redisConfig['port'],
db=self.__redisConfig['db'])
self.logger.info('Successfully instantiated cache!')
return redisInstance
except Exception as e:
self.logger.exception('Exception while instantiating cache. Details: {}'.format(str(e)))
raise str(e)


def setToCache(redisInstance, key, value):
try:
redisInstance.set(key, value)
self.logger.info('Cache set for key: {}'.format(key))
except Exception as e:
self.logger.exception('Exception while setting value to cache. Details: {}'.format(str(e)))
raise str(e)

def getFromCache(redisInstance, key):
try:
dataFromCache = redisInstance.get(key)
self.logger.info('Data from cache successful for key: {}'.format(key))
return dataFromCache
except Exception as e:
self.logger.exception('Data from cache for key: {} is unsuccessful. Details: {}'.format(key, str(e)))
raise str(e)

def pingCache(redisInstance):
try:
redisInstance.ping()
self.logger.info('Redis instance is available!')
return True
except Exception as e:
self.logger.exception('Redis instance is unavailable on ping!. Details : {}'.format(str(e)))
return False

def deleteFromCache(redisInstance, key):
try:
redisInstance.delete(key)
self.logger.info('{} deleted from Redis cache!'.format(key))
return True
except Exception as e:
self.logger.exception(('Redis instance is unavailable to delete key: {}. '
'Details: {}'.format(key, str(e))))
return False


return {
'initCache': instantiateCache,
'setToCache': setToCache,
'getFromCache': getFromCache,
'pingCache': pingCache,
'deleteFromCache': deleteFromCache
}
57 changes: 57 additions & 0 deletions nucleus/db/connectionDialects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
__author__ = "Pruthvi Kumar"
__copyright__ = "Copyright (C) 2018 Pruthvi Kumar | http://www.apricity.co.in"
__license__ = "Public Domain"
__version__ = "1.0"

from configparser import ConfigParser
from configuration import ProtonConfig
from nucleus.generics.logUtilities import LogUtilities


class ConnectionDialects(ProtonConfig, LogUtilities):
"""
One stop shop to get connectivity to any RDMBS for models.
Current implementation covers:
1. Postgres
2. MySql
3. SQL Server
To add more support, add supporting config parameters into databaseConfig.ini file.
NOTE: ConnectionDialect is reliant on databaseConfig.ini to establish valid connection. Please ensure
you don't delete any existing config parameters.
"""
def __init__(self):
super(ConnectionDialects, self).__init__()
self.logger = self.getLogger(logFileName='connectionDialects_logs',
logFilePath='{}/trace/connectionDialects_logs.log'.format(self.ROOT_DIR))
self.dialectStore = self._configStoreParser

def _configStoreParser(self):
# By default pyReady ships with support for postgresql, mysql and sqlserver.
supportedDatabases = ['postgresql',]
parser = ConfigParser()
configFile = '{}/databaseConfig.ini'.format(self.ROOT_DIR)
db = {}
parser.read(configFile)

def getParsedParameters(db, section):

if parser.has_section(section):
db[section] = {}
params = parser.items(section)
for param in params:
db[section][param[0]] = param[1]
else:
self.logger.exception('[ConnectionDialects]: Section {} is not found in "databaseConfig.ini" '
'file.'.format(section))
raise Exception('[ConnectionDialects]: Section {} is not found in "databaseConfig.ini" '
'file.'.format(section))
return db

list(map(lambda sdb: getParsedParameters(db, sdb), supportedDatabases))
return db

if __name__ == '__main__':
cdl = ConnectionDialects()
print(cdl.dialectStore())
Loading

0 comments on commit f90e190

Please sign in to comment.