-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PROTON openSourced! v0.0.1 is public facing. Supports PostgreSql
- Loading branch information
Showing
59 changed files
with
1,534 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__author__ = 'pruthvi kumar' |
Empty file.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": {}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
Oops, something went wrong.