From 5d81fc906262215a9f02b7ed0cc8ff0b12f33ac7 Mon Sep 17 00:00:00 2001 From: ermisw Date: Fri, 5 May 2023 14:52:01 +0200 Subject: [PATCH] fix --- KRParser/krparser.py | 3 +- KeyRequestParser.egg-info/PKG-INFO | 2 +- KeyRequestParser.egg-info/SOURCES.txt | 10 +- KeyRequestParser.egg-info/top_level.txt | 2 +- build/lib/KRParser/__init__.py | 0 build/lib/KRParser/helper.py | 46 ++++ build/lib/KRParser/keyrequests.py | 157 ++++++++++++ build/lib/KRParser/krparser.py | 303 ++++++++++++++++++++++++ build/lib/KRParser/patterns.py | 96 ++++++++ dist/KeyRequestParser-0.2-py3.10.egg | Bin 0 -> 29585 bytes 10 files changed, 611 insertions(+), 8 deletions(-) create mode 100644 build/lib/KRParser/__init__.py create mode 100644 build/lib/KRParser/helper.py create mode 100644 build/lib/KRParser/keyrequests.py create mode 100644 build/lib/KRParser/krparser.py create mode 100644 build/lib/KRParser/patterns.py create mode 100644 dist/KeyRequestParser-0.2-py3.10.egg diff --git a/KRParser/krparser.py b/KRParser/krparser.py index 29c3d0c..7305ba0 100644 --- a/KRParser/krparser.py +++ b/KRParser/krparser.py @@ -1,6 +1,7 @@ import re -from key_request_parser import patterns, keyrequests, helper +from KRParser import patterns, keyrequests, helper + from enum import Flag, auto import logging diff --git a/KeyRequestParser.egg-info/PKG-INFO b/KeyRequestParser.egg-info/PKG-INFO index 021b956..0ef3826 100644 --- a/KeyRequestParser.egg-info/PKG-INFO +++ b/KeyRequestParser.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: KeyRequestParser -Version: 0.1 +Version: 0.2 License: MIT Parses Keyrequests diff --git a/KeyRequestParser.egg-info/SOURCES.txt b/KeyRequestParser.egg-info/SOURCES.txt index ff060d8..edb4624 100644 --- a/KeyRequestParser.egg-info/SOURCES.txt +++ b/KeyRequestParser.egg-info/SOURCES.txt @@ -1,9 +1,9 @@ -__init__.py -helper.py -keyrequests.py -krparser.py -patterns.py setup.py +KRParser/__init__.py +KRParser/helper.py +KRParser/keyrequests.py +KRParser/krparser.py +KRParser/patterns.py KeyRequestParser.egg-info/PKG-INFO KeyRequestParser.egg-info/SOURCES.txt KeyRequestParser.egg-info/dependency_links.txt diff --git a/KeyRequestParser.egg-info/top_level.txt b/KeyRequestParser.egg-info/top_level.txt index 8b13789..ba3f2d2 100644 --- a/KeyRequestParser.egg-info/top_level.txt +++ b/KeyRequestParser.egg-info/top_level.txt @@ -1 +1 @@ - +KRParser diff --git a/build/lib/KRParser/__init__.py b/build/lib/KRParser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/KRParser/helper.py b/build/lib/KRParser/helper.py new file mode 100644 index 0000000..cace378 --- /dev/null +++ b/build/lib/KRParser/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/KRParser/keyrequests.py b/build/lib/KRParser/keyrequests.py new file mode 100644 index 0000000..a877da3 --- /dev/null +++ b/build/lib/KRParser/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/krparser.py b/build/lib/KRParser/krparser.py new file mode 100644 index 0000000..29c3d0c --- /dev/null +++ b/build/lib/KRParser/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/KRParser/patterns.py b/build/lib/KRParser/patterns.py new file mode 100644 index 0000000..ebd280b --- /dev/null +++ b/build/lib/KRParser/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.2-py3.10.egg b/dist/KeyRequestParser-0.2-py3.10.egg new file mode 100644 index 0000000000000000000000000000000000000000..9c0ec6f58fcb2cefd393250476dbc1d13404f211 GIT binary patch literal 29585 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