def query(self, uri) -> Tuple[bool, str]: """Get information on given uri""" if uri == "/": hlogger.debug("Getting a list of all registered components") return True, self._get_comps_data() elif comp := self._components.get(uri, None): hlogger.debug("Getting component metadata: %s", comp) return True, self._get_comp_data(comp)
def _prepare_outputs(self, comp, returns) -> Tuple[bool, str]: outputs = [] if not isinstance(returns, tuple): returns = (returns, ) for out_param, out_result in zip(comp.outputs, returns): output_data = out_param.from_result(out_result) outputs.append(output_data) payload = {"values": outputs} hlogger.debug("Return payload: %s", payload) return True, json.dumps(payload, cls=_HopsEncoder)
def do_GET(self): # grab the path before url params comp_uri = self._get_comp_uri() res, results = self.hops.query(uri=comp_uri) hlogger.debug(f"{res} : {results}") if res: self._prep_response() self.wfile.write(results.encode(encoding="utf_8")) else: self._prep_response(status=404)
def query(self, uri) -> Tuple[bool, str]: """Get information on given uri""" if uri == "/": hlogger.debug("Getting a list of all registered components") return True, self._get_comps_data() else: comp = self._components.get(uri, None) if comp: hlogger.debug("Getting component metadata: %s", comp) return True, self._get_comp_data(comp) return False, self._return_with_err("Unknown Hops url")
def do_POST(self): # read the message and convert it into a python dictionary comp_uri = self._get_comp_uri() length = int(self.headers.get("Content-Length")) data = self.rfile.read(length) res, results = self.hops.solve(uri=comp_uri, payload=data) hlogger.debug(f"{res} : {results}") if res: self._prep_response() self.wfile.write(results.encode(encoding="utf_8")) else: # TODO: write proper errors self._prep_response(500, "Execution Error") self.wfile.write(results.encode(encoding="utf_8"))
def solve(self, uri, payload) -> Tuple[bool, str]: """Perform Solve on given uri""" if uri == "/": hlogger.debug("Nothing to solve on root") return False, self._return_with_err("Nothing to solve on root") # FIXME: remove support for legacy solve behaviour elif uri == "/solve": data = json.loads(payload) comp_name = data["pointer"] for comp in self._components.values(): if comp_name == comp.uri.replace("/", ""): hlogger.info("Solving using legacy API: %s", comp) return self._process_solve_request(comp, payload) # FIXME: test this new api elif comp := self._components.get(uri, None): hlogger.info("Solving: %s", comp) return self._process_solve_request(comp, payload)
def _process_solve_request(self, comp, payload) -> Tuple[bool, str]: # parse payload for inputs res, inputs = self._prepare_inputs(comp, payload) if not res: hlogger.debug("Bad inputs: %s", inputs) return res, self._return_with_err("Bad inputs") # run try: solve_returned = self._solve(comp, inputs) hlogger.debug("Return data: %s", solve_returned) res, outputs = self._prepare_outputs(comp, solve_returned) return ( res, outputs if res else self._return_with_err("Bad outputs"), ) except Exception as solve_ex: # try to grab traceback data and create err msg _, _, exc_traceback = sys.exc_info() try: fmt_tb = traceback.format_tb(exc_traceback) # FIXME: can we safely assume we are only 2 levels in stack? ex_msg = "\n".join(fmt_tb[2:]) ex_msg = str(solve_ex) + f"\n{ex_msg}" except Exception: # otherwise use exception str as msg ex_msg = str(solve_ex) hlogger.debug("Exception occured in handler: %s", ex_msg) return False, self._return_with_err( "Exception occured in handler:\n%s" % ex_msg)
def __new__(cls, app=None, debug=False, *args, **kwargs) -> base.HopsBase: # set logger level hlogger.setLevel(logging.DEBUG if debug else logging.INFO) # determine the correct middleware base on the source app being wrapped # when running standalone with no source apps if app is None: hlogger.debug("Using Hops default http server") params._init_rhino3dm() return hmw.HopsDefault() # if wrapping another app app_type = repr(app) # if app is Flask if app_type.startswith("<Flask"): hlogger.debug("Using Hops Flask middleware") params._init_rhino3dm() return hmw.HopsFlask(app, *args, **kwargs) # if wrapping rhinoinside # paractically this is not necessary. it is implemented this way # mostly to provide a level of consistency on how Hops is used elif app_type.startswith("<module 'rhinoinside'"): # detemine if running with rhino.inside.cpython # and init the param module accordingly if not Hops.is_inside(): raise Exception("rhinoinside is not loaded yet") hlogger.debug("Using Hops default http server with rhinoinside") params._init_rhinoinside() return hmw.HopsDefault(*args, **kwargs) raise Exception("Unsupported app")
def __func_wrapper__(comp_func): # register python func as Hops component # determine name, and uri comp_name = name or comp_func.__qualname__ uri = rule or f"/{comp_name}" # create component instance comp = HopsComponent( uri=uri, name=comp_name, nickname=nickname, desc=description or comp_func.__doc__, cat=category or DEFAULT_CATEGORY, subcat=subcategory or DEFAULT_SUBCATEGORY, icon=self._prepare_icon(icon) if icon is not None else None, inputs=inputs or [], outputs=outputs or [], handler=comp_func, ) hlogger.debug("Component registered: %s", comp) # register by uri and solve uri, for fast lookup on query and solve self._components[uri] = comp self._components[comp.solve_uri] = comp return comp_func
def __func_wrapper__(comp_func): # register python func as Hops component if inputs: # inspect default parameters in function signature f_sig = inspect.signature(comp_func) f_params = f_sig.parameters.values() if len(inputs) != len(f_params): raise Exception("Number of function parameters is " "different from defined Hops inputs") # apply function param default values in order # to defined Hops inputs. this will override any # previously defined default values for hinput, fparam in zip(inputs, f_params): if fparam.default != inspect.Parameter.empty: hinput.default = fparam.default # determine name, and uri comp_name = name or comp_func.__qualname__ uri = rule or f"/{comp_name}" # create component instance comp = HopsComponent( uri=uri, name=comp_name, nickname=nickname, desc=description or comp_func.__doc__, cat=category or DEFAULT_CATEGORY, subcat=subcategory or DEFAULT_SUBCATEGORY, icon=self._prepare_icon(icon) if icon is not None else None, inputs=inputs or [], outputs=outputs or [], handler=comp_func, ) hlogger.debug("Component registered: %s", comp) # register by uri and solve uri, for fast lookup on query and solve self._components[uri] = comp self._components[comp.solve_uri] = comp return comp_func