From c9b54647215eec6892f042b538f583bb4e6cbf3f Mon Sep 17 00:00:00 2001 From: ermisw Date: Fri, 5 May 2023 13:58:32 +0200 Subject: [PATCH] added setup.py --- KeyRequestParser.egg-info/PKG-INFO | 6 + KeyRequestParser.egg-info/SOURCES.txt | 10 + .../dependency_links.txt | 1 + KeyRequestParser.egg-info/top_level.txt | 1 + build/lib/__init__.py | 0 build/lib/helper.py | 46 +++ build/lib/keyrequests.py | 157 +++++++++ build/lib/krparser.py | 303 ++++++++++++++++++ build/lib/patterns.py | 96 ++++++ dist/KeyRequestParser-0.1-py3.10.egg | Bin 0 -> 15085 bytes setup.py | 8 + 11 files changed, 628 insertions(+) create mode 100644 KeyRequestParser.egg-info/PKG-INFO create mode 100644 KeyRequestParser.egg-info/SOURCES.txt create mode 100644 KeyRequestParser.egg-info/dependency_links.txt create mode 100644 KeyRequestParser.egg-info/top_level.txt create mode 100644 build/lib/__init__.py create mode 100644 build/lib/helper.py create mode 100644 build/lib/keyrequests.py create mode 100644 build/lib/krparser.py create mode 100644 build/lib/patterns.py create mode 100644 dist/KeyRequestParser-0.1-py3.10.egg create mode 100644 setup.py diff --git a/KeyRequestParser.egg-info/PKG-INFO b/KeyRequestParser.egg-info/PKG-INFO new file mode 100644 index 0000000..021b956 --- /dev/null +++ b/KeyRequestParser.egg-info/PKG-INFO @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: KeyRequestParser +Version: 0.1 +License: MIT + +Parses Keyrequests diff --git a/KeyRequestParser.egg-info/SOURCES.txt b/KeyRequestParser.egg-info/SOURCES.txt new file mode 100644 index 0000000..ff060d8 --- /dev/null +++ b/KeyRequestParser.egg-info/SOURCES.txt @@ -0,0 +1,10 @@ +__init__.py +helper.py +keyrequests.py +krparser.py +patterns.py +setup.py +KeyRequestParser.egg-info/PKG-INFO +KeyRequestParser.egg-info/SOURCES.txt +KeyRequestParser.egg-info/dependency_links.txt +KeyRequestParser.egg-info/top_level.txt \ No newline at end of file diff --git a/KeyRequestParser.egg-info/dependency_links.txt b/KeyRequestParser.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/KeyRequestParser.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/KeyRequestParser.egg-info/top_level.txt b/KeyRequestParser.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/KeyRequestParser.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/build/lib/__init__.py b/build/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/helper.py b/build/lib/helper.py new file mode 100644 index 0000000..cace378 --- /dev/null +++ b/build/lib/helper.py @@ -0,0 +1,46 @@ +import requests +from requests.adapters import HTTPAdapter, Retry + +def get_requestOld(url, headers, params): + #try: + response = requests.get(url, headers=headers, params=params, verify=False) + response.raise_for_status() + # except requests.exceptions.HTTPError as errh: + # return "An Http Error occurred:" + repr(errh) + # except requests.exceptions.ConnectionError as errc: + # return "An Error Connecting to the API occurred:" + repr(errc) + # except requests.exceptions.Timeout as errt: + # return "A Timeout Error occurred:" + repr(errt) + # except requests.exceptions.RequestException as err: + # return "An Unknown Error occurred" + repr(err) + + return response + +def get_request(url, headers, params): + #try: + session = requests.Session() + retry = Retry(connect=3, backoff_factor=10) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + session.mount('https://', adapter) + + #response = requests.get(url, headers=headers, params=params, verify=False) + response = session.get(url,headers=headers, params=params, verify=False) + + response.raise_for_status() + # except requests.exceptions.HTTPError as errh: + # return "An Http Error occurred:" + repr(errh) + # except requests.exceptions.ConnectionError as errc: + # return "An Error Connecting to the API occurred:" + repr(errc) + # except requests.exceptions.Timeout as errt: + # return "A Timeout Error occurred:" + repr(errt) + # except requests.exceptions.RequestException as err: + # return "An Unknown Error occurred" + repr(err) + + return response + +def contains(list, filter): + for x in list: + if filter(x): + return True + return False \ No newline at end of file diff --git a/build/lib/keyrequests.py b/build/lib/keyrequests.py new file mode 100644 index 0000000..a877da3 --- /dev/null +++ b/build/lib/keyrequests.py @@ -0,0 +1,157 @@ +try: + # Python 3 + from collections.abc import MutableSequence +except ImportError: + # Python 2.7 + from collections import MutableSequence + +class KeyRequestGroup(MutableSequence): + """A container for manipulating lists of hosts""" + def __init__(self, data=None): + """Initialize the class""" + super(KeyRequestGroup, self).__init__() + if (data is not None): + self._list = list(data) + else: + self._list = list() + + def __repr__(self): + return "<{0} {1}>".format(self.__class__.__name__, self._list) + + def __len__(self): + """List length""" + return len(self._list) + + def __getitem__(self, ii): + """Get a list item""" + if isinstance(ii, slice): + return self.__class__(self._list[ii]) + else: + return self._list[ii] + + def __delitem__(self, ii): + """Delete an item""" + del self._list[ii] + + def __setitem__(self, ii, val): + # optional: self._acl_check(val) + self._list[ii] = val + + def __str__(self): + return str(self._list) + + def createExistsQuery(self, val): + + query="type(service_method)" + + val['services'] = list(map(lambda x: x.replace("~","") , val['services'])) + val['methods'] = list(map(lambda x: x.replace("~","") , val['methods'])) + #case Service Names exists + if len(val["services"]) > 0: + if val["services"][0].startswith("SERVICE-"): + query+=",fromRelationship.isServiceMethodOfService(type(\"SERVICE\"),entityId(\""+'","'.join(val["services"])+"\"))" + else: + query+=",fromRelationship.isServiceMethodOfService(type(\"SERVICE\"),entityName.in(\""+'","'.join(val["services"])+"\"))" + + if len(val["methods"]) > 0: + if val["methods"][0].startswith("SERVICE_METHOD-"): + query+=",entityId(\""+'","'.join(val["methods"])+"\")" + else: + query+=",entityName.in(\""+'","'.join(val["methods"])+"\")" + + val["existsQuery"]= query + + # def createServiceResolveQuery(self, val): + # query="type(SERVICE)" + # val['services'] = list(map(lambda x: x.replace("~","") , val['services'])) + + # if len(val["services"]) > 0: + # if val["services"][0].startswith("SERVICE-"): + # query+=",entityId(\""+'","'.join(val["services"])+"\")" + # else: + # query+=",entityName.in(\""+'","'.join(val["services"])+"\")" + + # val["resolveServiceQuery"]= query + + + def insert(self, ii, val): + self.createExistsQuery(val) + + self._list.insert(ii, val) + + + + def append(self, val): + + for g in val: + + if len(self._list) == 0: + #self._list.insert(ii, val) + self.insert(len(self._list), g) + return + + for group in self._list: + if len(set(group["services"]) - set(g["services"])) > 0 or len(set(group["methods"]) - set(g["methods"])) > 0: + self.insert(len(self._list), g) + + + +class KR: + + # def getNotExistingKeyRequests(self): + # return [k for k in self.keyRequests if k['exists']==False] + + # def hasNotExistingKeyRequests(self): + # for k in self.keyRequests: + # if k['exists']==False: + # return True + + # return False + + # def getNoData1WKeyRequests(self): + # return [k for k in self.keyRequests if k['hasData_1W']==False and k['exists']==True] + + # def hasNoData1WKeyRequests(self): + # for k in self.keyRequests: + # if k['hasData_1W']==False and k['exists'] == True: + # return True + + # return False + + def getKeyRequestByHasData(self,label): + return [k for k in self.keyRequests if k['hasData'][label]==False] + + def hasNoData(self,label): + for k in self.keyRequests: + if k['hasData'][label]==False: + return True + + return False + + + + + def checkKeyRequestsHasData(self): + pass + + def mergeServices(self, listServices): + listOfServiceIds=[o["entityId"] for o in self.services] + + for s in listServices: + if s["entityId"] not in listOfServiceIds: + self.services.append(s) + + def __init__(self, + metadata, + matchedGroups: KeyRequestGroup = None): + self.metadata=metadata + + if matchedGroups == None: + self.matchedGroups = KeyRequestGroup() + else: + self.matchedGroups = keyRequests_groups + + self.keyRequests=[] + self.services=[] + + diff --git a/build/lib/krparser.py b/build/lib/krparser.py new file mode 100644 index 0000000..29c3d0c --- /dev/null +++ b/build/lib/krparser.py @@ -0,0 +1,303 @@ +import re + +from key_request_parser import patterns, keyrequests, helper + +from enum import Flag, auto +import logging +import threading +import concurrent.futures +import time +from jsonmerge import merge + + + +class KROption(Flag): + VALIDATE_EXISTS = auto() + VALIDATE_HASDATA = auto() + RESOLVEKEYREQUETS = auto() + RESOLVESERVICES = auto() + +class KRParser: + #threadLimiter = threading.BoundedSemaphore(3) + patterns=[patterns.Pattern1(), patterns.Pattern2(), patterns.Pattern3(), patterns.Pattern5(), patterns.Pattern4() ] + lock = threading.Lock() + + def normalize(self,x): + #tmp=x.replace("~","") + tmp=x.replace("\n","") + #tmp=tmp.replace("\"/","\"") + #tmp=tmp.replace("\"/","") -_>was active + #tmp=tmp.replace("/\"","\"") + tmp=tmp.replace("/\"","") + tmp=tmp.replace("\"","") + tmp=tmp.replace("\t","") + + tmp=re.sub("([\s]*)\)", ")", tmp) + + + + tmp=re.sub("\([\s\n\r]*", "(", tmp) + tmp=re.sub("\,[\s\n\r]*", ",", tmp) + tmp=re.sub("\)[\s\n\r]*,", "),", tmp) + tmp=re.sub("in[\s\n\r]*\(", "in(", tmp) + + return tmp + + + def applyPatterns(self,subject): + groups=None + for p in self.patterns: + groups=p.parseServicesAndMethods(subject) + + if len(groups) > 0: + break + + return groups + + + def checkKeyRequetsHasData(self,kr, tfrom, DTAPIURL, DTAPIToken): + + DTAPIURL = DTAPIURL + "/api/v2/entities" + + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Api-Token ' + DTAPIToken + } + + for gid, group in enumerate(kr.matchedGroups): + params={"entitySelector": group["existsQuery"], "from":tfrom["tfrom"], "fields": "fromRelationships"} + response = helper.get_request(DTAPIURL, headers, params) + entities = (response.json())['entities'] + + if len(entities) > 0: + y=0 + for method in kr.keyRequests: + if method["groupId"] == gid: + found = [x for x in entities if x[method["comparer"]] == method[method["comparer"]]] + + if len(found) > 0: + method["hasData"][tfrom["label"]]=True + #method["displayName"]=found[0]["displayName"] + #method["entityId"]=found[0]["entityId"] + #method["services"]=found[0]["fromRelationships"]["isServiceMethodOfService"] + + # for idx,o in enumerate(method["services"]): + # tmpS=[p for p in kr.services if p["entityId"]==o["id"]] + # if len(tmpS)>0: + # method["services"][idx]=tmpS[0] + + else: + method["hasData"][tfrom["label"]]=False + + def resolveServices(self,services, DTAPIURL, DTAPIToken): + #DTAPIURL = DTAPIURL + "/api/v2/entities" + + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Api-Token ' + DTAPIToken + } + + for gid, service in enumerate(services): + query="type(SERVICE),entityId("+service["id"]+")" + + params=merge(self.serviceLookupParams,{"entitySelector": query}) + #params={"entitySelector": query,"from":"now-2y", "fields":"tags"} + response = helper.get_request(DTAPIURL, headers, params) + entities = (response.json())['entities'] + + if len(entities)>0: + services[gid]=entities[0] + + def resolveKeyRequests(self,kr, DTAPIURL, DTAPIToken, options): + DTAPIURL = DTAPIURL + "/api/v2/entities" + + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Api-Token ' + DTAPIToken + } + + + for gid, k in enumerate(kr.keyRequests): + try: + query="type(service_method)" + group=kr.matchedGroups[k["groupId"]] + + if len(group["services"])> 0: + if group["services"][0].startswith("SERVICE-"): + query+=",fromRelationship.isServiceMethodOfService(type(\"SERVICE\"),entityId(\""+'","'.join(group["services"])+"\"))" + else: + query+=",fromRelationship.isServiceMethodOfService(type(\"SERVICE\"),entityName.in(\""+'","'.join(group["services"])+"\"))" + + if k["comparer"]=="entityId": + query+=",entityId("+k["entityId"]+")" + else: + query+=",entityName.in(\""+k["displayName"]+"\")" + + params={"entitySelector": query, "from":"now-1y","fields": "fromRelationships"} + response = helper.get_request(DTAPIURL, headers, params) + entities = (response.json())['entities'] + + # if len(entities) > 1: + # kr.keyRequests[gid]['foundCount']=len(entities) + # print("Multiple keyrequest found: ") + + if len(entities)> 0: + kr.keyRequests[gid]["found"]=True + kr.keyRequests[gid]['foundCount']=len(entities) + kr.keyRequests[gid]["displayName"]=entities[0]["displayName"] + kr.keyRequests[gid]["entityId"]=entities[0]["entityId"] + + if "isServiceMethodOfService" in entities[0]["fromRelationships"]: + kr.keyRequests[gid]["services"]=entities[0]["fromRelationships"]["isServiceMethodOfService"] + + if KROption.RESOLVESERVICES in options and len( kr.keyRequests[gid]["services"])>0: + self.resolveServices(kr.keyRequests[gid]["services"], DTAPIURL, DTAPIToken) + + except Exception as err: + kr.keyRequests[gid]["exception"]="resolveKeyRequests failed: "+repr(err) + + + + #kr.mergeServices(entities) + + + def getKeyRequestsByServices(self, services): + #type(SERVICE_METHOD),fromRelationship.isServiceMethodOfService(type("SERVICE"),entityName.in("btc-user-composite-service - PROD")) + DTAPIURL = self.DTAPIURL + "/api/v2/entities" + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Api-Token ' + self.DTAPIToken + } + + if len(services) > 0: + if services[0].startswith("SERVICE-"): + query="type(service_method),fromRelationship.isServiceMethodOfService(type(\"SERVICE\"),entityId(\""+'","'.join(services)+"\"))" + else: + query="type(service_method),fromRelationship.isServiceMethodOfService(type(\"SERVICE\"),entityName.in(\""+'","'.join(services)+"\"))" + + params={"entitySelector": query} + response = helper.get_request(DTAPIURL, headers, params) + entities = (response.json())['entities'] + + return entities + + + def process(self, kr): + + for gid, group in enumerate(kr.matchedGroups): + + if len(group["services"]) > 0 and len(group["methods"])==0: + tmp_methods=self.getKeyRequestsByServices(group["services"]) + + for m in tmp_methods: + tmp={"displayName": None,"comparer": "entityId", "entityId":m["entityId"], "groupId":gid, "hasData":{}, "services":[], "found":False, "foundCount":0, "exception":""} #"exists":None, 'hasData_1W':None, + kr.keyRequests.append(tmp) + + for method in group["methods"]: + if method.startswith('SERVICE_METHOD-'): + tmp={"displayName": None,"comparer": "entityId", "entityId":method, "groupId":gid, "hasData":{}, "services":[], "found":False, "foundCount":0, "exception":""} #"exists":None, 'hasData_1W':None, + else: + tmp={"displayName":method,"comparer": "displayName", "entityId":None, "groupId":gid, "hasData":{}, "services":[], "found":False, "foundCount":0, "exception":""} #"exists":None, 'hasData_1W':None, + + kr.keyRequests.append(tmp) + + + + # for service in group["services"]: + # if service.startswith('SERVICE-'): + # tmp={"displayName": None,"comparer": "entityId", "entityId":service, "groupId":gid, "hasData":{}, "keyReuqests":[], "found":False, "foundCount":0, "exception":""} #"exists":None, 'hasData_1W':None, + # else: + # tmp={"displayName":service,"comparer": "displayName", "entityId":None, "groupId":gid, "hasData":{}, "keyReuqests":[], "found":False, "foundCount":0, "exception":""} #"exists":None, 'hasData_1W':None, + + # kr.services.append(tmp) + + + if self.options: + if KROption.RESOLVEKEYREQUETS in self.options: + self.resolveKeyRequests(kr,self.DTAPIURL, self.DTAPIToken, self.options) + + if KROption.VALIDATE_HASDATA in self.options: + self.checkKeyRequetsHasData(kr,{"label":"1W", "tfrom":"now-1w"},self.DTAPIURL, self.DTAPIToken) + self.checkKeyRequetsHasData(kr,{"label":"1M", "tfrom":"now-1M"},self.DTAPIURL, self.DTAPIToken) + # elif KROption.RESOLVEKEYREQUETS in self.options: + # self.checkKeyRequetsHasData(kr, {"label":"1W", "tfrom":"now-1w"},self.DTAPIURL, self.DTAPIToken) + # if KROption.RESOLVESERVICES in self.options: + # self.resolveServices(kr,self.DTAPIURL, self.DTAPIToken) + + return kr + + + def parseBySLO(self,index,row): + #normalize + print(index) + + try: + normFilter=self.normalize(row['filter']) + normExpresseion=self.normalize(row['metricExpression']) + + tmp_KR = keyrequests.KR({"sloName":row["name"], "env":row["env"], "metricExpression": normExpresseion, "filter": normFilter, "matchedGroups": None}) + + + #SLO with Filter + if normFilter.upper().startswith("TYPE(SERVICE_METHOD),") or normFilter.upper().startswith("TYPE(SERVICE),"): + subject=normFilter + else: + subject=normExpresseion + + groups=self.applyPatterns(subject) + + tmp_KR.matchedGroups.append(groups) + # for g in groups: + # #if g["methods"] != None and len(g["methods"]) > 0: + # tmp_KR.matchedGroups.append(g) + + #self.process(tmp_KR) + kr=self.process(tmp_KR) + with self.lock: + self.krs.append(kr) + + except Exception as err: + print(repr(err)) + #return self.process(tmp_KR) + + def parseBySLO_Threaded(self, slosF): + self.krs=[] + #i=1 + # threads = list() + # for index, row in slosF.iterrows(): + # logging.info("Main : create and start thread %d.", index) + # x = threading.Thread(target=self.parseBySLO, args=(row,)) + # threads.append(x) + # x.start() + + # #krs.append(krp.parseBySLO(row)) + + # for index, thread in enumerate(threads): + # logging.info("Main : before joining thread %d.", index) + # thread.join() + # logging.info("Main : thread %d done", index) + + # #resultSlos.extend(krs) + + + with concurrent.futures.ThreadPoolExecutor(10) as executor: + for index, row in slosF.iterrows(): + # if i % 25 == 0: + # time.sleep(0) + #args={index:index, } + executor.submit(self.parseBySLO, index,row) + # print(str(i)+"\n") + # i=i+1 + # x = threading.Thread(target=self.parseBySLO, args=(row,)) + # threads.append(x) + # x.start() + + return self.krs + + + def __init__(self, options: KROption=None ,serviceLookupParams={}, DTAPIURL=None, DTAPIToken=None ): + self.DTAPIURL= DTAPIURL + self.DTAPIToken=DTAPIToken + self.options=options + self.serviceLookupParams=merge({"from":"now-2y"},serviceLookupParams) + self.krs=[] \ No newline at end of file diff --git a/build/lib/patterns.py b/build/lib/patterns.py new file mode 100644 index 0000000..ebd280b --- /dev/null +++ b/build/lib/patterns.py @@ -0,0 +1,96 @@ +import re +import urllib + +class Pattern1: + + + def parseServicesAndMethods(self, metricExpression): + + #result = re.findall(r"type\(\"?service_method\"?\)[\s\n\r]*,[\s\n\r]*fromRelationship[\s\n\r]*\.[\s\n\r]*isServiceMethodOfService[\s\n\r]*\([\s\n\r]*type\(\"?service\"?\)[\s\n\r]*,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*[\s\n\r]*\([\s\n\r]*([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)[\s\n\r]*\)[\s\n\r]*\)[\s\n\r]*,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*[\s\n\r]*\([\s\n\r]*([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)\)[\s\n\r]*\)[\s\n\r]*\)[\s\n\r]*\)[\s\n\r]*\)[\s\n\r]*[\)]*", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + #result = re.findall(r"type\(\"?service_method\"?\)[\s\n\r]*,[\s\n\r]*fromRelationship[\s\n\r]*\.[\s\n\r]*isServiceMethodOfService[\s\n\r]*\([\s\n\r]*type\(\"?service\"?\)[\s\n\r]*,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*[\s\n\r]*\([\s\n\r]*([\"]*[^.*]*[\"]*)[\s\n\r]*\)[\s\n\r]*\)[\s\n\r]*\,[\s\n\r]*entityName[\s\n\r]*\.[in]*[\s\n\r]*\([\s\n\r]*([\"]*[^.*\)]*)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + + #result = re.findall(r"type\(\"?service_method\"?\)[\s\n\r]*,[\s\n\r]*fromRelationship[\s\n\r]*\.[\s\n\r]*isServiceMethodOfService[\s\n\r]*\([\s\n\r]*type\(\"?service\"?\)[\s\n\r]*,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*[\s\n\r]*\([\s\n\r]*(\"[^.*]*\")[\s\n\r]*\)[\s\n\r]*\)[\s\n\r]*\,[\s\n\r]*entityName[\s\n\r]*\.[in]*[\s\n\r]*\([\s\n\r]*(\"[^.*]*)\)\"", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + + #Endoce + metricExpression=re.sub(r'~([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)~', lambda m: str(urllib.parse.quote_plus(m.group(1))), metricExpression, flags=re.IGNORECASE|re.X|re.MULTILINE) + result = re.findall(r"type\(\"?service_method\"?\),fromRelationship\.isServiceMethodOfService\(type\(\"?service\"?\),entityName[\.]*[in]*\(([^\)]*)\)\)\,entityName[\.]*[in]*\(([^\)]*)\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + #result = re.findall(r"type\(\"?service_method\"?\)[\s\n\r]*,[\s\n\r]*fromRelationship[\s\n\r]*\.[\s\n\r]*isServiceMethodOfService[\s\n\r]*\([\s\n\r]*type\(\"?service\"?\)[\s\n\r]*,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*[\s\n\r]*\([\s\n\r]*([^\)]*)\)[\s\n\r]*\)[\s\n\r]*\,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*\([\s\n\r]*([^\)]*)[\s\n\r]*\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + #services=[] + #methods=[] + groups=[] + if result: + for r in result: + services=[s.strip() for s in urllib.parse.unquote_plus(r[0]).split(",")] + methods=[s.strip() for s in urllib.parse.unquote_plus(r[1]).split(",")] + groups.append({"services":services, "methods":methods}) + #return services, methods + return groups + +class Pattern2: + + def parseServicesAndMethods(self, metricExpression): + metricExpression=re.sub(r'~([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)~', lambda m: str(urllib.parse.quote_plus(m.group(1))), metricExpression, flags=re.IGNORECASE|re.X|re.MULTILINE) + result = re.findall(r"type\(\"?service_method\"?\),fromRelationship\.isServiceMethodOfService\(type\([~]*service[~]*\),entityName[\.]*[in]*\(([^\)]*)\),tag\(([^\)]*)\)\),entityName[\.]*[in]*\(([^\)]*)\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + # services=[] + # methods=[] + groups=[] + if result: + for r in result: + services=[s.strip() for s in urllib.parse.unquote_plus(r[0]).split(",")] + methods=[s.strip() for s in urllib.parse.unquote_plus(r[2]).split(",")] + groups.append({"services":services, "methods":methods}) + + return groups + + + +class Pattern3: + + def parseServicesAndMethods(self, metricExpression): + metricExpression=re.sub(r'~([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)~', lambda m: str(urllib.parse.quote_plus(m.group(1))), metricExpression, flags=re.IGNORECASE|re.X|re.MULTILINE) + result = re.findall(r"type\(\"?service_method\"?\)[\s\n\r]*,[\s\n\r]*entityId[\s\n\r]*[\s\n\r]*\([\s\n\r]*[\s\n\r]*([^\)]*)[\s\n\r]*\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + # services=[] + # methods=[] + groups=[] + if result: + for r in result: + methods=[s.strip() for s in urllib.parse.unquote_plus(r).split(",")] + groups.append({"services":[], "methods":methods}) + + return groups + +class Pattern4: + + def parseServicesAndMethods(self, metricExpression): + metricExpression=re.sub(r'~([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)~', lambda m: str(urllib.parse.quote_plus(m.group(1))), metricExpression, flags=re.IGNORECASE|re.X|re.MULTILINE) + result = re.findall(r"type\(\"?service\"?\)[\s\n\r]*,[\s\n\r]*[entityName|entityId].*[equals|in]*\(([^\)]*)\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + + groups=[] + if result: + for r in result: + if not r: + #methods=[s.strip() for s in r.split(",")] + services=[s.strip() for s in urllib.parse.unquote_plus(r).split(",")] + groups.append({"services":services, "methods":[]}) + + return groups + +class Pattern5: + + def parseServicesAndMethods(self, metricExpression): + + #Endoce + metricExpression=re.sub(r'~([A-Z0-9\:\<\>\_\$\.\s\-\,\(\),\[\]\\\\/*]*)~', lambda m: str(urllib.parse.quote_plus(m.group(1))), metricExpression, flags=re.IGNORECASE|re.X|re.MULTILINE) + result = re.findall(r"type\(\"?service_method\"?\),fromRelationship\.isServiceMethodOfService\(type\(\"?service\"?\),entityName[\.]*[in]*\(([^\)]*)\)\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + #result = re.findall(r"type\(\"?service_method\"?\)[\s\n\r]*,[\s\n\r]*fromRelationship[\s\n\r]*\.[\s\n\r]*isServiceMethodOfService[\s\n\r]*\([\s\n\r]*type\(\"?service\"?\)[\s\n\r]*,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*[\s\n\r]*\([\s\n\r]*([^\)]*)\)[\s\n\r]*\)[\s\n\r]*\,[\s\n\r]*entityName[\s\n\r]*[\.]*[\s\n\r]*[in]*\([\s\n\r]*([^\)]*)[\s\n\r]*\)", metricExpression,flags=re.IGNORECASE|re.X|re.MULTILINE) + #services=[] + #methods=[] + groups=[] + if result: + for r in result: + if not r: + services=[s.strip() for s in urllib.parse.unquote_plus(r).split(",")] + #methods=[s.strip() for s in urllib.parse.unquote_plus(r[1]).split(",")] + groups.append({"services":services, "methods":[]}) + #return services, methods + return groups \ No newline at end of file diff --git a/dist/KeyRequestParser-0.1-py3.10.egg b/dist/KeyRequestParser-0.1-py3.10.egg new file mode 100644 index 0000000000000000000000000000000000000000..11c82f701a6c35004b8ea2211e8dc34ff053e3a7 GIT binary patch literal 15085 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