def test_get_api_base_url_works(self): # test the api_base_url feature, and that it can contain a path config = AppConfig() backend_port = find_available_port("localhost", 10000) config.update_server_config( app__flask_secret_key="secret", app__api_base_url= f"http://localhost:{backend_port}/additional/path", multi_dataset__dataroot=f"{PROJECT_ROOT}/example-dataset", ) config.complete_config() with test_server(["-p", str(backend_port)], app_config=config) as server: session = requests.Session() self.assertEqual(server, f"http://localhost:{backend_port}") response = session.get( f"{server}/additional/path/d/pbmc3k.h5ad/api/v0.2/config") self.assertEqual(response.status_code, 200) data_config = response.json() self.assertEqual(data_config["config"]["displayNames"]["dataset"], "pbmc3k") # test the health check at the correct url response = session.get(f"{server}/additional/path/health") assert response.json()["status"] == "pass"
def start_test_server(command_line_args=[], app_config=None, env=None): """ Command line arguments can be passed in, as well as an app_config. This function is meant to be used like this, for example: with test_server(...) as server: r = requests.get(f"{server}/...") // check r where the server can be accessed within the context, and is terminated when the context is exited. The port is automatically set using find_available_port, unless passed in as a command line arg. The verbose flag is automatically set to True. If an app_config is provided, then this function writes a temporary yaml config file, which this server will read and parse. """ command = ["cellxgene", "--no-upgrade-check", "launch", "--verbose"] if "-p" in command_line_args: port = int(command_line_args[command_line_args.index("-p") + 1]) elif "--port" in command_line_args: port = int(command_line_args[command_line_args.index("--port") + 1]) else: start = random.randint(DEFAULT_SERVER_PORT, 2**16 - 1) port = int(os.environ.get("CXG_SERVER_PORT", start)) port = find_available_port("localhost", port) command += ["--port=%d" % port] command += command_line_args tempdir = None if app_config: tempdir = tempfile.TemporaryDirectory() config_file = os.path.join(tempdir.name, "config.yaml") app_config.write_config(config_file) command.extend(["-c", config_file]) server = f"http://localhost:{port}" ps = Popen(command, env=env) for _ in range(10): try: requests.get(f"{server}/health") break except requests.exceptions.ConnectionError: time.sleep(1) if tempdir: tempdir.cleanup() return ps, server
def handle_app(self, context): self.validate_correct_type_of_configuration_attribute( "app__verbose", bool) self.validate_correct_type_of_configuration_attribute( "app__debug", bool) self.validate_correct_type_of_configuration_attribute("app__host", str) self.validate_correct_type_of_configuration_attribute( "app__port", (type(None), int)) self.validate_correct_type_of_configuration_attribute( "app__open_browser", bool) self.validate_correct_type_of_configuration_attribute( "app__force_https", bool) self.validate_correct_type_of_configuration_attribute( "app__flask_secret_key", str) self.validate_correct_type_of_configuration_attribute( "app__generate_cache_control_headers", bool) if self.app__port: try: if not is_port_available(self.app__host, self.app__port): raise ConfigurationError( f"The port selected {self.app__port} is in use, please configure an open port." ) except OverflowError: raise ConfigurationError(f"Invalid port: {self.app__port}") else: try: default_server_port = int( os.environ.get("CXG_SERVER_PORT", DEFAULT_SERVER_PORT)) except ValueError: raise ConfigurationError( "Invalid port from environment variable CXG_SERVER_PORT: " + os.environ.get("CXG_SERVER_PORT")) try: self.app__port = find_available_port(self.app__host, default_server_port) except OverflowError: raise ConfigurationError( f"Invalid port: {default_server_port}") if self.app__debug: context["messagefn"]( "in debug mode, setting verbose=True and open_browser=False") self.app__verbose = True self.app__open_browser = False else: warnings.formatwarning = custom_format_warning if not self.app__verbose: sys.tracebacklimit = 0
def handle_app(self, context): self.validate_correct_type_of_configuration_attribute( "app__verbose", bool) self.validate_correct_type_of_configuration_attribute( "app__debug", bool) self.validate_correct_type_of_configuration_attribute("app__host", str) self.validate_correct_type_of_configuration_attribute( "app__port", (type(None), int)) self.validate_correct_type_of_configuration_attribute( "app__open_browser", bool) self.validate_correct_type_of_configuration_attribute( "app__force_https", bool) self.validate_correct_type_of_configuration_attribute( "app__flask_secret_key", str) self.validate_correct_type_of_configuration_attribute( "app__generate_cache_control_headers", bool) self.validate_correct_type_of_configuration_attribute( "app__server_timing_headers", bool) self.validate_correct_type_of_configuration_attribute( "app__csp_directives", (type(None), dict)) self.validate_correct_type_of_configuration_attribute( "app__api_base_url", (type(None), str)) self.validate_correct_type_of_configuration_attribute( "app__web_base_url", (type(None), str)) if self.app__port: try: if not is_port_available(self.app__host, self.app__port): raise ConfigurationError( f"The port selected {self.app__port} is in use, please configure an open port." ) except OverflowError: raise ConfigurationError(f"Invalid port: {self.app__port}") else: try: default_server_port = int( os.environ.get("CXG_SERVER_PORT", DEFAULT_SERVER_PORT)) except ValueError: raise ConfigurationError( "Invalid port from environment variable CXG_SERVER_PORT: " + os.environ.get("CXG_SERVER_PORT")) try: self.app__port = find_available_port(self.app__host, default_server_port) except OverflowError: raise ConfigurationError( f"Invalid port: {default_server_port}") if self.app__debug: context["messagefn"]( "in debug mode, setting verbose=True and open_browser=False") self.app__verbose = True self.app__open_browser = False else: warnings.formatwarning = custom_format_warning if not self.app__verbose: sys.tracebacklimit = 0 # CSP Directives are a dict of string: list(string) or string: string if self.app__csp_directives is not None: for k, v in self.app__csp_directives.items(): if not isinstance(k, str): raise ConfigurationError( "CSP directive names must be a string.") if isinstance(v, list): for policy in v: if not isinstance(policy, str): raise ConfigurationError( "CSP directive value must be a string or list of strings." ) elif not isinstance(v, str): raise ConfigurationError( "CSP directive value must be a string or list of strings." ) if self.app__web_base_url is None: self.app__web_base_url = self.app__api_base_url