def deleterest(self, data): """ 删除接口 待删除的数据添加在url后面restful风格 :param data:请求数据 :return:成功与否 """ try: if not data is None or not data == '': url = self.url # 替换参数 data = self.__getparams(data) url = url + '/' + str(data) logger.info('deleterest请求url:' + url) result = self.session.delete(url) res = result.text logger.info('deleterest返回' + res) try: res = res[res.find('{'):res.rfind('}') + 1] except Exception as e: logger.exception(e) self.jsonres = json.loads(res) self.writer.write(self.writer.row, self.writer.clo, 'PASS') self.writer.write(self.writer.row, self.writer.clo + 1, str(self.jsonres)) return True else: logger.error('data不能为空') except Exception as e: self.writer.writefalse(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(self.jsonres)) logger.exception(e) return False
def assertequals(self, jsonpaths, value): """ 定义断言相等的关键字,用来判断json的key对应的值和期望值相等 :param jsonpaths: 通过jsonpath获取返回的数据 :param value: 期望结果 :return: """ res = 'None' try: res = str(jsonpath.jsonpath(self.jsonres, jsonpaths)[0]) except Exception as e: logger.exception(e) value = self.__getparams(value) if res == str(value): logger.info('测试通过') self.writer.write(self.writer.row, self.writer.clo, 'PASS') self.writer.write(self.writer.row, self.writer.clo + 1, str(res)) return 'Pass' else: logger.info('测试失败') self.writer.writefalse(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(res)) return False
def __get_data1(self, s): """ 默认标准参数s是以key=value&key1=value2的格式,如果不是,就原样返回 :param s: :return: """ flg = False param = {} p = s.split('&') try: for pp in p: ppp = pp.split("=") # if ppp[1].isdigit(): # param[ppp[0]] = int(ppp[1]) # else: ppp_value = '' ppp_value = '='.join(ppp[1:]) param[ppp[0]] = ppp_value except Exception as e: flg = True logger.warn("url参数格式不标准!") print("url参数格式不标准!") logger.exception(e) print(e) # print(param) # 如果符合标准格式返回处理后的param,否则,返回原样s if flg: s = s.encode('utf-8') return s else: return param
def load_json_file(path: str): """ Loads and parses the content of a json file. :param path: :return: """ if not os.path.isfile(path): return None with open(path, 'r') as file: try: return json.load(file) except json.JSONDecodeError as err: if err.msg != "Extra data": logger.exception("Failed to load json file '%s'" % path) return None # Read only first object from file, ignore extra data file.seek(0) json_str = file.read(err.pos) try: return json.loads(json_str) except json.JSONDecodeError: logger.exception("Failed to read json file '%s'" % path) return None
def hover(self, xpath): """ 根据xpath,找到元素,并将鼠标悬停到元素 该关键字自动化过程中,如果认为移动了鼠标可能导致悬停失败 该关键字也可以用于页面滚动,但不一定都能滚动成功 :param xpath: 元素的xpath :return: """ try: ele = self.driver.find_element_by_xpath(xpath) except Exception as e: logger.exception(e) self.writer.write(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(traceback.format_exc())) # 定位失败,则直接返回 return False try: actions = ActionChains(self.driver) actions.move_to_element(ele).perform() self.writer.write(self.writer.row, self.writer.clo, 'PASS') self.writer.write(self.writer.row, self.writer.clo + 1, "悬停成功") return True except Exception as e: logger.exception(e) self.writer.write(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(traceback.format_exc())) # 定位失败,则直接返回 return False
def runapp(self, conf, t=''): """ 连接appium服务器,并根据conf配置,启动待测试APP :param conf:APP的启动配置,为标准json字符串 :param port:启动appium的端口 :param t:默认等待实际 :return:无 """ try: if t == '' or t is None: self.t = 15 else: self.t = int(t) conf = eval(conf) self.driver = webdriver.Remote( 'http://127.0.0.1:' + self.port + '/wd/hub', conf) self.driver.implicitly_wait(self.t) self.writer.write(self.writer.row, 7, "PASS") except Exception as e: self.writer.write(self.writer.row, 7, "FAIL") self.writer.write(self.writer.row, 8, traceback.print_exc()) logger.exception(e)
def assertequals(self, key, value): ''' 断言json的结果里面,某个键的值和value相等 :param key:json结果中的键 :param value:预期的值 :return:无 ''' value = self.__get_param(value) res = str(self.result) # logger.info(self.jsons) try: # print(self.jsons) if key not in self.jsons: pass else: res = str(jsonpath.jsonpath(self.jsons, str(key))[0]) except Exception as e: logger.info('没有对应的' + key) logger.exception(e) if res == str(value): logger.info('PASS') self.writer.write(self.writer.row, 7, 'PASS') self.writer.write(self.writer.row, 8, str(self.jsons)) else: logger.info('FAIL') self.writer.write(self.writer.row, 7, 'FAIL') self.writer.write(self.writer.row, 8, str(self.jsons))
def swipe(self, p1, p2): """ 从p1坐标点滑动到p2坐标点 :param p1: 起始坐标点 111,22 :param p2: 终止坐标点 333,22 :return: """ try: p1 = p1.split(',') x1 = int(p1[0]) y1 = int(p1[1]) p2 = p2.split(',') x2 = int(p2[0]) y2 = int(p2[1]) TouchAction(self.driver).press(x=x1, y=y1).move_to( x=x2, y=y2).release().perform() self.writer.write(self.writer.row, self.writer.clo, 'PASS') return True except Exception as e: logger.exception(e) self.writer.write(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(traceback.format_exc())) return False
def callmethod(self, m, l=None): # 调用服务,并获得接口返回值 if l == None or l == '': try: self.result = self.client.service.__getattr__(m)() except Exception as e: self.result = e.__str__() print(self.result) else: l = l.split('、') for i in range(len(l)): l[i] = self.__get_param(l[i]) if l[i] == 'None': l[i] = None try: self.result = self.client.service.__getattr__(m)(*l) except Exception as e: self.result = e.__str__() try: jsons = self.result jsons = jsons[jsons.find('{'):jsons.rfind('}') + 1] self.jsonres = json.loads(jsons) self.writer.write(self.writer.row, 7, 'PASS') self.writer.write(self.writer.row, 8, str(self.jsonres)) except Exception as e: # 异常处理的时候,分析逻辑问题 self.jsonres = {} logger.exception(e) print(str(traceback.format_exc())) self.writer.write(self.writer.row, 7, 'PASS') self.writer.write(self.writer.row, 8, str(self.result)) return True
def runapp(self, c, t): conf = { 'platformName': 'Android', 'platformVersion': '6.0.1', 'deviceName': '127.0.0.1:7555', 'appPackage': 'com.Glaciwaker.SmithStory', 'appActivity': 'com.unity3d.player.UnityPlayerActivity', 'noReset': 'true', } try: c = eval(c) for key in c: conf[key] = c[key] except Exception as e: logger.warn('app配置错误,请检查') logger.exception(e) #多个设备需要指定连接设备 conf['udid'] = conf['deviceName'] #确保设备是连上的 try: os.system('adb connect ' + conf['deviceName']) os.system('adb devices') except: pass #连接appium并且启动app self.driver = webdriver.Remote( 'http://localhost:' + self.port + '/wd/hub', conf) # self.driver = webdriver.Remote('http://127.0.0.1:4777/wd/hub', conf) #隐式等待 self.wait(t) return self.driver
def uploadfile(self, xpath, filepath): """ 根据xpath,找到元素 使用send_keys上传文件 :param xpath: 元素的xpath :param filepath: 需要上传文件的全路径 :return: 无 """ try: ele = self.driver.find_element_by_xpath(xpath) except Exception as e: logger.exception(e) self.writer.write(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(traceback.format_exc())) # 定位失败,则直接返回 return False try: ele.send_keys(filepath) self.writer.write(self.writer.row, self.writer.clo, 'PASS') return True except Exception as e: logger.exception(e) self.writer.write(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(traceback.format_exc())) return False
def get_config(path): """ powered by Jhx at 2020/1/27 用来将配置文件转换为字典,方便后续使用 :param path:配置文件路径 :return:返回配置文件dict """ global config config.clear() # 重新获取时,先清空配置 txt = Txt(path) data = txt.read() for s in data: if s.startswith('#'): # 跳过注释 continue if not s.find('='): logger.warn('配置文件格式错误,请检查:' + str(s)) continue try: key = s[0:s.find('=')] value = s[s.find('=') + 1:s.__len__()] config[key] = value except Exception as e: logger.warn('配置文件格式错误,请检查:' + str(s)) logger.exception(e) return config
def openBrower(self, br=None, dr=None): """ :param br: 指定打开的浏览器类型 (gc:谷歌浏览器,ff:火狐浏览器,ie:ie浏览器,默认gc) :param dr: 指定driver文件的位置,默认gcdriver路径 :return: """ if br is None or br == ' ': br = 'gc' try: if br == 'gc': dr = './web/lib/chromedriver' self.driver = webdriver.Chrome(executable_path=dr) # 隐式等待 self.driver.implicitly_wait(10) elif br == "ff": dr = './web/lib/geckodriver' self.driver = webdriver.Firefox(executable_path=dr) elif br == "ie": dr = './web/lib/IEDriverServer' self.driver = webdriver.Ie(executable_path=dr) else: self.driver = webdriver.Chrome(executable_path=dr) # 隐式等待 self.driver.implicitly_wait(10) self.__write_excel_res('PASS', '打开浏览器成功') except Exception as e: logger.exception(e) self.__write_excel_res('FAIL', traceback.format_exc()) self.writer.save_close() # 程序在这个异常时候停止运行 exit(-1)
def getparams(casepath, resultpath): global reader, writer, alllist, runtype reader.open_excel(casepath) # 这里已经设置读取第一个sheet页了 # 第一行 reader.readline() # 第二行 line = reader.readline() logger.exception(line) runtype = line[1] title = line[2] writer.copy_open(casepath, resultpath) sheetname = reader.get_sheets() # 获取所有的sheetNames writer.set_sheet(sheetname[0]) writer.write(1, 3, str(datetime.datetime.now().strftime( '%Y-%m-%d %H:%M:%S'))) # 对结果文件第二行第四列写入开始时间 for sheet in sheetname: # 设置当前读写的sheet页面 reader.set_sheet(sheet) writer.set_sheet(sheet) # 默认写第7列 writer.clo = 7 list = [sheet, '', '', '', '', ''] alllist.append(list) for i in range(reader.rows): list = [i] line = reader.readline() # 如果第一列或者第二列有内容,就是分组信息,不运行 if len(line[0]) > 0 or len(line[1]) > 0: pass else: list += line[2:7] alllist.append(list)
def send(self, text): # 这里使用SMTP_SSL就是默认使用465端口,如果发送失败,可以使用587 smtp = SMTP_SSL(self.mail_info['hostname']) smtp.set_debuglevel(0) ''' SMTP 'ehlo' command. Hostname to send for this command defaults to the FQDN of the local host. ''' smtp.ehlo(self.mail_info['hostname']) smtp.login(self.mail_info['username'], self.mail_info['password']) msg = MIMEText(text, 'html', self.mail_info['mail_encoding']) msg['Subject'] = Header(self.mail_info['mail_subject'], self.mail_info['mail_encoding']) msg['from'] = self.mail_info['from'] print(self.mail_info) print(text) msg['to'] = ','.join(self.mail_info['to']) msg['cc'] = ','.join(self.mail_info['cc']) receive = self.mail_info['to'] receive += self.mail_info['cc'] try: smtp.sendmail(self.mail_info['from'], receive, msg.as_string()) smtp.quit() logger.info('邮件发送成功') except Exception as e: logger.error('邮件发送失败:') logger.exception(e)
def read(self, coding='utf8'): #只读文件 coding: 打开文件的编码,默认utf8 try: #逐行读取 res = open(self.path, encoding=coding) for i in res: if len(i) > 1: self.data.append(i) #去掉末尾换行、空格 for i in range(len(self.data)): # 处理非法字符 self.data[i] = self.data[i].encode('utf-8').decode('utf-8-sig') self.data[i] = self.data[i].replace('\t', '') self.data[i] = self.data[i].replace('\n', '') replace_str = self.data[i][self.data[i].find('=') - 1] # 查询=号左边是否有空格 replace_str1 = self.data[i][self.data[i].find('=') + 1] # 查询=号右边是否有空格 if replace_str == ' ': self.data[i] = re.sub(r' ', '', self.data[i], count=1) if replace_str1 == ' ': self.data[i] = re.sub(r' ', '', self.data[i], count=1) # logger_yzy.debug('读取内容' + str(self.data)) except Exception as e: logger.exception(e) #return返回读取的内容 return self.data
def run(): while True: try: logger.info(f'Начало') assigned_open_issues_per_project = get_assigned_open_issues_per_project( ) logger.info('Всего задач: %s\n\n%s\n', sum(assigned_open_issues_per_project.values()), get_table(assigned_open_issues_per_project)) ok = db.add(assigned_open_issues_per_project) if ok is None: logger.info( "Количество открытых задач в проектах не поменялось. Пропускаю..." ) elif ok: logger.info("Добавляю запись") else: logger.info("Сегодня запись уже была добавлена. Пропускаю...") logger.info('\n' + '-' * 100 + '\n') break except Exception: logger.exception('Ошибка:') logger.info('Через 15 минут попробую снова...') wait(minutes=15)
def get(self, url, params=None): """ 发送post请求 :param url: url路径,可以是单纯的路径+全局的host。也可以是http/https开头的绝对路径 :param d: 标准url data传参 :param j: 传递json字符串的参数 :return: 无 """ if not (url.startswith('http') or url.startswith('https')): url = self.url + '/' + url + "?" + params else: url = url + "?" + params # 如果请求https请求,报ssl错误,就添加verify=False参数 res = self.session.get(url, verify=False) self.result = res.content.decode('utf8') logger.info(self.result) try: jsons = self.result jsons = jsons[jsons.find('{'):jsons.rfind('}') + 1] self.jsonres = json.loads(jsons) self.writer.write(self.writer.row, 7, 'PASS') self.writer.write(self.writer.row, 8, str(jsons)) except Exception as e: # 异常处理的时候,分析逻辑问题 self.jsonres = {} logger.exception(e) self.writer.write(self.writer.row, 7, 'PASS') self.writer.write(self.writer.row, 8, str(self.result))
def __init__(self): # 配置mysql参数 self.mysql_config = { 'mysqluser': "******", 'mysqlpassword': "******", 'mysqlport': 3306, 'mysqlhost': '192.168.150.128', 'mysqldb': 'test_project', 'mysqlcharset': "utf8" # 'mysqluser': "******", # 'mysqlpassword': "******", # 'mysqlport': 3306, # 'mysqlhost': 'localhost', # 'mysqldb': 'test_project', # 'mysqlcharset': "utf8" } # 从配置文件读取配置 for key in self.mysql_config: try: self.mysql_config[key] = config.config[key] except Exception as e: logger.exception(e) # 把端口处理为整数 try: self.mysql_config['mysqlport'] = int( self.mysql_config['mysqlport']) except Exception as e: logger.exception(e)
def inputlist(self, xpath, index, value): try: curr_time = datetime.datetime.now() # 处理日期 if (value.startswith('2019')): data = curr_time.date() value = str(data) index = int(index) elelist = self.driver.find_elements_by_xpath(xpath) elelist[index].clear() self.sleep(2) elelist[index].send_keys(value) self.writer.write(self.writer.row, self.writer.clo, 'PASS') return True else: index = int(index) elelist = self.driver.find_elements_by_xpath(xpath) elelist[index].clear() self.sleep(1) elelist[index].send_keys(value) self.sleep(1) self.writer.write(self.writer.row, self.writer.clo, 'PASS') return True except Exception as e: logger.exception(e) self.writer.writefalse(self.writer.row, self.writer.clo, 'FAIL') self.writer.write(self.writer.row, self.writer.clo + 1, str(traceback.format_exc())) return False
def get_config(path): """ powered by Mr Will at 2018-12-22 用来格式化打印日志到文件和控制台 :param path:配置文件路径 :return:返回配置文件dict """ global config # 重新获取时,先清空配置 config.clear() txt = Txt(path) data = txt.read() for s in data: # 跳过注释 if s.startswith('#'): continue if not s.find('=') > 0: logger.warn('配置文件格式错误,请检查:' + str(s)) continue try: key = s[0:s.find('=')] value = s[s.find('=') + 1:s.__len__()] config[key] = value except Exception as e: logger.warn('配置文件格式错误,请检查:' + str(s)) logger.exception(e) return config
def get(self, path, data1=None): try: path1 = self.__geturl(path) if data1 == None or data1 == '': result = self.session.get(path1) else: dic = self.__getparame(data1) dic1 = self.__todic(dic) result = self.session.get(path1, data=data1) # 兼容jsonpath设置 try: res = result.text res = res[res.find('{'):res.rfind('}') + 1] except Exception as e: logger.exception(e) try:#若返回的res不是一个json格式的字符串,解释json.loads(res)的时候会报错,所以做容错处理 self.jsoners = json.loads(res) self.res = '' except: self.jsoners = None self.res = res self.write.write(self.write.row, self.write.clo, 'PASS') self.write.write(self.write.row, self.write.clo + 1,str(self.jsoners) + ' ' + self.res[self.res.find('{'):self.res.find('}') + 1]) except Exception as e: self.write.write(self.write.row, self.write.clo, 'FAIL') self.write.write(self.write.row, self.write.clo + 1,str(self.jsoners) + ' ' + self.res[self.res.find('{'):self.res.find('}') + 1])
def openbrowser(self, br=None, dr=None): """ 打开浏览器 :param br:指定打开浏览器的类型 (gc:谷歌;ff:火狐;ie:ie浏览器;默认为gc) :param dr:指定driver文件的位置 (默认为:../lib/chromedriver) :return: """ if br is None or br == '': br = 'gc' if dr is None or dr == '': if br == 'gc': dr = './web/lib/chromedriver' if br == 'ie': dr = './web/lib/IEDriverServer' if br == 'ff': dr = './web/lib/geckodriver' try: if br == 'gc': # 谷歌浏览器配置对象 opt = Options() opt.add_argument('--user-data-dir=%s\\AppData\\Local\\Google' '\\Chrome\\user data' % os.environ["USERPROFILE"]) # 打开浏览器 self.driver = webdriver.Chrome(options=opt, executable_path=dr) # 隐式等待 self.driver.implicitly_wait(10) if br == 'ff': # opt = Options() # opt.add_argument('-profile=%s\\AppData\\Roaming\\Mozilla\Firefox\\Profiles\\rvuazmas.default' % os.environ["USERPROFILE"]) # webdriver.Firefox.profile = r"C:\\Users\Will\AppData\Roaming\Mozilla\Firefox\Profiles\rvuazmas.default" profile = webdriver.FirefoxProfile() profile.set_preference("browser.download.dir", "D:\\profile1") profile.set_preference("browser.download.folderList", 2) profile.set_preference( "browser.helperApps.neverAsk.saveToDisk", "application/octet-stream, application/vnd.ms-excel, text/csv, application/zip" ) profile.update_preferences() self.driver = webdriver.Firefox(executable_path=dr, firefox_profile=profile) if br == 'ie': self.driver = webdriver.Ie(executable_path=dr) self.__write_excel_res('PASS', '打开浏览器成功') except Exception as e: self.__write_excel_res('FAIL', traceback.format_exc()) logger.exception(e) # 程序在这个异常时,停止运行 self.writer.save_close() exit(-1)
def send(self, group_name, user_name, message, html=None): #html type is "Content" groups = self.bot.search(group_name) if groups is None or len(groups) == 0: logger.warn("无法找到微信群[%s]", group_name) logger.warn("因此, 发送消息到微信群失败:%s", message) return for group in groups: self.__send_html(group, html) # http://wxpy.readthedocs.io/zh/latest/faq.html, # 此api项目不支持@,这里@,只是一个字符串消息而已 try: self.random_wait() if user_name: group.send(message + "\n\n@" + user_name) else: group.send(message) except ResponseError as e: logger.exception(e, "发送消息给微信服务失败,原因:%s", str(e)) return logger.debug("成功发给[%s]消息:%s", group_name, message)
def all_check(self): live = [] try: for batch in self.batches: res = batch.check() if res: live.extend(res) for one in self.onebyone: for url in one.url_list: if one('检测' + url, url).check_stream(): live.append(url) if url != one.url_list[-1]: logger.debug('歇息会') time.sleep(15) except IOError: logger.exception("IOError") finally: event_t = Event(TO_MODIFY) event_t.args = (live,) event_u = Event(UPLOAD) event_u.args = (live,) return event_u, event_t
def postjson(self, path, data=''): try: if not path.startswith('http'): path = self.url + '/' + path print('请求url:' + str(path)) # 如果需要传参,就调用post,传递data if data == '': result = self.session.post(path) else: print('请求data数据' + str(data)) # 替换参数 data = self.__getparams(data) # 转为字典 # data = self.__todict(data) # print('转换后的数据' + str(data)) # 发送请求 result = self.session.post(path, data=data) print('返回 result' + result.text) res = result.text # print('res数据' + str(res)) try: res = res[res.find('{'):res.rfind('}') + 1] except Exception as e: logger.exception(e) self.jsonres = json.loads(res) return True except Exception as e: logger.exception(e) return False
def hover(self,element): ele = self.__element(element) try: actions = ActionChains(self.driver) actions.move_to_element(ele).perform() except Exception as e: logger.exception(e)
def get_config(path): """ 用来读取配置文件,生成字典格式 :param path: 配置文件路径 :return: 返回配置文件dict """ global config # 重新获取时,先清空配置 config.clear() txt = Txt(path) # 获取读取到列表文件 date = txt.read() # print(date) for s in date: # 跳过注释 if s.startswith('#'): continue if not s.find('=') > 0: logger.warring('配置文件格式错误,请检查:' + s) try: key = s[0:s.find('=')] value = s[s.find('=') + 1:s.__len__()] # 使config字典的key=value config[key] = value except Exception as e: logger.exception(e) return config
def _find_job(driver: RemoteWebDriver, job_url: str) -> List[JobItem]: items = [] try: wait = WebDriverWait(driver, 5) driver.get(job_url) wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'div.job-list'))) ele_jobs = driver.find_elements_by_css_selector('div.job-list ul > li') for ele_job in ele_jobs: ele_job = ele_job # type: WebElement title = ele_job.find_element_by_css_selector('div.job-title').text wage = ele_job.find_element_by_css_selector( 'div.job-limit span.red').text company = ele_job.find_element_by_css_selector( 'div.company-text').text button = ele_job.find_element_by_css_selector( 'button.btn-startchat') say_hello_url = HOMEPAGE + button.get_attribute('data-url') company = company.replace('\n', ' ') logger.info('[click] [%s] {%s} (%s)', title, company, wage) if is_black_list_company(company): continue item = JobItem(title, wage, company, say_hello_url) items.append(item) except NoSuchElementException as e: logger.exception(e) except TimeoutException as e: logger.exception(e) logger.error(job_url) return items
def main(): parser = argparse.ArgumentParser(prog=WORKER_MANAGER) parser.add_argument( "--webapi-uri", help="zimfarm API URI", required=not bool(DEFAULT_WEB_API_URL), default=DEFAULT_WEB_API_URL, dest="webapi_uri", ) parser.add_argument( "--socket-uri", help="zimfarm websocket URI (tcp://hostname:port)", required=not bool(DEFAULT_SOCKET_URI), default=DEFAULT_SOCKET_URI, dest="socket_uri", ) parser.add_argument( "--username", help="username to authenticate to zimfarm", required=not bool(os.getenv("USERNAME")), default=os.getenv("USERNAME"), ) parser.add_argument( "--workdir", help="directory in which workers create task-related files", required=not bool(DEFAULT_WORKDIR), default=DEFAULT_WORKDIR, dest="workdir", ) parser.add_argument( "--name", help="name of this worker", dest="worker_name", required=not bool(os.getenv("WORKER_NAME")), default=os.getenv("WORKER_NAME"), ) args = parser.parse_args() logger.info(f"starting zimfarm {WORKER_MANAGER}.") try: manager = WorkerManager( username=args.username, webapi_uri=args.webapi_uri, socket_uri=args.socket_uri, workdir=args.workdir, worker_name=args.worker_name, ) sys.exit(manager.run()) except Exception as exc: logger.error(f"Unhandled exception: {exc}") logger.exception(exc) logger.error("exiting.") sys.exit(1)