except Exception as e: logging.info(str(e)) # open database user_home_dir = os.path.expanduser("~") user_config_dir = os.path.expanduser("~") + "/.config/nginx-odoo" Path(user_config_dir).mkdir(parents=True, exist_ok=True) db_path = user_config_dir + "/database.db" db = DB(db_path) os.chmod(db_path, 0o600) db_perm = os.stat(db_path).st_mode & 0o777 if db_perm != 0o600: sys.exit('File permissions of {} must be 600 but are: {:o}'.format( db_path, db_perm)) db.cleanup() # check Odoo settings ODOO_PORT = os.environ.get('NGINX_ODOO_ODOO_PORT', '8069') ODOO_HOST = os.environ.get('NGINX_ODOO_ODOO_HOST', 'localhost') ODOO_DATABASE = os.environ.get('NGINX_ODOO_ODOO_DATABASE') if not ODOO_DATABASE: sys.exit('Odoo settings not set in .env') # try to connect to odoo try: odoo = odoorpc.ODOO(ODOO_HOST, port=ODOO_PORT) except Exception: sys.exit('Odoo not running at {}:{}'.format(ODOO_HOST, ODOO_PORT)) auth_params = {
class Target: ''' This is the target. It controls/contains the code to be debugged. ''' def __init__(self, dsn: str): self.database = DB(dsn, is_async=True) self.notice_queue = Queue() self.oid = None self.executor = None self.port = None def cleanup(self): ''' Cleanup routine for the target. ''' self.database.cleanup() def get_notices(self) -> List[str]: ''' Get all notices the target might have. Reads from an internal queue, does not use the DB itself since it is likely blocked. ''' notices = [] while not self.notice_queue.empty(): notices.append(self.notice_queue.get()) logger.debug(f'Target notices: {notices}') return notices @classmethod def _parse_port(cls, port_raw: str) -> int: return int(port_raw.split(':')[-1]) def wait_for_shutdown(self): ''' Wait until the target completed fully. ''' self.executor.join() def _run_executor_thread(self, func_call, func_oid): self.executor = Thread(target=self._run, args=(func_call, func_oid)) self.executor.daemon = True self.executor.start() @classmethod def assert_valid_function_call(cls, func_call: str) -> bool: return re.match(r'[_a-zA-Z0-9]+\([^\)]*\)(\.[^\)]*\))?', func_call) is not None def start(self, func_call: str) -> bool: ''' Start target debugging. Resolve the function to be debugged, find its OID and eventually start a thread calling it. ''' print(func_call) if not Target.assert_valid_function_call(func_call): logger.error(f'Function call seems incomplete: {func_call}') return False func_name, func_args = Target._parse_func_call(func_call) func_oid = get_func_oid_by_name(self.database, func_name) if not func_oid: logger.error('Function OID not found. Either function is not ' 'defined or there are multiple with the same name ' 'which is currently not support') return False logger.debug(f'Function OID is: {func_oid}') self._run_executor_thread(func_call, func_oid) # Wait here until the executor started logger.debug('Waiting for port') self.port = Target._parse_port(self.notice_queue.get()) logger.debug(f'Port is: {self.port}') return True def _run(self, func_call: str, func_oid: int): ''' Start a debugging session. Consumes the function call to debug with its arguments and returns the session ID once the debugger started. ''' self.oid = func_oid self.database.run_sql(f'SELECT * FROM pldbg_oid_debug({func_oid})') while True: logger.debug('Starting target function') try: result = self.database.run_sql(f'SELECT * FROM {func_call}', fetch_result=True, notice_queue=self.notice_queue) # This will now wait here until the function finishes. It will # eventually restart immediately. Otherwise, the proxy process # hangs until it hits a timeout. logger.debug(f'Target result: {result}') except QueryCanceled: logger.info('Stopped target query') break @classmethod def _parse_func_call(cls, func_call: str) -> Tuple[str, List[str]]: ''' Take a function call and return the function name and its arguments. ''' func_call = func_call.replace(' ', '').replace('(', ',').replace(')', '') func_call = func_call.split(',') return func_call[0], func_call[1:]