def test_destroy() -> None: # Only executed if tests are run with --destroy flag if os.getenv("TEST_DESTROY_MODE", "0") != "1": log.info("Skipping destroy test, TEST_DESTROY_MODE not enabled") return # Always enable during core tests if not Connector.check_availability("authentication"): # pragma: no cover log.warning("Skipping authentication test: service not available") return # if Connector.check_availability("sqlalchemy"): # sql = sqlalchemy.get_instance() # # Close previous connections, otherwise the new create_app will hang # sql.session.remove() # sql.session.close_all() auth = Connector.get_authentication_instance() user = auth.get_user(username=BaseAuthentication.default_user) assert user is not None create_app(mode=ServerModes.DESTROY) try: auth = Connector.get_authentication_instance() user = auth.get_user(username=BaseAuthentication.default_user) assert user is None except ServiceUnavailable: pass
def clearcache(): from restapi.server import create_app from restapi.services.cache import Cache create_app(name="Cache clearing") Cache.clear() log.info("Cache cleared")
def clean() -> None: # pragma: no cover """Destroy current services data""" from restapi.server import ServerModes, create_app log.info("Launching destruction app") create_app(name="Removing data", mode=ServerModes.DESTROY) log.info("Destruction completed")
def forced_clean() -> None: # pragma: no cover """DANGEROUS: Destroy current data without asking yes/no""" from restapi.server import ServerModes, create_app log.info("Launching destruction app") create_app(name="Removing data", mode=ServerModes.DESTROY) log.info("Destruction completed")
def clearcache() -> None: """Clear all data from the endpoints cache""" from restapi.server import create_app from restapi.services.cache import Cache create_app(name="Cache clearing") Cache.clear() log.info("Cache cleared")
def flask_cli(options=None): log.info("Launching the app") from restapi.server import create_app if options is None: options = {'name': 'RESTful HTTP API server'} app = create_app(**options) app.run(host=BIND_INTERFACE, threaded=True) else: create_app(**options) log.debug("cli execution completed")
def flask_cli(options=None): log.info("Launching the app") from restapi.server import create_app # log.warning("TEST") if options is None: options = {'name': 'RESTful HTTP API server'} app = create_app(**options) app.run(host='0.0.0.0', threaded=True) else: create_app(**options) # app.run(debug=False) log.warning("Completed")
def init(wait, force_user, force_group): """Initialize data for connected services""" if wait: mywait() from restapi.server import ServerModes, create_app log.info("Launching initialization app") options = { "force_user": force_user, "force_group": force_group, } create_app(name="Initializing services", mode=ServerModes.INIT, options=options) log.info("Initialization requested")
def test_destroy() -> None: auth = Connector.get_authentication_instance() user = auth.get_user(username=BaseAuthentication.default_user) assert user is not None create_app(mode=ServerModes.DESTROY) if Connector.check_availability("sqlalchemy"): with pytest.raises(ServiceUnavailable): auth = Connector.get_authentication_instance() user = auth.get_user(username=BaseAuthentication.default_user) else: auth = Connector.get_authentication_instance() user = auth.get_user(username=BaseAuthentication.default_user) assert user is None
def test_init() -> None: auth = Connector.get_authentication_instance() if Connector.authentication_service == "sqlalchemy": # Re-init does not work with MySQL due to issues with previous connections # Considering that: # 1) this is a workaround to test the initialization # (not the normal workflow used by the application) # 2) the init is already tested with any other DB, included postgres # 3) MySQL is not used by any project # => there is no need to go crazy in debugging this issue! if auth.db.is_mysql(): # type: ignore return # sql = sqlalchemy.get_instance() if Connector.check_availability("sqlalchemy"): # Prevents errors like: # sqlalchemy.exc.ResourceClosedError: This Connection is closed Connector.disconnect_all() # sql = sqlalchemy.get_instance() # # Close previous connections, otherwise the new create_app will hang # sql.session.remove() # sql.session.close_all() try: create_app(mode=ServerModes.INIT) # This is only a rough retry to prevent random errors from sqlalchemy except Exception: # pragma: no cover create_app(mode=ServerModes.INIT) auth = Connector.get_authentication_instance() try: user = auth.get_user(username=BaseAuthentication.default_user) # SqlAlchemy sometimes can raise an: # AttributeError: 'NoneType' object has no attribute 'twophase' # due to the multiple app created... should be an issue specific of this test # In that case... simply retry. except AttributeError: # pragma: no cover user = auth.get_user(username=BaseAuthentication.default_user) assert user is not None
def setUp(self): """ Note: in this base tests, I also want to check if i can run multiple Flask applications. Thi is why i prefer setUp on setUpClass """ logger.debug('### Setting up the Flask server ###') app = create_app(testing_mode=True) self.app = app.test_client()
def setUpClass(cls): logger.info('### Setting up flask server ###') app = create_app(testing_mode=True) cls.app = app.test_client() r = cls.app.post( AUTH_URI + '/login', data=json.dumps({'username': USER, 'password': PWD})) content = json.loads(r.data.decode('utf-8')) cls.auth_header = { 'Authorization': 'Bearer ' + content['Response']['data']['token']}
def test_init() -> None: # Only executed if tests are run with --destroy flag if os.getenv("TEST_DESTROY_MODE", "0") != "1": log.info("Skipping destroy test, TEST_DESTROY_MODE not enabled") return # Always enable during core tests if not Connector.check_availability("authentication"): # pragma: no cover log.warning("Skipping authentication test: service not available") return auth = Connector.get_authentication_instance() if Connector.authentication_service == "sqlalchemy": # Re-init does not work with MySQL due to issues with previous connections # Considering that: # 1) this is a workaround to test the initialization # (not the normal workflow used by the application) # 2) the init is already tested with any other DB, included postgres # 3) MySQL is not used by any project # => there is no need to go crazy in debugging this issue! if auth.db.is_mysql(): # type: ignore return # sql = sqlalchemy.get_instance() create_app(mode=ServerModes.INIT) auth = Connector.get_authentication_instance() try: user = auth.get_user(username=BaseAuthentication.default_user) # SqlAlchemy sometimes can raise an: # AttributeError: 'NoneType' object has no attribute 'twophase' # due to the multiple app created... should be an issue specific of this test # In that case... simply retry. except AttributeError: # pragma: no cover user = auth.get_user(username=BaseAuthentication.default_user) assert user is not None
def setUp(self): logger.info("### Setting up flask server ###") app = create_app(testing_mode=True) self.app = app.test_client() logger.info("*** VERIFY valid credentials") endpoint = AUTH_URI + "/login" r = self.app.post(endpoint, data=json.dumps({"username": USER, "password": PWD})) self.assertEqual(r.status_code, hcodes.HTTP_OK_BASIC) content = json.loads(r.data.decode("utf-8")) # Since unittests use class object and not instances # This is the only workaround to set a persistent variable # self.auth_header does not work self.__class__.auth_header = {"Authorization": "Bearer " + content["Response"]["data"]["token"]}
def setUp(self): """ Note: in this base tests, I also want to check if i can run multiple Flask applications. Thi is why i prefer setUp on setUpClass """ log.debug('### Setting up the Flask server ###') app = create_app(testing_mode=True) self.app = app.test_client() # Auth init from base/custom config ba.myinit() self._username = ba.default_user self._password = ba.default_password
if args is not None: if args.debug: enable_debug = True logger.warning("Enabling DEBUG mode") time.sleep(1) if not args.security: enable_security = False logger.warning("No security enabled! Are you really sure?") time.sleep(1) #Â The connection is HTTP internally to containers # The proxy will handle HTTPS calls # We can safely disable HTTPS on OAUTHLIB requests #Â http://stackoverflow.com/a/27785830/2114395 if PRODUCTION: os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' ############################# # BE FLASK app = create_app( name='REST_API', enable_security=enable_security, debug=enable_debug) if __name__ == "__main__": # Note: 'threaded' option avoid to see # angular request on this server dropping # and becoming slow if not totally frozen logger.info("*** Running Flask!") app.run(host=SERVER_HOSTS, port=SERVER_PORT, threaded=True)
from restapi.connectors import celery from restapi.server import ServerModes, create_app from restapi.utilities.logs import log instance = celery.get_instance() # Used by Celery to run the instance (--app app) celery_app = instance.celery_app # Reload Flask app code for the worker (needed to have the app context available) celery.CeleryExt.app = create_app(mode=ServerModes.WORKER) log.debug("Celery worker is ready {}", celery_app)
data["new_password"] = currentpwd data["password_confirm"] = currentpwd return get_auth_token(client, data) assert r.status_code == 200 assert content is not None headers: CaseInsensitiveDict[str] = CaseInsensitiveDict() headers["Authorization"] = f"Bearer {content}" return content, headers # No need to restore the logger after this test because # schemathesis test is the last one! # (just because in alphabetic order there are no other tests) set_logger("WARNING") app = create_app() client = werkzeug.Client(app, werkzeug.wrappers.Response) if Env.get_bool("AUTH_ENABLE"): BaseAuthentication.load_default_user() BaseAuthentication.load_roles() USER = BaseAuthentication.default_user PWD = BaseAuthentication.default_password data = {"username": USER, "password": PWD} token, auth_header = get_auth_token(client, data) # it does not handle custom headers => the endpoint will provide partial schema # due to missing authentication => skipping all private endpoints and schemas # schema = schemathesis.from_wsgi('/api/specs', app) r = client.get(f"/api/specs?access_token={token}") else:
def app() -> Flask: return create_app()
So we made some improvement along the code. """ # from restapi.resources.services.celery.tasks import MyCelery from commons.services.celery import celery_app from restapi.server import create_app from commons.meta import Meta from commons.logs import get_logger logger = get_logger(__name__) ################################################ # Reload Flask app code also for the worker # This is necessary to have the app context available app = create_app(worker_mode=True, debug=True) # Recover celery app with current app # celery_app = MyCelery(app)._current # celery_app = MyCelery(app)._current logger.debug("Celery %s" % celery_app) ################################################ # Import tasks modules to make sure all tasks are avaiable meta = Meta() main_package = "commons.tasks." # Base tasks submodules = meta.import_submodules_from_package(main_package + "base") # Custom tasks
def setUpClass(cls): logger.info('### Setting up flask server ###') app = create_app(testing=True) cls.app = app.test_client()
#!/usr/bin/env python """ RESTful API Python 3 Flask server """ import os from restapi.config import PRODUCTION from restapi.server import create_app # Connection internal to containers, proxy handle all HTTPS calls # We may safely disable HTTPS on OAUTHLIB requests if PRODUCTION: # http://stackoverflow.com/a/27785830/2114395 os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" app = create_app(name="REST_API") if __name__ == "__main__": app.run(host="0.0.0.0", threaded=True)
def app(): return create_app()
def test_cli() -> None: runner = CliRunner() response = runner.invoke(cli.verify, ["test"]) assert response.exit_code == 2 assert "Got unexpected extra argument (test)" in response.output response = runner.invoke(cli.verify, ["--services", "neo4j"]) assert response.exit_code == 2 assert "Error: No such option: --services" in response.output response = runner.invoke(cli.verify, ["--service", "x"]) assert response.exit_code == 1 for service in ("neo4j", "sqlalchemy"): if not Connector.check_availability(service): continue response = runner.invoke(cli.verify, ["--service", service]) assert response.exit_code == 0 response = runner.invoke(cli.wait, []) assert response.exit_code == 0 response = runner.invoke(cli.init, []) assert response.exit_code == 0 response = runner.invoke(cli.init, ["--wait"]) assert response.exit_code == 0 response = runner.invoke(cli.clean, []) assert response.exit_code == 1 assert "Do you want to continue? [y/N]:" in response.output response = runner.invoke(cli.tests, ["--file", "x"]) assert response.exit_code == 1 response = runner.invoke(cli.tests, ["--folder", "x"]) assert response.exit_code == 1 response = runner.invoke(cli.tests, ["--wait", "--file", "x"]) assert response.exit_code == 1 response = runner.invoke(cli.tests, ["--core", "--file", "x"]) assert response.exit_code == 1 variables = { "myhost": "myvalue", "myport": "111", } with pytest.raises(SystemExit): cli.get_service_address(variables, "host", "port", "myservice") with pytest.raises(SystemExit): cli.get_service_address(variables, "myhost", "port", "myservice") h, p = cli.get_service_address(variables, "myhost", "myport", "myservice") assert h == "myvalue" assert isinstance(p, int) assert p == 111 from restapi.server import create_app create_app(name="Cache clearing") # make_name prevents the use of rapydo default make_name function, that is only # working from the endpoints context since it is based on tokens from flask.requests @decorators.cache(timeout=3600, make_name=None) def random_values() -> int: return random.randrange(0, 100000) val = random_values() time.sleep(0.9) assert random_values() == val time.sleep(0.9) assert random_values() == val # Let's clear the cache response = runner.invoke(cli.clearcache, []) assert response.exit_code == 0 assert random_values() != val
def app(): app = create_app(testing_mode=True) return app
def test_celery(app: Flask, faker: Faker) -> None: if not Connector.check_availability(CONNECTOR): try: obj = connector.get_instance() pytest.fail("No exception raised") # pragma: no cover except ServiceUnavailable: pass log.warning("Skipping {} tests: service not available", CONNECTOR) return None log.info("Executing {} tests", CONNECTOR) obj = connector.get_instance() assert obj is not None task = obj.celery_app.send_task("test_task") assert task is not None assert task.id is not None if obj.variables.get("backend") == "RABBIT": log.warning( "Due to limitations on RABBIT backend task results will not be tested" ) else: try: r = task.get(timeout=10) assert r is not None # This is the task output, as defined in task_template.py.j2 assert r == "Task executed!" assert task.status == "SUCCESS" assert task.result == "Task executed!" except celery.exceptions.TimeoutError: # pragma: no cover pytest.fail( f"Task timeout, result={task.result}, status={task.status}") if CeleryExt.CELERYBEAT_SCHEDULER is None: try: obj.get_periodic_task("does_not_exist") pytest.fail("get_periodic_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") try: obj.delete_periodic_task("does_not_exist") pytest.fail( "delete_periodic_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") try: obj.create_periodic_task(name="task1", task="task.does.not.exists", every="60") pytest.fail( "create_periodic_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") try: obj.create_crontab_task(name="task2", task="task.does.not.exists", minute="0", hour="1") pytest.fail("create_crontab_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") else: assert obj.get_periodic_task("does_not_exist") is None assert not obj.delete_periodic_task("does_not_exist") obj.create_periodic_task(name="task1", task="task.does.not.exists", every="60") assert obj.delete_periodic_task("task1") assert not obj.delete_periodic_task("task1") obj.create_periodic_task( name="task1_bis", task="task.does.not.exists", every="60", period="seconds", args=["a", "b", "c"], kwargs={ "a": 1, "b": 2, "c": 3 }, ) assert obj.delete_periodic_task("task1_bis") assert not obj.delete_periodic_task("task1_bis") # cron at 01:00 obj.create_crontab_task(name="task2", task="task.does.not.exists", minute="0", hour="1") assert obj.delete_periodic_task("task2") assert not obj.delete_periodic_task("task2") obj.create_crontab_task( name="task2_bis", task="task.does.not.exists", minute="0", hour="1", day_of_week="*", day_of_month="*", month_of_year="*", args=["a", "b", "c"], kwargs={ "a": 1, "b": 2, "c": 3 }, ) assert obj.delete_periodic_task("task2_bis") assert not obj.delete_periodic_task("task2_bis") if CeleryExt.CELERYBEAT_SCHEDULER == "REDIS": obj.create_periodic_task( name="task3", task="task.does.not.exists", every=60, ) assert obj.delete_periodic_task("task3") obj.create_periodic_task(name="task4", task="task.does.not.exists", every=60, period="seconds") assert obj.delete_periodic_task("task4") obj.create_periodic_task(name="task5", task="task.does.not.exists", every=60, period="minutes") assert obj.delete_periodic_task("task5") obj.create_periodic_task(name="task6", task="task.does.not.exists", every=60, period="hours") assert obj.delete_periodic_task("task6") obj.create_periodic_task(name="task7", task="task.does.not.exists", every=60, period="days") assert obj.delete_periodic_task("task7") try: obj.create_periodic_task( name="task8", task="task.does.not.exists", every="60", period="years", # type: ignore ) except BadRequest as e: assert str(e) == "Invalid timedelta period: years" obj.create_periodic_task( name="task9", task="task.does.not.exists", every=timedelta(seconds=60), ) assert obj.delete_periodic_task("task9") try: obj.create_periodic_task( name="task10", task="task.does.not.exists", every=["60"], # type: ignore ) except AttributeError as e: assert str( e) == "Invalid input parameter every = ['60'] (type list)" try: obj.create_periodic_task( name="task11", task="task.does.not.exists", every="invalid", ) except AttributeError as e: assert str( e) == "Invalid input parameter every = invalid (type str)" else: obj.create_periodic_task(name="task3", task="task.does.not.exists", every="60", period="minutes") assert obj.delete_periodic_task("task3") obj.disconnect() # a second disconnect should not raise any error obj.disconnect() # Create new connector with short expiration time obj = connector.get_instance(expiration=2, verification=1) obj_id = id(obj) # Connector is expected to be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # The connection should have been checked and should be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # Connection should have been expired and a new connector been created obj = connector.get_instance(expiration=2, verification=1) assert id(obj) != obj_id assert obj.is_connected() obj.disconnect() assert not obj.is_connected() # ... close connection again ... nothing should happens obj.disconnect() with connector.get_instance() as obj: assert obj is not None app = create_app(mode=ServerModes.WORKER) assert app is not None from restapi.utilities.logs import LOGS_FILE assert os.environ["HOSTNAME"] == "backend-server" assert LOGS_FILE == "backend-server" # this decorator is expected to be used in celery context, i.e. the self reference # should contains a request, injected by celery. Let's mock this by injecting an # artificial self @send_errors_by_email def this_function_raises_exceptions(self): raise AttributeError("Just an exception") class FakeRequest: def __init__(self, task_id, task, args): self.id = task_id self.task = task self.args = args class FakeSelf: def __init__(self, task_id, task, args): self.request = FakeRequest(task_id, task, args) task_id = faker.pystr() task_name = faker.pystr() task_args = [faker.pystr()] this_function_raises_exceptions(FakeSelf(task_id, task_name, task_args)) mail = BaseTests.read_mock_email() assert mail.get("body") is not None assert f"Celery task {task_id} failed" in mail.get("body") assert f"Name: {task_name}" in mail.get("body") assert f"Arguments: {str(task_args)}" in mail.get("body") assert "Error: Traceback (most recent call last):" in mail.get("body") assert 'raise AttributeError("Just an exception")' in mail.get("body")
#!/usr/bin/env python # -*- coding: utf-8 -*- """ RESTful API Python 3 Flask server """ import os import pretty_errors from restapi.confs import PRODUCTION from restapi.server import create_app from restapi.utilities.logs import log # Connection internal to containers, proxy handle all HTTPS calls # We may safely disable HTTPS on OAUTHLIB requests if PRODUCTION: # http://stackoverflow.com/a/27785830/2114395 os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' BIND_INTERFACE = "0.0.0.0" ############################# # BE FLASK app = create_app(name='REST_API') if __name__ == "__main__": log.debug("Server running (w/ {})", pretty_errors.__name__) app.run(host=BIND_INTERFACE, threaded=True)
a flask templating framework like ours. So we made some improvement along the code. """ from restapi.server import create_app from utilities import CUSTOM_PACKAGE from utilities.meta import Meta from utilities.logs import get_logger log = get_logger(__name__) ################################################ # Reload Flask app code also for the worker # This is necessary to have the app context available app = create_app(worker_mode=True) celery_app = app.extensions.get('celery').celery_app celery_app.app = app def get_service(service): return celery_app.app.extensions.get(service).get_instance() celery_app.get_service = get_service ################################################ # Import tasks modules to make sure all tasks are available meta = Meta()
def test_celery(app: Flask, faker: Faker) -> None: log.info("Executing {} tests", CONNECTOR) obj = connector.get_instance() assert obj is not None task = obj.celery_app.send_task("test_task", args=("myinput", )) assert task is not None assert task.id is not None # Mocked task task_output = BaseTests.send_task(app, "test_task", "myinput") # As defined in task template assert task_output == "Task executed!" # wrong is a special value included in tasks template with pytest.raises(Ignore): BaseTests.send_task(app, "test_task", "wrong") project_title = get_project_configuration("project.title", default="YourProject") mail = BaseTests.read_mock_email() body = mail.get("body") headers = mail.get("headers") assert body is not None assert headers is not None assert f"Subject: {project_title}: Task test_task failed" in headers assert "this email is to notify you that a Celery task failed!" in body # fixed-id is a mocked value set in TESTING mode by @task in Celery connector assert "Task ID: fixed-id" in body assert "Task name: test_task" in body assert "Arguments: ('wrong',)" in body assert "Error Stack" in body assert "Traceback (most recent call last):" in body exc = ( "AttributeError: " "You can raise exceptions to stop the task execution in case of errors" ) assert exc in body # celery.exceptions.Ignore exceptions are ignored BaseTests.delete_mock_email() # ignore is a special value included in tasks template with pytest.raises(Ignore): BaseTests.send_task(app, "test_task", "ignore") # the errors decorator re-raise the Ignore exception, without any further action # No email is sent in case of Ignore exceptions with pytest.raises(FileNotFoundError): mail = BaseTests.read_mock_email() # retry is a special value included in tasks template with pytest.raises(CeleryRetryTask): BaseTests.send_task(app, "test_task", "retry") mail = BaseTests.read_mock_email() body = mail.get("body") headers = mail.get("headers") assert body is not None assert headers is not None assert f"Subject: {project_title}: Task test_task failed (failure #1)" in headers assert "this email is to notify you that a Celery task failed!" in body # fixed-id is a mocked value set in TESTING mode by @task in Celery connector assert "Task ID: fixed-id" in body assert "Task name: test_task" in body assert "Arguments: ('retry',)" in body assert "Error Stack" in body assert "Traceback (most recent call last):" in body exc = "CeleryRetryTask: Force the retry of this task" assert exc in body # retry2 is a special value included in tasks template # Can't easily import the custom exception defined in the task... # a generic exception is enough here with pytest.raises(Exception): BaseTests.send_task(app, "test_task", "retry2") mail = BaseTests.read_mock_email() body = mail.get("body") headers = mail.get("headers") assert body is not None assert headers is not None assert f"Subject: {project_title}: Task test_task failed (failure #1)" in headers assert "this email is to notify you that a Celery task failed!" in body # fixed-id is a mocked value set in TESTING mode by @task in Celery connector assert "Task ID: fixed-id" in body assert "Task name: test_task" in body assert "Arguments: ('retry2',)" in body assert "Error Stack" in body assert "Traceback (most recent call last):" in body exc = "MyException: Force the retry of this task by using a custom exception" assert exc in body with pytest.raises(AttributeError, match=r"Task not found"): BaseTests.send_task(app, "does-not-exist") if obj.variables.get("backend_service") == "RABBIT": log.warning( "Due to limitations on RABBIT backend task results will not be tested" ) else: try: r = task.get(timeout=10) assert r is not None # This is the task output, as defined in task_template.py.j2 assert r == "Task executed!" assert task.status == "SUCCESS" assert task.result == "Task executed!" except celery.exceptions.TimeoutError: # pragma: no cover pytest.fail( f"Task timeout, result={task.result}, status={task.status}") obj.disconnect() # a second disconnect should not raise any error obj.disconnect() # Create new connector with short expiration time obj = connector.get_instance(expiration=2, verification=1) obj_id = id(obj) # Connector is expected to be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # The connection should have been checked and should be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # Connection should have been expired and a new connector been created obj = connector.get_instance(expiration=2, verification=1) assert id(obj) != obj_id assert obj.is_connected() obj.disconnect() assert not obj.is_connected() # ... close connection again ... nothing should happen obj.disconnect() with connector.get_instance() as obj: assert obj is not None app = create_app(mode=ServerModes.WORKER) assert app is not None