class EndpointNode: SCOPE = "endpoint" ALLOWED_KEYS = ( ENDPOINTS_KEY, HEADERS_KEY, NAME_KEY, PARAMS, PATH_KEY, REQUESTS_KEY, ) def __init__(self, spec, parent=None): self.spec = spec self.parent = parent self.child_nodes = [] self.__build() self.vars = SpecEvaluator(self, spec.get("vars", {})) def __build(self): self._validate() self.child_nodes = [ EndpointNode(spec, parent=self) for spec in self.spec.get("endpoints", []) ] def __repr__(self): return f"<{self.__class__.__name__} {self.name}>" @property def name(self): return self.spec.get("name", "root") @property def path(self): path = self.spec.get("path", "").strip() url = join_urls(self.parent.path, path) if self.parent else path return self.vars.evaluate(url) @property def headers(self): return self._get_specs("headers") @property def params(self): return self._get_specs("params") def run(self): for request in self._get_requests(): try: yield request.run() except Exception as e: error_message = ( f"Error to make request `{request.full_url_path}`. {str(e)}" ) logger.error(error_message) continue def _validate(self): validate_keys(self.spec.keys(), self.ALLOWED_KEYS, self.SCOPE) def _get_specs(self, field_name): values = self.spec.get(field_name, {}) parent_values = getattr(self.parent, field_name, None) if parent_values: return {**parent_values, **values} return values def _get_requests(self): return chain( (RequestNode(spec, self) for spec in self.spec.get("requests", [])), *(child._get_requests() for child in self.child_nodes), )
class EndpointNode: """ Class that represents an endpoint. It follows a tree-like structure where each EndpointNode may contain multiple children EndpointNodes. Attributes: spec[dict]: dictionary containing the endpoint's specifications parent[EndpointNode, optional]: the parent node child_nodes[list of EndpointNodes]: the children nodes spec_vars[SpecEvaluator]: evaluator used to evaluate expressions and store spec variables """ SCOPE = "endpoint" ALLOWED_KEYS = ( ENDPOINTS_KEY, HEADERS_KEY, NAME_KEY, PARAMS_KEY, PATH_KEY, REQUESTS_KEY, DELAY_KEY, VARS_KEY, ) REQUIRED_KEYS = (NAME_KEY, ) ROOT_REQUIRED_KEYS = () def __init__(self, spec, parent=None): self.spec = spec self.parent = parent self.child_nodes = [] self.__build() self.spec_vars = SpecEvaluator(self, spec.get(VARS_KEY, {})) def __build(self): """Validate the EndpointNode keys and create children EndpointNodes from endpoints in its specifications. """ self._validate() self.child_nodes = [ EndpointNode(spec, parent=self) for spec in self.spec.get(ENDPOINTS_KEY, []) ] def __repr__(self): return f"<{self.__class__.__name__} {self.name}>" @property def name(self): """Get the endpoint's name. The name is prepended by the parent's name, if it is not a root node. Returns: [str]: The endpoint's name. """ name = self.spec.get(NAME_KEY, "") if self.is_root or not self.parent.name: return name return f"{self.parent.name}::{name}" @property def path(self): """Get the endpoint's path. The path is prepended by the parent's path, if it is not a root node. The returned path already has all variables evaluated. Returns: [str]: The endpoint's url. """ path = str(self.spec.get(PATH_KEY, "")).strip() url = join_urls(self.parent.path, path) if self.parent else path return self.spec_vars.evaluate(url) @property def headers(self): """Get the headers used in the endpoint call. The headers of the call include the parent's headers. Returns: [dict]: the headers used in the endpoint call. """ return self._get_specs(HEADERS_KEY) @property def params(self): """Get the parameters used in the endpoint call. The parameters of the call include the parent's parameters. Returns: [dict]: the parameters used in the endpoint call. """ return self._get_specs(PARAMS_KEY) @property def delay(self): """Get the time in milliseconds to be waited before making the endpoint call. Returns: [int]: the time to be waited. """ delay = self.spec.get(DELAY_KEY, 0) return delay or getattr(self.parent, DELAY_KEY, 0) @property def is_root(self): """Check if the EndpointNode is a root node. Returns: [bool]: true if the node has no parent, false otherwise. """ return not self.parent def run(self): """Run the requests of the node and all children nodes. Returns: [iterator]: Iterator that yields the test result of each request. """ for request in self._get_requests(): try: yield request.run() except Exception as e: error_message = f"\nError to make request `{request.full_url_path}`. \n{str(e)}\n" logger.error(error_message) session.exit_code = ExitCode.REQUEST_ERROR continue def _validate(self): """Private method that checks if the specification has any invalid key or if there is any required key missing. """ required_keys = (self.ROOT_REQUIRED_KEYS if self.is_root else self.REQUIRED_KEYS) scope = ROOT_SCOPE if self.is_root else self.SCOPE validate_keys(self.spec.keys(), self.ALLOWED_KEYS, required_keys, scope) def _get_specs(self, field_name): """Get a specification of the endpoint. Args: field_name [str]: name of the specification field. Returns: [dict]: a dictionary containing the values of the field. """ values = self.spec.get(field_name, {}) parent_values = getattr(self.parent, field_name, None) if parent_values: return {**parent_values, **values} return values def _get_requests(self): """Get all requests from the node and children nodes as RequestNodes. Returns: [iterator]: Iterator that yields a RequestNode for each request. """ return chain( (RequestNode(spec, self) for spec in self.spec.get(REQUESTS_KEY, [])), *(child._get_requests() for child in self.child_nodes), )
class EndpointNode: SCOPE = "endpoint" ALLOWED_KEYS = ( ENDPOINTS_KEY, HEADERS_KEY, NAME_KEY, PARAMS_KEY, PATH_KEY, REQUESTS_KEY, ) REQUIRED_KEYS = (NAME_KEY, ) ROOT_REQUIRED_KEYS = () def __init__(self, spec, parent=None): self.spec = spec self.parent = parent self.child_nodes = [] self.__build() self.vars = SpecEvaluator(self, spec.get(VARS_KEY, {})) def __build(self): self._validate() self.child_nodes = [ EndpointNode(spec, parent=self) for spec in self.spec.get(ENDPOINTS_KEY, []) ] def __repr__(self): return f"<{self.__class__.__name__} {self.name}>" @property def name(self): name = self.spec.get(NAME_KEY, "") if self.is_root or not self.parent.name: return name return f"{self.parent.name}::{name}" @property def path(self): path = str(self.spec.get(PATH_KEY, "")).strip() url = join_urls(self.parent.path, path) if self.parent else path return self.vars.evaluate(url) @property def headers(self): return self._get_specs(HEADERS_KEY) @property def params(self): return self._get_specs(PARAMS_KEY) @property def is_root(self): return not self.parent def run(self): for request in self._get_requests(): try: yield request.run() except Exception as e: error_message = ( f"\nError to make request `{request.full_url_path}`. \n{str(e)}\n" ) logger.error(error_message) session.exit_code = ExitCode.REQUEST_ERROR continue def _validate(self): if self.is_root: return validate_keys( self.spec.keys(), self.ALLOWED_KEYS, self.ROOT_REQUIRED_KEYS, ROOT_SCOPE, ) validate_keys(self.spec.keys(), self.ALLOWED_KEYS, self.REQUIRED_KEYS, self.SCOPE) def _get_specs(self, field_name): values = self.spec.get(field_name, {}) parent_values = getattr(self.parent, field_name, None) if parent_values: return {**parent_values, **values} return values def _get_requests(self): return chain( (RequestNode(spec, self) for spec in self.spec.get(REQUESTS_KEY, [])), *(child._get_requests() for child in self.child_nodes), )